Commit | Line | Data |
---|---|---|
233e7676 | 1 | ;;; GNU Guix --- Functional package management for GNU |
98fefb21 | 2 | ;;; Copyright © 2010, 2011, 2012, 2013 Ludovic Courtès <ludo@gnu.org> |
f9bbf2a8 | 3 | ;;; Copyright © 2012, 2013 Nikita Karetnikov <nikita@karetnikov.org> |
22189ed1 | 4 | ;;; |
233e7676 | 5 | ;;; This file is part of GNU Guix. |
22189ed1 | 6 | ;;; |
233e7676 | 7 | ;;; GNU Guix is free software; you can redistribute it and/or modify it |
22189ed1 NK |
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 | ;;; | |
233e7676 | 12 | ;;; GNU Guix is distributed in the hope that it will be useful, but |
22189ed1 NK |
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 | |
233e7676 | 18 | ;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>. |
22189ed1 NK |
19 | |
20 | (define-module (guix gnu-maintenance) | |
21 | #:use-module (web uri) | |
22 | #:use-module (web client) | |
23 | #:use-module (web response) | |
24 | #:use-module (ice-9 regex) | |
f9bbf2a8 | 25 | #:use-module (ice-9 rdelim) |
98fefb21 | 26 | #:use-module (ice-9 match) |
22189ed1 NK |
27 | #:use-module (srfi srfi-1) |
28 | #:use-module (srfi srfi-11) | |
29 | #:use-module (srfi srfi-26) | |
98fefb21 LC |
30 | #:use-module (system foreign) |
31 | #:use-module (guix ftp-client) | |
98eb8cbe | 32 | #:use-module (guix ui) |
0d1e6ce4 | 33 | #:use-module (guix utils) |
ef010c0f | 34 | #:use-module (guix packages) |
f9bbf2a8 NK |
35 | #:export (gnu-package-name |
36 | gnu-package-mundane-name | |
37 | gnu-package-copyright-holder | |
38 | gnu-package-savannah | |
39 | gnu-package-fsd | |
40 | gnu-package-language | |
41 | gnu-package-logo | |
42 | gnu-package-doc-category | |
43 | gnu-package-doc-summary | |
44 | gnu-package-doc-urls | |
45 | gnu-package-download-url | |
46 | ||
47 | official-gnu-packages | |
48 | find-packages | |
ef010c0f | 49 | gnu-package? |
f9bbf2a8 | 50 | |
98fefb21 LC |
51 | releases |
52 | latest-release | |
53 | gnu-package-name->name+version)) | |
54 | ||
55 | ;;; Commentary: | |
56 | ;;; | |
57 | ;;; Code for dealing with the maintenance of GNU packages, such as | |
58 | ;;; auto-updates. | |
59 | ;;; | |
60 | ;;; Code: | |
61 | ||
62 | \f | |
63 | ;;; | |
64 | ;;; List of GNU packages. | |
65 | ;;; | |
22189ed1 NK |
66 | |
67 | (define (http-fetch uri) | |
f9bbf2a8 | 68 | "Return an input port containing the textual data at URI, a string." |
22189ed1 | 69 | (let*-values (((resp data) |
ef8c0340 LC |
70 | (let ((uri (string->uri uri))) |
71 | ;; Try hard to use the API du jour to get an input port. | |
72 | (if (version>? "2.0.7" (version)) | |
73 | (if (defined? 'http-get*) | |
74 | (http-get* uri) | |
75 | (http-get uri)) ; old Guile, returns a string | |
76 | (http-get uri #:streaming? #t)))) ; 2.0.8 or later | |
22189ed1 NK |
77 | ((code) |
78 | (response-code resp))) | |
79 | (case code | |
80 | ((200) | |
ef8c0340 | 81 | (cond ((not data) |
f9bbf2a8 NK |
82 | (begin |
83 | ;; XXX: Guile 2.0.5 and earlier did not support chunked transfer | |
84 | ;; encoding, which is required when fetching %PACKAGE-LIST-URL | |
85 | ;; (see <http://lists.gnu.org/archive/html/guile-devel/2011-09/msg00089.html>). | |
86 | ;; Since users may still be using these versions, warn them and | |
87 | ;; bail out. | |
98eb8cbe NK |
88 | (warning (_ "using Guile ~a, ~a ~s encoding~%") |
89 | (version) | |
90 | "which does not support HTTP" | |
91 | (response-transfer-encoding resp)) | |
92 | (leave (_ "download failed; use a newer Guile~%") | |
f9bbf2a8 | 93 | uri resp))) |
ef8c0340 | 94 | ((string? data) ; old `http-get' returns a string |
f9bbf2a8 | 95 | (open-input-string data)) |
ef8c0340 LC |
96 | (else ; input port |
97 | data))) | |
22189ed1 | 98 | (else |
f9bbf2a8 | 99 | (error "download failed" uri code |
22189ed1 NK |
100 | (response-reason-phrase resp)))))) |
101 | ||
102 | (define %package-list-url | |
103 | (string-append "http://cvs.savannah.gnu.org/" | |
104 | "viewvc/*checkout*/gnumaint/" | |
105 | "gnupackages.txt?root=womb")) | |
106 | ||
f9bbf2a8 NK |
107 | (define-record-type* <gnu-package-descriptor> |
108 | gnu-package-descriptor | |
109 | make-gnu-package-descriptor | |
110 | ||
111 | gnu-package-descriptor? | |
112 | ||
113 | (name gnu-package-name) | |
114 | (mundane-name gnu-package-mundane-name) | |
115 | (copyright-holder gnu-package-copyright-holder) | |
116 | (savannah gnu-package-savannah) | |
117 | (fsd gnu-package-fsd) | |
118 | (language gnu-package-language) | |
119 | (logo gnu-package-logo) | |
120 | (doc-category gnu-package-doc-category) | |
121 | (doc-summary gnu-package-doc-summary) | |
122 | (doc-urls gnu-package-doc-urls) | |
123 | (download-url gnu-package-download-url)) | |
124 | ||
22189ed1 | 125 | (define (official-gnu-packages) |
f9bbf2a8 NK |
126 | "Return a list of records, which are GNU packages." |
127 | (define (group-package-fields port state) | |
128 | ;; Return a list of alists. Each alist contains fields of a GNU | |
129 | ;; package. | |
130 | (let ((line (read-line port)) | |
131 | (field-rx (make-regexp "^([[:graph:]]+): (.*)$")) | |
132 | (doc-urls-rx (make-regexp "^doc-url: (.*)$")) | |
133 | (end-rx (make-regexp "^# End. .+Do not remove this line.+"))) | |
134 | ||
135 | (define (match-field str) | |
136 | ;; Packages are separated by empty strings. If STR is an | |
137 | ;; empty string, create a new list to store fields of a | |
138 | ;; different package. Otherwise, match and create a key-value | |
139 | ;; pair. | |
140 | (match str | |
141 | ("" | |
142 | (group-package-fields port (cons '() state))) | |
143 | (str | |
144 | (cond ((regexp-exec doc-urls-rx str) | |
145 | => | |
146 | (lambda (match) | |
147 | (if (equal? (assoc-ref (first state) "doc-urls") #f) | |
148 | (group-package-fields | |
149 | port (cons (cons (cons "doc-urls" | |
150 | (list | |
151 | (match:substring match 1))) | |
152 | (first state)) | |
153 | (drop state 1))) | |
154 | (group-package-fields | |
155 | port (cons (cons (cons "doc-urls" | |
156 | (cons (match:substring match 1) | |
157 | (assoc-ref (first state) | |
158 | "doc-urls"))) | |
159 | (assoc-remove! (first state) | |
160 | "doc-urls")) | |
161 | (drop state 1)))))) | |
162 | ((regexp-exec field-rx str) | |
163 | => | |
164 | (lambda (match) | |
165 | (group-package-fields | |
166 | port (cons (cons (cons (match:substring match 1) | |
167 | (match:substring match 2)) | |
168 | (first state)) | |
169 | (drop state 1))))) | |
170 | (else (group-package-fields port state)))))) | |
171 | ||
172 | (if (or (eof-object? line) | |
173 | (regexp-exec end-rx line)) ; don't include dummy fields | |
174 | (remove null-list? state) | |
175 | (match-field line)))) | |
176 | ||
177 | (define (alist->record alist make keys) | |
178 | ;; Apply MAKE, which should be a syntactic constructor, to the | |
179 | ;; values associated with KEYS in ALIST. | |
180 | (let ((args (map (cut assoc-ref alist <>) keys))) | |
181 | (apply make args))) | |
182 | ||
183 | (reverse | |
184 | (map (lambda (alist) | |
185 | (alist->record alist | |
186 | make-gnu-package-descriptor | |
187 | (list "package" "mundane-name" "copyright-holder" | |
188 | "savannah" "fsd" "language" "logo" | |
189 | "doc-category" "doc-summary" "doc-urls" | |
190 | "download-url"))) | |
191 | (group-package-fields (http-fetch %package-list-url) | |
192 | '(()))))) | |
22189ed1 | 193 | |
f9bbf2a8 NK |
194 | (define (find-packages regexp) |
195 | "Find GNU packages which satisfy REGEXP." | |
196 | (let ((name-rx (make-regexp regexp))) | |
197 | (filter (lambda (package) | |
198 | (false-if-exception | |
199 | (regexp-exec name-rx (gnu-package-name package)))) | |
200 | (official-gnu-packages)))) | |
ef010c0f LC |
201 | |
202 | (define gnu-package? | |
203 | (memoize | |
204 | (lambda (package) | |
205 | "Return true if PACKAGE is a GNU package. This procedure may access the | |
206 | network to check in GNU's database." | |
207 | ;; TODO: Find a way to determine that a package is non-GNU without going | |
208 | ;; through the network. | |
f9bbf2a8 NK |
209 | (let ((url (and=> (package-source package) origin-uri)) |
210 | (name (package-name package))) | |
296540a6 | 211 | (or (and (string? url) (string-prefix? "mirror://gnu" url)) |
f9bbf2a8 | 212 | (and (member name (map gnu-package-name (official-gnu-packages))) |
296540a6 | 213 | #t)))))) |
ef010c0f | 214 | |
98fefb21 LC |
215 | \f |
216 | ;;; | |
217 | ;;; Latest release. | |
218 | ;;; | |
219 | ||
220 | (define (ftp-server/directory project) | |
221 | "Return the FTP server and directory where PROJECT's tarball are | |
222 | stored." | |
223 | (define quirks | |
224 | '(("commoncpp2" "ftp.gnu.org" "/gnu/commoncpp") | |
225 | ("ucommon" "ftp.gnu.org" "/gnu/commoncpp") | |
226 | ("libzrtpcpp" "ftp.gnu.org" "/gnu/ccrtp") | |
227 | ("libosip2" "ftp.gnu.org" "/gnu/osip") | |
228 | ("libgcrypt" "ftp.gnupg.org" "/gcrypt/libgcrypt") | |
229 | ("libgpg-error" "ftp.gnupg.org" "/gcrypt/libgpg-error") | |
230 | ("libassuan" "ftp.gnupg.org" "/gcrypt/libassuan") | |
231 | ("gnupg" "ftp.gnupg.org" "/gcrypt/gnupg") | |
232 | ("freefont-ttf" "ftp.gnu.org" "/gnu/freefont") | |
233 | ("gnu-ghostscript" "ftp.gnu.org" "/gnu/ghostscript") | |
234 | ("mit-scheme" "ftp.gnu.org" "/gnu/mit-scheme/stable.pkg") | |
235 | ("icecat" "ftp.gnu.org" "/gnu/gnuzilla") | |
236 | ("source-highlight" "ftp.gnu.org" "/gnu/src-highlite") | |
237 | ("TeXmacs" "ftp.texmacs.org" "/TeXmacs/targz"))) | |
238 | ||
239 | (match (assoc project quirks) | |
240 | ((_ server directory) | |
241 | (values server directory)) | |
242 | (_ | |
243 | (values "ftp.gnu.org" (string-append "/gnu/" project))))) | |
244 | ||
245 | (define (releases project) | |
246 | "Return the list of releases of PROJECT as a list of release name/directory | |
247 | pairs. Example: (\"mit-scheme-9.0.1\" . \"/gnu/mit-scheme/stable.pkg/9.0.1\"). " | |
248 | ;; TODO: Parse something like fencepost.gnu.org:/gd/gnuorg/packages-ftp. | |
249 | (define release-rx | |
250 | (make-regexp (string-append "^" project | |
251 | "-([0-9]|[^-])*(-src)?\\.tar\\."))) | |
252 | ||
253 | (define alpha-rx | |
254 | (make-regexp "^.*-.*[0-9](-|~)?(alpha|beta|rc|cvs|svn|git)-?[0-9\\.]*\\.tar\\.")) | |
255 | ||
256 | (define (sans-extension tarball) | |
257 | (let ((end (string-contains tarball ".tar"))) | |
258 | (substring tarball 0 end))) | |
259 | ||
6a917ef7 LC |
260 | (define (release-file file) |
261 | ;; Return #f if FILE is not a release tarball, otherwise return | |
262 | ;; PACKAGE-VERSION. | |
263 | (and (not (string-suffix? ".sig" file)) | |
264 | (regexp-exec release-rx file) | |
265 | (not (regexp-exec alpha-rx file)) | |
266 | (let ((s (sans-extension file))) | |
267 | (and (regexp-exec %package-name-rx s) s)))) | |
268 | ||
98fefb21 LC |
269 | (let-values (((server directory) (ftp-server/directory project))) |
270 | (define conn (ftp-open server)) | |
271 | ||
272 | (let loop ((directories (list directory)) | |
273 | (result '())) | |
6a917ef7 LC |
274 | (match directories |
275 | (() | |
276 | (ftp-close conn) | |
277 | result) | |
278 | ((directory rest ...) | |
279 | (let* ((files (ftp-list conn directory)) | |
280 | (subdirs (filter-map (match-lambda | |
281 | ((name 'directory . _) name) | |
282 | (_ #f)) | |
283 | files))) | |
284 | (loop (append (map (cut string-append directory "/" <>) | |
285 | subdirs) | |
286 | rest) | |
287 | (append | |
288 | ;; Filter out signatures, deltas, and files which | |
289 | ;; are potentially not releases of PROJECT--e.g., | |
290 | ;; in /gnu/guile, filter out guile-oops and | |
291 | ;; guile-www; in mit-scheme, filter out binaries. | |
292 | (filter-map (match-lambda | |
293 | ((file 'file . _) | |
294 | (and=> (release-file file) | |
295 | (cut cons <> directory))) | |
296 | (_ #f)) | |
297 | files) | |
298 | result)))))))) | |
98fefb21 | 299 | |
98fefb21 LC |
300 | (define (latest-release project) |
301 | "Return (\"FOO-X.Y\" . \"/bar/foo\") or #f." | |
302 | (let ((releases (releases project))) | |
303 | (and (not (null? releases)) | |
304 | (fold (lambda (release latest) | |
0d1e6ce4 | 305 | (if (version>? (car release) (car latest)) |
98fefb21 LC |
306 | release |
307 | latest)) | |
308 | '("" . "") | |
309 | releases)))) | |
310 | ||
311 | (define %package-name-rx | |
312 | ;; Regexp for a package name, e.g., "foo-X.Y". Since TeXmacs uses | |
313 | ;; "TeXmacs-X.Y-src", the `-src' suffix is allowed. | |
314 | (make-regexp "^(.*)-(([0-9]|\\.)+)(-src)?")) | |
315 | ||
316 | (define (gnu-package-name->name+version name+version) | |
317 | "Return the package name and version number extracted from NAME+VERSION." | |
318 | (let ((match (regexp-exec %package-name-rx name+version))) | |
319 | (if (not match) | |
320 | (values name+version #f) | |
321 | (values (match:substring match 1) (match:substring match 2))))) | |
322 | ||
323 | ;;; gnu-maintenance.scm ends here |