1 | ;;;; chicken-install.scm |
---|
2 | ; |
---|
3 | ; Copyright (c) 2008, The Chicken Team |
---|
4 | ; All rights reserved. |
---|
5 | ; |
---|
6 | ; Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following |
---|
7 | ; conditions are met: |
---|
8 | ; |
---|
9 | ; Redistributions of source code must retain the above copyright notice, this list of conditions and the following |
---|
10 | ; disclaimer. |
---|
11 | ; Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following |
---|
12 | ; disclaimer in the documentation and/or other materials provided with the distribution. |
---|
13 | ; Neither the name of the author nor the names of its contributors may be used to endorse or promote |
---|
14 | ; products derived from this software without specific prior written permission. |
---|
15 | ; |
---|
16 | ; THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS |
---|
17 | ; OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY |
---|
18 | ; AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR |
---|
19 | ; CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
---|
20 | ; CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR |
---|
21 | ; SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
---|
22 | ; THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR |
---|
23 | ; OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
---|
24 | ; POSSIBILITY OF SUCH DAMAGE. |
---|
25 | |
---|
26 | |
---|
27 | (require-library setup-utils setup-download) |
---|
28 | (require-library srfi-1 posix data-structures utils ports regex ports extras |
---|
29 | srfi-13 files) |
---|
30 | |
---|
31 | |
---|
32 | (foreign-declare #<<EOF |
---|
33 | #ifndef C_INSTALL_BIN_HOME |
---|
34 | # define C_INSTALL_BIN_HOME NULL |
---|
35 | #endif |
---|
36 | EOF |
---|
37 | ) |
---|
38 | |
---|
39 | |
---|
40 | (module main () |
---|
41 | |
---|
42 | (import scheme chicken srfi-1 posix data-structures utils ports regex ports extras |
---|
43 | srfi-13 files) |
---|
44 | (import setup-utils setup-download) |
---|
45 | |
---|
46 | (import foreign) |
---|
47 | |
---|
48 | (define +default-repository-files+ |
---|
49 | '("setup-api.so" "setup-api.import.so" |
---|
50 | "setup-utils.so" "setup-utils.import.so" |
---|
51 | "setup-download.so" "setup-download.import.so" |
---|
52 | "chicken.import.so" |
---|
53 | "lolevel.import.so" |
---|
54 | "srfi-1.import.so" |
---|
55 | "srfi-4.import.so" |
---|
56 | "data-structures.import.so" |
---|
57 | "ports.import.so" |
---|
58 | "files.import.so" |
---|
59 | "posix.import.so" |
---|
60 | "srfi-13.import.so" |
---|
61 | "srfi-69.import.so" |
---|
62 | "extras.import.so" |
---|
63 | "regex.import.so" |
---|
64 | "srfi-14.import.so" |
---|
65 | "tcp.import.so" |
---|
66 | "foreign.import.so" |
---|
67 | "scheme.import.so" |
---|
68 | "srfi-18.import.so" |
---|
69 | "utils.import.so" |
---|
70 | "csi.import.so")) |
---|
71 | |
---|
72 | (define *program-path* |
---|
73 | (or (and-let* ((p (getenv "CHICKEN_PREFIX"))) |
---|
74 | (make-pathname p "bin") ) |
---|
75 | (foreign-value "C_INSTALL_BIN_HOME" c-string) ) ) |
---|
76 | |
---|
77 | (define *keep* #f) |
---|
78 | (define *force* #f) |
---|
79 | (define *sudo* #f) |
---|
80 | (define *prefix* #f) |
---|
81 | (define *host-extension* #f) |
---|
82 | (define *run-tests* #f) |
---|
83 | (define *retrieve-only* #f) |
---|
84 | (define *no-install* #f) |
---|
85 | (define *username* #f) |
---|
86 | (define *password* #f) |
---|
87 | (define *default-sources* '()) |
---|
88 | (define *default-location* #f) |
---|
89 | (define *default-transport* 'http) |
---|
90 | |
---|
91 | (define (load-defaults) |
---|
92 | (let ((deff (make-pathname (chicken-home) "setup.defaults"))) |
---|
93 | (cond ((not (file-exists? deff)) '()) |
---|
94 | (else |
---|
95 | (set! *default-sources* (read-file deff)) |
---|
96 | (pair? *default-sources*))))) |
---|
97 | |
---|
98 | (define (deps key meta) |
---|
99 | (or (and-let* ((d (assq key meta))) |
---|
100 | (cdr d)) |
---|
101 | '())) |
---|
102 | |
---|
103 | (define (init-repository dir) |
---|
104 | (let ((src (repository-path)) |
---|
105 | (copy (if (or (feature? 'mingw32) (feature? 'msvc)) |
---|
106 | "copy" |
---|
107 | "cp -r"))) |
---|
108 | (print "copying required files to " dir " ...") |
---|
109 | (for-each |
---|
110 | (lambda (f) |
---|
111 | (system* "~a ~a ~a" copy (make-pathname src f) dir)) |
---|
112 | +default-repository-files+))) |
---|
113 | |
---|
114 | (define (ext-version x) |
---|
115 | (cond ((or (eq? x 'chicken) |
---|
116 | (equal? x "chicken") |
---|
117 | (member (->string x) ##sys#core-library-modules)) |
---|
118 | (chicken-version) ) |
---|
119 | ((extension-information x) => |
---|
120 | (lambda (info) |
---|
121 | (and-let* ((a (assq 'version info))) |
---|
122 | (->string (cadr a))))) |
---|
123 | (else #f))) |
---|
124 | |
---|
125 | (define (outdated-dependencies meta) |
---|
126 | (let ((ds (append |
---|
127 | (deps 'depends meta) |
---|
128 | (deps 'needs meta) |
---|
129 | (if *run-tests* (deps 'test-depends meta) '())))) |
---|
130 | (let loop ((deps ds) (missing '()) (upgrade '())) |
---|
131 | (if (null? deps) |
---|
132 | (values (reverse missing) (reverse upgrade)) |
---|
133 | (let ((dep (car deps)) |
---|
134 | (rest (cdr deps))) |
---|
135 | (cond ((or (symbol? dep) (string? dep)) |
---|
136 | (loop rest |
---|
137 | (if (ext-version dep) |
---|
138 | missing |
---|
139 | (cons (->string dep) missing)) |
---|
140 | upgrade)) |
---|
141 | ((and (list? dep) (= 2 (length dep)) |
---|
142 | (or (string? (car dep)) (symbol? (car dep)))) |
---|
143 | (let ((v (ext-version (car dep)))) |
---|
144 | (cond ((not v) |
---|
145 | (warning |
---|
146 | "installed extension has unknown version - assuming it is outdated" |
---|
147 | (car dep)) |
---|
148 | (loop rest missing |
---|
149 | (alist-cons |
---|
150 | (->string (car dep)) |
---|
151 | (->string (cadr dep)) |
---|
152 | upgrade))) |
---|
153 | ((version>=? (->string (cadr dep)) v) |
---|
154 | (loop rest missing |
---|
155 | (alist-cons |
---|
156 | (->string (car dep)) (->string (cadr dep)) |
---|
157 | upgrade))) |
---|
158 | (else (loop rest missing upgrade))))) |
---|
159 | (else |
---|
160 | (warning |
---|
161 | "invalid dependency syntax in extension meta information" |
---|
162 | dep) |
---|
163 | (loop rest missing upgrade)))))))) |
---|
164 | |
---|
165 | (define *eggs+dirs* '()) |
---|
166 | (define *checked* '()) |
---|
167 | |
---|
168 | (define (try name version) |
---|
169 | (let loop ((defs (if (and *default-location* *default-transport*) |
---|
170 | `(((location ,*default-location*) |
---|
171 | (transport ,*default-transport*))) |
---|
172 | *default-sources*))) |
---|
173 | (and (pair? defs) |
---|
174 | (let* ((def (car defs)) |
---|
175 | (loc (cadr (or (assq 'location def) |
---|
176 | (error "missing location entry" def)))) |
---|
177 | (trans (cadr (or (assq 'transport def) |
---|
178 | (error "missing transport entry" def))))) |
---|
179 | (or (condition-case |
---|
180 | (retrieve-extension |
---|
181 | name trans loc |
---|
182 | version: version |
---|
183 | destination: (and *retrieve-only* (current-directory)) |
---|
184 | tests: *run-tests* |
---|
185 | username: *username* |
---|
186 | password: *password*) |
---|
187 | ((exn net) |
---|
188 | (print "TCP connect timeout") |
---|
189 | #f) |
---|
190 | ((exn http-fetch) |
---|
191 | (print "HTTP protocol error") |
---|
192 | #f) |
---|
193 | (e () (abort e))) |
---|
194 | (loop (cdr defs))))))) |
---|
195 | |
---|
196 | (define (retrieve eggs) |
---|
197 | (print "retrieving ...") |
---|
198 | (for-each |
---|
199 | (lambda (egg) |
---|
200 | (cond ((assoc egg *eggs+dirs*) => |
---|
201 | (lambda (a) |
---|
202 | ;; push to front |
---|
203 | (set! *eggs+dirs* (cons a (delete a *eggs+dirs* eq?))) ) ) |
---|
204 | (else |
---|
205 | (let* ((name (if (pair? egg) (car egg) egg)) |
---|
206 | (version (and (pair? egg) (cdr egg))) |
---|
207 | (dir (try name version))) |
---|
208 | (unless dir |
---|
209 | (error "extension or version not found")) |
---|
210 | (print " " name " located at " dir) |
---|
211 | (set! *eggs+dirs* (alist-cons name dir *eggs+dirs*))))) ) |
---|
212 | eggs) |
---|
213 | (unless *retrieve-only* |
---|
214 | (for-each |
---|
215 | (lambda (e+d) |
---|
216 | (unless (member (car e+d) *checked*) |
---|
217 | (set! *checked* (cons (car e+d) *checked*)) |
---|
218 | (let ((mfile (make-pathname (cdr e+d) (car e+d) "meta"))) |
---|
219 | (cond ((file-exists? mfile) |
---|
220 | (let ((meta (with-input-from-file mfile read))) |
---|
221 | (print "checking dependencies for `" (car e+d) "' ...") |
---|
222 | (let-values (((missing upgrade) (outdated-dependencies meta))) |
---|
223 | (when (pair? missing) |
---|
224 | (print " missing: " (string-intersperse missing ", ")) |
---|
225 | (retrieve missing)) |
---|
226 | (when (and (pair? upgrade) |
---|
227 | (or *force* |
---|
228 | (yes-or-no? |
---|
229 | (string-concatenate |
---|
230 | (append |
---|
231 | (list "The following installed extensions are outdated, because `" |
---|
232 | (car e+d) "' requires later versions:\n") |
---|
233 | (map (lambda (e) |
---|
234 | (sprintf |
---|
235 | " ~a (~a -> ~a)~%" |
---|
236 | (car e) |
---|
237 | (let ((v (assq 'version (extension-information (car e))))) |
---|
238 | (if v (cadr v) "???")) |
---|
239 | (cdr e))) |
---|
240 | upgrade) |
---|
241 | '("\nDo you want to replace the existing extensions?"))) |
---|
242 | "no") ) ) |
---|
243 | (let ((ueggs (unzip1 upgrade))) |
---|
244 | (print " upgrade: " (string-intersperse ueggs ", ")) |
---|
245 | (for-each |
---|
246 | (lambda (e) |
---|
247 | (print "removing previously installed extension `" e "' ...") |
---|
248 | (remove-extension e *sudo*) ) |
---|
249 | ueggs) |
---|
250 | (retrieve ueggs)))))) |
---|
251 | (else |
---|
252 | (warning |
---|
253 | (string-append |
---|
254 | "extension `" (car e+d) "' has no .meta file " |
---|
255 | "- assuming it has no dependencies"))))))) |
---|
256 | *eggs+dirs*))) |
---|
257 | |
---|
258 | (define (install eggs) |
---|
259 | (retrieve eggs) |
---|
260 | (unless *retrieve-only* |
---|
261 | (for-each ; we assume the order reflects the dependency tree... |
---|
262 | (lambda (e+d) |
---|
263 | (print "installing " (car e+d) " ...") |
---|
264 | (print "changing current directory to " (cdr e+d)) |
---|
265 | (parameterize ((current-directory (cdr e+d))) |
---|
266 | (let ((cmd (sprintf |
---|
267 | "~a/csi -bnq -e \"(require-library setup-api)\" -e \"(import setup-api)\" ~a ~a ~a ~a ~a ~a" |
---|
268 | *program-path* |
---|
269 | (if *sudo* "-e \"(sudo-install #t)\"" "") |
---|
270 | (if *keep* "-e \"(keep-intermediates #t)\"" "") |
---|
271 | (if *no-install* "-e \"(setup-install-flag #f)\"" "") |
---|
272 | (if *host-extension* "-e \"(host-extension #t)\"" "") |
---|
273 | (if *prefix* |
---|
274 | (sprintf "-e \"(installation-prefix \\\"~a\\\")\"" *prefix*) |
---|
275 | "") |
---|
276 | (make-pathname (cdr e+d) (car e+d) "setup")))) |
---|
277 | (system* cmd)) |
---|
278 | (when (and *run-tests* |
---|
279 | (file-exists? "tests") |
---|
280 | (directory? "tests") |
---|
281 | (file-exists? "tests/run.scm") ) |
---|
282 | (current-directory "tests") |
---|
283 | (let ((cmd (sprintf "~a/csi -s run.scm ~a" *program-path* (car e+d)))) |
---|
284 | (print cmd) |
---|
285 | (system* cmd))))) |
---|
286 | *eggs+dirs*))) |
---|
287 | |
---|
288 | (define (cleanup) |
---|
289 | (unless *keep* |
---|
290 | (and-let* ((tmpdir (temporary-directory))) |
---|
291 | (remove-directory tmpdir)))) |
---|
292 | |
---|
293 | (define (usage code) |
---|
294 | (print #<<EOF |
---|
295 | usage: chicken-install [OPTION | EXTENSION[:VERSION]] ... |
---|
296 | |
---|
297 | -h -help show this message and exit |
---|
298 | -v -version show version and exit |
---|
299 | -force don't ask, install even if versions don't match |
---|
300 | -k -keep keep temporary files |
---|
301 | -l -location LOCATION install from given location instead of default |
---|
302 | -t -transport TRANSPORT use given transport instead of default |
---|
303 | -s -sudo use sudo(1) for installing or removing files |
---|
304 | -r -retrieve only retrieve egg into current directory, don't install |
---|
305 | -n -no-install do not install, just build (implies `-keep') |
---|
306 | -p -prefix PREFIX change installation prefix to PREFIX |
---|
307 | -host-extension when cross-compiling, compile extension for host |
---|
308 | -test run included test-cases, if available |
---|
309 | -username USER set username for transports that require this |
---|
310 | -password PASS set password for transports that require this |
---|
311 | -i -init DIRECTORY initialize empty alternative repository |
---|
312 | EOF |
---|
313 | );| |
---|
314 | (exit code)) |
---|
315 | |
---|
316 | (define *short-options* '(#\h #\k #\l #\t #\s #\p #\r #\n #\v #\i)) |
---|
317 | |
---|
318 | (define (main args) |
---|
319 | (let ((defaults (load-defaults))) |
---|
320 | (let loop ((args args) (eggs '())) |
---|
321 | (cond ((null? args) |
---|
322 | (when (null? eggs) |
---|
323 | (let ((setups (glob "*.setup"))) |
---|
324 | (cond ((pair? setups) |
---|
325 | (set! *eggs+dirs* |
---|
326 | (append |
---|
327 | (map (lambda (s) (cons (pathname-file s) ".")) setups) |
---|
328 | *eggs+dirs*))) |
---|
329 | (else |
---|
330 | (print "no setup-scripts to process") |
---|
331 | (exit 1))) ) ) |
---|
332 | (unless defaults |
---|
333 | (unless *default-transport* |
---|
334 | (error "no default transport defined - please use `-transport' option")) |
---|
335 | (unless *default-location* |
---|
336 | (error "no default location defined - please use `-location' option"))) |
---|
337 | (install (reverse eggs))) |
---|
338 | (else |
---|
339 | (let ((arg (car args))) |
---|
340 | (cond ((or (string=? arg "-help") |
---|
341 | (string=? arg "-h") |
---|
342 | (string=? arg "--help")) |
---|
343 | (usage 0)) |
---|
344 | ((string=? arg "-force") |
---|
345 | (set! *force* #t) |
---|
346 | (loop (cdr args) eggs)) |
---|
347 | ((or (string=? arg "-k") (string=? arg "-keep")) |
---|
348 | (set! *keep* #t) |
---|
349 | (loop (cdr args) eggs)) |
---|
350 | ((or (string=? arg "-s") (string=? arg "-sudo")) |
---|
351 | (set! *sudo* #t) |
---|
352 | (loop (cdr args) eggs)) |
---|
353 | ((or (string=? arg "-r") (string=? arg "-retrieve")) |
---|
354 | (set! *retrieve-only* #t) |
---|
355 | (loop (cdr args) eggs)) |
---|
356 | ((or (string=? arg "-l") (string=? arg "-location")) |
---|
357 | (unless (pair? (cdr args)) (usage 1)) |
---|
358 | (set! *default-location* (cadr args)) |
---|
359 | (loop (cddr args) eggs)) |
---|
360 | ((or (string=? arg "-t") (string=? arg "-transport")) |
---|
361 | (unless (pair? (cdr args)) (usage 1)) |
---|
362 | (set! *default-transport* (string->symbol (cadr args))) |
---|
363 | (loop (cddr args) eggs)) |
---|
364 | ((or (string=? arg "-p") (string=? arg "-prefix")) |
---|
365 | (unless (pair? (cdr args)) (usage 1)) |
---|
366 | (set! *prefix* (cadr args)) |
---|
367 | (loop (cddr args) eggs)) |
---|
368 | ((or (string=? arg "-n") (string=? arg "-no-install")) |
---|
369 | (set! *keep* #t) |
---|
370 | (set! *no-install* #t) |
---|
371 | (loop (cdr args) eggs)) |
---|
372 | ((or (string=? arg "-v") (string=? arg "-version")) |
---|
373 | (print (chicken-version)) |
---|
374 | (exit 0)) |
---|
375 | ((or (string=? arg "-i") (string=? arg "-init")) |
---|
376 | (unless (pair? (cdr args)) (usage 1)) |
---|
377 | (init-repository (cadr args)) |
---|
378 | (exit 0)) |
---|
379 | ((string=? "-test" arg) |
---|
380 | (set! *run-tests* #t) |
---|
381 | (loop (cdr args) eggs)) |
---|
382 | ((string=? "-host-extension" arg) |
---|
383 | (set! *host-extension* #t) |
---|
384 | (loop (cdr args) eggs)) |
---|
385 | ((string=? "-username" arg) |
---|
386 | (unless (pair? (cdr args)) (usage 1)) |
---|
387 | (set! *username* (cadr args)) |
---|
388 | (loop (cddr args) eggs)) |
---|
389 | ((string=? "-password" arg) |
---|
390 | (unless (pair? (cdr args)) (usage 1)) |
---|
391 | (set! *password* (cadr args)) |
---|
392 | (loop (cddr args) eggs)) |
---|
393 | ((and (positive? (string-length arg)) |
---|
394 | (char=? #\- (string-ref arg 0))) |
---|
395 | (if (> (string-length arg) 2) |
---|
396 | (let ((sos (string->list (substring arg 1)))) |
---|
397 | (if (null? (lset-intersection eq? *short-options* sos)) |
---|
398 | (loop (append (map (cut string #\- <>) sos) (cdr args)) eggs) |
---|
399 | (usage 1))) |
---|
400 | (usage 1))) |
---|
401 | ((equal? "setup" (pathname-extension arg)) |
---|
402 | (let ((egg (pathname-file arg))) |
---|
403 | (set! *eggs+dirs* |
---|
404 | (alist-cons |
---|
405 | egg |
---|
406 | (let ((dir (pathname-directory arg))) |
---|
407 | (if dir |
---|
408 | (if (absolute-pathname? dir) |
---|
409 | dir |
---|
410 | (make-pathname (current-directory) dir) ) |
---|
411 | (current-directory))) |
---|
412 | *eggs+dirs*)) |
---|
413 | (loop (cdr args) (cons egg eggs)))) |
---|
414 | ((string-match "([^:]+):(.+)" arg) => |
---|
415 | (lambda (m) |
---|
416 | (loop (cdr args) (alist-cons (cadr m) (caddr m) eggs)))) |
---|
417 | (else (loop (cdr args) (cons arg eggs)))))))))) |
---|
418 | |
---|
419 | (handle-exceptions ex |
---|
420 | (begin |
---|
421 | (print-error-message ex (current-error-port)) |
---|
422 | (cleanup) |
---|
423 | (exit 1)) |
---|
424 | (main (command-line-arguments)) |
---|
425 | (cleanup)) |
---|
426 | |
---|
427 | ) |
---|