activation: Shared system home directories are now 555 and root-owned.
[jackhill/guix/guix.git] / guix / upstream.scm
CommitLineData
0a7c5a09 1;;; GNU Guix --- Functional package management for GNU
3c0128b0 2;;; Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 Ludovic Courtès <ludo@gnu.org>
7e6b490d 3;;; Copyright © 2015 Alex Kost <alezost@gmail.com>
7e634c2f 4;;; Copyright © 2019 Ricardo Wurmus <rekado@elephly.net>
0a7c5a09
LC
5;;;
6;;; This file is part of GNU Guix.
7;;;
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.
12;;;
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.
17;;;
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/>.
20
21(define-module (guix upstream)
22 #:use-module (guix records)
23 #:use-module (guix utils)
adf0c531 24 #:use-module (guix discovery)
0a7c5a09
LC
25 #:use-module ((guix download)
26 #:select (download-to-store))
0a7c5a09
LC
27 #:use-module (guix gnupg)
28 #:use-module (guix packages)
29 #:use-module (guix ui)
30 #:use-module (guix base32)
8d5d0628
LC
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)
0a7c5a09
LC
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 (ice-9 match)
41 #:use-module (ice-9 regex)
42 #:export (upstream-source
43 upstream-source?
44 upstream-source-package
45 upstream-source-version
46 upstream-source-urls
47 upstream-source-signature-urls
3195e19d 48 upstream-source-archive-types
7e634c2f 49 upstream-source-input-changes
0a7c5a09 50
97abc907 51 url-prefix-predicate
0a7c5a09
LC
52 coalesce-sources
53
54 upstream-updater
55 upstream-updater?
56 upstream-updater-name
7e6b490d 57 upstream-updater-description
0a7c5a09
LC
58 upstream-updater-predicate
59 upstream-updater-latest
60
7e634c2f
RW
61 upstream-input-change?
62 upstream-input-change-name
63 upstream-input-change-type
64 upstream-input-change-action
65 changed-inputs
66
adf0c531 67 %updaters
e9c72306
LC
68 lookup-updater
69
0a7c5a09 70 download-tarball
e9c72306
LC
71 package-latest-release
72 package-latest-release*
0a7c5a09
LC
73 package-update
74 update-package-source))
75
76;;; Commentary:
77;;;
78;;; This module provides tools to represent and manipulate a upstream source
79;;; code, and to auto-update package recipes.
80;;;
81;;; Code:
82
83;; Representation of upstream's source. There can be several URLs--e.g.,
84;; tar.gz, tar.gz, etc. There can be correspond signature URLs, one per
85;; source URL.
86(define-record-type* <upstream-source>
87 upstream-source make-upstream-source
88 upstream-source?
89 (package upstream-source-package) ;string
90 (version upstream-source-version) ;string
91 (urls upstream-source-urls) ;list of strings
92 (signature-urls upstream-source-signature-urls ;#f | list of strings
7e634c2f
RW
93 (default #f))
94 (input-changes upstream-source-input-changes
95 (default '()) (thunked)))
96
97;; Representation of an upstream input change.
98(define-record-type* <upstream-input-change>
99 upstream-input-change make-upstream-input-change
100 upstream-input-change?
101 (name upstream-input-change-name) ;string
102 (type upstream-input-change-type) ;symbol: regular | native | propagated
103 (action upstream-input-change-action)) ;symbol: add | remove
104
105(define (changed-inputs package package-sexp)
106 "Return a list of input changes for PACKAGE based on the newly imported
107S-expression PACKAGE-SEXP."
108 (match package-sexp
109 ((and expr ('package fields ...))
110 (let* ((input->name (match-lambda ((name pkg . out) name)))
111 (new-regular
112 (match expr
113 ((path *** ('inputs
114 ('quasiquote ((label ('unquote sym)) ...)))) label)
115 (_ '())))
116 (new-native
117 (match expr
118 ((path *** ('native-inputs
119 ('quasiquote ((label ('unquote sym)) ...)))) label)
120 (_ '())))
121 (new-propagated
122 (match expr
123 ((path *** ('propagated-inputs
124 ('quasiquote ((label ('unquote sym)) ...)))) label)
125 (_ '())))
126 (current-regular
127 (map input->name (package-inputs package)))
128 (current-native
129 (map input->name (package-native-inputs package)))
130 (current-propagated
131 (map input->name (package-propagated-inputs package))))
132 (append-map
133 (match-lambda
134 ((action type names)
135 (map (lambda (name)
136 (upstream-input-change
137 (name name)
138 (type type)
139 (action action)))
140 names)))
141 `((add regular
142 ,(lset-difference equal?
143 new-regular current-regular))
144 (remove regular
145 ,(lset-difference equal?
146 current-regular new-regular))
147 (add native
148 ,(lset-difference equal?
149 new-native current-native))
150 (remove native
151 ,(lset-difference equal?
152 current-native new-native))
153 (add propagated
154 ,(lset-difference equal?
155 new-propagated current-propagated))
156 (remove propagated
157 ,(lset-difference equal?
158 current-propagated new-propagated))))))
159 (_ '())))
0a7c5a09 160
97abc907
LC
161(define (url-prefix-predicate prefix)
162 "Return a predicate that returns true when passed a package where one of its
163source URLs starts with PREFIX."
164 (lambda (package)
165 (define matching-uri?
166 (match-lambda
167 ((? string? uri)
168 (string-prefix? prefix uri))
169 (_
170 #f)))
171
172 (match (package-source package)
173 ((? origin? origin)
174 (match (origin-uri origin)
175 ((? matching-uri?) #t)
176 (_ #f)))
177 (_ #f))))
178
0a7c5a09
LC
179(define (upstream-source-archive-types release)
180 "Return the available types of archives for RELEASE---a list of strings such
181as \"gz\" or \"xz\"."
182 (map file-extension (upstream-source-urls release)))
183
184(define (coalesce-sources sources)
185 "Coalesce the elements of SOURCES, a list of <upstream-source>, that
186correspond to the same version."
187 (define (same-version? r1 r2)
188 (string=? (upstream-source-version r1) (upstream-source-version r2)))
189
190 (define (release>? r1 r2)
191 (version>? (upstream-source-version r1) (upstream-source-version r2)))
192
193 (fold (lambda (release result)
194 (match result
195 ((head . tail)
196 (if (same-version? release head)
197 (cons (upstream-source
198 (inherit release)
199 (urls (append (upstream-source-urls release)
200 (upstream-source-urls head)))
201 (signature-urls
6efa6f76 202 (let ((one (upstream-source-signature-urls release))
f1eacbaf 203 (two (upstream-source-signature-urls head)))
6efa6f76 204 (and one two (append one two)))))
0a7c5a09
LC
205 tail)
206 (cons release result)))
207 (()
208 (list release))))
209 '()
210 (sort sources release>?)))
211
212\f
213;;;
214;;; Auto-update.
215;;;
216
7e6b490d
AK
217(define-record-type* <upstream-updater>
218 upstream-updater make-upstream-updater
0a7c5a09 219 upstream-updater?
7e6b490d
AK
220 (name upstream-updater-name)
221 (description upstream-updater-description)
222 (pred upstream-updater-predicate)
223 (latest upstream-updater-latest))
0a7c5a09 224
adf0c531
EF
225(define (importer-modules)
226 "Return the list of importer modules."
227 (cons (resolve-interface '(guix gnu-maintenance))
228 (all-modules (map (lambda (entry)
229 `(,entry . "guix/import"))
3c0128b0
LC
230 %load-path)
231 #:warn warn-about-load-error)))
adf0c531
EF
232
233(define %updaters
234 ;; The list of publically-known updaters.
235 (delay (fold-module-public-variables (lambda (obj result)
236 (if (upstream-updater? obj)
237 (cons obj result)
238 result))
239 '()
240 (importer-modules))))
241
0a7c5a09
LC
242(define (lookup-updater package updaters)
243 "Return an updater among UPDATERS that matches PACKAGE, or #f if none of
244them matches."
245 (any (match-lambda
3b2dc9ed 246 (($ <upstream-updater> name description pred latest)
0a7c5a09
LC
247 (and (pred package) latest)))
248 updaters))
249
e9c72306 250(define (package-latest-release package updaters)
7d27a025 251 "Return an upstream source to update PACKAGE, a <package> object, or #f if
e9c72306
LC
252none of UPDATERS matches PACKAGE. It is the caller's responsibility to ensure
253that the returned source is newer than the current one."
0a7c5a09
LC
254 (match (lookup-updater package updaters)
255 ((? procedure? latest-release)
e9c72306
LC
256 (latest-release package))
257 (_ #f)))
258
259(define (package-latest-release* package updaters)
260 "Like 'package-latest-release', but ensure that the return source is newer
261than that of PACKAGE."
262 (match (package-latest-release package updaters)
263 ((and source ($ <upstream-source> name version))
264 (and (version>? version (package-version package))
265 source))
266 (_
267 #f)))
0a7c5a09 268
8d5d0628
LC
269(define (uncompressed-tarball name tarball)
270 "Return a derivation that decompresses TARBALL."
271 (define (ref package)
272 (module-ref (resolve-interface '(gnu packages compression))
273 package))
274
275 (define compressor
276 (cond ((or (string-suffix? ".gz" tarball)
277 (string-suffix? ".tgz" tarball))
278 (file-append (ref 'gzip) "/bin/gzip"))
279 ((string-suffix? ".bz2" tarball)
280 (file-append (ref 'bzip2) "/bin/bzip2"))
281 ((string-suffix? ".xz" tarball)
282 (file-append (ref 'xz) "/bin/xz"))
283 ((string-suffix? ".lz" tarball)
284 (file-append (ref 'lzip) "/bin/lzip"))
285 (else
286 (error "unknown archive type" tarball))))
287
288 (gexp->derivation (file-sans-extension name)
289 #~(begin
290 (copy-file #+tarball #+name)
291 (and (zero? (system* #+compressor "-d" #+name))
292 (copy-file #+(file-sans-extension name)
293 #$output)))))
294
0a7c5a09
LC
295(define* (download-tarball store url signature-url
296 #:key (key-download 'interactive))
297 "Download the tarball at URL to the store; check its OpenPGP signature at
298SIGNATURE-URL, unless SIGNATURE-URL is false. On success, return the tarball
14959038
LC
299file name; return #f on failure (network failure or authentication failure).
300KEY-DOWNLOAD specifies a download policy for missing OpenPGP keys; allowed
301values: 'interactive' (default), 'always', and 'never'."
0a7c5a09
LC
302 (let ((tarball (download-to-store store url)))
303 (if (not signature-url)
304 tarball
8d5d0628
LC
305 (let* ((sig (download-to-store store signature-url))
306
307 ;; Sometimes we get a signature over the uncompressed tarball.
308 ;; In that case, decompress the tarball in the store so that we
309 ;; can check the signature.
310 (data (if (string-prefix? (basename url)
311 (basename signature-url))
312 tarball
313 (run-with-store store
314 (mlet %store-monad ((drv (uncompressed-tarball
315 (basename url) tarball)))
316 (mbegin %store-monad
317 (built-derivations (list drv))
318 (return (derivation->output-path drv)))))))
319
320 (ret (gnupg-verify* sig data #:key-download key-download)))
0a7c5a09
LC
321 (if ret
322 tarball
323 (begin
69daee23 324 (warning (G_ "signature verification failed for `~a'~%")
0a7c5a09 325 url)
69daee23 326 (warning (G_ "(could be because the public key is not in your keyring)~%"))
0a7c5a09
LC
327 #f))))))
328
329(define (find2 pred lst1 lst2)
330 "Like 'find', but operate on items from both LST1 and LST2. Return two
331values: the item from LST1 and the item from LST2 that match PRED."
332 (let loop ((lst1 lst1) (lst2 lst2))
333 (match lst1
334 ((head1 . tail1)
335 (match lst2
336 ((head2 . tail2)
337 (if (pred head1 head2)
338 (values head1 head2)
339 (loop tail1 tail2)))))
340 (()
341 (values #f #f)))))
342
343(define* (package-update store package updaters
344 #:key (key-download 'interactive))
7e634c2f
RW
345 "Return the new version, the file name of the new version tarball, and input
346changes for PACKAGE; return #f (three values) when PACKAGE is up-to-date.
347KEY-DOWNLOAD specifies a download policy for missing OpenPGP keys; allowed
348values: 'always', 'never', and 'interactive' (default)."
e9c72306 349 (match (package-latest-release* package updaters)
7e634c2f 350 (($ <upstream-source> _ version urls signature-urls changes)
0a7c5a09
LC
351 (let*-values (((name)
352 (package-name package))
353 ((archive-type)
354 (match (and=> (package-source package) origin-uri)
355 ((? string? uri)
618f0582
LC
356 (let ((type (file-extension (basename uri))))
357 ;; Sometimes we have URLs such as
358 ;; "https://github.com/…/tarball/v0.1", in which case
359 ;; we must not consider "1" as the extension.
360 (and (or (string-contains type "z")
361 (string=? type "tar"))
362 type)))
0a7c5a09
LC
363 (_
364 "gz")))
365 ((url signature-url)
366 (find2 (lambda (url sig-url)
af72a21a
LC
367 ;; Some URIs lack a file extension, like
368 ;; 'https://crates.io/???/0.1/download'. In that
369 ;; case, pick the first URL.
370 (or (not archive-type)
371 (string-suffix? archive-type url)))
0a7c5a09
LC
372 urls
373 (or signature-urls (circular-list #f)))))
374 (let ((tarball (download-tarball store url signature-url
375 #:key-download key-download)))
7e634c2f 376 (values version tarball changes))))
0a7c5a09 377 (#f
7e634c2f 378 (values #f #f #f))))
0a7c5a09
LC
379
380(define (update-package-source package version hash)
381 "Modify the source file that defines PACKAGE to refer to VERSION,
382whose tarball has SHA256 HASH (a bytevector). Return the new version string
383if an update was made, and #f otherwise."
2b8e9d9e
SB
384 (define (update-expression expr old-version version old-hash hash)
385 ;; Update package expression EXPR, replacing occurrences OLD-VERSION by
386 ;; VERSION and occurrences of OLD-HASH by HASH (base32 representation
387 ;; thereof).
0a7c5a09
LC
388 (let ((old-hash (bytevector->nix-base32-string old-hash))
389 (hash (bytevector->nix-base32-string hash)))
2b8e9d9e
SB
390 (string-replace-substring
391 (string-replace-substring expr old-hash hash)
392 old-version version)))
393
394 (let ((name (package-name package))
395 (version-loc (package-field-location package 'version)))
396 (if version-loc
397 (let* ((loc (package-location package))
398 (old-version (package-version package))
399 (old-hash (origin-sha256 (package-source package)))
400 (file (and=> (location-file loc)
401 (cut search-path %load-path <>))))
0a7c5a09 402 (if file
2b8e9d9e
SB
403 (and (edit-expression
404 ;; Be sure to use absolute filename.
405 (assq-set! (location->source-properties loc)
406 'filename file)
407 (cut update-expression <>
408 old-version version old-hash hash))
409 version)
0a7c5a09 410 (begin
69daee23 411 (warning (G_ "~a: could not locate source file")
0a7c5a09
LC
412 (location-file loc))
413 #f)))
414 (begin
415 (format (current-error-port)
69daee23 416 (G_ "~a: ~a: no `version' field in source; skipping~%")
0a7c5a09
LC
417 (location->string (package-location package))
418 name)))))
419
420;;; upstream.scm ends here