gnu: xpra: Update to 2.1.2.
[guix.git] / guix / upstream.scm
blob5083e6b80548f0015409665a24b9dd95d3f6c4a8
1 ;;; GNU Guix --- Functional package management for GNU
2 ;;; Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017 Ludovic Courtès <ludo@gnu.org>
3 ;;; Copyright © 2015 Alex Kost <alezost@gmail.com>
4 ;;;
5 ;;; This file is part of GNU Guix.
6 ;;;
7 ;;; GNU Guix is free software; you can redistribute it and/or modify it
8 ;;; under the terms of the GNU General Public License as published by
9 ;;; the Free Software Foundation; either version 3 of the License, or (at
10 ;;; your option) any later version.
11 ;;;
12 ;;; GNU Guix is distributed in the hope that it will be useful, but
13 ;;; WITHOUT ANY WARRANTY; without even the implied warranty of
14 ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 ;;; GNU General Public License for more details.
16 ;;;
17 ;;; You should have received a copy of the GNU General Public License
18 ;;; along with GNU Guix.  If not, see <http://www.gnu.org/licenses/>.
20 (define-module (guix upstream)
21   #:use-module (guix records)
22   #:use-module (guix utils)
23   #:use-module ((guix download)
24                 #:select (download-to-store))
25   #:use-module (guix gnupg)
26   #:use-module (guix packages)
27   #:use-module (guix ui)
28   #:use-module (guix base32)
29   #:use-module (guix gexp)
30   #:use-module (guix store)
31   #:use-module ((guix derivations)
32                 #:select (built-derivations derivation->output-path))
33   #:use-module (guix monads)
34   #:use-module (srfi srfi-1)
35   #:use-module (srfi srfi-9)
36   #:use-module (srfi srfi-11)
37   #:use-module (srfi srfi-26)
38   #:use-module (ice-9 match)
39   #:use-module (ice-9 regex)
40   #:export (upstream-source
41             upstream-source?
42             upstream-source-package
43             upstream-source-version
44             upstream-source-urls
45             upstream-source-signature-urls
46             upstream-source-archive-types
48             coalesce-sources
50             upstream-updater
51             upstream-updater?
52             upstream-updater-name
53             upstream-updater-description
54             upstream-updater-predicate
55             upstream-updater-latest
57             lookup-updater
59             download-tarball
60             package-latest-release
61             package-latest-release*
62             package-update
63             update-package-source))
65 ;;; Commentary:
66 ;;;
67 ;;; This module provides tools to represent and manipulate a upstream source
68 ;;; code, and to auto-update package recipes.
69 ;;;
70 ;;; Code:
72 ;; Representation of upstream's source.  There can be several URLs--e.g.,
73 ;; tar.gz, tar.gz, etc.  There can be correspond signature URLs, one per
74 ;; source URL.
75 (define-record-type* <upstream-source>
76   upstream-source make-upstream-source
77   upstream-source?
78   (package        upstream-source-package)        ;string
79   (version        upstream-source-version)        ;string
80   (urls           upstream-source-urls)           ;list of strings
81   (signature-urls upstream-source-signature-urls  ;#f | list of strings
82                   (default #f)))
84 (define (upstream-source-archive-types release)
85   "Return the available types of archives for RELEASE---a list of strings such
86 as \"gz\" or \"xz\"."
87   (map file-extension (upstream-source-urls release)))
89 (define (coalesce-sources sources)
90   "Coalesce the elements of SOURCES, a list of <upstream-source>, that
91 correspond to the same version."
92   (define (same-version? r1 r2)
93     (string=? (upstream-source-version r1) (upstream-source-version r2)))
95   (define (release>? r1 r2)
96     (version>? (upstream-source-version r1) (upstream-source-version r2)))
98   (fold (lambda (release result)
99           (match result
100             ((head . tail)
101              (if (same-version? release head)
102                  (cons (upstream-source
103                         (inherit release)
104                         (urls (append (upstream-source-urls release)
105                                       (upstream-source-urls head)))
106                         (signature-urls
107                          (let ((one (upstream-source-signature-urls release))
108                                (two (upstream-source-signature-urls head)))
109                            (and one two (append one two)))))
110                        tail)
111                  (cons release result)))
112             (()
113              (list release))))
114         '()
115         (sort sources release>?)))
119 ;;; Auto-update.
122 (define-record-type* <upstream-updater>
123   upstream-updater make-upstream-updater
124   upstream-updater?
125   (name        upstream-updater-name)
126   (description upstream-updater-description)
127   (pred        upstream-updater-predicate)
128   (latest      upstream-updater-latest))
130 (define (lookup-updater package updaters)
131   "Return an updater among UPDATERS that matches PACKAGE, or #f if none of
132 them matches."
133   (any (match-lambda
134          (($ <upstream-updater> name description pred latest)
135           (and (pred package) latest)))
136        updaters))
138 (define (package-latest-release package updaters)
139   "Return an upstream source to update PACKAGE, a <package> object, or #f if
140 none of UPDATERS matches PACKAGE.  It is the caller's responsibility to ensure
141 that the returned source is newer than the current one."
142   (match (lookup-updater package updaters)
143     ((? procedure? latest-release)
144      (latest-release package))
145     (_ #f)))
147 (define (package-latest-release* package updaters)
148   "Like 'package-latest-release', but ensure that the return source is newer
149 than that of PACKAGE."
150   (match (package-latest-release package updaters)
151     ((and source ($ <upstream-source> name version))
152      (and (version>? version (package-version package))
153           source))
154     (_
155      #f)))
157 (define (uncompressed-tarball name tarball)
158   "Return a derivation that decompresses TARBALL."
159   (define (ref package)
160     (module-ref (resolve-interface '(gnu packages compression))
161                 package))
163   (define compressor
164     (cond ((or (string-suffix? ".gz" tarball)
165                (string-suffix? ".tgz" tarball))
166            (file-append (ref 'gzip) "/bin/gzip"))
167           ((string-suffix? ".bz2" tarball)
168            (file-append (ref 'bzip2) "/bin/bzip2"))
169           ((string-suffix? ".xz" tarball)
170            (file-append (ref 'xz) "/bin/xz"))
171           ((string-suffix? ".lz" tarball)
172            (file-append (ref 'lzip) "/bin/lzip"))
173           (else
174            (error "unknown archive type" tarball))))
176   (gexp->derivation (file-sans-extension name)
177                     #~(begin
178                         (copy-file #+tarball #+name)
179                         (and (zero? (system* #+compressor "-d" #+name))
180                              (copy-file #+(file-sans-extension name)
181                                         #$output)))))
183 (define* (download-tarball store url signature-url
184                            #:key (key-download 'interactive))
185   "Download the tarball at URL to the store; check its OpenPGP signature at
186 SIGNATURE-URL, unless SIGNATURE-URL is false.  On success, return the tarball
187 file name; return #f on failure (network failure or authentication failure).
188 KEY-DOWNLOAD specifies a download policy for missing OpenPGP keys; allowed
189 values: 'interactive' (default), 'always', and 'never'."
190   (let ((tarball (download-to-store store url)))
191     (if (not signature-url)
192         tarball
193         (let* ((sig  (download-to-store store signature-url))
195                ;; Sometimes we get a signature over the uncompressed tarball.
196                ;; In that case, decompress the tarball in the store so that we
197                ;; can check the signature.
198                (data (if (string-prefix? (basename url)
199                                          (basename signature-url))
200                          tarball
201                          (run-with-store store
202                            (mlet %store-monad ((drv (uncompressed-tarball
203                                                      (basename url) tarball)))
204                              (mbegin %store-monad
205                                (built-derivations (list drv))
206                                (return (derivation->output-path drv)))))))
208                (ret  (gnupg-verify* sig data #:key-download key-download)))
209           (if ret
210               tarball
211               (begin
212                 (warning (G_ "signature verification failed for `~a'~%")
213                          url)
214                 (warning (G_ "(could be because the public key is not in your keyring)~%"))
215                 #f))))))
217 (define (find2 pred lst1 lst2)
218   "Like 'find', but operate on items from both LST1 and LST2.  Return two
219 values: the item from LST1 and the item from LST2 that match PRED."
220   (let loop ((lst1 lst1) (lst2 lst2))
221     (match lst1
222       ((head1 . tail1)
223        (match lst2
224          ((head2 . tail2)
225           (if (pred head1 head2)
226               (values head1 head2)
227               (loop tail1 tail2)))))
228       (()
229        (values #f #f)))))
231 (define* (package-update store package updaters
232                          #:key (key-download 'interactive))
233   "Return the new version and the file name of the new version tarball for
234 PACKAGE, or #f and #f when PACKAGE is up-to-date.  KEY-DOWNLOAD specifies a
235 download policy for missing OpenPGP keys; allowed values: 'always', 'never',
236 and 'interactive' (default)."
237   (match (package-latest-release* package updaters)
238     (($ <upstream-source> _ version urls signature-urls)
239      (let*-values (((name)
240                     (package-name package))
241                    ((archive-type)
242                     (match (and=> (package-source package) origin-uri)
243                       ((? string? uri)
244                        (file-extension (basename uri)))
245                       (_
246                        "gz")))
247                    ((url signature-url)
248                     (find2 (lambda (url sig-url)
249                              ;; Some URIs lack a file extension, like
250                              ;; 'https://crates.io/???/0.1/download'.  In that
251                              ;; case, pick the first URL.
252                              (or (not archive-type)
253                                  (string-suffix? archive-type url)))
254                            urls
255                            (or signature-urls (circular-list #f)))))
256        (let ((tarball (download-tarball store url signature-url
257                                         #:key-download key-download)))
258          (values version tarball))))
259     (#f
260      (values #f #f))))
262 (define (update-package-source package version hash)
263   "Modify the source file that defines PACKAGE to refer to VERSION,
264 whose tarball has SHA256 HASH (a bytevector).  Return the new version string
265 if an update was made, and #f otherwise."
266   (define (update-expression expr old-version version old-hash hash)
267     ;; Update package expression EXPR, replacing occurrences OLD-VERSION by
268     ;; VERSION and occurrences of OLD-HASH by HASH (base32 representation
269     ;; thereof).
270     (let ((old-hash (bytevector->nix-base32-string old-hash))
271           (hash     (bytevector->nix-base32-string hash)))
272       (string-replace-substring
273        (string-replace-substring expr old-hash hash)
274        old-version version)))
276   (let ((name        (package-name package))
277         (version-loc (package-field-location package 'version)))
278     (if version-loc
279         (let* ((loc         (package-location package))
280                (old-version (package-version package))
281                (old-hash    (origin-sha256 (package-source package)))
282                (file        (and=> (location-file loc)
283                                    (cut search-path %load-path <>))))
284           (if file
285               (and (edit-expression
286                     ;; Be sure to use absolute filename.
287                     (assq-set! (location->source-properties loc)
288                                'filename file)
289                     (cut update-expression <>
290                          old-version version old-hash hash))
291                    version)
292               (begin
293                 (warning (G_ "~a: could not locate source file")
294                          (location-file loc))
295                 #f)))
296         (begin
297           (format (current-error-port)
298                   (G_ "~a: ~a: no `version' field in source; skipping~%")
299                   (location->string (package-location package))
300                   name)))))
302 ;;; upstream.scm ends here