X-Git-Url: https://git.hcoop.net/jackhill/guix/guix.git/blobdiff_plain/c08ea55e7ec25261e4596bf6726a83c1ed056b94..7e6d8d366a61f951936ed83371877ce006f679f6:/gnu/packages.scm diff --git a/gnu/packages.scm b/gnu/packages.scm index 5629061788..a1814205f9 100644 --- a/gnu/packages.scm +++ b/gnu/packages.scm @@ -1,5 +1,5 @@ ;;; GNU Guix --- Functional package management for GNU -;;; Copyright © 2012, 2013, 2014, 2015, 2016, 2017 Ludovic Courtès +;;; Copyright © 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019 Ludovic Courtès ;;; Copyright © 2013 Mark H Weaver ;;; Copyright © 2014 Eric Bavier ;;; Copyright © 2016, 2017 Alex Kost @@ -28,10 +28,14 @@ #:use-module (guix memoization) #:use-module ((guix build utils) #:select ((package-name->name+version - . hyphen-separated-name->name+version))) + . hyphen-separated-name->name+version) + mkdir-p)) #:autoload (guix profiles) (packages->manifest) + #:use-module (guix describe) #:use-module (ice-9 vlist) #:use-module (ice-9 match) + #:autoload (ice-9 binary-ports) (put-bytevector) + #:autoload (system base compile) (compile) #:use-module (srfi srfi-1) #:use-module (srfi srfi-11) #:use-module (srfi srfi-26) @@ -46,16 +50,21 @@ %auxiliary-files-path %bootstrap-binaries-path %package-module-path + %default-package-module-path fold-packages + fold-available-packages find-packages-by-name + find-package-locations find-best-packages-by-name - find-newest-available-packages specification->package specification->package+output - specifications->manifest)) + specification->location + specifications->manifest + + generate-package-cache)) ;;; Commentary: ;;; @@ -110,8 +119,37 @@ for system '~a'") file-name system))))))) (define %distro-root-directory - ;; Absolute file name of the module hierarchy. - (dirname (search-path %load-path "guix.scm"))) + ;; Absolute file name of the module hierarchy. Since (gnu packages …) might + ;; live in a directory different from (guix), try to get the best match. + (letrec-syntax ((dirname* (syntax-rules () + ((_ file) + (dirname file)) + ((_ file head tail ...) + (dirname (dirname* file tail ...))))) + (try (syntax-rules () + ((_ (file things ...) rest ...) + (match (search-path %load-path file) + (#f + (try rest ...)) + (absolute + (dirname* absolute things ...)))) + ((_) + #f)))) + (try ("gnu/packages/base.scm" gnu/ packages/) + ("gnu/packages.scm" gnu/) + ("guix.scm")))) + +(define %default-package-module-path + ;; Default search path for package modules. + `((,%distro-root-directory . "gnu/packages"))) + +(define (cache-is-authoritative?) + "Return true if the pre-computed package cache is authoritative. It is not +authoritative when entries have been added via GUIX_PACKAGE_PATH or '-L' +flags." + (equal? (%package-module-path) + (append %default-package-module-path + (package-path-entries)))) (define %package-module-path ;; Search path for package modules. Each item must be either a directory @@ -119,16 +157,21 @@ for system '~a'") ;; to narrow the search. (let* ((not-colon (char-set-complement (char-set #\:))) (environment (string-tokenize (or (getenv "GUIX_PACKAGE_PATH") "") - not-colon))) - ;; Automatically add items from $GUIX_PACKAGE_PATH to Guile's search path. - (for-each (lambda (directory) - (set! %load-path (cons directory %load-path)) - (set! %load-compiled-path - (cons directory %load-compiled-path))) - environment) + not-colon)) + (channels (package-path-entries))) + ;; Automatically add channels and items from $GUIX_PACKAGE_PATH to Guile's + ;; search path. For historical reasons, $GUIX_PACKAGE_PATH goes to the + ;; front; channels go to the back so that they don't override Guix' own + ;; modules. + (set! %load-path + (append environment %load-path channels)) + (set! %load-compiled-path + (append environment %load-compiled-path channels)) (make-parameter - (append environment `((,%distro-root-directory . "gnu/packages")))))) + (append environment + %default-package-module-path + channels)))) (define %patch-path ;; Define it after '%package-module-path' so that '%load-path' contains user @@ -140,19 +183,95 @@ for system '~a'") directory)) %load-path))) -(define (fold-packages proc init) - "Call (PROC PACKAGE RESULT) for each available package, using INIT as -the initial value of RESULT. It is guaranteed to never traverse the -same package twice." +(define (fold-available-packages proc init) + "Fold PROC over the list of available packages. For each available package, +PROC is called along these lines: + + (PROC NAME VERSION RESULT + #:outputs OUTPUTS + #:location LOCATION + …) + +PROC can use #:allow-other-keys to ignore the bits it's not interested in. +When a package cache is available, this procedure does not actually load any +package module." + (define cache + (load-package-cache (current-profile))) + + (if (and cache (cache-is-authoritative?)) + (vhash-fold (lambda (name vector result) + (match vector + (#(name version module symbol outputs + supported? deprecated? + file line column) + (proc name version result + #:outputs outputs + #:location (and file + (location file line column)) + #:supported? supported? + #:deprecated? deprecated?)))) + init + cache) + (fold-packages (lambda (package result) + (proc (package-name package) + (package-version package) + result + #:outputs (package-outputs package) + #:location (package-location package) + #:supported? + (->bool + (member (%current-system) + (package-supported-systems package))) + #:deprecated? + (->bool + (package-superseded package)))) + init))) + +(define* (fold-packages proc init + #:optional + (modules (all-modules (%package-module-path) + #:warn + warn-about-load-error)) + #:key (select? (negate hidden-package?))) + "Call (PROC PACKAGE RESULT) for each available package defined in one of +MODULES that matches SELECT?, using INIT as the initial value of RESULT. It +is guaranteed to never traverse the same package twice." (fold-module-public-variables (lambda (object result) - (if (and (package? object) - (not (hidden-package? object))) + (if (and (package? object) (select? object)) (proc object result) result)) init - (all-modules (%package-module-path)))) - -(define find-packages-by-name + modules)) + +(define %package-cache-file + ;; Location of the package cache. + "/lib/guix/package.cache") + +(define load-package-cache + (mlambda (profile) + "Attempt to load the package cache. On success return a vhash keyed by +package names. Return #f on failure." + (match profile + (#f #f) + (profile + (catch 'system-error + (lambda () + (define lst + (load-compiled (string-append profile %package-cache-file))) + (fold (lambda (item vhash) + (match item + (#(name version module symbol outputs + supported? deprecated? + file line column) + (vhash-cons name item vhash)))) + vlist-null + lst)) + (lambda args + (if (= ENOENT (system-error-errno args)) + #f + (apply throw args)))))))) + +(define find-packages-by-name/direct ;bypass the cache (let ((packages (delay (fold-packages (lambda (p r) (vhash-cons (package-name p) p r)) @@ -167,32 +286,65 @@ decreasing version order." version>?))) (if version (filter (lambda (package) - (string-prefix? version (package-version package))) + (version-prefix? version (package-version package))) matching) matching))))) -(define find-newest-available-packages - (mlambda () - "Return a vhash keyed by package names, and with -associated values of the form - - (newest-version newest-package ...) - -where the preferred package is listed first." - - ;; FIXME: Currently, the preferred package is whichever one - ;; was found last by 'fold-packages'. Find a better solution. - (fold-packages (lambda (p r) - (let ((name (package-name p)) - (version (package-version p))) - (match (vhash-assoc name r) - ((_ newest-so-far . pkgs) - (case (version-compare version newest-so-far) - ((>) (vhash-cons name `(,version ,p) r)) - ((=) (vhash-cons name `(,version ,p ,@pkgs) r)) - ((<) r))) - (#f (vhash-cons name `(,version ,p) r))))) - vlist-null))) +(define (cache-lookup cache name) + "Lookup package NAME in CACHE. Return a list sorted in increasing version +order." + (define (package-version? (vector-ref v2 1) (vector-ref v1 1))) + + (sort (vhash-fold* cons '() name cache) + package-versionbool (member (%current-system) + (package-supported-systems package))) + ,(->bool (package-superseded package)) + ,@(let ((loc (package-location package))) + (if loc + `(,(location-file loc) + ,(location-line loc) + ,(location-column loc)) + '(#f #f #f)))) + result))) + (_ + result))) + + (define exp + (fold-module-public-variables* expand-cache '() + (all-modules (%package-module-path) + #:warn + warn-about-load-error))) + + (mkdir-p (dirname cache-file)) + (call-with-output-file cache-file + (lambda (port) + ;; Store the cache as a '.go' file. This makes loading fast and reduces + ;; heap usage since some of the static data is directly mmapped. + (put-bytevector port + (compile `'(,@exp) + #:to 'bytecode + #:opts '(#:to-file? #t))))) + cache-file) (define %sigint-prompt @@ -258,6 +465,30 @@ present, return the preferred newest version." (let-values (((name version) (package-name->name+version spec))) (%find-package spec name version))) +(define (specification->location spec) + "Return the location of the highest-numbered package matching SPEC, a +specification such as \"guile@2\" or \"emacs\"." + (let-values (((name version) (package-name->name+version spec))) + (match (find-package-locations name version) + (() + (if version + (leave (G_ "~A: package not found for version ~a~%") name version) + (leave (G_ "~A: unknown package~%") name))) + (lst + (let* ((highest (match lst (((version . _) _ ...) version))) + (locations (take-while (match-lambda + ((version . location) + (string=? version highest))) + lst))) + (match locations + (((version . location) . rest) + (unless (null? rest) + (warning (G_ "ambiguous package specification `~a'~%") spec) + (warning (G_ "choosing ~a@~a from ~a~%") + name version + (location->string location))) + location))))))) + (define* (specification->package+output spec #:optional (output "out")) "Return the package and output specified by SPEC, or #f and #f; SPEC may optionally contain a version number and an output name, as in these examples: