diff options
| author | Ludovic Courtès <ludo@gnu.org> | 2013-04-12 17:30:27 +0200 |
|---|---|---|
| committer | Ludovic Courtès <ludo@gnu.org> | 2013-04-12 17:31:01 +0200 |
| commit | fe0cff14f6c5facee4192529f5c7b7a972f185ca (patch) | |
| tree | 6e8c21cbcb5a4b0656d2184940457fe87dd92095 | |
| parent | d7c5d27795500c1db3bca6c2ebf9066e32d36adb (diff) | |
substitute-binary: Implement `--substitute'.
This allows build outputs to be transparently downloaded from
http://hydra.gnu.org, for example.
* config-daemon.ac: Check for `gzip', `bzip2', and `xz'.
* guix/config.scm.in (%gzip, %bzip2, %xz): New variable.
* guix/scripts/substitute-binary.scm (fetch): Return SIZE as a second value.
(<narinfo>): Change `url' to `uri'.
(make-narinfo): Rename to...
(narinfo-maker): ... this. Handle relative URLs.
(fetch-narinfo): Adjust accordingly.
(filtered-port, decompressed-port): New procedures.
(guix-substitute-binary): Implement the `--substitute' case.
* tests/store.scm ("substitute query"): Use (%store-prefix) instead
of (getenv "NIX_STORE_DIR").
("substitute"): New test.
| -rw-r--r-- | config-daemon.ac | 8 | ||||
| -rw-r--r-- | guix/config.scm.in | 14 | ||||
| -rwxr-xr-x | guix/scripts/substitute-binary.scm | 100 | ||||
| -rw-r--r-- | tests/store.scm | 55 |
4 files changed, 154 insertions, 23 deletions
diff --git a/config-daemon.ac b/config-daemon.ac index eed1e23f9e5..7c51f2b95c5 100644 --- a/config-daemon.ac +++ b/config-daemon.ac | |||
| @@ -11,6 +11,14 @@ if test "x$guix_build_daemon" = "xyes"; then | |||
| 11 | AC_PROG_RANLIB | 11 | AC_PROG_RANLIB |
| 12 | AC_CONFIG_HEADER([nix/config.h]) | 12 | AC_CONFIG_HEADER([nix/config.h]) |
| 13 | 13 | ||
| 14 | dnl Decompressors, for use by the substituter. | ||
| 15 | AC_PATH_PROG([GZIP], [gzip]) | ||
| 16 | AC_PATH_PROG([BZIP2], [bzip2]) | ||
| 17 | AC_PATH_PROG([XZ], [xz]) | ||
| 18 | AC_SUBST([GZIP]) | ||
| 19 | AC_SUBST([BZIP2]) | ||
| 20 | AC_SUBST([XZ]) | ||
| 21 | |||
| 14 | dnl Use 64-bit file system calls so that we can support files > 2 GiB. | 22 | dnl Use 64-bit file system calls so that we can support files > 2 GiB. |
| 15 | AC_SYS_LARGEFILE | 23 | AC_SYS_LARGEFILE |
| 16 | 24 | ||
diff --git a/guix/config.scm.in b/guix/config.scm.in index ab7b0669b85..772ea8c289c 100644 --- a/guix/config.scm.in +++ b/guix/config.scm.in | |||
| @@ -26,7 +26,10 @@ | |||
| 26 | %system | 26 | %system |
| 27 | %libgcrypt | 27 | %libgcrypt |
| 28 | %nixpkgs | 28 | %nixpkgs |
| 29 | %nix-instantiate)) | 29 | %nix-instantiate |
| 30 | %gzip | ||
| 31 | %bzip2 | ||
| 32 | %xz)) | ||
| 30 | 33 | ||
| 31 | ;;; Commentary: | 34 | ;;; Commentary: |
| 32 | ;;; | 35 | ;;; |
| @@ -67,4 +70,13 @@ | |||
| 67 | (define %nix-instantiate | 70 | (define %nix-instantiate |
| 68 | "@NIX_INSTANTIATE@") | 71 | "@NIX_INSTANTIATE@") |
| 69 | 72 | ||
| 73 | (define %gzip | ||
| 74 | "@GZIP@") | ||
| 75 | |||
| 76 | (define %bzip2 | ||
| 77 | "@BZIP2@") | ||
| 78 | |||
| 79 | (define %xz | ||
| 80 | "@XZ@") | ||
| 81 | |||
| 70 | ;;; config.scm ends here | 82 | ;;; config.scm ends here |
diff --git a/guix/scripts/substitute-binary.scm b/guix/scripts/substitute-binary.scm index 64df4f09d69..2b447ce7f2b 100755 --- a/guix/scripts/substitute-binary.scm +++ b/guix/scripts/substitute-binary.scm | |||
| @@ -20,10 +20,13 @@ | |||
| 20 | #:use-module (guix ui) | 20 | #:use-module (guix ui) |
| 21 | #:use-module (guix store) | 21 | #:use-module (guix store) |
| 22 | #:use-module (guix utils) | 22 | #:use-module (guix utils) |
| 23 | #:use-module (guix config) | ||
| 24 | #:use-module (guix nar) | ||
| 23 | #:use-module (ice-9 rdelim) | 25 | #:use-module (ice-9 rdelim) |
| 24 | #:use-module (ice-9 regex) | 26 | #:use-module (ice-9 regex) |
| 25 | #:use-module (ice-9 match) | 27 | #:use-module (ice-9 match) |
| 26 | #:use-module (ice-9 threads) | 28 | #:use-module (ice-9 threads) |
| 29 | #:use-module (ice-9 format) | ||
| 27 | #:use-module (srfi srfi-1) | 30 | #:use-module (srfi srfi-1) |
| 28 | #:use-module (srfi srfi-9) | 31 | #:use-module (srfi srfi-9) |
| 29 | #:use-module (srfi srfi-11) | 32 | #:use-module (srfi srfi-11) |
| @@ -70,9 +73,12 @@ pairs." | |||
| 70 | (apply make args))) | 73 | (apply make args))) |
| 71 | 74 | ||
| 72 | (define (fetch uri) | 75 | (define (fetch uri) |
| 76 | "Return a binary input port to URI and the number of bytes it's expected to | ||
| 77 | provide." | ||
| 73 | (case (uri-scheme uri) | 78 | (case (uri-scheme uri) |
| 74 | ((file) | 79 | ((file) |
| 75 | (open-input-file (uri-path uri))) | 80 | (let ((port (open-input-file (uri-path uri)))) |
| 81 | (values port (stat:size (stat port))))) | ||
| 76 | ((http) | 82 | ((http) |
| 77 | (let*-values (((resp port) | 83 | (let*-values (((resp port) |
| 78 | ;; XXX: `http-get*' was introduced in 2.0.7, and deprecated | 84 | ;; XXX: `http-get*' was introduced in 2.0.7, and deprecated |
| @@ -86,7 +92,7 @@ pairs." | |||
| 86 | (response-content-length resp))) | 92 | (response-content-length resp))) |
| 87 | (case code | 93 | (case code |
| 88 | ((200) ; OK | 94 | ((200) ; OK |
| 89 | port) | 95 | (values port size)) |
| 90 | ((301 ; moved permanently | 96 | ((301 ; moved permanently |
| 91 | 302) ; found (redirection) | 97 | 302) ; found (redirection) |
| 92 | (let ((uri (response-location resp))) | 98 | (let ((uri (response-location resp))) |
| @@ -120,11 +126,11 @@ failure." | |||
| 120 | '("StoreDir" "WantMassQuery"))))) | 126 | '("StoreDir" "WantMassQuery"))))) |
| 121 | 127 | ||
| 122 | (define-record-type <narinfo> | 128 | (define-record-type <narinfo> |
| 123 | (%make-narinfo path url compression file-hash file-size nar-hash nar-size | 129 | (%make-narinfo path uri compression file-hash file-size nar-hash nar-size |
| 124 | references deriver system) | 130 | references deriver system) |
| 125 | narinfo? | 131 | narinfo? |
| 126 | (path narinfo-path) | 132 | (path narinfo-path) |
| 127 | (url narinfo-url) | 133 | (uri narinfo-uri) |
| 128 | (compression narinfo-compression) | 134 | (compression narinfo-compression) |
| 129 | (file-hash narinfo-file-hash) | 135 | (file-hash narinfo-file-hash) |
| 130 | (file-size narinfo-file-size) | 136 | (file-size narinfo-file-size) |
| @@ -134,18 +140,26 @@ failure." | |||
| 134 | (deriver narinfo-deriver) | 140 | (deriver narinfo-deriver) |
| 135 | (system narinfo-system)) | 141 | (system narinfo-system)) |
| 136 | 142 | ||
| 137 | (define (make-narinfo path url compression file-hash file-size nar-hash nar-size | 143 | (define (narinfo-maker cache-url) |
| 138 | references deriver system) | 144 | "Return a narinfo constructor for narinfos originating from CACHE-URL." |
| 139 | "Return a new <narinfo> object." | 145 | (lambda (path url compression file-hash file-size nar-hash nar-size |
| 140 | (%make-narinfo path url compression file-hash | 146 | references deriver system) |
| 141 | (and=> file-size string->number) | 147 | "Return a new <narinfo> object." |
| 142 | nar-hash | 148 | (%make-narinfo path |
| 143 | (and=> nar-size string->number) | 149 | |
| 144 | (string-tokenize references) | 150 | ;; Handle the case where URL is a relative URL. |
| 145 | (match deriver | 151 | (or (string->uri url) |
| 146 | ((or #f "") #f) | 152 | (string->uri (string-append cache-url "/" url))) |
| 147 | (_ deriver)) | 153 | |
| 148 | system)) | 154 | compression file-hash |
| 155 | (and=> file-size string->number) | ||
| 156 | nar-hash | ||
| 157 | (and=> nar-size string->number) | ||
| 158 | (string-tokenize references) | ||
| 159 | (match deriver | ||
| 160 | ((or #f "") #f) | ||
| 161 | (_ deriver)) | ||
| 162 | system))) | ||
| 149 | 163 | ||
| 150 | (define (fetch-narinfo cache path) | 164 | (define (fetch-narinfo cache path) |
| 151 | "Return the <narinfo> record for PATH, or #f if CACHE does not hold PATH." | 165 | "Return the <narinfo> record for PATH, or #f if CACHE does not hold PATH." |
| @@ -159,11 +173,36 @@ failure." | |||
| 159 | (store-path-hash-part path) | 173 | (store-path-hash-part path) |
| 160 | ".narinfo")) | 174 | ".narinfo")) |
| 161 | (lambda (properties) | 175 | (lambda (properties) |
| 162 | (alist->record properties make-narinfo | 176 | (alist->record properties (narinfo-maker (cache-url cache)) |
| 163 | '("StorePath" "URL" "Compression" | 177 | '("StorePath" "URL" "Compression" |
| 164 | "FileHash" "FileSize" "NarHash" "NarSize" | 178 | "FileHash" "FileSize" "NarHash" "NarSize" |
| 165 | "References" "Deriver" "System"))))) | 179 | "References" "Deriver" "System"))))) |
| 166 | 180 | ||
| 181 | (define (filtered-port command input) | ||
| 182 | "Return an input port (and PID) where data drained from INPUT is filtered | ||
| 183 | through COMMAND. INPUT must be a file input port." | ||
| 184 | (let ((i+o (pipe))) | ||
| 185 | (match (primitive-fork) | ||
| 186 | (0 | ||
| 187 | (close-port (car i+o)) | ||
| 188 | (close-port (current-input-port)) | ||
| 189 | (dup2 (fileno input) 0) | ||
| 190 | (close-port (current-output-port)) | ||
| 191 | (dup2 (fileno (cdr i+o)) 1) | ||
| 192 | (apply execl (car command) command)) | ||
| 193 | (child | ||
| 194 | (close-port (cdr i+o)) | ||
| 195 | (values (car i+o) child))))) | ||
| 196 | |||
| 197 | (define (decompressed-port compression input) | ||
| 198 | "Return an input port where INPUT is decompressed according to COMPRESSION." | ||
| 199 | (match compression | ||
| 200 | ("none" (values input #f)) | ||
| 201 | ("bzip2" (filtered-port `(,%bzip2 "-dc") input)) | ||
| 202 | ("xz" (filtered-port `(,%xz "-dc") input)) | ||
| 203 | ("gzip" (filtered-port `(,%gzip "-dc") input)) | ||
| 204 | (else (error "unsupported compression scheme" compression)))) | ||
| 205 | |||
| 167 | (define %cache-url | 206 | (define %cache-url |
| 168 | (or (getenv "GUIX_BINARY_SUBSTITUTE_URL") | 207 | (or (getenv "GUIX_BINARY_SUBSTITUTE_URL") |
| 169 | "http://hydra.gnu.org")) | 208 | "http://hydra.gnu.org")) |
| @@ -222,10 +261,29 @@ failure." | |||
| 222 | (error "unknown `--query' command" wtf))) | 261 | (error "unknown `--query' command" wtf))) |
| 223 | (loop (read-line))))))) | 262 | (loop (read-line))))))) |
| 224 | (("--substitute" store-path destination) | 263 | (("--substitute" store-path destination) |
| 225 | ;; Download PATH and add it to the store. | 264 | ;; Download STORE-PATH and add store it as a Nar in file DESTINATION. |
| 226 | ;; TODO: Implement. | 265 | (let* ((cache (open-cache %cache-url)) |
| 227 | (format (current-error-port) "substitution not implemented yet~%") | 266 | (narinfo (fetch-narinfo cache store-path)) |
| 228 | #f) | 267 | (uri (narinfo-uri narinfo))) |
| 268 | ;; Tell the daemon what the expected hash of the Nar itself is. | ||
| 269 | (format #t "~a~%" (narinfo-hash narinfo)) | ||
| 270 | |||
| 271 | (let*-values (((raw download-size) | ||
| 272 | (fetch uri)) | ||
| 273 | ((input pid) | ||
| 274 | (decompressed-port (narinfo-compression narinfo) | ||
| 275 | raw))) | ||
| 276 | ;; Note that Hydra currently generates Nars on the fly and doesn't | ||
| 277 | ;; specify a Content-Length, so DOWNLOAD-SIZE is #f in practice. | ||
| 278 | (format (current-error-port) | ||
| 279 | (_ "downloading `~a' from `~a'~:[~*~; (~,1f KiB)~]...~%") | ||
| 280 | store-path (uri->string uri) | ||
| 281 | download-size | ||
| 282 | (and=> download-size (cut / <> 1024.0))) | ||
| 283 | |||
| 284 | ;; Unpack the Nar at INPUT into DESTINATION. | ||
| 285 | (restore-file input destination) | ||
| 286 | (or (not pid) (zero? (cdr (waitpid pid))))))) | ||
| 229 | (("--version") | 287 | (("--version") |
| 230 | (show-version-and-exit "guix substitute-binary")))) | 288 | (show-version-and-exit "guix substitute-binary")))) |
| 231 | 289 | ||
diff --git a/tests/store.scm b/tests/store.scm index c75b99c6a9e..4ee20a93522 100644 --- a/tests/store.scm +++ b/tests/store.scm | |||
| @@ -23,9 +23,11 @@ | |||
| 23 | #:use-module (guix base32) | 23 | #:use-module (guix base32) |
| 24 | #:use-module (guix packages) | 24 | #:use-module (guix packages) |
| 25 | #:use-module (guix derivations) | 25 | #:use-module (guix derivations) |
| 26 | #:use-module (guix nar) | ||
| 26 | #:use-module (gnu packages) | 27 | #:use-module (gnu packages) |
| 27 | #:use-module (gnu packages bootstrap) | 28 | #:use-module (gnu packages bootstrap) |
| 28 | #:use-module (ice-9 match) | 29 | #:use-module (ice-9 match) |
| 30 | #:use-module (rnrs io ports) | ||
| 29 | #:use-module (web uri) | 31 | #:use-module (web uri) |
| 30 | #:use-module (srfi srfi-1) | 32 | #:use-module (srfi srfi-1) |
| 31 | #:use-module (srfi srfi-11) | 33 | #:use-module (srfi srfi-11) |
| @@ -141,7 +143,7 @@ | |||
| 141 | (call-with-output-file (string-append dir "/nix-cache-info") | 143 | (call-with-output-file (string-append dir "/nix-cache-info") |
| 142 | (lambda (p) | 144 | (lambda (p) |
| 143 | (format p "StoreDir: ~a\nWantMassQuery: 0\n" | 145 | (format p "StoreDir: ~a\nWantMassQuery: 0\n" |
| 144 | (getenv "NIX_STORE_DIR")))) | 146 | (%store-prefix)))) |
| 145 | (call-with-output-file (string-append dir "/" (store-path-hash-part o) | 147 | (call-with-output-file (string-append dir "/" (store-path-hash-part o) |
| 146 | ".narinfo") | 148 | ".narinfo") |
| 147 | (lambda (p) | 149 | (lambda (p) |
| @@ -167,6 +169,57 @@ Deriver: ~a~%" | |||
| 167 | (null? (substitutable-references s)) | 169 | (null? (substitutable-references s)) |
| 168 | (equal? (substitutable-nar-size s) 1234))))))) | 170 | (equal? (substitutable-nar-size s) 1234))))))) |
| 169 | 171 | ||
| 172 | (test-assert "substitute" | ||
| 173 | (let* ((s (open-connection)) | ||
| 174 | (c (random-text)) ; contents of the output | ||
| 175 | (d (build-expression->derivation | ||
| 176 | s "substitute-me" (%current-system) | ||
| 177 | `(call-with-output-file %output | ||
| 178 | (lambda (p) | ||
| 179 | (exit 1) ; would actually fail | ||
| 180 | (display ,c p))) | ||
| 181 | '() | ||
| 182 | #:guile-for-build | ||
| 183 | (package-derivation s %bootstrap-guile (%current-system)))) | ||
| 184 | (o (derivation-path->output-path d)) | ||
| 185 | (dir (and=> (getenv "GUIX_BINARY_SUBSTITUTE_URL") | ||
| 186 | (compose uri-path string->uri)))) | ||
| 187 | ;; Create fake substituter data, to be read by `substitute-binary'. | ||
| 188 | (call-with-output-file (string-append dir "/nix-cache-info") | ||
| 189 | (lambda (p) | ||
| 190 | (format p "StoreDir: ~a\nWantMassQuery: 0\n" | ||
| 191 | (%store-prefix)))) | ||
| 192 | (call-with-output-file (string-append dir "/example.out") | ||
| 193 | (lambda (p) | ||
| 194 | (display c p))) | ||
| 195 | (call-with-output-file (string-append dir "/example.nar") | ||
| 196 | (lambda (p) | ||
| 197 | (write-file (string-append dir "/example.out") p))) | ||
| 198 | (call-with-output-file (string-append dir "/" (store-path-hash-part o) | ||
| 199 | ".narinfo") | ||
| 200 | (lambda (p) | ||
| 201 | (format p "StorePath: ~a | ||
| 202 | URL: ~a | ||
| 203 | Compression: none | ||
| 204 | NarSize: 1234 | ||
| 205 | NarHash: sha256:~a | ||
| 206 | References: | ||
| 207 | System: ~a | ||
| 208 | Deriver: ~a~%" | ||
| 209 | o ; StorePath | ||
| 210 | "example.nar" ; relative URL | ||
| 211 | (call-with-input-file (string-append dir "/example.nar") | ||
| 212 | (compose bytevector->nix-base32-string sha256 | ||
| 213 | get-bytevector-all)) | ||
| 214 | (%current-system) ; System | ||
| 215 | (basename d)))) ; Deriver | ||
| 216 | |||
| 217 | ;; Make sure we use `substitute-binary'. | ||
| 218 | (set-build-options s #:use-substitutes? #t) | ||
| 219 | (and (has-substitutes? s o) | ||
| 220 | (build-derivations s (list d)) | ||
| 221 | (equal? c (call-with-input-file o get-string-all))))) | ||
| 222 | |||
| 170 | (test-end "store") | 223 | (test-end "store") |
| 171 | 224 | ||
| 172 | 225 | ||
