diff options
| author | Ludovic Courtès <ludo@gnu.org> | 2019-01-11 17:23:39 +0100 |
|---|---|---|
| committer | Ludovic Courtès <ludo@gnu.org> | 2019-01-15 20:24:09 +0100 |
| commit | 5fbdc9a5aa63fd51c65d30fe3d30608d01fe1bc8 (patch) | |
| tree | ab2940f0c7250e8267609e3db9f6e4b517bd0546 | |
| parent | 1d90e9d7c906b1e9e94d1642bfd60c51609fd0df (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.scm | 127 | ||||
| -rw-r--r-- | guix/channels.scm | 36 | ||||
| -rw-r--r-- | guix/scripts/package.scm | 8 | ||||
| -rw-r--r-- | guix/scripts/pull.scm | 1 | ||||
| -rw-r--r-- | tests/packages.scm | 18 |
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 | ||
| 145 | authoritative when entries have been added via GUIX_PACKAGE_PATH or '-L' | ||
| 146 | flags." | ||
| 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 | ||
| 206 | package 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 | ||
| 248 | order." | ||
| 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, | ||
| 257 | then only return packages whose version is prefixed by VERSION, sorted in | ||
| 258 | decreasing 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 |
| 207 | version numbers; otherwise, return the list of packages named NAME and at | 279 | version 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 | |||
| 296 | The primary purpose of the cache is to speed up package lookup by name such | ||
| 297 | that we don't have to traverse and load all the package modules, thereby also | ||
| 298 | reducing 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 | ||
| 424 | be 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 |
| 421 | channel instances." | 452 | channel 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 |
| 127 | specified in MANIFEST, a manifest object. When ALLOW-COLLISIONS? is true, | 128 | specified in MANIFEST, a manifest object. When ALLOW-COLLISIONS? is true, |
| 128 | do not treat collisions in MANIFEST as an error." | 129 | do not treat collisions in MANIFEST as an error. HOOKS is a list of \"profile |
| 130 | hooks\" 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 |
