grafts.scm 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558
  1. ;;; GNU Guix --- Functional package management for GNU
  2. ;;; Copyright © 2014-2019, 2022 Ludovic Courtès <ludo@gnu.org>
  3. ;;; Copyright © 2021 Mark H Weaver <mhw@netris.org>
  4. ;;;
  5. ;;; This file is part of GNU Guix.
  6. ;;;
  7. ;;; GNU Guix is free software; you can redistribute it and/or modify it
  8. ;;; under the terms of the GNU General Public License as published by
  9. ;;; the Free Software Foundation; either version 3 of the License, or (at
  10. ;;; your option) any later version.
  11. ;;;
  12. ;;; GNU Guix is distributed in the hope that it will be useful, but
  13. ;;; WITHOUT ANY WARRANTY; without even the implied warranty of
  14. ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. ;;; GNU General Public License for more details.
  16. ;;;
  17. ;;; You should have received a copy of the GNU General Public License
  18. ;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
  19. (define-module (test-grafts)
  20. #:use-module (guix gexp)
  21. #:use-module (guix monads)
  22. #:use-module (guix derivations)
  23. #:use-module (guix store)
  24. #:use-module (guix utils)
  25. #:use-module (guix grafts)
  26. #:use-module (guix tests)
  27. #:use-module (gnu packages bootstrap)
  28. #:use-module (srfi srfi-1)
  29. #:use-module (srfi srfi-64)
  30. #:use-module (rnrs bytevectors)
  31. #:use-module (rnrs io ports)
  32. #:use-module (ice-9 vlist))
  33. (define %store
  34. (open-connection-for-tests))
  35. ;; When grafting, do not add dependency on 'glibc-utf8-locales'.
  36. (%graft-with-utf8-locale? #f)
  37. (define (bootstrap-binary name)
  38. (let ((bin (search-bootstrap-binary name (%current-system))))
  39. (and %store
  40. (add-to-store %store name #t "sha256" bin))))
  41. (define %bash
  42. (bootstrap-binary "bash"))
  43. (define %mkdir
  44. (bootstrap-binary "mkdir"))
  45. (test-begin "grafts")
  46. (test-equal "graft-derivation, grafted item is a direct dependency"
  47. '((type . graft) (graft (count . 2)))
  48. (let* ((build `(begin
  49. (mkdir %output)
  50. (chdir %output)
  51. (symlink %output "self")
  52. (call-with-output-file "text"
  53. (lambda (output)
  54. (format output "foo/~a/bar" ,%mkdir)))
  55. (symlink ,%bash "sh")))
  56. (orig (build-expression->derivation %store "grafted" build
  57. #:inputs `(("a" ,%bash)
  58. ("b" ,%mkdir))))
  59. (one (add-text-to-store %store "bash" "fake bash"))
  60. (two (build-expression->derivation %store "mkdir"
  61. '(call-with-output-file %output
  62. (lambda (port)
  63. (display "fake mkdir" port)))))
  64. (grafted (graft-derivation %store orig
  65. (list (graft
  66. (origin %bash)
  67. (replacement one))
  68. (graft
  69. (origin %mkdir)
  70. (replacement two))))))
  71. (and (build-derivations %store (list grafted))
  72. (let ((properties (derivation-properties grafted))
  73. (two (derivation->output-path two))
  74. (grafted (derivation->output-path grafted)))
  75. (and (string=? (format #f "foo/~a/bar" two)
  76. (call-with-input-file (string-append grafted "/text")
  77. get-string-all))
  78. (string=? (readlink (string-append grafted "/sh")) one)
  79. (string=? (readlink (string-append grafted "/self"))
  80. grafted)
  81. properties)))))
  82. (test-assert "graft-derivation, grafted item uses a different name"
  83. (let* ((build `(begin
  84. (mkdir %output)
  85. (chdir %output)
  86. (symlink %output "self")
  87. (symlink ,%bash "sh")))
  88. (orig (build-expression->derivation %store "grafted" build
  89. #:inputs `(("a" ,%bash))))
  90. (repl (add-text-to-store %store "BaSH" "fake bash"))
  91. (grafted (graft-derivation %store orig
  92. (list (graft
  93. (origin %bash)
  94. (replacement repl))))))
  95. (and (build-derivations %store (list grafted))
  96. (let ((grafted (derivation->output-path grafted)))
  97. (and (string=? (readlink (string-append grafted "/sh")) repl)
  98. (string=? (readlink (string-append grafted "/self"))
  99. grafted))))))
  100. ;; Make sure 'derivation-file-name' always gets to see an absolute file name.
  101. (fluid-set! %file-port-name-canonicalization 'absolute)
  102. (test-assert "graft-derivation, grafted item is an indirect dependency"
  103. (let* ((build `(begin
  104. (mkdir %output)
  105. (chdir %output)
  106. (symlink %output "self")
  107. (call-with-output-file "text"
  108. (lambda (output)
  109. (format output "foo/~a/bar" ,%mkdir)))
  110. (symlink ,%bash "sh")))
  111. (dep (build-expression->derivation %store "dep" build
  112. #:inputs `(("a" ,%bash)
  113. ("b" ,%mkdir))))
  114. (orig (build-expression->derivation %store "thing"
  115. '(symlink
  116. (assoc-ref %build-inputs
  117. "dep")
  118. %output)
  119. #:inputs `(("dep" ,dep))))
  120. (one (add-text-to-store %store "bash" "fake bash"))
  121. (two (build-expression->derivation %store "mkdir"
  122. '(call-with-output-file %output
  123. (lambda (port)
  124. (display "fake mkdir" port)))))
  125. (grafted (graft-derivation %store orig
  126. (list (graft
  127. (origin %bash)
  128. (replacement one))
  129. (graft
  130. (origin %mkdir)
  131. (replacement two))))))
  132. (and (build-derivations %store (list grafted))
  133. (let* ((two (derivation->output-path two))
  134. (grafted (derivation->output-path grafted))
  135. (dep (readlink grafted)))
  136. (and (string=? (format #f "foo/~a/bar" two)
  137. (call-with-input-file (string-append dep "/text")
  138. get-string-all))
  139. (string=? (readlink (string-append dep "/sh")) one)
  140. (string=? (readlink (string-append dep "/self")) dep)
  141. (equal? (references %store grafted) (list dep))
  142. (lset= string=?
  143. (list one two dep)
  144. (references %store dep)))))))
  145. (test-assert "graft-derivation, preserve empty directories"
  146. (run-with-store %store
  147. (mlet* %store-monad ((fake (text-file "bash" "Fake bash."))
  148. (graft -> (graft
  149. (origin %bash)
  150. (replacement fake)))
  151. (drv (gexp->derivation
  152. "to-graft"
  153. (with-imported-modules '((guix build utils))
  154. #~(begin
  155. (use-modules (guix build utils))
  156. (mkdir-p (string-append #$output
  157. "/a/b/c/d"))
  158. (symlink #$%bash
  159. (string-append #$output
  160. "/bash"))))))
  161. (grafted ((store-lift graft-derivation) drv
  162. (list graft)))
  163. (_ (built-derivations (list grafted)))
  164. (out -> (derivation->output-path grafted)))
  165. (return (and (string=? (readlink (string-append out "/bash"))
  166. fake)
  167. (file-is-directory? (string-append out "/a/b/c/d")))))))
  168. (test-assert "graft-derivation, no dependencies on grafted output"
  169. (run-with-store %store
  170. (mlet* %store-monad ((fake (text-file "bash" "Fake bash."))
  171. (graft -> (graft
  172. (origin %bash)
  173. (replacement fake)))
  174. (drv (gexp->derivation "foo" #~(mkdir #$output)))
  175. (grafted ((store-lift graft-derivation) drv
  176. (list graft))))
  177. (return (eq? grafted drv)))))
  178. (test-assert "graft-derivation, multiple outputs"
  179. (let* ((build `(begin
  180. (symlink (assoc-ref %build-inputs "a")
  181. (assoc-ref %outputs "one"))
  182. (symlink (assoc-ref %outputs "one")
  183. (assoc-ref %outputs "two"))))
  184. (orig (build-expression->derivation %store "grafted" build
  185. #:inputs `(("a" ,%bash))
  186. #:outputs '("one" "two")))
  187. (repl (add-text-to-store %store "bash" "fake bash"))
  188. (grafted (graft-derivation %store orig
  189. (list (graft
  190. (origin %bash)
  191. (replacement repl))))))
  192. (and (build-derivations %store (list grafted))
  193. (let ((one (derivation->output-path grafted "one"))
  194. (two (derivation->output-path grafted "two")))
  195. (and (string=? (readlink one) repl)
  196. (string=? (readlink two) one))))))
  197. (test-assert "graft-derivation, replaced derivation has multiple outputs"
  198. ;; Here we have a replacement just for output "one" of P1 and not for the
  199. ;; other output. Make sure the graft for P1:one correctly applies to the
  200. ;; dependents of P1. See <http://bugs.gnu.org/24712>.
  201. (let* ((p1 (build-expression->derivation
  202. %store "p1"
  203. `(let ((one (assoc-ref %outputs "one"))
  204. (two (assoc-ref %outputs "two")))
  205. (mkdir one)
  206. (mkdir two))
  207. #:outputs '("one" "two")))
  208. (p1r (build-expression->derivation
  209. %store "P1"
  210. `(let ((other (assoc-ref %outputs "ONE")))
  211. (mkdir other)
  212. (call-with-output-file (string-append other "/replacement")
  213. (const #t)))
  214. #:outputs '("ONE")))
  215. (p2 (build-expression->derivation
  216. %store "p2"
  217. `(let ((out (assoc-ref %outputs "aaa")))
  218. (mkdir (assoc-ref %outputs "zzz"))
  219. (mkdir out) (chdir out)
  220. (symlink (assoc-ref %build-inputs "p1:one") "one")
  221. (symlink (assoc-ref %build-inputs "p1:two") "two"))
  222. #:outputs '("aaa" "zzz")
  223. #:inputs `(("p1:one" ,p1 "one")
  224. ("p1:two" ,p1 "two"))))
  225. (p3 (build-expression->derivation
  226. %store "p3"
  227. `(symlink (assoc-ref %build-inputs "p2:aaa")
  228. (assoc-ref %outputs "out"))
  229. #:inputs `(("p2:aaa" ,p2 "aaa")
  230. ("p2:zzz" ,p2 "zzz"))))
  231. (p1g (graft
  232. (origin p1)
  233. (origin-output "one")
  234. (replacement p1r)
  235. (replacement-output "ONE")))
  236. (p3d (graft-derivation %store p3 (list p1g))))
  237. (and (not (find (lambda (input)
  238. ;; INPUT should not be P2:zzz since the result of P3
  239. ;; does not depend on it. See
  240. ;; <http://bugs.gnu.org/24886>.
  241. (and (string=? (derivation-input-path input)
  242. (derivation-file-name p2))
  243. (member "zzz"
  244. (derivation-input-sub-derivations input))))
  245. (derivation-inputs p3d)))
  246. (build-derivations %store (list p3d))
  247. (let ((out (derivation->output-path (pk 'p2d p3d))))
  248. (and (not (string=? (readlink out)
  249. (derivation->output-path p2 "aaa")))
  250. (string=? (derivation->output-path p1 "two")
  251. (readlink (string-append out "/two")))
  252. (file-exists? (string-append out "/one/replacement")))))))
  253. (test-assert "graft-derivation with #:outputs"
  254. ;; Call 'graft-derivation' with a narrowed set of outputs passed as
  255. ;; #:outputs.
  256. (let* ((p1 (build-expression->derivation
  257. %store "p1"
  258. `(let ((one (assoc-ref %outputs "one"))
  259. (two (assoc-ref %outputs "two")))
  260. (mkdir one)
  261. (mkdir two))
  262. #:outputs '("one" "two")))
  263. (p1r (build-expression->derivation
  264. %store "P1"
  265. `(let ((other (assoc-ref %outputs "ONE")))
  266. (mkdir other)
  267. (call-with-output-file (string-append other "/replacement")
  268. (const #t)))
  269. #:outputs '("ONE")))
  270. (p2 (build-expression->derivation
  271. %store "p2"
  272. `(let ((aaa (assoc-ref %outputs "aaa"))
  273. (zzz (assoc-ref %outputs "zzz")))
  274. (mkdir zzz) (chdir zzz)
  275. (mkdir aaa) (chdir aaa)
  276. (symlink (assoc-ref %build-inputs "p1:two") "two"))
  277. #:outputs '("aaa" "zzz")
  278. #:inputs `(("p1:one" ,p1 "one")
  279. ("p1:two" ,p1 "two"))))
  280. (p1g (graft
  281. (origin p1)
  282. (origin-output "one")
  283. (replacement p1r)
  284. (replacement-output "ONE")))
  285. (p2g (graft-derivation %store p2 (list p1g)
  286. #:outputs '("aaa"))))
  287. ;; P2:aaa depends on P1:two, but not on P1:one, so nothing to graft.
  288. (eq? p2g p2)))
  289. (test-equal "graft-derivation, unused outputs not depended on"
  290. '("aaa")
  291. ;; Make sure that the result of 'graft-derivation' does not pull outputs
  292. ;; that are irrelevant to the grafting process. See
  293. ;; <http://bugs.gnu.org/24886>.
  294. (let* ((p1 (build-expression->derivation
  295. %store "p1"
  296. `(let ((one (assoc-ref %outputs "one"))
  297. (two (assoc-ref %outputs "two")))
  298. (mkdir one)
  299. (mkdir two))
  300. #:outputs '("one" "two")))
  301. (p1r (build-expression->derivation
  302. %store "P1"
  303. `(let ((other (assoc-ref %outputs "ONE")))
  304. (mkdir other)
  305. (call-with-output-file (string-append other "/replacement")
  306. (const #t)))
  307. #:outputs '("ONE")))
  308. (p2 (build-expression->derivation
  309. %store "p2"
  310. `(let ((aaa (assoc-ref %outputs "aaa"))
  311. (zzz (assoc-ref %outputs "zzz")))
  312. (mkdir zzz) (chdir zzz)
  313. (symlink (assoc-ref %build-inputs "p1:two") "two")
  314. (mkdir aaa) (chdir aaa)
  315. (symlink (assoc-ref %build-inputs "p1:one") "one"))
  316. #:outputs '("aaa" "zzz")
  317. #:inputs `(("p1:one" ,p1 "one")
  318. ("p1:two" ,p1 "two"))))
  319. (p1g (graft
  320. (origin p1)
  321. (origin-output "one")
  322. (replacement p1r)
  323. (replacement-output "ONE")))
  324. (p2g (graft-derivation %store p2 (list p1g)
  325. #:outputs '("aaa"))))
  326. ;; Here P2G should only depend on P1:one and P1R:one; it must not depend
  327. ;; on P1:two or P1R:two since these are unused in the grafting process.
  328. (and (not (eq? p2g p2))
  329. (let* ((inputs (derivation-inputs p2g))
  330. (match-input (lambda (drv)
  331. (lambda (input)
  332. (string=? (derivation-input-path input)
  333. (derivation-file-name drv)))))
  334. (p1-inputs (filter (match-input p1) inputs))
  335. (p1r-inputs (filter (match-input p1r) inputs))
  336. (p2-inputs (filter (match-input p2) inputs)))
  337. (and (equal? p1-inputs
  338. (list (derivation-input p1 '("one"))))
  339. (equal? p1r-inputs
  340. (list (derivation-input p1r '("ONE"))))
  341. (equal? p2-inputs
  342. (list (derivation-input p2 '("aaa"))))
  343. (derivation-output-names p2g))))))
  344. (test-assert "graft-derivation, renaming" ;<http://bugs.gnu.org/23132>
  345. (let* ((build `(begin
  346. (use-modules (guix build utils))
  347. (mkdir-p (string-append (assoc-ref %outputs "out") "/"
  348. (assoc-ref %build-inputs "in")))))
  349. (orig (build-expression->derivation %store "thing-to-graft" build
  350. #:modules '((guix build utils))
  351. #:inputs `(("in" ,%bash))))
  352. (repl (add-text-to-store %store "bash" "fake bash"))
  353. (grafted (graft-derivation %store orig
  354. (list (graft
  355. (origin %bash)
  356. (replacement repl))))))
  357. (and (build-derivations %store (list grafted))
  358. (let ((out (derivation->output-path grafted)))
  359. (file-is-directory? (string-append out "/" repl))))))
  360. (test-assert "graft-derivation, grafts are not shadowed"
  361. ;; We build a DAG as below, where dotted arrows represent replacements and
  362. ;; solid arrows represent dependencies:
  363. ;;
  364. ;; P1 ·············> P1R
  365. ;; |\__________________.
  366. ;; v v
  367. ;; P2 ·············> P2R
  368. ;; |
  369. ;; v
  370. ;; P3
  371. ;;
  372. ;; We want to make sure that the two grafts we want to apply to P3 are
  373. ;; honored and not shadowed by other computed grafts.
  374. (let* ((p1 (build-expression->derivation
  375. %store "p1"
  376. '(mkdir (assoc-ref %outputs "out"))))
  377. (p1r (build-expression->derivation
  378. %store "P1"
  379. '(let ((out (assoc-ref %outputs "out")))
  380. (mkdir out)
  381. (call-with-output-file (string-append out "/replacement")
  382. (const #t)))))
  383. (p2 (build-expression->derivation
  384. %store "p2"
  385. `(let ((out (assoc-ref %outputs "out")))
  386. (mkdir out)
  387. (chdir out)
  388. (symlink (assoc-ref %build-inputs "p1") "p1"))
  389. #:inputs `(("p1" ,p1))))
  390. (p2r (build-expression->derivation
  391. %store "P2"
  392. `(let ((out (assoc-ref %outputs "out")))
  393. (mkdir out)
  394. (chdir out)
  395. (symlink (assoc-ref %build-inputs "p1") "p1")
  396. (call-with-output-file (string-append out "/replacement")
  397. (const #t)))
  398. #:inputs `(("p1" ,p1))))
  399. (p3 (build-expression->derivation
  400. %store "p3"
  401. `(let ((out (assoc-ref %outputs "out")))
  402. (mkdir out)
  403. (chdir out)
  404. (symlink (assoc-ref %build-inputs "p2") "p2"))
  405. #:inputs `(("p2" ,p2))))
  406. (p1g (graft
  407. (origin p1)
  408. (replacement p1r)))
  409. (p2g (graft
  410. (origin p2)
  411. (replacement (graft-derivation %store p2r (list p1g)))))
  412. (p3d (graft-derivation %store p3 (list p1g p2g))))
  413. (and (build-derivations %store (list p3d))
  414. (let ((out (derivation->output-path (pk p3d))))
  415. ;; Make sure OUT refers to the replacement of P2, which in turn
  416. ;; refers to the replacement of P1, as specified by P1G and P2G.
  417. ;; It used to be the case that P2G would be shadowed by a simple
  418. ;; P2->P2R graft, which is not what we want.
  419. (and (file-exists? (string-append out "/p2/replacement"))
  420. (file-exists? (string-append out "/p2/p1/replacement")))))))
  421. (define buffer-size
  422. ;; Must be equal to REQUEST-SIZE in 'replace-store-references'.
  423. (expt 2 20))
  424. (test-equal "replace-store-references, <http://bugs.gnu.org/28212>"
  425. (string-append (make-string (- buffer-size 47) #\a)
  426. "/gnu/store/" (make-string 32 #\8)
  427. "-SoMeTHiNG"
  428. (list->string (map integer->char (iota 77 33))))
  429. ;; Create input data where the right-hand-size of the dash ("-something"
  430. ;; here) goes beyond the end of the internal buffer of
  431. ;; 'replace-store-references'.
  432. (let* ((content (string-append (make-string (- buffer-size 47) #\a)
  433. "/gnu/store/" (make-string 32 #\7)
  434. "-something"
  435. (list->string
  436. (map integer->char (iota 77 33)))))
  437. (replacement (alist->vhash
  438. `((,(make-string 32 #\7)
  439. . ,(string->utf8 (string-append
  440. (make-string 32 #\8)
  441. "-SoMeTHiNG")))))))
  442. (call-with-output-string
  443. (lambda (output)
  444. ((@@ (guix build graft) replace-store-references)
  445. (open-input-string content) output
  446. replacement
  447. "/gnu/store")))))
  448. (define (insert-nuls char-size str)
  449. (string-join (map string (string->list str))
  450. (make-string (- char-size 1) #\nul)))
  451. (define (nuls-to-underscores s)
  452. (string-replace-substring s "\0" "_"))
  453. (define (annotate-buffer-boundary s)
  454. (string-append (string-take s buffer-size)
  455. "|"
  456. (string-drop s buffer-size)))
  457. (define (abbreviate-leading-fill s)
  458. (let ((s* (string-trim s #\=)))
  459. (format #f "[~a =s]~a"
  460. (- (string-length s)
  461. (string-length s*))
  462. s*)))
  463. (define (prettify-for-display s)
  464. (abbreviate-leading-fill
  465. (annotate-buffer-boundary
  466. (nuls-to-underscores s))))
  467. (define (two-sample-refs-with-gap char-size1 char-size2 gap offset
  468. char1 name1 char2 name2)
  469. (string-append
  470. (make-string (- buffer-size offset) #\=)
  471. (insert-nuls char-size1
  472. (string-append "/gnu/store/" (make-string 32 char1) name1))
  473. gap
  474. (insert-nuls char-size2
  475. (string-append "/gnu/store/" (make-string 32 char2) name2))
  476. (list->string (map integer->char (iota 77 33)))))
  477. (define (sample-map-entry old-char new-char new-name)
  478. (cons (make-string 32 old-char)
  479. (string->utf8 (string-append (make-string 32 new-char)
  480. new-name))))
  481. (define (test-two-refs-with-gap char-size1 char-size2 gap offset)
  482. (test-equal
  483. (format #f "test-two-refs-with-gap, char-sizes ~a ~a, gap ~s, offset ~a"
  484. char-size1 char-size2 gap offset)
  485. (prettify-for-display
  486. (two-sample-refs-with-gap char-size1 char-size2 gap offset
  487. #\6 "-BlahBlaH"
  488. #\8"-SoMeTHiNG"))
  489. (prettify-for-display
  490. (let* ((content (two-sample-refs-with-gap char-size1 char-size2 gap offset
  491. #\5 "-blahblah"
  492. #\7 "-something"))
  493. (replacement (alist->vhash
  494. (list (sample-map-entry #\5 #\6 "-BlahBlaH")
  495. (sample-map-entry #\7 #\8 "-SoMeTHiNG")))))
  496. (call-with-output-string
  497. (lambda (output)
  498. ((@@ (guix build graft) replace-store-references)
  499. (open-input-string content) output
  500. replacement
  501. "/gnu/store")))))))
  502. (for-each (lambda (char-size1)
  503. (for-each (lambda (char-size2)
  504. (for-each (lambda (gap)
  505. (for-each (lambda (offset)
  506. (test-two-refs-with-gap char-size1
  507. char-size2
  508. gap
  509. offset))
  510. ;; offsets to test
  511. (map (lambda (i)
  512. (+ i (* 40 char-size1)))
  513. (iota 30))))
  514. ;; gaps
  515. '("" "-" " " "a")))
  516. ;; char-size2 values to test
  517. '(1 2)))
  518. ;; char-size1 values to test
  519. '(1 2 4))
  520. (test-end)