[bug#77708] gexp: ‘with-parameters‘ is respected by caches.
Commit Message
* guix/gexp.scm (lower-object, lower+expand-object):
Use (%parameterized-counter) as additional cache key.
(%parameterized-counter): New parameter.
(%parameterized-counter-next-value): New variable.
(%parameterized-counters): New variable.
(add-parameterized-counter): New procedure.
(compile-parameterized): Add %parameterized-counter to parameters.
* guix/packages.scm (cache!): Use ‘hash-set!‘ instead of ‘hashq-set!‘. Use
`(,(scm->pointer package) . ,(%parameterized-counter)) as key.
(cached, package->derivation, package->cross-derivation):
Use (%parameterized-counter) as additional cache key.
* tests/gexp.scm ("with-parameters for custom parameter"): New test.
---
As noted by Ludo' [1], several objects dependent on packages
(such as derivations or grafts) are cached by the package and do not
take parameters (apart from %current-system, %current-target-system and
%graft?) into account. To fix that, my idea was to introduce an
additional parameter `%parameterized-counter', which uniquely identifies
a set of parameters and values in the <parameterized> object and which
is used as additional key by the caches.
To prevent a collision, the parameters and values are stored in a hash table,
which keeps them alive forever. Would it be preferable to use something like a
cryptographic hash instead?
For `cache!' in (guix packages), I used
`(,(scm->pointer package) . ,(%parameterized-counter)) as key together with
hash-set! and hash-ref instead of hashq-set! and hashq-ref. Is that OK?
[1] https://issues.guix.gnu.org/75879
guix/gexp.scm | 48 +++++++++++++++++++++++++++++++++++++++--------
guix/packages.scm | 22 +++++++++++-----------
tests/gexp.scm | 31 ++++++++++++++++++++++++++++++
3 files changed, 82 insertions(+), 19 deletions(-)
@@ -5,6 +5,7 @@
;;; Copyright © 2019, 2020 Mathieu Othacehe <m.othacehe@gmail.com>
;;; Copyright © 2020 Maxim Cournoyer <maxim.cournoyer@gmail.com>
;;; Copyright © 2021, 2022 Maxime Devos <maximedevos@telenet.be>
+;;; Copyright © 2025 David Elsing <david.elsing@posteo.net>
;;;
;;; This file is part of GNU Guix.
;;;
@@ -32,6 +33,7 @@ (define-module (guix gexp)
#:use-module (srfi srfi-1)
#:use-module (srfi srfi-9)
#:use-module (srfi srfi-9 gnu)
+ #:use-module (srfi srfi-11)
#:use-module (srfi srfi-26)
#:use-module (srfi srfi-34)
#:use-module (srfi srfi-35)
@@ -94,6 +96,7 @@ (define-module (guix gexp)
with-parameters
parameterized?
+ %parameterized-counter
load-path-expression
gexp-modules
@@ -302,7 +305,7 @@ (define* (lower-object obj
(not (derivation? lowered)))
(loop lowered)
(return lowered)))
- obj
+ obj (%parameterized-counter)
system target graft?)))))))
(define* (lower+expand-object obj
@@ -321,7 +324,7 @@ (define* (lower+expand-object obj
(lowered (if (derivation? obj)
(return obj)
(mcached (lower obj system target)
- obj
+ obj (%parameterized-counter)
system target graft?))))
;; LOWER might return something that needs to be further
;; lowered.
@@ -731,13 +734,40 @@ (define-syntax-rule (with-parameters ((param value) ...) body ...)
(lambda ()
body ...)))
+;; Counter which uniquely identifies specific parameters and values used for
+;; <parameterized>.
+(define %parameterized-counter
+ (make-parameter #f))
+
+(define %parameterized-counter-next-value 0)
+
+(define %parameterized-counters (make-hash-table))
+
+;; Add %parameterized-counter to PARAMETERS and its value,
+;; which depends on PARAMETERS and VALUES, to PARAMETER-VALUES.
+(define (add-parameterized-counter parameters parameter-values)
+ (let* ((key `(,parameters . ,parameter-values))
+ (counter
+ (match (hash-ref %parameterized-counters key)
+ (#f
+ (let ((val %parameterized-counter-next-value))
+ (hash-set! %parameterized-counters key val)
+ (set! %parameterized-counter-next-value (+ val 1))
+ val))
+ (counter counter))))
+ (values
+ (cons %parameterized-counter parameters)
+ (cons counter parameter-values))))
+
(define-gexp-compiler compile-parameterized <parameterized>
compiler =>
(lambda (parameterized system target)
(match (parameterized-bindings parameterized)
(((parameters values) ...)
- (let ((thunk (parameterized-thunk parameterized))
- (values (map (lambda (thunk) (thunk)) values)))
+ (let*-values (((parameters values)
+ (add-parameterized-counter
+ parameters (map (lambda (thunk) (thunk)) values)))
+ ((thunk) (parameterized-thunk parameterized)))
;; Install the PARAMETERS for the store monad.
(state-with-parameters parameters values
;; Install the PARAMETERS for the dynamic extent of THUNK.
@@ -762,11 +792,13 @@ (define-gexp-compiler compile-parameterized <parameterized>
expander => (lambda (parameterized lowered output)
(match (parameterized-bindings parameterized)
(((parameters values) ...)
- (let ((fluids (map parameter-fluid parameters))
- (thunk (parameterized-thunk parameterized)))
+ (let*-values (((parameters values)
+ (add-parameterized-counter
+ parameters (map (lambda (thunk) (thunk)) values)))
+ ((thunk) (parameterized-thunk parameterized)))
;; Install the PARAMETERS for the dynamic extent of THUNK.
- (with-fluids* fluids
- (map (lambda (thunk) (thunk)) values)
+ (with-fluids* (map parameter-fluid parameters)
+ values
(lambda ()
(match (thunk)
((? struct? base)
@@ -11,7 +11,7 @@
;;; Copyright © 2022 jgart <jgart@dismail.de>
;;; Copyright © 2023 Simon Tournier <zimon.toutoune@gmail.com>
;;; Copyright © 2024 Janneke Nieuwenhuizen <janneke@gnu.org>
-;;; Copyright © 2024 David Elsing <david.elsing@posteo.net>
+;;; Copyright © 2024, 2025 David Elsing <david.elsing@posteo.net>
;;;
;;; This file is part of GNU Guix.
;;;
@@ -57,6 +57,7 @@ (define-module (guix packages)
#:use-module (srfi srfi-34)
#:use-module (srfi srfi-35)
#:use-module (srfi srfi-71)
+ #:use-module (system foreign)
#:use-module (rnrs bytevectors)
#:use-module (web uri)
#:autoload (texinfo) (texi-fragment->stexi)
@@ -1689,13 +1690,12 @@ (define (cache! cache package system thunk)
SYSTEM."
;; FIXME: This memoization should be associated with the open store, because
;; otherwise it breaks when switching to a different store.
- (let ((result (thunk)))
- ;; Use `hashq-set!' instead of `hash-set!' because `hash' returns the
- ;; same value for all structs (as of Guile 2.0.6), and because pointer
- ;; equality is sufficient in practice.
- (hashq-set! cache package
- `((,system . ,result)
- ,@(or (hashq-ref cache package) '())))
+ (let ((result (thunk))
+ (key `(,(scm->pointer package) . ,(%parameterized-counter))))
+ (hash-set! cache key
+ `((,system . ,result)
+ ,@(or (hash-ref cache key)
+ '())))
result))
(define-syntax cached
@@ -1828,7 +1828,7 @@ (define (input-graft system)
(with-parameters ((%current-system system))
replacement))
(replacement-output output))))
- package output system)
+ package output (%parameterized-counter) system)
(return #f))))
(_
(return #f)))))
@@ -2068,7 +2068,7 @@ (define* (package->derivation package
#:system system
#:guile guile)))))
(return drv)))
- package system #f graft?))
+ package (%parameterized-counter) system #f graft?))
(define* (package->cross-derivation package target
#:optional (system (%current-system))
@@ -2091,7 +2091,7 @@ (define* (package->cross-derivation package target
#:system system
#:guile guile)))))
(return drv)))
- package system target graft?))
+ package (%parameterized-counter) system target graft?))
(define* (package-output store package
#:optional (output "out") (system (%current-system)))
@@ -1,6 +1,7 @@
;;; GNU Guix --- Functional package management for GNU
;;; Copyright © 2014-2025 Ludovic Courtès <ludo@gnu.org>
;;; Copyright © 2021-2022 Maxime Devos <maximedevos@telenet.be>
+;;; Copyright © 2025 David Elsing <david.elsing@posteo.net>
;;;
;;; This file is part of GNU Guix.
;;;
@@ -487,6 +488,36 @@ (define (match-input thing)
(return (and (eq? drv0 result0)
(eq? drv1 result1)))))
+(test-assertm "with-parameters for custom parameter"
+ (mlet* %store-monad
+ ((%param -> (make-parameter "A"))
+ (pkg -> (package
+ (name "testp")
+ (version "0")
+ (source #f)
+ (build-system trivial-build-system)
+ (arguments
+ (list
+ #:builder
+ #~(let ((port (open-file (string-append #$output) "w")))
+ (display (string-append #$(%param) "\n") port)
+ (close-port port))))
+ (home-page #f)
+ (synopsis #f)
+ (description #f)
+ (license #f)))
+ (obj1 -> (with-parameters ((%param "B")) pkg))
+ (obj2 -> (with-parameters ((%param "C")) pkg))
+ (result0 (package->derivation pkg))
+ (result1 (lower-object obj1))
+ (result2 (lower-object obj2))
+ (result3 (lower-object pkg)))
+ (return (and (not
+ (or (eq? result0 result1)
+ (eq? result0 result2)
+ (eq? result1 result2)))
+ (eq? result0 result3)))))
+
(test-assert "with-parameters + file-append"
(let* ((system (match (%current-system)
("aarch64-linux" "x86_64-linux")