summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLudovic Courtès <ludo@gnu.org>2020-12-10 15:12:34 +0100
committerLudovic Courtès <ludo@gnu.org>2020-12-15 17:32:10 +0100
commit6a060ff27ff68384d7c90076baa36c349fff689d (patch)
treef7b1f9c7a52e84848fbcaa90d4dc38c25d7d65eb
parentdea1ee1fd740248307f74ca4cb70b94742264098 (diff)
store-copy: 'populate-store' can optionally deduplicate files.
Until now deduplication was performed as an additional pass after copying files, which involve re-traversing all the files that had just been copied. * guix/store/deduplication.scm (copy-file/deduplicate): New procedure. * tests/store-deduplication.scm ("copy-file/deduplicate"): New test. * guix/build/store-copy.scm (populate-store): Add #:deduplicate? parameter and honor it. * tests/gexp.scm ("gexp->derivation, store copy"): Pass #:deduplicate? #f to 'populate-store'. * gnu/build/image.scm (initialize-root-partition): Pass #:deduplicate? to 'populate-store'. Pass #:deduplicate? #f to 'register-closure'. * gnu/build/vm.scm (root-partition-initializer): Likewise. * gnu/build/install.scm (populate-single-profile-directory): Pass #:deduplicate? #f to 'populate-store'. * gnu/build/linux-initrd.scm (build-initrd): Likewise. * guix/scripts/pack.scm (self-contained-tarball)[import-module?]: New procedure. [build]: Pass it as an argument to 'source-module-closure'. * guix/scripts/pack.scm (squashfs-image)[build]: Wrap in 'with-extensions'. * gnu/system/linux-initrd.scm (expression->initrd)[import-module?]: New procedure. [builder]: Pass it to 'source-module-closure'. * gnu/system/install.scm (cow-store-service-type)[import-module?]: New procedure. Pass it to 'source-module-closure'.
-rw-r--r--gnu/build/image.scm5
-rw-r--r--gnu/build/install.scm3
-rw-r--r--gnu/build/linux-initrd.scm3
-rw-r--r--gnu/build/vm.scm5
-rw-r--r--gnu/system/install.scm12
-rw-r--r--gnu/system/linux-initrd.scm10
-rw-r--r--guix/build/store-copy.scm13
-rw-r--r--guix/scripts/pack.scm258
-rw-r--r--guix/store/deduplication.scm16
-rw-r--r--tests/gexp.scm3
-rw-r--r--tests/store-deduplication.scm18
11 files changed, 207 insertions, 139 deletions
diff --git a/gnu/build/image.scm b/gnu/build/image.scm
index 0deea10a9d5..8f50f27f78a 100644
--- a/gnu/build/image.scm
+++ b/gnu/build/image.scm
@@ -186,7 +186,8 @@ rest of the store when registering the closures. SYSTEM-DIRECTORY is the name
186of the directory of the 'system' derivation. Pass WAL-MODE? to 186of the directory of the 'system' derivation. Pass WAL-MODE? to
187register-closure." 187register-closure."
188 (populate-root-file-system system-directory root) 188 (populate-root-file-system system-directory root)
189 (populate-store references-graphs root) 189 (populate-store references-graphs root
190 #:deduplicate? deduplicate?)
190 191
191 ;; Populate /dev. 192 ;; Populate /dev.
192 (when make-device-nodes 193 (when make-device-nodes
@@ -195,7 +196,7 @@ register-closure."
195 (when register-closures? 196 (when register-closures?
196 (for-each (lambda (closure) 197 (for-each (lambda (closure)
197 (register-closure root closure 198 (register-closure root closure
198 #:deduplicate? deduplicate? 199 #:deduplicate? #f
199 #:wal-mode? wal-mode?)) 200 #:wal-mode? wal-mode?))
200 references-graphs)) 201 references-graphs))
201 202
diff --git a/gnu/build/install.scm b/gnu/build/install.scm
index 63995e1d09f..f5c8407b894 100644
--- a/gnu/build/install.scm
+++ b/gnu/build/install.scm
@@ -214,7 +214,8 @@ This is used to create the self-contained tarballs with 'guix pack'."
214 (symlink old (scope new))) 214 (symlink old (scope new)))
215 215
216 ;; Populate the store. 216 ;; Populate the store.
217 (populate-store (list closure) directory) 217 (populate-store (list closure) directory
218 #:deduplicate? #f)
218 219
219 (when database 220 (when database
220 (install-database-and-gc-roots directory database profile 221 (install-database-and-gc-roots directory database profile
diff --git a/gnu/build/linux-initrd.scm b/gnu/build/linux-initrd.scm
index 99796adba65..bb2ed0db0c7 100644
--- a/gnu/build/linux-initrd.scm
+++ b/gnu/build/linux-initrd.scm
@@ -127,7 +127,8 @@ REFERENCES-GRAPHS."
127 (mkdir "contents") 127 (mkdir "contents")
128 128
129 ;; Copy the closures of all the items referenced in REFERENCES-GRAPHS. 129 ;; Copy the closures of all the items referenced in REFERENCES-GRAPHS.
130 (populate-store references-graphs "contents") 130 (populate-store references-graphs "contents"
131 #:deduplicate? #f)
131 132
132 (with-directory-excursion "contents" 133 (with-directory-excursion "contents"
133 ;; Make '/init'. 134 ;; Make '/init'.
diff --git a/gnu/build/vm.scm b/gnu/build/vm.scm
index abb0317faf6..03be5697b75 100644
--- a/gnu/build/vm.scm
+++ b/gnu/build/vm.scm
@@ -395,7 +395,8 @@ system that is passed to 'populate-root-file-system'."
395 (when copy-closures? 395 (when copy-closures?
396 ;; Populate the store. 396 ;; Populate the store.
397 (populate-store (map (cut string-append "/xchg/" <>) closures) 397 (populate-store (map (cut string-append "/xchg/" <>) closures)
398 target)) 398 target
399 #:deduplicate? deduplicate?))
399 400
400 ;; Populate /dev. 401 ;; Populate /dev.
401 (make-device-nodes target) 402 (make-device-nodes target)
@@ -412,7 +413,7 @@ system that is passed to 'populate-root-file-system'."
412 (for-each (lambda (closure) 413 (for-each (lambda (closure)
413 (register-closure target 414 (register-closure target
414 (string-append "/xchg/" closure) 415 (string-append "/xchg/" closure)
415 #:deduplicate? deduplicate?)) 416 #:deduplicate? #f))
416 closures) 417 closures)
417 (unless copy-closures? 418 (unless copy-closures?
418 (umount target-store))) 419 (umount target-store)))
diff --git a/gnu/system/install.scm b/gnu/system/install.scm
index a6b9e3d9523..e7534634739 100644
--- a/gnu/system/install.scm
+++ b/gnu/system/install.scm
@@ -1,5 +1,5 @@
1;;; GNU Guix --- Functional package management for GNU 1;;; GNU Guix --- Functional package management for GNU
2;;; Copyright © 2014, 2015, 2016, 2017, 2018, 2019 Ludovic Courtès <ludo@gnu.org> 2;;; Copyright © 2014, 2015, 2016, 2017, 2018, 2019, 2020 Ludovic Courtès <ludo@gnu.org>
3;;; Copyright © 2015 Mark H Weaver <mhw@netris.org> 3;;; Copyright © 2015 Mark H Weaver <mhw@netris.org>
4;;; Copyright © 2016 Andreas Enge <andreas@enge.fr> 4;;; Copyright © 2016 Andreas Enge <andreas@enge.fr>
5;;; Copyright © 2017 Marius Bakke <mbakke@fastmail.com> 5;;; Copyright © 2017 Marius Bakke <mbakke@fastmail.com>
@@ -176,6 +176,13 @@ manual."
176 (shepherd-service-type 176 (shepherd-service-type
177 'cow-store 177 'cow-store
178 (lambda _ 178 (lambda _
179 (define (import-module? module)
180 ;; Since we don't use deduplication support in 'populate-store', don't
181 ;; import (guix store deduplication) and its dependencies, which
182 ;; includes Guile-Gcrypt.
183 (and (guix-module-name? module)
184 (not (equal? module '(guix store deduplication)))))
185
179 (shepherd-service 186 (shepherd-service
180 (requirement '(root-file-system user-processes)) 187 (requirement '(root-file-system user-processes))
181 (provision '(cow-store)) 188 (provision '(cow-store))
@@ -190,7 +197,8 @@ the given target.")
190 ,@%default-modules)) 197 ,@%default-modules))
191 (start 198 (start
192 (with-imported-modules (source-module-closure 199 (with-imported-modules (source-module-closure
193 '((gnu build install))) 200 '((gnu build install))
201 #:select? import-module?)
194 #~(case-lambda 202 #~(case-lambda
195 ((target) 203 ((target)
196 (mount-cow-store target #$%backing-directory) 204 (mount-cow-store target #$%backing-directory)
diff --git a/gnu/system/linux-initrd.scm b/gnu/system/linux-initrd.scm
index 4fb1d863c9d..c6ba9bb5605 100644
--- a/gnu/system/linux-initrd.scm
+++ b/gnu/system/linux-initrd.scm
@@ -76,12 +76,20 @@ the derivations referenced by EXP are automatically copied to the initrd."
76 (define init 76 (define init
77 (program-file "init" exp #:guile guile)) 77 (program-file "init" exp #:guile guile))
78 78
79 (define (import-module? module)
80 ;; Since we don't use deduplication support in 'populate-store', don't
81 ;; import (guix store deduplication) and its dependencies, which includes
82 ;; Guile-Gcrypt. That way we can run tests with '--bootstrap'.
83 (and (guix-module-name? module)
84 (not (equal? module '(guix store deduplication)))))
85
79 (define builder 86 (define builder
80 ;; Do not use "guile-zlib" extension here, otherwise it would drag the 87 ;; Do not use "guile-zlib" extension here, otherwise it would drag the
81 ;; non-static "zlib" package to the initrd closure. It is not needed 88 ;; non-static "zlib" package to the initrd closure. It is not needed
82 ;; anyway because the modules are stored uncompressed within the initrd. 89 ;; anyway because the modules are stored uncompressed within the initrd.
83 (with-imported-modules (source-module-closure 90 (with-imported-modules (source-module-closure
84 '((gnu build linux-initrd))) 91 '((gnu build linux-initrd))
92 #:select? import-module?)
85 #~(begin 93 #~(begin
86 (use-modules (gnu build linux-initrd)) 94 (use-modules (gnu build linux-initrd))
87 95
diff --git a/guix/build/store-copy.scm b/guix/build/store-copy.scm
index 95dcb8e1140..7f0672cd9de 100644
--- a/guix/build/store-copy.scm
+++ b/guix/build/store-copy.scm
@@ -20,6 +20,7 @@
20 #:use-module ((guix build utils) #:hide (copy-recursively)) 20 #:use-module ((guix build utils) #:hide (copy-recursively))
21 #:use-module (guix sets) 21 #:use-module (guix sets)
22 #:use-module (guix progress) 22 #:use-module (guix progress)
23 #:autoload (guix store deduplication) (copy-file/deduplicate)
23 #:use-module (srfi srfi-1) 24 #:use-module (srfi srfi-1)
24 #:use-module (srfi srfi-9) 25 #:use-module (srfi srfi-9)
25 #:use-module (srfi srfi-26) 26 #:use-module (srfi srfi-26)
@@ -242,10 +243,13 @@ permissions. Write verbose output to the LOG port."
242 lstat))) 243 lstat)))
243 244
244(define* (populate-store reference-graphs target 245(define* (populate-store reference-graphs target
245 #:key (log-port (current-error-port))) 246 #:key
247 (deduplicate? #t)
248 (log-port (current-error-port)))
246 "Populate the store under directory TARGET with the items specified in 249 "Populate the store under directory TARGET with the items specified in
247REFERENCE-GRAPHS, a list of reference-graph files. Items copied to TARGET 250REFERENCE-GRAPHS, a list of reference-graph files. Items copied to TARGET
248maintain timestamps and permissions." 251maintain timestamps and permissions. When DEDUPLICATE? is true, deduplicate
252regular files as they are copied to TARGET."
249 (define store 253 (define store
250 (string-append target (%store-directory))) 254 (string-append target (%store-directory)))
251 255
@@ -273,6 +277,11 @@ maintain timestamps and permissions."
273 (string-append target thing) 277 (string-append target thing)
274 #:keep-mtime? #t 278 #:keep-mtime? #t
275 #:keep-permissions? #t 279 #:keep-permissions? #t
280 #:copy-file
281 (if deduplicate?
282 (cut copy-file/deduplicate <> <>
283 #:store store)
284 copy-file)
276 #:log (%make-void-port "w")) 285 #:log (%make-void-port "w"))
277 (report)) 286 (report))
278 things))))) 287 things)))))
diff --git a/guix/scripts/pack.scm b/guix/scripts/pack.scm
index 1612ec8f04b..440c4b0903f 100644
--- a/guix/scripts/pack.scm
+++ b/guix/scripts/pack.scm
@@ -203,12 +203,19 @@ added to the pack."
203 #+(file-append glibc-utf8-locales "/lib/locale")) 203 #+(file-append glibc-utf8-locales "/lib/locale"))
204 (setlocale LC_ALL "en_US.utf8")))) 204 (setlocale LC_ALL "en_US.utf8"))))
205 205
206 (define (import-module? module)
207 ;; Since we don't use deduplication support in 'populate-store', don't
208 ;; import (guix store deduplication) and its dependencies, which includes
209 ;; Guile-Gcrypt. That way we can run tests with '--bootstrap'.
210 (and (not-config? module)
211 (not (equal? '(guix store deduplication) module))))
212
206 (define build 213 (define build
207 (with-imported-modules (source-module-closure 214 (with-imported-modules (source-module-closure
208 `((guix build utils) 215 `((guix build utils)
209 (guix build union) 216 (guix build union)
210 (gnu build install)) 217 (gnu build install))
211 #:select? not-config?) 218 #:select? import-module?)
212 #~(begin 219 #~(begin
213 (use-modules (guix build utils) 220 (use-modules (guix build utils)
214 ((guix build union) #:select (relative-file-name)) 221 ((guix build union) #:select (relative-file-name))
@@ -382,138 +389,139 @@ added to the pack."
382 `(("/bin" -> "bin") ,@symlinks))) 389 `(("/bin" -> "bin") ,@symlinks)))
383 390
384 (define build 391 (define build
385 (with-imported-modules (source-module-closure 392 (with-extensions (list guile-gcrypt)
386 '((guix build utils) 393 (with-imported-modules (source-module-closure
387 (guix build store-copy) 394 '((guix build utils)
388 (guix build union) 395 (guix build store-copy)
389 (gnu build install)) 396 (guix build union)
390 #:select? not-config?) 397 (gnu build install))
391 #~(begin 398 #:select? not-config?)
392 (use-modules (guix build utils) 399 #~(begin
393 (guix build store-copy) 400 (use-modules (guix build utils)
394 ((guix build union) #:select (relative-file-name)) 401 (guix build store-copy)
395 (gnu build install) 402 ((guix build union) #:select (relative-file-name))
396 (srfi srfi-1) 403 (gnu build install)
397 (srfi srfi-26) 404 (srfi srfi-1)
398 (ice-9 match)) 405 (srfi srfi-26)
406 (ice-9 match))
399 407
400 (define database #+database) 408 (define database #+database)
401 (define entry-point #$entry-point) 409 (define entry-point #$entry-point)
402 410
403 (define (mksquashfs args) 411 (define (mksquashfs args)
404 (apply invoke "mksquashfs" 412 (apply invoke "mksquashfs"
405 `(,@args 413 `(,@args
406 414
407 ;; Do not create a "recovery file" when appending to the 415 ;; Do not create a "recovery file" when appending to the
408 ;; file system since it's useless in this case. 416 ;; file system since it's useless in this case.
409 "-no-recovery" 417 "-no-recovery"
410 418
411 ;; Do not attempt to store extended attributes. 419 ;; Do not attempt to store extended attributes.
412 ;; See <https://bugs.gnu.org/40043>. 420 ;; See <https://bugs.gnu.org/40043>.
413 "-no-xattrs" 421 "-no-xattrs"
414 422
415 ;; Set file times and the file system creation time to 423 ;; Set file times and the file system creation time to
416 ;; one second after the Epoch. 424 ;; one second after the Epoch.
417 "-all-time" "1" "-mkfs-time" "1" 425 "-all-time" "1" "-mkfs-time" "1"
418 426
419 ;; Reset all UIDs and GIDs. 427 ;; Reset all UIDs and GIDs.
420 "-force-uid" "0" "-force-gid" "0"))) 428 "-force-uid" "0" "-force-gid" "0")))
421 429
422 (setenv "PATH" #+(file-append archiver "/bin")) 430 (setenv "PATH" #+(file-append archiver "/bin"))
423 431
424 ;; We need an empty file in order to have a valid file argument when 432 ;; We need an empty file in order to have a valid file argument when
425 ;; we reparent the root file system. Read on for why that's 433 ;; we reparent the root file system. Read on for why that's
426 ;; necessary. 434 ;; necessary.
427 (with-output-to-file ".empty" (lambda () (display ""))) 435 (with-output-to-file ".empty" (lambda () (display "")))
428 436
429 ;; Create the squashfs image in several steps. 437 ;; Create the squashfs image in several steps.
430 ;; Add all store items. Unfortunately mksquashfs throws away all 438 ;; Add all store items. Unfortunately mksquashfs throws away all
431 ;; ancestor directories and only keeps the basename. We fix this 439 ;; ancestor directories and only keeps the basename. We fix this
432 ;; in the following invocations of mksquashfs. 440 ;; in the following invocations of mksquashfs.
433 (mksquashfs `(,@(map store-info-item 441 (mksquashfs `(,@(map store-info-item
434 (call-with-input-file "profile" 442 (call-with-input-file "profile"
435 read-reference-graph)) 443 read-reference-graph))
436 #$environment 444 #$environment
437 ,#$output 445 ,#$output
438 446
439 ;; Do not perform duplicate checking because we 447 ;; Do not perform duplicate checking because we
440 ;; don't have any dupes. 448 ;; don't have any dupes.
441 "-no-duplicates" 449 "-no-duplicates"
442 "-comp" 450 "-comp"
443 ,#+(compressor-name compressor))) 451 ,#+(compressor-name compressor)))
444 452
445 ;; Here we reparent the store items. For each sub-directory of 453 ;; Here we reparent the store items. For each sub-directory of
446 ;; the store prefix we need one invocation of "mksquashfs". 454 ;; the store prefix we need one invocation of "mksquashfs".
447 (for-each (lambda (dir) 455 (for-each (lambda (dir)
448 (mksquashfs `(".empty" 456 (mksquashfs `(".empty"
449 ,#$output 457 ,#$output
450 "-root-becomes" ,dir))) 458 "-root-becomes" ,dir)))
451 (reverse (string-tokenize (%store-directory) 459 (reverse (string-tokenize (%store-directory)
452 (char-set-complement (char-set #\/))))) 460 (char-set-complement (char-set #\/)))))
453 461
454 ;; Add symlinks and mount points. 462 ;; Add symlinks and mount points.
455 (mksquashfs 463 (mksquashfs
456 `(".empty" 464 `(".empty"
457 ,#$output 465 ,#$output
458 ;; Create SYMLINKS via pseudo file definitions. 466 ;; Create SYMLINKS via pseudo file definitions.
459 ,@(append-map 467 ,@(append-map
460 (match-lambda 468 (match-lambda
461 ((source '-> target) 469 ((source '-> target)
462 ;; Create relative symlinks to work around a bug in 470 ;; Create relative symlinks to work around a bug in
463 ;; Singularity 2.x: 471 ;; Singularity 2.x:
464 ;; https://bugs.gnu.org/34913 472 ;; https://bugs.gnu.org/34913
465 ;; https://github.com/sylabs/singularity/issues/1487 473 ;; https://github.com/sylabs/singularity/issues/1487
466 (let ((target (string-append #$profile "/" target))) 474 (let ((target (string-append #$profile "/" target)))
467 (list "-p" 475 (list "-p"
468 (string-join 476 (string-join
469 ;; name s mode uid gid symlink 477 ;; name s mode uid gid symlink
470 (list source 478 (list source
471 "s" "777" "0" "0" 479 "s" "777" "0" "0"
472 (relative-file-name (dirname source) 480 (relative-file-name (dirname source)
473 target))))))) 481 target)))))))
474 '#$symlinks*) 482 '#$symlinks*)
475 483
476 "-p" "/.singularity.d d 555 0 0" 484 "-p" "/.singularity.d d 555 0 0"
477 485
478 ;; Create the environment file. 486 ;; Create the environment file.
479 "-p" "/.singularity.d/env d 555 0 0" 487 "-p" "/.singularity.d/env d 555 0 0"
480 "-p" ,(string-append 488 "-p" ,(string-append
481 "/.singularity.d/env/90-environment.sh s 777 0 0 " 489 "/.singularity.d/env/90-environment.sh s 777 0 0 "
482 (relative-file-name "/.singularity.d/env" 490 (relative-file-name "/.singularity.d/env"
483 #$environment)) 491 #$environment))
484 492
485 ;; Create /.singularity.d/actions, and optionally the 'run' 493 ;; Create /.singularity.d/actions, and optionally the 'run'
486 ;; script, used by 'singularity run'. 494 ;; script, used by 'singularity run'.
487 "-p" "/.singularity.d/actions d 555 0 0" 495 "-p" "/.singularity.d/actions d 555 0 0"
488 496
489 ,@(if entry-point 497 ,@(if entry-point
490 `(;; This one if for Singularity 2.x. 498 `( ;; This one if for Singularity 2.x.
491 "-p" 499 "-p"
492 ,(string-append 500 ,(string-append
493 "/.singularity.d/actions/run s 777 0 0 " 501 "/.singularity.d/actions/run s 777 0 0 "
494 (relative-file-name "/.singularity.d/actions" 502 (relative-file-name "/.singularity.d/actions"
495 (string-append #$profile "/" 503 (string-append #$profile "/"
496 entry-point))) 504 entry-point)))
497 505
498 ;; This one is for Singularity 3.x. 506 ;; This one is for Singularity 3.x.
499 "-p" 507 "-p"
500 ,(string-append 508 ,(string-append
501 "/.singularity.d/runscript s 777 0 0 " 509 "/.singularity.d/runscript s 777 0 0 "
502 (relative-file-name "/.singularity.d" 510 (relative-file-name "/.singularity.d"
503 (string-append #$profile "/" 511 (string-append #$profile "/"
504 entry-point)))) 512 entry-point))))
505 '()) 513 '())
506 514
507 ;; Create empty mount points. 515 ;; Create empty mount points.
508 "-p" "/proc d 555 0 0" 516 "-p" "/proc d 555 0 0"
509 "-p" "/sys d 555 0 0" 517 "-p" "/sys d 555 0 0"
510 "-p" "/dev d 555 0 0" 518 "-p" "/dev d 555 0 0"
511 "-p" "/home d 555 0 0")) 519 "-p" "/home d 555 0 0"))
512 520
513 (when database 521 (when database
514 ;; Initialize /var/guix. 522 ;; Initialize /var/guix.
515 (install-database-and-gc-roots "var-etc" database #$profile) 523 (install-database-and-gc-roots "var-etc" database #$profile)
516 (mksquashfs `("var-etc" ,#$output)))))) 524 (mksquashfs `("var-etc" ,#$output)))))))
517 525
518 (gexp->derivation (string-append name 526 (gexp->derivation (string-append name
519 (compressor-extension compressor) 527 (compressor-extension compressor)
diff --git a/guix/store/deduplication.scm b/guix/store/deduplication.scm
index b4d37d45255..8564f12107f 100644
--- a/guix/store/deduplication.scm
+++ b/guix/store/deduplication.scm
@@ -34,7 +34,8 @@
34 #:use-module (guix serialization) 34 #:use-module (guix serialization)
35 #:export (nar-sha256 35 #:export (nar-sha256
36 deduplicate 36 deduplicate
37 dump-file/deduplicate)) 37 dump-file/deduplicate
38 copy-file/deduplicate))
38 39
39;; XXX: This port is used as a workaround on Guile <= 2.2.4 where 40;; XXX: This port is used as a workaround on Guile <= 2.2.4 where
40;; 'port-position' throws to 'out-of-range' when the offset is great than or 41;; 'port-position' throws to 'out-of-range' when the offset is great than or
@@ -256,3 +257,16 @@ down the road."
256 (get-hash))))) 257 (get-hash)))))
257 258
258 (deduplicate file hash #:store store)) 259 (deduplicate file hash #:store store))
260
261(define* (copy-file/deduplicate source target
262 #:key (store (%store-directory)))
263 "Like 'copy-file', but additionally deduplicate TARGET in STORE."
264 (call-with-input-file source
265 (lambda (input)
266 (let ((stat (stat input)))
267 (dump-file/deduplicate target input (stat:size stat)
268 (if (zero? (logand (stat:mode stat)
269 #o100))
270 'regular
271 'executable)
272 #:store store)))))
diff --git a/tests/gexp.scm b/tests/gexp.scm
index a0e55178fa6..6e92f0e4b39 100644
--- a/tests/gexp.scm
+++ b/tests/gexp.scm
@@ -736,7 +736,8 @@
736 (zero? (logand #o222 (stat:mode st))))))) 736 (zero? (logand #o222 (stat:mode st)))))))
737 737
738 (mkdir #$output) 738 (mkdir #$output)
739 (populate-store '("graph") #$output) 739 (populate-store '("graph") #$output
740 #:deduplicate? #f)
740 741
741 ;; Check whether 'populate-store' canonicalizes 742 ;; Check whether 'populate-store' canonicalizes
742 ;; permissions and timestamps. 743 ;; permissions and timestamps.
diff --git a/tests/store-deduplication.scm b/tests/store-deduplication.scm
index e2870a363dc..7b01acae24c 100644
--- a/tests/store-deduplication.scm
+++ b/tests/store-deduplication.scm
@@ -1,5 +1,5 @@
1;;; GNU Guix --- Functional package management for GNU 1;;; GNU Guix --- Functional package management for GNU
2;;; Copyright © 2018 Ludovic Courtès <ludo@gnu.org> 2;;; Copyright © 2018, 2020 Ludovic Courtès <ludo@gnu.org>
3;;; 3;;;
4;;; This file is part of GNU Guix. 4;;; This file is part of GNU Guix.
5;;; 5;;;
@@ -25,6 +25,7 @@
25 #:use-module (rnrs bytevectors) 25 #:use-module (rnrs bytevectors)
26 #:use-module (ice-9 binary-ports) 26 #:use-module (ice-9 binary-ports)
27 #:use-module (srfi srfi-1) 27 #:use-module (srfi srfi-1)
28 #:use-module (srfi srfi-26)
28 #:use-module (srfi srfi-64)) 29 #:use-module (srfi srfi-64))
29 30
30(test-begin "store-deduplication") 31(test-begin "store-deduplication")
@@ -106,4 +107,19 @@
106 (cons (apply = (map (compose stat:ino stat) identical)) 107 (cons (apply = (map (compose stat:ino stat) identical))
107 (map (compose stat:nlink stat) identical)))))) 108 (map (compose stat:nlink stat) identical))))))
108 109
110(test-assert "copy-file/deduplicate"
111 (call-with-temporary-directory
112 (lambda (store)
113 (let ((source (search-path %load-path "gnu/packages/emacs-xyz.scm")))
114 (for-each (lambda (target)
115 (copy-file/deduplicate source
116 (string-append store target)
117 #:store store))
118 '("/a" "/b" "/c"))
119 (and (directory-exists? (string-append store "/.links"))
120 (file=? source (string-append store "/a"))
121 (apply = (map (compose stat:ino stat
122 (cut string-append store <>))
123 '("/a" "/b" "/c"))))))))
124
109(test-end "store-deduplication") 125(test-end "store-deduplication")