grafts.scm 25 KB

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