Commit | Line | Data |
---|---|---|
f65cf81a LC |
1 | ;;; GNU Guix --- Functional package management for GNU |
2 | ;;; Copyright © 2013 Ludovic Courtès <ludo@gnu.org> | |
3 | ;;; | |
4 | ;;; This file is part of GNU Guix. | |
5 | ;;; | |
6 | ;;; GNU Guix is free software; you can redistribute it and/or modify it | |
7 | ;;; under the terms of the GNU General Public License as published by | |
8 | ;;; the Free Software Foundation; either version 3 of the License, or (at | |
9 | ;;; your option) any later version. | |
10 | ;;; | |
11 | ;;; GNU Guix is distributed in the hope that it will be useful, but | |
12 | ;;; WITHOUT ANY WARRANTY; without even the implied warranty of | |
13 | ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
14 | ;;; GNU General Public License for more details. | |
15 | ;;; | |
16 | ;;; You should have received a copy of the GNU General Public License | |
17 | ;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>. | |
18 | ||
19 | (define-module (guix scripts substitute-binary) | |
20 | #:use-module (guix ui) | |
21 | #:use-module (guix store) | |
22 | #:use-module (guix utils) | |
23 | #:use-module (ice-9 rdelim) | |
24 | #:use-module (ice-9 regex) | |
25 | #:use-module (ice-9 match) | |
26 | #:use-module (ice-9 threads) | |
27 | #:use-module (srfi srfi-1) | |
28 | #:use-module (srfi srfi-9) | |
29 | #:use-module (srfi srfi-11) | |
30 | #:use-module (srfi srfi-26) | |
31 | #:use-module (web uri) | |
32 | #:use-module (web client) | |
33 | #:use-module (web response) | |
34 | #:export (guix-substitute-binary)) | |
35 | ||
36 | ;;; Comment: | |
37 | ;;; | |
38 | ;;; This is the "binary substituter". It is invoked by the daemon do check | |
39 | ;;; for the existence of available "substitutes" (pre-built binaries), and to | |
40 | ;;; actually use them as a substitute to building things locally. | |
41 | ;;; | |
42 | ;;; If possible, substitute a binary for the requested store path, using a Nix | |
43 | ;;; "binary cache". This program implements the Nix "substituter" protocol. | |
44 | ;;; | |
45 | ;;; Code: | |
46 | ||
47 | (define (fields->alist port) | |
48 | "Read recutils-style record from PORT and return them as a list of key/value | |
49 | pairs." | |
50 | (define field-rx | |
51 | (make-regexp "^([[:graph:]]+): (.*)$")) | |
52 | ||
53 | (let loop ((line (read-line port)) | |
54 | (result '())) | |
55 | (cond ((eof-object? line) | |
56 | (reverse result)) | |
57 | ((regexp-exec field-rx line) | |
58 | => | |
59 | (lambda (match) | |
60 | (loop (read-line port) | |
61 | (alist-cons (match:substring match 1) | |
62 | (match:substring match 2) | |
63 | result)))) | |
64 | (else | |
65 | (error "unmatched line" line))))) | |
66 | ||
67 | (define (alist->record alist make keys) | |
68 | "Apply MAKE to the values associated with KEYS in ALIST." | |
69 | (let ((args (map (cut assoc-ref alist <>) keys))) | |
70 | (apply make args))) | |
71 | ||
72 | (define (fetch uri) | |
73 | (case (uri-scheme uri) | |
74 | ((file) | |
75 | (open-input-file (uri-path uri))) | |
76 | ((http) | |
77 | (let*-values (((resp port) | |
78 | ;; XXX: `http-get*' was introduced in 2.0.7, and deprecated | |
79 | ;; in 2.0.8 (!). Assume it is available here. | |
80 | (if (version>? "2.0.7" (version)) | |
81 | (http-get* uri #:decode-body? #f) | |
82 | (http-get uri #:streaming? #t))) | |
83 | ((code) | |
84 | (response-code resp)) | |
85 | ((size) | |
86 | (response-content-length resp))) | |
87 | (case code | |
88 | ((200) ; OK | |
89 | port) | |
90 | ((301 ; moved permanently | |
91 | 302) ; found (redirection) | |
92 | (let ((uri (response-location resp))) | |
93 | (format #t "following redirection to `~a'...~%" | |
94 | (uri->string uri)) | |
95 | (fetch uri))) | |
96 | (else | |
97 | (error "download failed" (uri->string uri) | |
98 | code (response-reason-phrase resp)))))))) | |
99 | ||
100 | (define-record-type <cache> | |
101 | (%make-cache url store-directory wants-mass-query?) | |
102 | cache? | |
103 | (url cache-url) | |
104 | (store-directory cache-store-directory) | |
105 | (wants-mass-query? cache-wants-mass-query?)) | |
106 | ||
107 | (define (open-cache url) | |
108 | "Open the binary cache at URL. Return a <cache> object on success, or #f on | |
109 | failure." | |
110 | (define (download-cache-info url) | |
111 | ;; Download the `nix-cache-info' from URL, and return its contents as an | |
112 | ;; list of key/value pairs. | |
113 | (and=> (false-if-exception (fetch (string->uri url))) | |
114 | fields->alist)) | |
115 | ||
116 | (and=> (download-cache-info (string-append url "/nix-cache-info")) | |
117 | (lambda (properties) | |
118 | (alist->record properties | |
119 | (cut %make-cache url <...>) | |
120 | '("StoreDir" "WantMassQuery"))))) | |
121 | ||
122 | (define-record-type <narinfo> | |
123 | (%make-narinfo path url compression file-hash file-size nar-hash nar-size | |
124 | references deriver system) | |
125 | narinfo? | |
126 | (path narinfo-path) | |
127 | (url narinfo-url) | |
128 | (compression narinfo-compression) | |
129 | (file-hash narinfo-file-hash) | |
130 | (file-size narinfo-file-size) | |
131 | (nar-hash narinfo-hash) | |
132 | (nar-size narinfo-size) | |
133 | (references narinfo-references) | |
134 | (deriver narinfo-deriver) | |
135 | (system narinfo-system)) | |
136 | ||
137 | (define (make-narinfo path url compression file-hash file-size nar-hash nar-size | |
138 | references deriver system) | |
139 | "Return a new <narinfo> object." | |
140 | (%make-narinfo path url compression file-hash | |
141 | (and=> file-size string->number) | |
142 | nar-hash | |
143 | (and=> nar-size string->number) | |
144 | (string-tokenize references) | |
145 | (match deriver | |
146 | ((or #f "") #f) | |
147 | (_ deriver)) | |
148 | system)) | |
149 | ||
150 | (define (fetch-narinfo cache path) | |
151 | "Return the <narinfo> record for PATH, or #f if CACHE does not hold PATH." | |
152 | (define (download url) | |
153 | ;; Download the `nix-cache-info' from URL, and return its contents as an | |
154 | ;; list of key/value pairs. | |
155 | (and=> (false-if-exception (fetch (string->uri url))) | |
156 | fields->alist)) | |
157 | ||
158 | (and=> (download (string-append (cache-url cache) "/" | |
159 | (store-path-hash-part path) | |
160 | ".narinfo")) | |
161 | (lambda (properties) | |
162 | (alist->record properties make-narinfo | |
163 | '("StorePath" "URL" "Compression" | |
164 | "FileHash" "FileSize" "NarHash" "NarSize" | |
165 | "References" "Deriver" "System"))))) | |
166 | ||
167 | (define %cache-url | |
168 | (or (getenv "GUIX_BINARY_SUBSTITUTE_URL") | |
169 | "http://hydra.gnu.org")) | |
170 | ||
171 | \f | |
172 | ;;; | |
173 | ;;; Entry point. | |
174 | ;;; | |
175 | ||
176 | (define (guix-substitute-binary . args) | |
177 | "Implement the build daemon's substituter protocol." | |
178 | (match args | |
179 | (("--query") | |
180 | (let ((cache (open-cache %cache-url))) | |
181 | (let loop ((command (read-line))) | |
182 | (or (eof-object? command) | |
183 | (begin | |
184 | (match (string-tokenize command) | |
185 | (("have" paths ..1) | |
186 | ;; Return the subset of PATHS available in CACHE. | |
187 | (let ((substitutable | |
188 | (if cache | |
189 | (par-map (cut fetch-narinfo cache <>) | |
190 | paths) | |
191 | '()))) | |
192 | (for-each (lambda (narinfo) | |
193 | (when narinfo | |
462f8e9f | 194 | (format #t "~a~%" (narinfo-path narinfo)))) |
d7c5d277 | 195 | (filter narinfo? substitutable)) |
462f8e9f | 196 | (newline))) |
f65cf81a LC |
197 | (("info" paths ..1) |
198 | ;; Reply info about PATHS if it's in CACHE. | |
199 | (let ((substitutable | |
200 | (if cache | |
201 | (par-map (cut fetch-narinfo cache <>) | |
202 | paths) | |
203 | '()))) | |
204 | (for-each (lambda (narinfo) | |
205 | (format #t "~a\n~a\n~a\n" | |
206 | (narinfo-path narinfo) | |
207 | (or (and=> (narinfo-deriver narinfo) | |
208 | (cute string-append | |
209 | (%store-prefix) "/" | |
210 | <>)) | |
211 | "") | |
212 | (length (narinfo-references narinfo))) | |
213 | (for-each (cute format #t "~a/~a~%" | |
214 | (%store-prefix) <>) | |
215 | (narinfo-references narinfo)) | |
216 | (format #t "~a\n~a\n" | |
217 | (or (narinfo-file-size narinfo) 0) | |
462f8e9f | 218 | (or (narinfo-size narinfo) 0))) |
d7c5d277 | 219 | (filter narinfo? substitutable)) |
462f8e9f | 220 | (newline))) |
f65cf81a LC |
221 | (wtf |
222 | (error "unknown `--query' command" wtf))) | |
223 | (loop (read-line))))))) | |
224 | (("--substitute" store-path destination) | |
225 | ;; Download PATH and add it to the store. | |
226 | ;; TODO: Implement. | |
227 | (format (current-error-port) "substitution not implemented yet~%") | |
228 | #f) | |
229 | (("--version") | |
230 | (show-version-and-exit "guix substitute-binary")))) | |
231 | ||
232 | ;;; substitute-binary.scm ends here |