grafts: Fix corner case involving multiple-output derivations.

Fixes a bug that would occur with references to two outputs of the same
derivation, with one of them referring to the other one.

For example, the references of libreoffice include both mariadb:dev and
mariadb:lib; additionally, mariadb:dev refers to mariadb:lib.  In this
case, the glibc graft would not be applied on one of the mariadb paths,
and both the grafted and ungrafted glibc would end up in the closure of
libreoffice.

Fixes <https://issues.guix.gnu.org/66662>.

* guix/grafts.scm (non-self-references): Simplify and include references
to outputs of DRV other than OUTPUTS.
(reference-origins): Simplify and possibly return outputs of DRV itself.
(cumulative-grafts)[graft-origin?]: Add OUTPUT parameter and honor it.
[dependency-grafts]: Adjust accordingly.
* tests/grafts.scm ("graft-derivation, multiple outputs need to be replaced"):
New test.

Change-Id: Iac2005024ab7049037537b3af55298696ec90e3c
This commit is contained in:
Ludovic Courtès 2023-10-27 23:44:39 +02:00
parent 2de3004267
commit 67effc1560
No known key found for this signature in database
GPG Key ID: 090B11993D9AEBB5
2 changed files with 68 additions and 25 deletions

View File

@ -1,5 +1,5 @@
;;; GNU Guix --- Functional package management for GNU ;;; GNU Guix --- Functional package management for GNU
;;; Copyright © 2014-2022 Ludovic Courtès <ludo@gnu.org> ;;; Copyright © 2014-2023 Ludovic Courtès <ludo@gnu.org>
;;; ;;;
;;; This file is part of GNU Guix. ;;; This file is part of GNU Guix.
;;; ;;;
@ -176,11 +176,8 @@ references."
(append-map (cut references/cached store <>) items)))) (append-map (cut references/cached store <>) items))))
(append-map (cut references/cached store <>) items))) (append-map (cut references/cached store <>) items)))
(let ((refs (references* (map (cut derivation->output-path drv <>) (let* ((self (map (cut derivation->output-path drv <>) outputs))
outputs))) (refs (references* self)))
(self (match (derivation->output-paths drv)
(((names . items) ...)
items))))
(remove (cut member <> self) refs))) (remove (cut member <> self) refs)))
(define %graft-cache (define %graft-cache
@ -207,7 +204,7 @@ references."
(return result))))))) (return result)))))))
(define (reference-origins drv items) (define (reference-origins drv items)
"Return the derivation/output pairs among the inputs of DRV, recursively, "Return the derivation/output pairs among DRV and its inputs, recursively,
that produce ITEMS. Elements of ITEMS not produced by a derivation (i.e., that produce ITEMS. Elements of ITEMS not produced by a derivation (i.e.,
it's a content-addressed \"source\"), or not produced by a dependency of DRV, it's a content-addressed \"source\"), or not produced by a dependency of DRV,
have no corresponding element in the resulting list." have no corresponding element in the resulting list."
@ -238,13 +235,10 @@ have no corresponding element in the resulting list."
((set-contains? visited drv) ((set-contains? visited drv)
(loop rest items result visited)) (loop rest items result visited))
(else (else
(let* ((inputs (let ((result items (lookup-derivers drv result items)))
(loop (append rest
(map derivation-input-derivation (map derivation-input-derivation
(derivation-inputs drv))) (derivation-inputs drv)))
(result items
(fold2 lookup-derivers
result items inputs)))
(loop (append rest inputs)
items result items result
(set-insert drv visited))))))))) (set-insert drv visited)))))))))
@ -258,16 +252,17 @@ GRAFTS to the dependencies of DRV. Return the resulting list of grafts.
This is a monadic procedure in %STATE-MONAD where the state is a vhash mapping This is a monadic procedure in %STATE-MONAD where the state is a vhash mapping
derivations to the corresponding set of grafts." derivations to the corresponding set of grafts."
(define (graft-origin? drv graft) (define (graft-origin? drv output graft)
;; Return true if DRV corresponds to the origin of GRAFT. ;; Return true if DRV and OUTPUT correspond to the origin of GRAFT.
(match graft (match graft
(($ <graft> (? derivation? origin) output) (($ <graft> (? derivation? origin) origin-output)
(and (string=? origin-output output)
(match (assoc-ref (derivation->output-paths drv) output) (match (assoc-ref (derivation->output-paths drv) output)
((? string? result) ((? string? result)
(string=? result (string=? result
(derivation->output-path origin output))) (derivation->output-path origin output)))
(_ (_
#f))) #f))))
(_ (_
#f))) #f)))
@ -278,7 +273,7 @@ derivations to the corresponding set of grafts."
((drv . output) ((drv . output)
;; If GRAFTS already contains a graft from DRV, do not ;; If GRAFTS already contains a graft from DRV, do not
;; override it. ;; override it.
(if (find (cut graft-origin? drv <>) grafts) (if (find (cut graft-origin? drv output <>) grafts)
(state-return grafts) (state-return grafts)
(cumulative-grafts store drv grafts (cumulative-grafts store drv grafts
#:outputs (list output) #:outputs (list output)

View File

@ -1,5 +1,5 @@
;;; GNU Guix --- Functional package management for GNU ;;; GNU Guix --- Functional package management for GNU
;;; Copyright © 2014-2019, 2022 Ludovic Courtès <ludo@gnu.org> ;;; Copyright © 2014-2019, 2022-2023 Ludovic Courtès <ludo@gnu.org>
;;; Copyright © 2021 Mark H Weaver <mhw@netris.org> ;;; Copyright © 2021 Mark H Weaver <mhw@netris.org>
;;; ;;;
;;; This file is part of GNU Guix. ;;; This file is part of GNU Guix.
@ -268,6 +268,54 @@
(readlink (string-append out "/two"))) (readlink (string-append out "/two")))
(file-exists? (string-append out "/one/replacement"))))))) (file-exists? (string-append out "/one/replacement")))))))
(test-assert "graft-derivation, multiple outputs need to be replaced"
;; Build a reference graph like this:
;;
;; ,- p2:out --.
;; v v
;; p1:one <---- p1:two
;; |
;; `-> p0
;;
;; Graft p0r in lieu of p0, and make sure all the paths from the grafted p2
;; lead to p0r. See <https://issues.guix.gnu.org/66662>.
(let* ((p0 (build-expression->derivation
%store "p0" '(mkdir (assoc-ref %outputs "out"))))
(p0r (build-expression->derivation
%store "P0"
'(let ((out (assoc-ref %outputs "out")))
(mkdir out)
(call-with-output-file (string-append out "/replacement")
(const #t)))))
(p1 (build-expression->derivation
%store "p1"
`(let ((one (assoc-ref %outputs "one"))
(two (assoc-ref %outputs "two"))
(p0 (assoc-ref %build-inputs "p0")))
(mkdir one)
(mkdir two)
(symlink p0 (string-append one "/p0"))
(symlink one (string-append two "/link")))
#:inputs `(("p0" ,p0))
#:outputs '("one" "two")))
(p2 (build-expression->derivation
%store "p2"
`(let ((out (assoc-ref %outputs "out")))
(mkdir out) (chdir out)
(symlink (assoc-ref %build-inputs "p1:one") "one")
(symlink (assoc-ref %build-inputs "p1:two") "two"))
#:inputs `(("p1:one" ,p1 "one")
("p1:two" ,p1 "two"))))
(p0g (list (graft
(origin p0)
(replacement p0r))))
(p2d (graft-derivation %store p2 p0g)))
(build-derivations %store (list p2d))
(let ((out (derivation->output-path (pk 'p2d p2d))))
(equal? (stat (string-append out "/one/p0/replacement"))
(stat (string-append out "/two/link/p0/replacement"))))))
(test-assert "graft-derivation with #:outputs" (test-assert "graft-derivation with #:outputs"
;; Call 'graft-derivation' with a narrowed set of outputs passed as ;; Call 'graft-derivation' with a narrowed set of outputs passed as
;; #:outputs. ;; #:outputs.