channels: Compute a package cache and use it.
* gnu/packages.scm (cache-is-authoritative?, load-package-cache) (cache-lookup, generate-package-cache): New procedures. (%package-cache-file): New variable. (find-packages-by-name): Rename to... (find-packages-by-name/direct): ... this. (find-packages-by-name): Rewrite to use the package cache when 'cache-is-authoritative?' returns true. * tests/packages.scm ("find-packages-by-name + version, with cache") ("find-packages-by-name with cache"): New tests. * guix/channels.scm (package-cache-file): New procedure. (%channel-profile-hooks): New variable. (channel-instances->derivation): Use it in #:hooks. * guix/scripts/package.scm (build-and-use-profile): Add #:hooks and honor it. * guix/scripts/pull.scm (build-and-install): Pass #:hooks to UPDATE-PROFILE.
This commit is contained in:
parent
1d90e9d7c9
commit
5fbdc9a5aa
127
gnu/packages.scm
127
gnu/packages.scm
|
@ -28,11 +28,14 @@
|
||||||
#:use-module (guix memoization)
|
#:use-module (guix memoization)
|
||||||
#:use-module ((guix build utils)
|
#:use-module ((guix build utils)
|
||||||
#:select ((package-name->name+version
|
#:select ((package-name->name+version
|
||||||
. hyphen-separated-name->name+version)))
|
. hyphen-separated-name->name+version)
|
||||||
|
mkdir-p))
|
||||||
#:autoload (guix profiles) (packages->manifest)
|
#:autoload (guix profiles) (packages->manifest)
|
||||||
#:use-module (guix describe)
|
#:use-module (guix describe)
|
||||||
#:use-module (ice-9 vlist)
|
#:use-module (ice-9 vlist)
|
||||||
#:use-module (ice-9 match)
|
#: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-1)
|
||||||
#:use-module (srfi srfi-11)
|
#:use-module (srfi srfi-11)
|
||||||
#:use-module (srfi srfi-26)
|
#:use-module (srfi srfi-26)
|
||||||
|
@ -56,7 +59,9 @@
|
||||||
|
|
||||||
specification->package
|
specification->package
|
||||||
specification->package+output
|
specification->package+output
|
||||||
specifications->manifest))
|
specifications->manifest
|
||||||
|
|
||||||
|
generate-package-cache))
|
||||||
|
|
||||||
;;; Commentary:
|
;;; Commentary:
|
||||||
;;;
|
;;;
|
||||||
|
@ -135,6 +140,14 @@ for system '~a'")
|
||||||
;; Default search path for package modules.
|
;; Default search path for package modules.
|
||||||
`((,%distro-root-directory . "gnu/packages")))
|
`((,%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
|
(define %package-module-path
|
||||||
;; Search path for package modules. Each item must be either a directory
|
;; Search path for package modules. Each item must be either a directory
|
||||||
;; name or a pair whose car is a directory and whose cdr is a sub-directory
|
;; name or a pair whose car is a directory and whose cdr is a sub-directory
|
||||||
|
@ -183,7 +196,35 @@ is guaranteed to never traverse the same package twice."
|
||||||
init
|
init
|
||||||
modules))
|
modules))
|
||||||
|
|
||||||
(define find-packages-by-name
|
(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
|
(let ((packages (delay
|
||||||
(fold-packages (lambda (p r)
|
(fold-packages (lambda (p r)
|
||||||
(vhash-cons (package-name p) p r))
|
(vhash-cons (package-name p) p r))
|
||||||
|
@ -202,6 +243,37 @@ decreasing version order."
|
||||||
matching)
|
matching)
|
||||||
matching)))))
|
matching)))))
|
||||||
|
|
||||||
|
(define (cache-lookup cache name)
|
||||||
|
"Lookup package NAME in CACHE. Return a list sorted in increasing version
|
||||||
|
order."
|
||||||
|
(define (package-version<? v1 v2)
|
||||||
|
(version>? (vector-ref v2 1) (vector-ref v1 1)))
|
||||||
|
|
||||||
|
(sort (vhash-fold* cons '() name cache)
|
||||||
|
package-version<?))
|
||||||
|
|
||||||
|
(define* (find-packages-by-name name #:optional version)
|
||||||
|
"Return the list of packages with the given NAME. If VERSION is not #f,
|
||||||
|
then only return packages whose version is prefixed by VERSION, sorted in
|
||||||
|
decreasing version order."
|
||||||
|
(define cache
|
||||||
|
(load-package-cache (current-profile)))
|
||||||
|
|
||||||
|
(if (and (cache-is-authoritative?) cache)
|
||||||
|
(match (cache-lookup cache name)
|
||||||
|
(#f #f)
|
||||||
|
((#(_ versions modules symbols _ _ _ _ _ _) ...)
|
||||||
|
(fold (lambda (version* module symbol result)
|
||||||
|
(if (or (not version)
|
||||||
|
(version-prefix? version version*))
|
||||||
|
(cons (module-ref (resolve-interface module)
|
||||||
|
symbol)
|
||||||
|
result)
|
||||||
|
result))
|
||||||
|
'()
|
||||||
|
versions modules symbols)))
|
||||||
|
(find-packages-by-name/direct name version)))
|
||||||
|
|
||||||
(define (find-best-packages-by-name name version)
|
(define (find-best-packages-by-name name version)
|
||||||
"If version is #f, return the list of packages named NAME with the highest
|
"If version is #f, return the list of packages named NAME with the highest
|
||||||
version numbers; otherwise, return the list of packages named NAME and at
|
version numbers; otherwise, return the list of packages named NAME and at
|
||||||
|
@ -218,6 +290,55 @@ VERSION."
|
||||||
(string=? (package-version p) highest))
|
(string=? (package-version p) highest))
|
||||||
matches))))))
|
matches))))))
|
||||||
|
|
||||||
|
(define (generate-package-cache directory)
|
||||||
|
"Generate under DIRECTORY a cache of all the available packages.
|
||||||
|
|
||||||
|
The primary purpose of the cache is to speed up package lookup by name such
|
||||||
|
that we don't have to traverse and load all the package modules, thereby also
|
||||||
|
reducing the memory footprint."
|
||||||
|
(define cache-file
|
||||||
|
(string-append directory %package-cache-file))
|
||||||
|
|
||||||
|
(define (expand-cache module symbol variable result)
|
||||||
|
(match (false-if-exception (variable-ref variable))
|
||||||
|
((? package? package)
|
||||||
|
(if (hidden-package? package)
|
||||||
|
result
|
||||||
|
(cons `#(,(package-name package)
|
||||||
|
,(package-version package)
|
||||||
|
,(module-name module)
|
||||||
|
,symbol
|
||||||
|
,(package-outputs package)
|
||||||
|
,(->bool (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
|
(define %sigint-prompt
|
||||||
;; The prompt to jump to upon SIGINT.
|
;; The prompt to jump to upon SIGINT.
|
||||||
|
|
|
@ -21,6 +21,7 @@
|
||||||
#:use-module (guix git)
|
#:use-module (guix git)
|
||||||
#:use-module (guix records)
|
#:use-module (guix records)
|
||||||
#:use-module (guix gexp)
|
#:use-module (guix gexp)
|
||||||
|
#:use-module (guix modules)
|
||||||
#:use-module (guix discovery)
|
#:use-module (guix discovery)
|
||||||
#:use-module (guix monads)
|
#:use-module (guix monads)
|
||||||
#:use-module (guix profiles)
|
#:use-module (guix profiles)
|
||||||
|
@ -31,7 +32,8 @@
|
||||||
#:use-module (srfi srfi-2)
|
#:use-module (srfi srfi-2)
|
||||||
#:use-module (srfi srfi-9)
|
#:use-module (srfi srfi-9)
|
||||||
#:use-module (srfi srfi-11)
|
#:use-module (srfi srfi-11)
|
||||||
#:autoload (guix self) (whole-package)
|
#:autoload (guix self) (whole-package make-config.scm)
|
||||||
|
#:autoload (guix inferior) (gexp->derivation-in-inferior) ;FIXME: circular dep
|
||||||
#:use-module (ice-9 match)
|
#:use-module (ice-9 match)
|
||||||
#:export (channel
|
#:export (channel
|
||||||
channel?
|
channel?
|
||||||
|
@ -52,6 +54,7 @@
|
||||||
checkout->channel-instance
|
checkout->channel-instance
|
||||||
latest-channel-derivation
|
latest-channel-derivation
|
||||||
channel-instances->manifest
|
channel-instances->manifest
|
||||||
|
%channel-profile-hooks
|
||||||
channel-instances->derivation))
|
channel-instances->derivation))
|
||||||
|
|
||||||
;;; Commentary:
|
;;; Commentary:
|
||||||
|
@ -416,11 +419,40 @@ channel instances."
|
||||||
(zip instances derivations))))
|
(zip instances derivations))))
|
||||||
(return (manifest entries))))
|
(return (manifest entries))))
|
||||||
|
|
||||||
|
(define (package-cache-file manifest)
|
||||||
|
"Build a package cache file for the instance in MANIFEST. This is meant to
|
||||||
|
be used as a profile hook."
|
||||||
|
(mlet %store-monad ((profile (profile-derivation manifest
|
||||||
|
#:hooks '())))
|
||||||
|
|
||||||
|
(define build
|
||||||
|
#~(begin
|
||||||
|
(use-modules (gnu packages))
|
||||||
|
|
||||||
|
(if (defined? 'generate-package-cache)
|
||||||
|
(begin
|
||||||
|
;; Delegate package cache generation to the inferior.
|
||||||
|
(format (current-error-port)
|
||||||
|
"Generating package cache for '~a'...~%"
|
||||||
|
#$profile)
|
||||||
|
(generate-package-cache #$output))
|
||||||
|
(mkdir #$output))))
|
||||||
|
|
||||||
|
(gexp->derivation-in-inferior "guix-package-cache" build
|
||||||
|
profile
|
||||||
|
#:properties '((type . profile-hook)
|
||||||
|
(hook . package-cache)))))
|
||||||
|
|
||||||
|
(define %channel-profile-hooks
|
||||||
|
;; The default channel profile hooks.
|
||||||
|
(cons package-cache-file %default-profile-hooks))
|
||||||
|
|
||||||
(define (channel-instances->derivation instances)
|
(define (channel-instances->derivation instances)
|
||||||
"Return the derivation of the profile containing INSTANCES, a list of
|
"Return the derivation of the profile containing INSTANCES, a list of
|
||||||
channel instances."
|
channel instances."
|
||||||
(mlet %store-monad ((manifest (channel-instances->manifest instances)))
|
(mlet %store-monad ((manifest (channel-instances->manifest instances)))
|
||||||
(profile-derivation manifest)))
|
(profile-derivation manifest
|
||||||
|
#:hooks %channel-profile-hooks)))
|
||||||
|
|
||||||
(define latest-channel-instances*
|
(define latest-channel-instances*
|
||||||
(store-lift latest-channel-instances))
|
(store-lift latest-channel-instances))
|
||||||
|
|
|
@ -120,21 +120,21 @@ denote ranges as interpreted by 'matching-generations'."
|
||||||
|
|
||||||
(define* (build-and-use-profile store profile manifest
|
(define* (build-and-use-profile store profile manifest
|
||||||
#:key
|
#:key
|
||||||
|
(hooks %default-profile-hooks)
|
||||||
allow-collisions?
|
allow-collisions?
|
||||||
bootstrap? use-substitutes?
|
bootstrap? use-substitutes?
|
||||||
dry-run?)
|
dry-run?)
|
||||||
"Build a new generation of PROFILE, a file name, using the packages
|
"Build a new generation of PROFILE, a file name, using the packages
|
||||||
specified in MANIFEST, a manifest object. When ALLOW-COLLISIONS? is true,
|
specified in MANIFEST, a manifest object. When ALLOW-COLLISIONS? is true,
|
||||||
do not treat collisions in MANIFEST as an error."
|
do not treat collisions in MANIFEST as an error. HOOKS is a list of \"profile
|
||||||
|
hooks\" run when building the profile."
|
||||||
(when (equal? profile %current-profile)
|
(when (equal? profile %current-profile)
|
||||||
(ensure-default-profile))
|
(ensure-default-profile))
|
||||||
|
|
||||||
(let* ((prof-drv (run-with-store store
|
(let* ((prof-drv (run-with-store store
|
||||||
(profile-derivation manifest
|
(profile-derivation manifest
|
||||||
#:allow-collisions? allow-collisions?
|
#:allow-collisions? allow-collisions?
|
||||||
#:hooks (if bootstrap?
|
#:hooks (if bootstrap? '() hooks)
|
||||||
'()
|
|
||||||
%default-profile-hooks)
|
|
||||||
#:locales? (not bootstrap?))))
|
#:locales? (not bootstrap?))))
|
||||||
(prof (derivation->output-path prof-drv)))
|
(prof (derivation->output-path prof-drv)))
|
||||||
(show-what-to-build store (list prof-drv)
|
(show-what-to-build store (list prof-drv)
|
||||||
|
|
|
@ -188,6 +188,7 @@ true, display what would be built without actually building it."
|
||||||
(mlet %store-monad ((manifest (channel-instances->manifest instances)))
|
(mlet %store-monad ((manifest (channel-instances->manifest instances)))
|
||||||
(mbegin %store-monad
|
(mbegin %store-monad
|
||||||
(update-profile profile manifest
|
(update-profile profile manifest
|
||||||
|
#:hooks %channel-profile-hooks
|
||||||
#:dry-run? dry-run?)
|
#:dry-run? dry-run?)
|
||||||
(munless dry-run?
|
(munless dry-run?
|
||||||
(return (display-profile-news profile))))))
|
(return (display-profile-news profile))))))
|
||||||
|
|
|
@ -1005,6 +1005,24 @@
|
||||||
(((? (cut eq? hello <>))) #t)
|
(((? (cut eq? hello <>))) #t)
|
||||||
(wrong (pk 'find-packages-by-name wrong #f))))
|
(wrong (pk 'find-packages-by-name wrong #f))))
|
||||||
|
|
||||||
|
(test-equal "find-packages-by-name with cache"
|
||||||
|
(find-packages-by-name "guile")
|
||||||
|
(call-with-temporary-directory
|
||||||
|
(lambda (cache)
|
||||||
|
(generate-package-cache cache)
|
||||||
|
(mock ((guix describe) current-profile (const cache))
|
||||||
|
(mock ((gnu packages) cache-is-authoritative? (const #t))
|
||||||
|
(find-packages-by-name "guile"))))))
|
||||||
|
|
||||||
|
(test-equal "find-packages-by-name + version, with cache"
|
||||||
|
(find-packages-by-name "guile" "2")
|
||||||
|
(call-with-temporary-directory
|
||||||
|
(lambda (cache)
|
||||||
|
(generate-package-cache cache)
|
||||||
|
(mock ((guix describe) current-profile (const cache))
|
||||||
|
(mock ((gnu packages) cache-is-authoritative? (const #t))
|
||||||
|
(find-packages-by-name "guile" "2"))))))
|
||||||
|
|
||||||
(test-assert "--search-paths with pattern"
|
(test-assert "--search-paths with pattern"
|
||||||
;; Make sure 'guix package --search-paths' correctly reports environment
|
;; Make sure 'guix package --search-paths' correctly reports environment
|
||||||
;; variables when file patterns are used (in particular, it must follow
|
;; variables when file patterns are used (in particular, it must follow
|
||||||
|
|
Loading…
Reference in New Issue