summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLudovic Courtès <ludo@gnu.org>2019-01-11 17:23:39 +0100
committerLudovic Courtès <ludo@gnu.org>2019-01-15 20:24:09 +0100
commit5fbdc9a5aa63fd51c65d30fe3d30608d01fe1bc8 (patch)
treeab2940f0c7250e8267609e3db9f6e4b517bd0546
parent1d90e9d7c906b1e9e94d1642bfd60c51609fd0df (diff)
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.
-rw-r--r--gnu/packages.scm127
-rw-r--r--guix/channels.scm36
-rw-r--r--guix/scripts/package.scm8
-rw-r--r--guix/scripts/pull.scm1
-rw-r--r--tests/packages.scm18
5 files changed, 181 insertions, 9 deletions
diff --git a/gnu/packages.scm b/gnu/packages.scm
index 4a85cf4b877..6796db80a47 100644
--- a/gnu/packages.scm
+++ b/gnu/packages.scm
@@ -28,11 +28,14 @@
28 #:use-module (guix memoization) 28 #:use-module (guix memoization)
29 #:use-module ((guix build utils) 29 #:use-module ((guix build utils)
30 #:select ((package-name->name+version 30 #:select ((package-name->name+version
31 . hyphen-separated-name->name+version))) 31 . hyphen-separated-name->name+version)
32 mkdir-p))
32 #:autoload (guix profiles) (packages->manifest) 33 #:autoload (guix profiles) (packages->manifest)
33 #:use-module (guix describe) 34 #:use-module (guix describe)
34 #:use-module (ice-9 vlist) 35 #:use-module (ice-9 vlist)
35 #:use-module (ice-9 match) 36 #:use-module (ice-9 match)
37 #:autoload (ice-9 binary-ports) (put-bytevector)
38 #:autoload (system base compile) (compile)
36 #:use-module (srfi srfi-1) 39 #:use-module (srfi srfi-1)
37 #:use-module (srfi srfi-11) 40 #:use-module (srfi srfi-11)
38 #:use-module (srfi srfi-26) 41 #:use-module (srfi srfi-26)
@@ -56,7 +59,9 @@
56 59
57 specification->package 60 specification->package
58 specification->package+output 61 specification->package+output
59 specifications->manifest)) 62 specifications->manifest
63
64 generate-package-cache))
60 65
61;;; Commentary: 66;;; Commentary:
62;;; 67;;;
@@ -135,6 +140,14 @@ for system '~a'")
135 ;; Default search path for package modules. 140 ;; Default search path for package modules.
136 `((,%distro-root-directory . "gnu/packages"))) 141 `((,%distro-root-directory . "gnu/packages")))
137 142
143(define (cache-is-authoritative?)
144 "Return true if the pre-computed package cache is authoritative. It is not
145authoritative when entries have been added via GUIX_PACKAGE_PATH or '-L'
146flags."
147 (equal? (%package-module-path)
148 (append %default-package-module-path
149 (package-path-entries))))
150
138(define %package-module-path 151(define %package-module-path
139 ;; Search path for package modules. Each item must be either a directory 152 ;; Search path for package modules. Each item must be either a directory
140 ;; name or a pair whose car is a directory and whose cdr is a sub-directory 153 ;; 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."
183 init 196 init
184 modules)) 197 modules))
185 198
186(define find-packages-by-name 199(define %package-cache-file
200 ;; Location of the package cache.
201 "/lib/guix/package.cache")
202
203(define load-package-cache
204 (mlambda (profile)
205 "Attempt to load the package cache. On success return a vhash keyed by
206package names. Return #f on failure."
207 (match profile
208 (#f #f)
209 (profile
210 (catch 'system-error
211 (lambda ()
212 (define lst
213 (load-compiled (string-append profile %package-cache-file)))
214 (fold (lambda (item vhash)
215 (match item
216 (#(name version module symbol outputs
217 supported? deprecated?
218 file line column)
219 (vhash-cons name item vhash))))
220 vlist-null
221 lst))
222 (lambda args
223 (if (= ENOENT (system-error-errno args))
224 #f
225 (apply throw args))))))))
226
227(define find-packages-by-name/direct ;bypass the cache
187 (let ((packages (delay 228 (let ((packages (delay
188 (fold-packages (lambda (p r) 229 (fold-packages (lambda (p r)
189 (vhash-cons (package-name p) p r)) 230 (vhash-cons (package-name p) p r))
@@ -202,6 +243,37 @@ decreasing version order."
202 matching) 243 matching)
203 matching))))) 244 matching)))))
204 245
246(define (cache-lookup cache name)
247 "Lookup package NAME in CACHE. Return a list sorted in increasing version
248order."
249 (define (package-version<? v1 v2)
250 (version>? (vector-ref v2 1) (vector-ref v1 1)))
251
252 (sort (vhash-fold* cons '() name cache)
253 package-version<?))
254
255(define* (find-packages-by-name name #:optional version)
256 "Return the list of packages with the given NAME. If VERSION is not #f,
257then only return packages whose version is prefixed by VERSION, sorted in
258decreasing version order."
259 (define cache
260 (load-package-cache (current-profile)))
261
262 (if (and (cache-is-authoritative?) cache)
263 (match (cache-lookup cache name)
264 (#f #f)
265 ((#(_ versions modules symbols _ _ _ _ _ _) ...)
266 (fold (lambda (version* module symbol result)
267 (if (or (not version)
268 (version-prefix? version version*))
269 (cons (module-ref (resolve-interface module)
270 symbol)
271 result)
272 result))
273 '()
274 versions modules symbols)))
275 (find-packages-by-name/direct name version)))
276
205(define (find-best-packages-by-name name version) 277(define (find-best-packages-by-name name version)
206 "If version is #f, return the list of packages named NAME with the highest 278 "If version is #f, return the list of packages named NAME with the highest
207version numbers; otherwise, return the list of packages named NAME and at 279version numbers; otherwise, return the list of packages named NAME and at
@@ -218,6 +290,55 @@ VERSION."
218 (string=? (package-version p) highest)) 290 (string=? (package-version p) highest))
219 matches)))))) 291 matches))))))
220 292
293(define (generate-package-cache directory)
294 "Generate under DIRECTORY a cache of all the available packages.
295
296The primary purpose of the cache is to speed up package lookup by name such
297that we don't have to traverse and load all the package modules, thereby also
298reducing the memory footprint."
299 (define cache-file
300 (string-append directory %package-cache-file))
301
302 (define (expand-cache module symbol variable result)
303 (match (false-if-exception (variable-ref variable))
304 ((? package? package)
305 (if (hidden-package? package)
306 result
307 (cons `#(,(package-name package)
308 ,(package-version package)
309 ,(module-name module)
310 ,symbol
311 ,(package-outputs package)
312 ,(->bool (member (%current-system)
313 (package-supported-systems package)))
314 ,(->bool (package-superseded package))
315 ,@(let ((loc (package-location package)))
316 (if loc
317 `(,(location-file loc)
318 ,(location-line loc)
319 ,(location-column loc))
320 '(#f #f #f))))
321 result)))
322 (_
323 result)))
324
325 (define exp
326 (fold-module-public-variables* expand-cache '()
327 (all-modules (%package-module-path)
328 #:warn
329 warn-about-load-error)))
330
331 (mkdir-p (dirname cache-file))
332 (call-with-output-file cache-file
333 (lambda (port)
334 ;; Store the cache as a '.go' file. This makes loading fast and reduces
335 ;; heap usage since some of the static data is directly mmapped.
336 (put-bytevector port
337 (compile `'(,@exp)
338 #:to 'bytecode
339 #:opts '(#:to-file? #t)))))
340 cache-file)
341
221 342
222(define %sigint-prompt 343(define %sigint-prompt
223 ;; The prompt to jump to upon SIGINT. 344 ;; The prompt to jump to upon SIGINT.
diff --git a/guix/channels.scm b/guix/channels.scm
index 6b860f3bd81..cd8a0131bd8 100644
--- a/guix/channels.scm
+++ b/guix/channels.scm
@@ -21,6 +21,7 @@
21 #:use-module (guix git) 21 #:use-module (guix git)
22 #:use-module (guix records) 22 #:use-module (guix records)
23 #:use-module (guix gexp) 23 #:use-module (guix gexp)
24 #:use-module (guix modules)
24 #:use-module (guix discovery) 25 #:use-module (guix discovery)
25 #:use-module (guix monads) 26 #:use-module (guix monads)
26 #:use-module (guix profiles) 27 #:use-module (guix profiles)
@@ -31,7 +32,8 @@
31 #:use-module (srfi srfi-2) 32 #:use-module (srfi srfi-2)
32 #:use-module (srfi srfi-9) 33 #:use-module (srfi srfi-9)
33 #:use-module (srfi srfi-11) 34 #:use-module (srfi srfi-11)
34 #:autoload (guix self) (whole-package) 35 #:autoload (guix self) (whole-package make-config.scm)
36 #:autoload (guix inferior) (gexp->derivation-in-inferior) ;FIXME: circular dep
35 #:use-module (ice-9 match) 37 #:use-module (ice-9 match)
36 #:export (channel 38 #:export (channel
37 channel? 39 channel?
@@ -52,6 +54,7 @@
52 checkout->channel-instance 54 checkout->channel-instance
53 latest-channel-derivation 55 latest-channel-derivation
54 channel-instances->manifest 56 channel-instances->manifest
57 %channel-profile-hooks
55 channel-instances->derivation)) 58 channel-instances->derivation))
56 59
57;;; Commentary: 60;;; Commentary:
@@ -416,11 +419,40 @@ channel instances."
416 (zip instances derivations)))) 419 (zip instances derivations))))
417 (return (manifest entries)))) 420 (return (manifest entries))))
418 421
422(define (package-cache-file manifest)
423 "Build a package cache file for the instance in MANIFEST. This is meant to
424be used as a profile hook."
425 (mlet %store-monad ((profile (profile-derivation manifest
426 #:hooks '())))
427
428 (define build
429 #~(begin
430 (use-modules (gnu packages))
431
432 (if (defined? 'generate-package-cache)
433 (begin
434 ;; Delegate package cache generation to the inferior.
435 (format (current-error-port)
436 "Generating package cache for '~a'...~%"
437 #$profile)
438 (generate-package-cache #$output))
439 (mkdir #$output))))
440
441 (gexp->derivation-in-inferior "guix-package-cache" build
442 profile
443 #:properties '((type . profile-hook)
444 (hook . package-cache)))))
445
446(define %channel-profile-hooks
447 ;; The default channel profile hooks.
448 (cons package-cache-file %default-profile-hooks))
449
419(define (channel-instances->derivation instances) 450(define (channel-instances->derivation instances)
420 "Return the derivation of the profile containing INSTANCES, a list of 451 "Return the derivation of the profile containing INSTANCES, a list of
421channel instances." 452channel instances."
422 (mlet %store-monad ((manifest (channel-instances->manifest instances))) 453 (mlet %store-monad ((manifest (channel-instances->manifest instances)))
423 (profile-derivation manifest))) 454 (profile-derivation manifest
455 #:hooks %channel-profile-hooks)))
424 456
425(define latest-channel-instances* 457(define latest-channel-instances*
426 (store-lift latest-channel-instances)) 458 (store-lift latest-channel-instances))
diff --git a/guix/scripts/package.scm b/guix/scripts/package.scm
index ba33790edaa..e9bed0be1ed 100644
--- a/guix/scripts/package.scm
+++ b/guix/scripts/package.scm
@@ -120,21 +120,21 @@ denote ranges as interpreted by 'matching-generations'."
120 120
121(define* (build-and-use-profile store profile manifest 121(define* (build-and-use-profile store profile manifest
122 #:key 122 #:key
123 (hooks %default-profile-hooks)
123 allow-collisions? 124 allow-collisions?
124 bootstrap? use-substitutes? 125 bootstrap? use-substitutes?
125 dry-run?) 126 dry-run?)
126 "Build a new generation of PROFILE, a file name, using the packages 127 "Build a new generation of PROFILE, a file name, using the packages
127specified in MANIFEST, a manifest object. When ALLOW-COLLISIONS? is true, 128specified in MANIFEST, a manifest object. When ALLOW-COLLISIONS? is true,
128do not treat collisions in MANIFEST as an error." 129do not treat collisions in MANIFEST as an error. HOOKS is a list of \"profile
130hooks\" run when building the profile."
129 (when (equal? profile %current-profile) 131 (when (equal? profile %current-profile)
130 (ensure-default-profile)) 132 (ensure-default-profile))
131 133
132 (let* ((prof-drv (run-with-store store 134 (let* ((prof-drv (run-with-store store
133 (profile-derivation manifest 135 (profile-derivation manifest
134 #:allow-collisions? allow-collisions? 136 #:allow-collisions? allow-collisions?
135 #:hooks (if bootstrap? 137 #:hooks (if bootstrap? '() hooks)
136 '()
137 %default-profile-hooks)
138 #:locales? (not bootstrap?)))) 138 #:locales? (not bootstrap?))))
139 (prof (derivation->output-path prof-drv))) 139 (prof (derivation->output-path prof-drv)))
140 (show-what-to-build store (list prof-drv) 140 (show-what-to-build store (list prof-drv)
diff --git a/guix/scripts/pull.scm b/guix/scripts/pull.scm
index 0339b149fa8..513434c5f16 100644
--- a/guix/scripts/pull.scm
+++ b/guix/scripts/pull.scm
@@ -188,6 +188,7 @@ true, display what would be built without actually building it."
188 (mlet %store-monad ((manifest (channel-instances->manifest instances))) 188 (mlet %store-monad ((manifest (channel-instances->manifest instances)))
189 (mbegin %store-monad 189 (mbegin %store-monad
190 (update-profile profile manifest 190 (update-profile profile manifest
191 #:hooks %channel-profile-hooks
191 #:dry-run? dry-run?) 192 #:dry-run? dry-run?)
192 (munless dry-run? 193 (munless dry-run?
193 (return (display-profile-news profile)))))) 194 (return (display-profile-news profile))))))
diff --git a/tests/packages.scm b/tests/packages.scm
index eb8ede3207b..2720ba5a155 100644
--- a/tests/packages.scm
+++ b/tests/packages.scm
@@ -1005,6 +1005,24 @@
1005 (((? (cut eq? hello <>))) #t) 1005 (((? (cut eq? hello <>))) #t)
1006 (wrong (pk 'find-packages-by-name wrong #f)))) 1006 (wrong (pk 'find-packages-by-name wrong #f))))
1007 1007
1008(test-equal "find-packages-by-name with cache"
1009 (find-packages-by-name "guile")
1010 (call-with-temporary-directory
1011 (lambda (cache)
1012 (generate-package-cache cache)
1013 (mock ((guix describe) current-profile (const cache))
1014 (mock ((gnu packages) cache-is-authoritative? (const #t))
1015 (find-packages-by-name "guile"))))))
1016
1017(test-equal "find-packages-by-name + version, with cache"
1018 (find-packages-by-name "guile" "2")
1019 (call-with-temporary-directory
1020 (lambda (cache)
1021 (generate-package-cache cache)
1022 (mock ((guix describe) current-profile (const cache))
1023 (mock ((gnu packages) cache-is-authoritative? (const #t))
1024 (find-packages-by-name "guile" "2"))))))
1025
1008(test-assert "--search-paths with pattern" 1026(test-assert "--search-paths with pattern"
1009 ;; Make sure 'guix package --search-paths' correctly reports environment 1027 ;; Make sure 'guix package --search-paths' correctly reports environment
1010 ;; variables when file patterns are used (in particular, it must follow 1028 ;; variables when file patterns are used (in particular, it must follow