diff options
| -rw-r--r-- | guix/store.scm | 100 | ||||
| -rw-r--r-- | tests/store.scm | 46 |
2 files changed, 146 insertions, 0 deletions
diff --git a/guix/store.scm b/guix/store.scm index cc5c24a77de..f41a1e26900 100644 --- a/guix/store.scm +++ b/guix/store.scm | |||
| @@ -78,6 +78,7 @@ | |||
| 78 | add-data-to-store | 78 | add-data-to-store |
| 79 | add-text-to-store | 79 | add-text-to-store |
| 80 | add-to-store | 80 | add-to-store |
| 81 | add-file-tree-to-store | ||
| 81 | binary-file | 82 | binary-file |
| 82 | build-things | 83 | build-things |
| 83 | build | 84 | build |
| @@ -137,6 +138,7 @@ | |||
| 137 | set-current-system | 138 | set-current-system |
| 138 | text-file | 139 | text-file |
| 139 | interned-file | 140 | interned-file |
| 141 | interned-file-tree | ||
| 140 | 142 | ||
| 141 | %store-prefix | 143 | %store-prefix |
| 142 | store-path | 144 | store-path |
| @@ -951,6 +953,101 @@ where FILE is the entry's absolute file name and STAT is the result of | |||
| 951 | (hash-set! cache args path) | 953 | (hash-set! cache args path) |
| 952 | path)))))) | 954 | path)))))) |
| 953 | 955 | ||
| 956 | (define %not-slash | ||
| 957 | (char-set-complement (char-set #\/))) | ||
| 958 | |||
| 959 | (define* (add-file-tree-to-store server tree | ||
| 960 | #:key | ||
| 961 | (hash-algo "sha256") | ||
| 962 | (recursive? #t)) | ||
| 963 | "Add the given TREE to the store on SERVER. TREE must be an entry such as: | ||
| 964 | |||
| 965 | (\"my-tree\" directory | ||
| 966 | (\"a\" regular (data \"hello\")) | ||
| 967 | (\"b\" symlink \"a\") | ||
| 968 | (\"c\" directory | ||
| 969 | (\"d\" executable (file \"/bin/sh\")))) | ||
| 970 | |||
| 971 | This is a generalized version of 'add-to-store'. It allows you to reproduce | ||
| 972 | an arbitrary directory layout in the store without creating a derivation." | ||
| 973 | |||
| 974 | ;; Note: The format of TREE was chosen to allow trees to be compared with | ||
| 975 | ;; 'equal?', which in turn allows us to memoize things. | ||
| 976 | |||
| 977 | (define root | ||
| 978 | ;; TREE is a single entry. | ||
| 979 | (list tree)) | ||
| 980 | |||
| 981 | (define basename | ||
| 982 | (match tree | ||
| 983 | ((name . _) name))) | ||
| 984 | |||
| 985 | (define (lookup file) | ||
| 986 | (let loop ((components (string-tokenize file %not-slash)) | ||
| 987 | (tree root)) | ||
| 988 | (match components | ||
| 989 | ((basename) | ||
| 990 | (assoc basename tree)) | ||
| 991 | ((head . rest) | ||
| 992 | (loop rest | ||
| 993 | (match (assoc-ref tree head) | ||
| 994 | (('directory . entries) entries))))))) | ||
| 995 | |||
| 996 | (define (file-type+size file) | ||
| 997 | (match (lookup file) | ||
| 998 | ((_ (and type (or 'directory 'symlink)) . _) | ||
| 999 | (values type 0)) | ||
| 1000 | ((_ type ('file file)) | ||
| 1001 | (values type (stat:size (stat file)))) | ||
| 1002 | ((_ type ('data (? string? data))) | ||
| 1003 | (values type (string-length data))) | ||
| 1004 | ((_ type ('data (? bytevector? data))) | ||
| 1005 | (values type (bytevector-length data))))) | ||
| 1006 | |||
| 1007 | (define (file-port file) | ||
| 1008 | (match (lookup file) | ||
| 1009 | ((_ (or 'regular 'executable) content) | ||
| 1010 | (match content | ||
| 1011 | (('file (? string? file)) | ||
| 1012 | (open-file file "r0b")) | ||
| 1013 | (('data (? string? str)) | ||
| 1014 | (open-input-string str)) | ||
| 1015 | (('data (? bytevector? bv)) | ||
| 1016 | (open-bytevector-input-port bv)))))) | ||
| 1017 | |||
| 1018 | (define (symlink-target file) | ||
| 1019 | (match (lookup file) | ||
| 1020 | ((_ 'symlink target) target))) | ||
| 1021 | |||
| 1022 | (define (directory-entries directory) | ||
| 1023 | (match (lookup directory) | ||
| 1024 | ((_ 'directory (names . _) ...) names))) | ||
| 1025 | |||
| 1026 | (define cache | ||
| 1027 | (nix-server-add-to-store-cache server)) | ||
| 1028 | |||
| 1029 | (or (hash-ref cache tree) | ||
| 1030 | (begin | ||
| 1031 | ;; We don't use the 'operation' macro so we can use 'write-file-tree' | ||
| 1032 | ;; instead of 'write-file'. | ||
| 1033 | (record-operation 'add-to-store/tree) | ||
| 1034 | (let ((port (nix-server-socket server))) | ||
| 1035 | (write-int (operation-id add-to-store) port) | ||
| 1036 | (write-string basename port) | ||
| 1037 | (write-int 1 port) ;obsolete, must be #t | ||
| 1038 | (write-int (if recursive? 1 0) port) | ||
| 1039 | (write-string hash-algo port) | ||
| 1040 | (write-file-tree basename port | ||
| 1041 | #:file-type+size file-type+size | ||
| 1042 | #:file-port file-port | ||
| 1043 | #:symlink-target symlink-target | ||
| 1044 | #:directory-entries directory-entries) | ||
| 1045 | (let loop ((done? (process-stderr server))) | ||
| 1046 | (or done? (loop (process-stderr server)))) | ||
| 1047 | (let ((result (read-store-path port))) | ||
| 1048 | (hash-set! cache tree result) | ||
| 1049 | result))))) | ||
| 1050 | |||
| 954 | (define build-things | 1051 | (define build-things |
| 955 | (let ((build (operation (build-things (string-list things) | 1052 | (let ((build (operation (build-things (string-list things) |
| 956 | (integer mode)) | 1053 | (integer mode)) |
| @@ -1402,6 +1499,9 @@ where FILE is the entry's absolute file name and STAT is the result of | |||
| 1402 | #:select? select?) | 1499 | #:select? select?) |
| 1403 | store))) | 1500 | store))) |
| 1404 | 1501 | ||
| 1502 | (define interned-file-tree | ||
| 1503 | (store-lift add-file-tree-to-store)) | ||
| 1504 | |||
| 1405 | (define build | 1505 | (define build |
| 1406 | ;; Monadic variant of 'build-things'. | 1506 | ;; Monadic variant of 'build-things'. |
| 1407 | (store-lift build-things)) | 1507 | (store-lift build-things)) |
diff --git a/tests/store.scm b/tests/store.scm index afecec940ad..47fab0df18a 100644 --- a/tests/store.scm +++ b/tests/store.scm | |||
| @@ -210,6 +210,52 @@ | |||
| 210 | (valid-path? store path) | 210 | (valid-path? store path) |
| 211 | (file-exists? path))))) | 211 | (file-exists? path))))) |
| 212 | 212 | ||
| 213 | (test-equal "add-file-tree-to-store" | ||
| 214 | `(42 | ||
| 215 | ("." directory #t) | ||
| 216 | ("./bar" directory #t) | ||
| 217 | ("./foo" directory #t) | ||
| 218 | ("./foo/a" regular "file a") | ||
| 219 | ("./foo/b" symlink "a") | ||
| 220 | ("./foo/c" directory #t) | ||
| 221 | ("./foo/c/p" regular "file p") | ||
| 222 | ("./foo/c/q" directory #t) | ||
| 223 | ("./foo/c/q/x" regular "#!/bin/sh\nexit 42") | ||
| 224 | ("./foo/c/q/y" symlink "..") | ||
| 225 | ("./foo/c/q/z" directory #t)) | ||
| 226 | (let* ((tree `("file-tree" directory | ||
| 227 | ("foo" directory | ||
| 228 | ("a" regular (data "file a")) | ||
| 229 | ("b" symlink "a") | ||
| 230 | ("c" directory | ||
| 231 | ("p" regular (data ,(string->utf8 "file p"))) | ||
| 232 | ("q" directory | ||
| 233 | ("x" executable | ||
| 234 | (data "#!/bin/sh\nexit 42")) | ||
| 235 | ("y" symlink "..") | ||
| 236 | ("z" directory)))) | ||
| 237 | ("bar" directory))) | ||
| 238 | (result (add-file-tree-to-store %store tree))) | ||
| 239 | (cons (status:exit-val (system* (string-append result "/foo/c/q/x"))) | ||
| 240 | (with-directory-excursion result | ||
| 241 | (map (lambda (file) | ||
| 242 | (let ((type (stat:type (lstat file)))) | ||
| 243 | `(,file ,type | ||
| 244 | ,(match type | ||
| 245 | ((or 'regular 'executable) | ||
| 246 | (call-with-input-file file | ||
| 247 | get-string-all)) | ||
| 248 | ('symlink (readlink file)) | ||
| 249 | ('directory #t))))) | ||
| 250 | (find-files "." #:directories? #t)))))) | ||
| 251 | |||
| 252 | (test-equal "add-file-tree-to-store, flat" | ||
| 253 | "Hello, world!" | ||
| 254 | (let* ((tree `("flat-file" regular (data "Hello, world!"))) | ||
| 255 | (result (add-file-tree-to-store %store tree))) | ||
| 256 | (and (file-exists? result) | ||
| 257 | (call-with-input-file result get-string-all)))) | ||
| 258 | |||
| 213 | (test-assert "references" | 259 | (test-assert "references" |
| 214 | (let* ((t1 (add-text-to-store %store "random1" | 260 | (let* ((t1 (add-text-to-store %store "random1" |
| 215 | (random-text))) | 261 | (random-text))) |
