1 ;;; GNU Guix --- Functional package management for GNU
2 ;;; Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019 Ludovic Courtès <ludo@gnu.org>
3 ;;; Copyright © 2015 Alex Kost <alezost@gmail.com>
4 ;;; Copyright © 2019 Ricardo Wurmus <rekado@elephly.net>
6 ;;; This file is part of GNU Guix.
8 ;;; GNU Guix is free software; you can redistribute it and/or modify it
9 ;;; under the terms of the GNU General Public License as published by
10 ;;; the Free Software Foundation; either version 3 of the License, or (at
11 ;;; your option) any later version.
13 ;;; GNU Guix is distributed in the hope that it will be useful, but
14 ;;; WITHOUT ANY WARRANTY; without even the implied warranty of
15 ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 ;;; GNU General Public License for more details.
18 ;;; You should have received a copy of the GNU General Public License
19 ;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
21 (define-module (guix upstream)
22 #:use-module (guix records)
23 #:use-module (guix utils)
24 #:use-module (guix discovery)
25 #:use-module ((guix download)
26 #:select (download-to-store url-fetch))
27 #:use-module (guix gnupg)
28 #:use-module (guix packages)
29 #:use-module (guix ui)
30 #:use-module (guix base32)
31 #:use-module (guix gexp)
32 #:use-module (guix store)
33 #:use-module ((guix derivations)
34 #:select (built-derivations derivation->output-path))
35 #:use-module (guix monads)
36 #:use-module (srfi srfi-1)
37 #:use-module (srfi srfi-9)
38 #:use-module (srfi srfi-11)
39 #:use-module (srfi srfi-26)
40 #:use-module (srfi srfi-34)
41 #:use-module (srfi srfi-35)
42 #:use-module (rnrs bytevectors)
43 #:use-module (ice-9 match)
44 #:use-module (ice-9 regex)
45 #:export (upstream-source
47 upstream-source-package
48 upstream-source-version
50 upstream-source-signature-urls
51 upstream-source-archive-types
52 upstream-source-input-changes
60 upstream-updater-description
61 upstream-updater-predicate
62 upstream-updater-latest
64 upstream-input-change?
65 upstream-input-change-name
66 upstream-input-change-type
67 upstream-input-change-action
74 package-latest-release
75 package-latest-release*
77 update-package-source))
81 ;;; This module provides tools to represent and manipulate a upstream source
82 ;;; code, and to auto-update package recipes.
86 ;; Representation of upstream's source. There can be several URLs--e.g.,
87 ;; tar.gz, tar.gz, etc. There can be correspond signature URLs, one per
89 (define-record-type* <upstream-source>
90 upstream-source make-upstream-source
92 (package upstream-source-package) ;string
93 (version upstream-source-version) ;string
94 (urls upstream-source-urls) ;list of strings
95 (signature-urls upstream-source-signature-urls ;#f | list of strings
97 (input-changes upstream-source-input-changes
98 (default '()) (thunked)))
100 ;; Representation of an upstream input change.
101 (define-record-type* <upstream-input-change>
102 upstream-input-change make-upstream-input-change
103 upstream-input-change?
104 (name upstream-input-change-name) ;string
105 (type upstream-input-change-type) ;symbol: regular | native | propagated
106 (action upstream-input-change-action)) ;symbol: add | remove
108 (define (changed-inputs package package-sexp)
109 "Return a list of input changes for PACKAGE based on the newly imported
110 S-expression PACKAGE-SEXP."
112 ((and expr ('package fields ...))
113 (let* ((input->name (match-lambda ((name pkg . out) name)))
117 ('quasiquote ((label ('unquote sym)) ...)))) label)
121 ((path *** ('native-inputs
122 ('quasiquote ((label ('unquote sym)) ...)))) label)
126 ((path *** ('propagated-inputs
127 ('quasiquote ((label ('unquote sym)) ...)))) label)
130 (map input->name (package-inputs package)))
132 (map input->name (package-native-inputs package)))
134 (map input->name (package-propagated-inputs package))))
139 (upstream-input-change
145 ,(lset-difference equal?
146 new-regular current-regular))
148 ,(lset-difference equal?
149 current-regular new-regular))
151 ,(lset-difference equal?
152 new-native current-native))
154 ,(lset-difference equal?
155 current-native new-native))
157 ,(lset-difference equal?
158 new-propagated current-propagated))
160 ,(lset-difference equal?
161 current-propagated new-propagated))))))
164 (define (url-prefix-predicate prefix)
165 "Return a predicate that returns true when passed a package where one of its
166 source URLs starts with PREFIX."
168 (define matching-uri?
171 (string-prefix? prefix uri))
175 (match (package-source package)
177 (match (origin-uri origin)
178 ((? matching-uri?) #t)
182 (define (upstream-source-archive-types release)
183 "Return the available types of archives for RELEASE---a list of strings such
184 as \"gz\" or \"xz\"."
185 (map file-extension (upstream-source-urls release)))
187 (define (coalesce-sources sources)
188 "Coalesce the elements of SOURCES, a list of <upstream-source>, that
189 correspond to the same version."
190 (define (same-version? r1 r2)
191 (string=? (upstream-source-version r1) (upstream-source-version r2)))
193 (define (release>? r1 r2)
194 (version>? (upstream-source-version r1) (upstream-source-version r2)))
196 (fold (lambda (release result)
199 (if (same-version? release head)
200 (cons (upstream-source
202 (urls (append (upstream-source-urls release)
203 (upstream-source-urls head)))
205 (let ((one (upstream-source-signature-urls release))
206 (two (upstream-source-signature-urls head)))
207 (and one two (append one two)))))
209 (cons release result)))
213 (sort sources release>?)))
220 (define-record-type* <upstream-updater>
221 upstream-updater make-upstream-updater
223 (name upstream-updater-name)
224 (description upstream-updater-description)
225 (pred upstream-updater-predicate)
226 (latest upstream-updater-latest))
228 (define (importer-modules)
229 "Return the list of importer modules."
230 (cons (resolve-interface '(guix gnu-maintenance))
231 (all-modules (map (lambda (entry)
232 `(,entry . "guix/import"))
234 #:warn warn-about-load-error)))
237 ;; The list of publically-known updaters.
238 (delay (fold-module-public-variables (lambda (obj result)
239 (if (upstream-updater? obj)
243 (importer-modules))))
245 (define (lookup-updater package updaters)
246 "Return an updater among UPDATERS that matches PACKAGE, or #f if none of
249 (($ <upstream-updater> name description pred latest)
250 (and (pred package) latest)))
253 (define (package-latest-release package updaters)
254 "Return an upstream source to update PACKAGE, a <package> object, or #f if
255 none of UPDATERS matches PACKAGE. It is the caller's responsibility to ensure
256 that the returned source is newer than the current one."
257 (match (lookup-updater package updaters)
258 ((? procedure? latest-release)
259 (latest-release package))
262 (define (package-latest-release* package updaters)
263 "Like 'package-latest-release', but ensure that the return source is newer
264 than that of PACKAGE."
265 (match (package-latest-release package updaters)
266 ((and source ($ <upstream-source> name version))
267 (and (version>? version (package-version package))
272 (define (uncompressed-tarball name tarball)
273 "Return a derivation that decompresses TARBALL."
274 (define (ref package)
275 (module-ref (resolve-interface '(gnu packages compression))
279 (cond ((or (string-suffix? ".gz" tarball)
280 (string-suffix? ".tgz" tarball))
281 (file-append (ref 'gzip) "/bin/gzip"))
282 ((string-suffix? ".bz2" tarball)
283 (file-append (ref 'bzip2) "/bin/bzip2"))
284 ((string-suffix? ".xz" tarball)
285 (file-append (ref 'xz) "/bin/xz"))
286 ((string-suffix? ".lz" tarball)
287 (file-append (ref 'lzip) "/bin/lzip"))
289 (error "unknown archive type" tarball))))
291 (gexp->derivation (file-sans-extension name)
293 (copy-file #+tarball #+name)
294 (and (zero? (system* #+compressor "-d" #+name))
295 (copy-file #+(file-sans-extension name)
298 (define* (download-tarball store url signature-url
299 #:key (key-download 'interactive))
300 "Download the tarball at URL to the store; check its OpenPGP signature at
301 SIGNATURE-URL, unless SIGNATURE-URL is false. On success, return the tarball
302 file name; return #f on failure (network failure or authentication failure).
303 KEY-DOWNLOAD specifies a download policy for missing OpenPGP keys; allowed
304 values: 'interactive' (default), 'always', and 'never'."
305 (let ((tarball (download-to-store store url)))
306 (if (not signature-url)
308 (let* ((sig (download-to-store store signature-url))
310 ;; Sometimes we get a signature over the uncompressed tarball.
311 ;; In that case, decompress the tarball in the store so that we
312 ;; can check the signature.
313 (data (if (string-prefix? (basename url)
314 (basename signature-url))
316 (run-with-store store
317 (mlet %store-monad ((drv (uncompressed-tarball
318 (basename url) tarball)))
320 (built-derivations (list drv))
321 (return (derivation->output-path drv)))))))
323 (ret (gnupg-verify* sig data #:key-download key-download)))
327 (warning (G_ "signature verification failed for `~a'~%")
329 (warning (G_ "(could be because the public key is not in your keyring)~%"))
332 (define (find2 pred lst1 lst2)
333 "Like 'find', but operate on items from both LST1 and LST2. Return two
334 values: the item from LST1 and the item from LST2 that match PRED."
335 (let loop ((lst1 lst1) (lst2 lst2))
340 (if (pred head1 head2)
342 (loop tail1 tail2)))))
346 (define* (package-update/url-fetch store package source
348 "Return the version, tarball, and SOURCE, to update PACKAGE to
349 SOURCE, an <upstream-source>."
351 (($ <upstream-source> _ version urls signature-urls)
352 (let*-values (((archive-type)
353 (match (and=> (package-source package) origin-uri)
355 (let ((type (file-extension (basename uri))))
356 ;; Sometimes we have URLs such as
357 ;; "https://github.com/…/tarball/v0.1", in which case
358 ;; we must not consider "1" as the extension.
359 (and (or (string-contains type "z")
360 (string=? type "tar"))
365 (find2 (lambda (url sig-url)
366 ;; Some URIs lack a file extension, like
367 ;; 'https://crates.io/???/0.1/download'. In that
368 ;; case, pick the first URL.
369 (or (not archive-type)
370 (string-suffix? archive-type url)))
372 (or signature-urls (circular-list #f)))))
373 (let ((tarball (download-tarball store url signature-url
374 #:key-download key-download)))
375 (values version tarball source))))))
377 (define %method-updates
378 ;; Mapping of origin methods to source update procedures.
379 `((,url-fetch . ,package-update/url-fetch)))
381 (define* (package-update store package updaters
382 #:key (key-download 'interactive))
383 "Return the new version, the file name of the new version tarball, and input
384 changes for PACKAGE; return #f (three values) when PACKAGE is up-to-date.
385 KEY-DOWNLOAD specifies a download policy for missing OpenPGP keys; allowed
386 values: 'always', 'never', and 'interactive' (default)."
387 (match (package-latest-release* package updaters)
388 ((? upstream-source? source)
389 (let ((method (match (package-source package)
391 (origin-method origin))
394 (match (assq method %method-updates)
396 (raise (condition (&message
397 (message (format #f (G_ "cannot download for \
401 (location (package-location package))))))
403 (update store package source
404 #:key-download key-download)))))
408 (define* (update-package-source package source hash)
409 "Modify the source file that defines PACKAGE to refer to SOURCE, an
410 <upstream-source> whose tarball has SHA256 HASH (a bytevector). Return the
411 new version string if an update was made, and #f otherwise."
412 (define (update-expression expr replacements)
413 ;; Apply REPLACEMENTS to package expression EXPR, a string. REPLACEMENTS
414 ;; must be a list of replacement pairs, either bytevectors or strings.
415 (fold (lambda (replacement str)
417 (((? bytevector? old-bv) . (? bytevector? new-bv))
418 (string-replace-substring
420 (bytevector->nix-base32-string old-bv)
421 (bytevector->nix-base32-string new-bv)))
423 (string-replace-substring str old new))))
427 (let ((name (package-name package))
428 (version (upstream-source-version source))
429 (version-loc (package-field-location package 'version)))
431 (let* ((loc (package-location package))
432 (old-version (package-version package))
433 (old-hash (origin-sha256 (package-source package)))
434 (old-url (match (origin-uri (package-source package))
435 ((? string? url) url)
437 (new-url (match (upstream-source-urls source)
438 ((first _ ...) first)))
439 (file (and=> (location-file loc)
440 (cut search-path %load-path <>))))
442 ;; Be sure to use absolute filename. Replace the URL directory
443 ;; when OLD-URL is available; this is useful notably for
444 ;; mirror://cpan/ URLs where the directory may change as a
445 ;; function of the person who uploads the package. Note that
446 ;; package definitions usually concatenate fragments of the URL,
447 ;; which is why we only attempt to replace a subset of the URL.
448 (let ((properties (assq-set! (location->source-properties loc)
450 (replacements `((,old-version . ,version)
452 ,@(if (and old-url new-url)
453 `((,(dirname old-url) .
456 (and (edit-expression properties
457 (cut update-expression <> replacements))
460 (warning (G_ "~a: could not locate source file")
464 (format (current-error-port)
465 (G_ "~a: ~a: no `version' field in source; skipping~%")
466 (location->string (package-location package))
469 ;;; upstream.scm ends here