Merge branch 'master' into python-build-system
[jackhill/guix/guix.git] / guix / upstream.scm
1 ;;; GNU Guix --- Functional package management for GNU
2 ;;; Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016 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/>.
19
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 (srfi srfi-1)
30 #:use-module (srfi srfi-9)
31 #:use-module (srfi srfi-11)
32 #:use-module (srfi srfi-26)
33 #:use-module (ice-9 match)
34 #:use-module (ice-9 regex)
35 #:export (upstream-source
36 upstream-source?
37 upstream-source-package
38 upstream-source-version
39 upstream-source-urls
40 upstream-source-signature-urls
41 upstream-source-archive-types
42
43 coalesce-sources
44
45 upstream-updater
46 upstream-updater?
47 upstream-updater-name
48 upstream-updater-description
49 upstream-updater-predicate
50 upstream-updater-latest
51
52 lookup-updater
53
54 download-tarball
55 package-latest-release
56 package-latest-release*
57 package-update
58 update-package-source))
59
60 ;;; Commentary:
61 ;;;
62 ;;; This module provides tools to represent and manipulate a upstream source
63 ;;; code, and to auto-update package recipes.
64 ;;;
65 ;;; Code:
66
67 ;; Representation of upstream's source. There can be several URLs--e.g.,
68 ;; tar.gz, tar.gz, etc. There can be correspond signature URLs, one per
69 ;; source URL.
70 (define-record-type* <upstream-source>
71 upstream-source make-upstream-source
72 upstream-source?
73 (package upstream-source-package) ;string
74 (version upstream-source-version) ;string
75 (urls upstream-source-urls) ;list of strings
76 (signature-urls upstream-source-signature-urls ;#f | list of strings
77 (default #f)))
78
79 (define (upstream-source-archive-types release)
80 "Return the available types of archives for RELEASE---a list of strings such
81 as \"gz\" or \"xz\"."
82 (map file-extension (upstream-source-urls release)))
83
84 (define (coalesce-sources sources)
85 "Coalesce the elements of SOURCES, a list of <upstream-source>, that
86 correspond to the same version."
87 (define (same-version? r1 r2)
88 (string=? (upstream-source-version r1) (upstream-source-version r2)))
89
90 (define (release>? r1 r2)
91 (version>? (upstream-source-version r1) (upstream-source-version r2)))
92
93 (fold (lambda (release result)
94 (match result
95 ((head . tail)
96 (if (same-version? release head)
97 (cons (upstream-source
98 (inherit release)
99 (urls (append (upstream-source-urls release)
100 (upstream-source-urls head)))
101 (signature-urls
102 (let ((one (upstream-source-signature-urls release))
103 (two (upstream-source-signature-urls head)))
104 (and one two (append one two)))))
105 tail)
106 (cons release result)))
107 (()
108 (list release))))
109 '()
110 (sort sources release>?)))
111
112 \f
113 ;;;
114 ;;; Auto-update.
115 ;;;
116
117 (define-record-type* <upstream-updater>
118 upstream-updater make-upstream-updater
119 upstream-updater?
120 (name upstream-updater-name)
121 (description upstream-updater-description)
122 (pred upstream-updater-predicate)
123 (latest upstream-updater-latest))
124
125 (define (lookup-updater package updaters)
126 "Return an updater among UPDATERS that matches PACKAGE, or #f if none of
127 them matches."
128 (any (match-lambda
129 (($ <upstream-updater> _ _ pred latest)
130 (and (pred package) latest)))
131 updaters))
132
133 (define (package-latest-release package updaters)
134 "Return an upstream source to update PACKAGE, a <package> object, or #f if
135 none of UPDATERS matches PACKAGE. It is the caller's responsibility to ensure
136 that the returned source is newer than the current one."
137 (match (lookup-updater package updaters)
138 ((? procedure? latest-release)
139 (latest-release package))
140 (_ #f)))
141
142 (define (package-latest-release* package updaters)
143 "Like 'package-latest-release', but ensure that the return source is newer
144 than that of PACKAGE."
145 (match (package-latest-release package updaters)
146 ((and source ($ <upstream-source> name version))
147 (and (version>? version (package-version package))
148 source))
149 (_
150 #f)))
151
152 (define* (download-tarball store url signature-url
153 #:key (key-download 'interactive))
154 "Download the tarball at URL to the store; check its OpenPGP signature at
155 SIGNATURE-URL, unless SIGNATURE-URL is false. On success, return the tarball
156 file name; return #f on failure (network failure or authentication failure).
157 KEY-DOWNLOAD specifies a download policy for missing OpenPGP keys; allowed
158 values: 'interactive' (default), 'always', and 'never'."
159 (let ((tarball (download-to-store store url)))
160 (if (not signature-url)
161 tarball
162 (let* ((sig (download-to-store store signature-url))
163 (ret (gnupg-verify* sig tarball #:key-download key-download)))
164 (if ret
165 tarball
166 (begin
167 (warning (_ "signature verification failed for `~a'~%")
168 url)
169 (warning (_ "(could be because the public key is not in your keyring)~%"))
170 #f))))))
171
172 (define (find2 pred lst1 lst2)
173 "Like 'find', but operate on items from both LST1 and LST2. Return two
174 values: the item from LST1 and the item from LST2 that match PRED."
175 (let loop ((lst1 lst1) (lst2 lst2))
176 (match lst1
177 ((head1 . tail1)
178 (match lst2
179 ((head2 . tail2)
180 (if (pred head1 head2)
181 (values head1 head2)
182 (loop tail1 tail2)))))
183 (()
184 (values #f #f)))))
185
186 (define* (package-update store package updaters
187 #:key (key-download 'interactive))
188 "Return the new version and the file name of the new version tarball for
189 PACKAGE, or #f and #f when PACKAGE is up-to-date. KEY-DOWNLOAD specifies a
190 download policy for missing OpenPGP keys; allowed values: 'always', 'never',
191 and 'interactive' (default)."
192 (match (package-latest-release* package updaters)
193 (($ <upstream-source> _ version urls signature-urls)
194 (let*-values (((name)
195 (package-name package))
196 ((archive-type)
197 (match (and=> (package-source package) origin-uri)
198 ((? string? uri)
199 (or (file-extension uri) "gz"))
200 (_
201 "gz")))
202 ((url signature-url)
203 (find2 (lambda (url sig-url)
204 (string-suffix? archive-type url))
205 urls
206 (or signature-urls (circular-list #f)))))
207 (let ((tarball (download-tarball store url signature-url
208 #:key-download key-download)))
209 (values version tarball))))
210 (#f
211 (values #f #f))))
212
213 (define (update-package-source package version hash)
214 "Modify the source file that defines PACKAGE to refer to VERSION,
215 whose tarball has SHA256 HASH (a bytevector). Return the new version string
216 if an update was made, and #f otherwise."
217 (define (update-expression expr old-version version old-hash hash)
218 ;; Update package expression EXPR, replacing occurrences OLD-VERSION by
219 ;; VERSION and occurrences of OLD-HASH by HASH (base32 representation
220 ;; thereof).
221 (let ((old-hash (bytevector->nix-base32-string old-hash))
222 (hash (bytevector->nix-base32-string hash)))
223 (string-replace-substring
224 (string-replace-substring expr old-hash hash)
225 old-version version)))
226
227 (let ((name (package-name package))
228 (version-loc (package-field-location package 'version)))
229 (if version-loc
230 (let* ((loc (package-location package))
231 (old-version (package-version package))
232 (old-hash (origin-sha256 (package-source package)))
233 (file (and=> (location-file loc)
234 (cut search-path %load-path <>))))
235 (if file
236 (and (edit-expression
237 ;; Be sure to use absolute filename.
238 (assq-set! (location->source-properties loc)
239 'filename file)
240 (cut update-expression <>
241 old-version version old-hash hash))
242 version)
243 (begin
244 (warning (_ "~a: could not locate source file")
245 (location-file loc))
246 #f)))
247 (begin
248 (format (current-error-port)
249 (_ "~a: ~a: no `version' field in source; skipping~%")
250 (location->string (package-location package))
251 name)))))
252
253 ;;; upstream.scm ends here