packages: Generalize package module search.
[jackhill/guix/guix.git] / gnu / packages.scm
CommitLineData
233e7676 1;;; GNU Guix --- Functional package management for GNU
84836a57 2;;; Copyright © 2012, 2013, 2014 Ludovic Courtès <ludo@gnu.org>
c2868b1e 3;;; Copyright © 2013 Mark H Weaver <mhw@netris.org>
7d193ec3 4;;; Copyright © 2014 Eric Bavier <bavier@member.fsf.org>
6b1891b0 5;;;
233e7676 6;;; This file is part of GNU Guix.
6b1891b0 7;;;
233e7676 8;;; GNU Guix is free software; you can redistribute it and/or modify it
6b1891b0
LC
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;;;
233e7676 13;;; GNU Guix is distributed in the hope that it will be useful, but
6b1891b0
LC
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
233e7676 19;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
6b1891b0 20
59a43334 21(define-module (gnu packages)
6b1891b0 22 #:use-module (guix packages)
98eb8cbe 23 #:use-module (guix ui)
800cdeef 24 #:use-module (guix utils)
4ea44419
AK
25 #:use-module ((guix ftp-client) #:select (ftp-open))
26 #:use-module (guix gnu-maintenance)
6b1891b0 27 #:use-module (ice-9 ftw)
c2868b1e 28 #:use-module (ice-9 vlist)
dc5669cd 29 #:use-module (ice-9 match)
6b1891b0 30 #:use-module (srfi srfi-1)
5e3b388b 31 #:use-module (srfi srfi-11)
6b1891b0 32 #:use-module (srfi srfi-26)
800cdeef
LC
33 #:use-module (srfi srfi-39)
34 #:export (search-patch
ac5aa288 35 search-bootstrap-binary
800cdeef 36 %patch-directory
0b3651bc 37 %bootstrap-binaries-path
7d193ec3 38
ba326ce4 39 fold-packages
7d193ec3 40
dc5669cd 41 find-packages-by-name
3f26bfc1 42 find-best-packages-by-name
7d193ec3
EB
43 find-newest-available-packages
44
45 package-direct-dependents
46 package-transitive-dependents
4ea44419
AK
47 package-covering-dependents
48
5e3b388b
CR
49 check-package-freshness
50
51 specification->package))
6b1891b0
LC
52
53;;; Commentary:
54;;;
55;;; General utilities for the software distribution---i.e., the modules under
59a43334 56;;; (gnu packages ...).
6b1891b0
LC
57;;;
58;;; Code:
59
0b3651bc
LC
60;; By default, we store patches and bootstrap binaries alongside Guile
61;; modules. This is so that these extra files can be found without
62;; requiring a special setup, such as a specific installation directory
63;; and an extra environment variable. One advantage of this setup is
64;; that everything just works in an auto-compilation setting.
a9f60c42
LC
65
66(define %patch-path
800cdeef 67 (make-parameter
b211a661 68 (map (cut string-append <> "/gnu/packages/patches")
0b3651bc 69 %load-path)))
800cdeef 70
a9f60c42 71(define %bootstrap-binaries-path
ac5aa288 72 (make-parameter
1ffa7090 73 (map (cut string-append <> "/gnu/packages/bootstrap")
0b3651bc 74 %load-path)))
ac5aa288 75
800cdeef
LC
76(define (search-patch file-name)
77 "Search the patch FILE-NAME."
128663e4 78 (search-path (%patch-path) file-name))
800cdeef 79
ac5aa288
LC
80(define (search-bootstrap-binary file-name system)
81 "Search the bootstrap binary FILE-NAME for SYSTEM."
128663e4
LC
82 (search-path (%bootstrap-binaries-path)
83 (string-append system "/" file-name)))
ac5aa288 84
84836a57
LC
85(define %distro-root-directory
86 ;; Absolute file name of the module hierarchy.
87 (dirname (search-path %load-path "guix.scm")))
6b1891b0 88
84836a57
LC
89(define* (scheme-files directory)
90 "Return the list of Scheme files found under DIRECTORY."
6b1891b0
LC
91 (file-system-fold (const #t) ; enter?
92 (lambda (path stat result) ; leaf
93 (if (string-suffix? ".scm" path)
84836a57 94 (cons path result)
6b1891b0
LC
95 result))
96 (lambda (path stat result) ; down
97 result)
98 (lambda (path stat result) ; up
99 result)
100 (const #f) ; skip
101 (lambda (path stat errno result)
98eb8cbe
NK
102 (warning (_ "cannot access `~a': ~a~%")
103 path (strerror errno))
6b1891b0
LC
104 result)
105 '()
84836a57 106 directory
6b1891b0
LC
107 stat))
108
84836a57
LC
109(define (file-name->module-name file)
110 "Return the module name (a list of symbols) corresponding to FILE."
6b1891b0
LC
111 (define not-slash
112 (char-set-complement (char-set #\/)))
113
84836a57
LC
114 (map string->symbol
115 (string-tokenize (string-drop-right file 4) not-slash)))
116
117(define* (package-modules directory #:optional sub-directory)
118 "Return the list of modules that provide packages for the distribution.
119Optionally, narrow the search to SUB-DIRECTORY."
120 (define prefix-len
121 (string-length directory))
122
123 (filter-map (lambda (file)
124 (let ((file (substring file prefix-len)))
125 (false-if-exception
126 (resolve-interface (file-name->module-name file)))))
127 (scheme-files (if sub-directory
128 (string-append directory "/" sub-directory)
129 directory))))
6b1891b0 130
ba326ce4
LC
131(define (fold-packages proc init)
132 "Call (PROC PACKAGE RESULT) for each available package, using INIT as
c2868b1e
MW
133the initial value of RESULT. It is guaranteed to never traverse the
134same package twice."
135 (identity ; discard second return value
136 (fold2 (lambda (module result seen)
137 (fold2 (lambda (var result seen)
138 (if (and (package? var)
139 (not (vhash-assq var seen)))
140 (values (proc var result)
141 (vhash-consq var #t seen))
142 (values result seen)))
143 result
144 seen
145 (module-map (lambda (sym var)
146 (false-if-exception (variable-ref var)))
147 module)))
148 init
149 vlist-null
84836a57 150 (package-modules %distro-root-directory "gnu/packages"))))
ba326ce4 151
6b1891b0
LC
152(define* (find-packages-by-name name #:optional version)
153 "Return the list of packages with the given NAME. If VERSION is not #f,
154then only return packages whose version is equal to VERSION."
155 (define right-package?
156 (if version
157 (lambda (p)
ba326ce4 158 (and (string=? (package-name p) name)
6b1891b0
LC
159 (string=? (package-version p) version)))
160 (lambda (p)
ba326ce4
LC
161 (string=? (package-name p) name))))
162
163 (fold-packages (lambda (package result)
164 (if (right-package? package)
165 (cons package result)
166 result))
167 '()))
dc5669cd 168
3f26bfc1
LC
169(define find-newest-available-packages
170 (memoize
171 (lambda ()
172 "Return a vhash keyed by package names, and with
dc5669cd
MW
173associated values of the form
174
175 (newest-version newest-package ...)
176
177where the preferred package is listed first."
178
3f26bfc1
LC
179 ;; FIXME: Currently, the preferred package is whichever one
180 ;; was found last by 'fold-packages'. Find a better solution.
181 (fold-packages (lambda (p r)
182 (let ((name (package-name p))
183 (version (package-version p)))
184 (match (vhash-assoc name r)
185 ((_ newest-so-far . pkgs)
186 (case (version-compare version newest-so-far)
187 ((>) (vhash-cons name `(,version ,p) r))
188 ((=) (vhash-cons name `(,version ,p ,@pkgs) r))
189 ((<) r)))
190 (#f (vhash-cons name `(,version ,p) r)))))
191 vlist-null))))
192
193(define (find-best-packages-by-name name version)
194 "If version is #f, return the list of packages named NAME with the highest
195version numbers; otherwise, return the list of packages named NAME and at
196VERSION."
197 (if version
198 (find-packages-by-name name version)
199 (match (vhash-assoc name (find-newest-available-packages))
200 ((_ version pkgs ...) pkgs)
201 (#f '()))))
7d193ec3
EB
202
203\f
204(define* (vhash-refq vhash key #:optional (dflt #f))
205 "Look up KEY in the vhash VHASH, and return the value (if any) associated
206with it. If KEY is not found, return DFLT (or `#f' if no DFLT argument is
207supplied). Uses `eq?' for equality testing."
208 (or (and=> (vhash-assq key vhash) cdr)
209 dflt))
210
211(define package-dependencies
212 (memoize
213 (lambda ()
214 "Return a vhash keyed by package, and with associated values that are a
215list of packages that depend on that package."
216 (fold-packages
217 (lambda (package dag)
218 (fold
219 (lambda (in d)
220 ;; Insert a graph edge from each of package's inputs to package.
221 (vhash-consq in
222 (cons package (vhash-refq d in '()))
223 (vhash-delq in d)))
224 dag
225 (match (package-direct-inputs package)
226 (((labels packages . _) ...)
227 packages) )))
228 vlist-null))))
229
230(define (package-direct-dependents packages)
231 "Return a list of packages from the distribution that directly depend on the
232packages in PACKAGES."
233 (delete-duplicates
234 (concatenate
235 (map (lambda (p)
236 (vhash-refq (package-dependencies) p '()))
237 packages))))
238
239(define (package-transitive-dependents packages)
240 "Return the transitive dependent packages of the distribution packages in
241PACKAGES---i.e. the dependents of those packages, plus their dependents,
242recursively."
243 (let ((dependency-dag (package-dependencies)))
244 (fold-tree
245 cons '()
246 (lambda (node) (vhash-refq dependency-dag node))
247 ;; Start with the dependents to avoid including PACKAGES in the result.
248 (package-direct-dependents packages))))
249
250(define (package-covering-dependents packages)
251 "Return a minimal list of packages from the distribution whose dependencies
252include all of PACKAGES and all packages that depend on PACKAGES."
253 (let ((dependency-dag (package-dependencies)))
254 (fold-tree-leaves
255 cons '()
256 (lambda (node) (vhash-refq dependency-dag node))
257 ;; Start with the dependents to avoid including PACKAGES in the result.
258 (package-direct-dependents packages))))
4ea44419
AK
259
260\f
261(define %sigint-prompt
262 ;; The prompt to jump to upon SIGINT.
263 (make-prompt-tag "interruptible"))
264
265(define (call-with-sigint-handler thunk handler)
266 "Call THUNK and return its value. Upon SIGINT, call HANDLER with the signal
267number in the context of the continuation of the call to this function, and
268return its return value."
269 (call-with-prompt %sigint-prompt
270 (lambda ()
271 (sigaction SIGINT
272 (lambda (signum)
273 (sigaction SIGINT SIG_DFL)
274 (abort-to-prompt %sigint-prompt signum)))
275 (dynamic-wind
276 (const #t)
277 thunk
278 (cut sigaction SIGINT SIG_DFL)))
279 (lambda (k signum)
280 (handler signum))))
281
282(define-syntax-rule (waiting exp fmt rest ...)
283 "Display the given message while EXP is being evaluated."
284 (let* ((message (format #f fmt rest ...))
285 (blank (make-string (string-length message) #\space)))
286 (display message (current-error-port))
287 (force-output (current-error-port))
288 (call-with-sigint-handler
289 (lambda ()
290 (dynamic-wind
291 (const #f)
292 (lambda () exp)
293 (lambda ()
294 ;; Clear the line.
295 (display #\cr (current-error-port))
296 (display blank (current-error-port))
297 (display #\cr (current-error-port))
298 (force-output (current-error-port)))))
299 (lambda (signum)
300 (format (current-error-port) " interrupted by signal ~a~%" SIGINT)
301 #f))))
302
303(define ftp-open*
304 ;; Memoizing version of `ftp-open'. The goal is to avoid initiating a new
305 ;; FTP connection for each package, esp. since most of them are to the same
306 ;; server. This has a noticeable impact when doing "guix upgrade -u".
307 (memoize ftp-open))
308
309(define (check-package-freshness package)
310 "Check whether PACKAGE has a newer version available upstream, and report
311it."
312 ;; TODO: Automatically inject the upstream version when desired.
313
314 (catch #t
315 (lambda ()
316 (when (false-if-exception (gnu-package? package))
317 (let ((name (package-name package))
318 (full-name (package-full-name package)))
319 (match (waiting (latest-release name
320 #:ftp-open ftp-open*
321 #:ftp-close (const #f))
322 (_ "looking for the latest release of GNU ~a...") name)
323 ((latest-version . _)
324 (when (version>? latest-version full-name)
325 (format (current-error-port)
326 (_ "~a: note: using ~a \
327but ~a is available upstream~%")
328 (location->string (package-location package))
329 full-name latest-version)))
330 (_ #t)))))
331 (lambda (key . args)
332 ;; Silently ignore networking errors rather than preventing
333 ;; installation.
334 (case key
335 ((getaddrinfo-error ftp-error) #f)
336 (else (apply throw key args))))))
5e3b388b
CR
337
338(define (specification->package spec)
339 "Return a package matching SPEC. SPEC may be a package name, or a package
340name followed by a hyphen and a version number. If the version number is not
341present, return the preferred newest version."
342 (let-values (((name version)
343 (package-name->name+version spec)))
344 (match (find-best-packages-by-name name version)
345 ((p) ; one match
346 p)
347 ((p x ...) ; several matches
348 (warning (_ "ambiguous package specification `~a'~%") spec)
349 (warning (_ "choosing ~a from ~a~%")
350 (package-full-name p)
351 (location->string (package-location p)))
352 p)
353 (_ ; no matches
354 (if version
355 (leave (_ "~A: package not found for version ~a~%")
356 name version)
357 (leave (_ "~A: unknown package~%") name))))))