~ chicken-core (chicken-5) /chicken-install.scm
Trap1;;;; chicken-install.scm
2;
3; Copyright (c) 2008-2022, 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(declare
27 (uses chicken-ffi-syntax)) ; populate ##sys#chicken-ffi-macro-environment
28
29(module main ()
30
31(import (scheme))
32(import (chicken base))
33(import (chicken condition))
34(import (chicken foreign))
35(import (chicken keyword))
36(import (chicken file))
37(import (chicken file posix))
38(import (chicken fixnum))
39(import (chicken format))
40(import (chicken irregex))
41(import (chicken module))
42(import (chicken tcp))
43(import (chicken port))
44(import (chicken platform))
45(import (chicken internal))
46(import (chicken io))
47(import (chicken sort))
48(import (chicken time))
49(import (chicken pathname))
50(import (chicken process))
51(import (chicken process-context))
52(import (chicken pretty-print))
53(import (chicken string))
54
55(define +defaults-version+ 2)
56(define +module-db+ "modules.db")
57(define +defaults-file+ "setup.defaults")
58(define +short-options+ '(#\h #\k #\s #\r #\n #\u #\v))
59(define +one-hour+ (* 60 60))
60(define +internal-modules+ '(chicken.internal chicken.internal.syntax))
61
62(include "mini-srfi-1.scm")
63(include "egg-environment.scm")
64(include "egg-information.scm")
65(include "egg-compile.scm")
66(include "egg-download.scm")
67
68(define user-defaults #f)
69(define quiet #t)
70(define default-servers '())
71(define default-locations '())
72(define mappings '())
73(define aliases '())
74(define override '())
75(define hacks '())
76(define proxy-host #f)
77(define proxy-port #f)
78(define proxy-user-pass #f)
79(define retrieve-only #f)
80(define retrieve-recursive #f)
81(define do-not-build #f)
82(define no-install #f)
83(define no-install-dependencies #f)
84(define list-versions-only #f)
85(define canonical-eggs '())
86(define requested-eggs '())
87(define dependencies '())
88(define checked-eggs '())
89(define run-tests #f)
90(define force-install #f)
91(define host-extension cross-chicken)
92(define target-extension cross-chicken)
93(define sudo-install #f)
94(define sudo-program (or (get-environment-variable "SUDO") "sudo"))
95(define update-module-db #f)
96(define purge-mode #f)
97(define keepfiles #f)
98(define print-repository #f)
99(define cached-only #f)
100
101(define platform
102 (if (eq? (software-version) 'mingw32) 'windows 'unix))
103
104(define current-status
105 (list ##sys#build-id default-prefix
106 (get-environment-variable "CSC_OPTIONS")
107 (get-environment-variable "LD_LIBRARY_PATH")
108 (get-environment-variable "DYLD_LIBRARY_PATH")
109 (get-environment-variable "CHICKEN_INCLUDE_PATH")
110 (get-environment-variable "DYLD_LIBRARY_PATH")))
111
112(define (repo-path)
113 (if (and cross-chicken (not host-extension))
114 (##sys#split-path (destination-repository 'target))
115 (repository-path)))
116
117(define (install-path)
118 (if (and cross-chicken (not host-extension))
119 (destination-repository 'target)
120 (destination-repository 'host)))
121
122(define (build-script-extension mode platform)
123 (string-append "build"
124 (if (eq? mode 'target) ".target" "")
125 (if (eq? platform 'windows) ".bat" ".sh")))
126
127(define (install-script-extension mode platform)
128 (string-append "install"
129 (if (eq? mode 'target) ".target" "")
130 (if (eq? platform 'windows) ".bat" ".sh")))
131
132
133;;; validate egg-information tree
134
135(define (egg-version? v)
136 (and (list? v)
137 (pair? v)
138 (null? (cdr v))
139 (let ((str (->string (car v))))
140 (irregex-match '(seq (+ numeric)
141 (? #\. (+ numeric)
142 (? #\. (+ numeric))))
143 str))))
144
145(define (optname? x)
146 (and (list? x)
147 (or (null? x)
148 (string? (car x))
149 (symbol? (car x)))))
150
151(define (nameprop? x)
152 (and (list? x) (or (symbol? (car x)) (string? (car x)))))
153
154(define (name-or-predefd? x)
155 (or (optname? x)
156 (and (pair? x)
157 (pair? (car x))
158 (eq? 'predefined (caar x))
159 (optname? (cdar x)))))
160
161;; ENTRY = (NAME TOPLEVEL? NESTED? NAMED? [VALIDATOR])
162(define egg-info-items
163 `((synopsis #t #f #f)
164 (author #t #f #f)
165 (category #t #f #f)
166 (license #t #f #f)
167 (version #t #f #f ,egg-version?)
168 (dependencies #t #f #f ,list?)
169 (source-dependencies #f #f #f ,list?)
170 (component-dependencies #f #f #f ,list?)
171 (test-dependencies #t #f #f ,list?)
172 (build-dependencies #t #f #f ,list?)
173 (components #t #t #f)
174 (foreign-dependencies #t #f #f ,list?)
175 (platform #t #f #f)
176 (installed-files #t #f #f ,list?)
177 (maintainer #t #f #f)
178 (files #f #f #f ,list?)
179 (distribution-files #t #f #f ,list?) ;; handled by henrietta-cache
180 (source #f #f #f)
181 (csc-options #f #f #f)
182 (link-options #f #f #f)
183 (custom-build #f #f #f)
184 (linkage #f #f #f)
185 (objects #f #f #f)
186 (destination #f #f #f ,list?)
187 (install-name #f #f #f ,nameprop?)
188 (target #f #t #f)
189 (host #f #t #f)
190 (types-file #f #f #f ,name-or-predefd?)
191 (inline-file #f #f #f ,optname?)
192 (extension #f #t #t)
193 (c-object #f #t #t)
194 (generated-source-file #f #t #t)
195 (program #f #t #t)
196 (data #f #t #t)
197 (modules #f #f #f)
198 (component-options #t #f #f)
199 (cond-expand * #t #f)
200 (error * #f #f)
201 (c-include #f #f #t)
202 (scheme-include #f #f #t)))
203
204(define (validate-egg-info info)
205 (define (validate info top?)
206 (for-each
207 (lambda (item)
208 (cond ((or (not (pair? item))
209 (not (list? item))
210 (not (symbol? (car item))))
211 (error "invalid egg information item" item))
212 ((assq (car item) egg-info-items) =>
213 (lambda (a)
214 (apply (lambda (name toplevel nested named #!optional validator)
215 (cond ((and top?
216 (not (eq? toplevel '*))
217 (not toplevel))
218 (error "egg information item not allowed at toplevel"
219 item))
220 ((and (not (eq? toplevel '*))
221 toplevel
222 (not top?))
223 (error "egg information item only allowed at toplevel" item))
224 ((and named
225 (or (null? (cdr item))
226 (not (symbol? (cadr item)))))
227 (error "unnamed egg information item" item))
228 ((and validator
229 (not (validator (cdr item))))
230 (error "egg information item has invalid structure" item)))
231 (when nested
232 (cond (named (validate (cddr item) #f))
233 ((eq? name 'cond-expand)
234 (for-each
235 (lambda (clause)
236 (unless (and (list? clause)
237 (>= (length clause) 1))
238 (error "invalid syntax in `cond-expand' clause" clause))
239 (validate (cdr clause) top?))
240 (cdr item)))
241 (else (validate (cdr item) #f)))))
242 a)))
243 (else (error "unknown egg information item" item))))
244 info))
245 (validate info #t)
246 info)
247
248
249;; utilities
250
251;; Simpler replacement for SRFI-13's "string-suffix?"
252(define (string-suffix? suffix s)
253 (let ((len-s (string-length s))
254 (len-suffix (string-length suffix)))
255 (and (not (< len-s len-suffix))
256 (string=? suffix
257 (substring s (- len-s len-suffix))))))
258
259(define (d flag . args)
260 (let ((flag (and (not (string? flag)) flag))
261 (fstr (if (string? flag) flag (car args)))
262 (args (if (string? flag) args (cdr args))))
263 (when (or flag (not quiet))
264 (flush-output)
265 (let ((port (current-error-port)))
266 (apply fprintf port fstr args)
267 (flush-output port) ) )))
268
269(define (version>=? v1 v2)
270 (define (version->list v)
271 (map (lambda (x) (or (string->number x) x))
272 (irregex-split "[-\\._]" (->string v))))
273 (let loop ((p1 (version->list v1))
274 (p2 (version->list v2)))
275 (cond ((null? p1) (null? p2))
276 ((null? p2))
277 ((number? (car p1))
278 (and (number? (car p2))
279 (or (> (car p1) (car p2))
280 (and (= (car p1) (car p2))
281 (loop (cdr p1) (cdr p2))))))
282 ((number? (car p2)))
283 ((string>? (car p1) (car p2)))
284 (else
285 (and (string=? (car p1) (car p2))
286 (loop (cdr p1) (cdr p2)))))))
287
288
289;; load defaults file ("setup.defaults")
290
291(define (load-defaults)
292 (let* ((cfg-dir (system-config-directory))
293 (user-file (and cfg-dir (make-pathname (list cfg-dir "chicken")
294 +defaults-file+)))
295 (deff (or user-defaults
296 (and user-file (file-exists? user-file))
297 (make-pathname host-sharedir +defaults-file+))))
298 (define (broken x)
299 (error "invalid entry in defaults file" deff x))
300 (cond ((not (file-exists? deff)) '())
301 (else
302 (for-each
303 (lambda (x)
304 (unless (and (list? x) (positive? (length x)))
305 (broken x))
306 (case (car x)
307 ((version)
308 (cond ((not (pair? (cdr x))) (broken x))
309 ((not (= (cadr x) +defaults-version+))
310 (error
311 (sprintf
312 "version of installed `~a' does not match chicken-install version (~a)"
313 +defaults-file+
314 +defaults-version+)
315 (cadr x)))
316 ;; others are ignored
317 ))
318 ((server)
319 (set! default-servers
320 (append default-servers (cdr x))))
321 ((map)
322 (set! mappings
323 (append
324 mappings
325 (map (lambda (m)
326 (let ((p (list-index (cut eq? '-> <>) m)))
327 (unless p (broken x))
328 (let-values (((from to) (split-at m p)))
329 (cons from (cdr to)))))
330 (cdr x)))))
331 ((alias)
332 (set! aliases
333 (append
334 aliases
335 (map (lambda (a)
336 (if (and (list? a) (= 2 (length a)) (every string? a))
337 (cons (car a) (cadr a))
338 (broken x)))
339 (cdr x)))))
340 ((override)
341 (set! override
342 (if (and (pair? (cdr x)) (string? (cadr x)))
343 (call-with-input-file (cadr x) read-list)
344 (cdr x))))
345 ((location)
346 (set! default-locations
347 (append default-locations (cdr x))))
348 ((hack)
349 (set! hacks (append hacks (list (eval (cadr x))))))
350 (else (broken x))))
351 (call-with-input-file deff read-list))))))
352
353
354;; set variables with HTTP proxy information
355
356(define (setup-proxy uri)
357 (and-let* (((string? uri))
358 (m (irregex-match "(http://)?([^:]+):?([0-9]*)" uri))
359 (port (irregex-match-substring m 3)))
360 (set! proxy-user-pass (get-environment-variable "proxy_auth"))
361 (set! proxy-host (irregex-match-substring m 2))
362 (set! proxy-port (or (string->number port) 80))))
363
364
365;; apply egg->egg mappings loaded from defaults
366
367(define (canonical x)
368 (cond ((symbol? x) (cons (symbol->string x) #f))
369 ((string? x) (cons x #f))
370 ((pair? x) x)
371 (else (error "internal error - bad egg spec" x))))
372
373(define (apply-mappings eggs)
374 (define (same? e1 e2)
375 (equal? (car (canonical e1)) (car (canonical e2))))
376 (let ((eggs2
377 (delete-duplicates
378 (append-map
379 (lambda (egg)
380 (cond ((find (lambda (m) (find (cut same? egg <>) (car m)))
381 mappings) =>
382 (lambda (m) (map ->string (cdr m))))
383 (else (list egg))))
384 eggs)
385 same?)))
386 (unless (and (= (length eggs) (length eggs2))
387 (every (lambda (egg)
388 (find (cut same? <> egg) eggs2))
389 eggs))
390 (d "mapped ~s to ~s~%" eggs eggs2))
391 eggs2))
392
393
394;; override versions, if specified in "overrides" file
395
396(define (override-version egg)
397 (let ((name (string->symbol (if (pair? egg) (car egg) egg))))
398 (cond ((assq name override) =>
399 (lambda (a)
400 (if (and (pair? egg)
401 (pair? (cdr a))
402 (not (equal? (cadr a) (cdr egg))))
403 (warning
404 (sprintf
405 "version `~a' of extension `~a' overrides explicitly given version `~a'"
406 (cadr a) name (cdr egg)))
407 (d "overriding: ~a~%" a))
408 (if (null? (cdr a))
409 (and (pair? egg) (cdr egg))
410 (cadr a))))
411 ((pair? egg) (cdr egg))
412 (else #f))))
413
414
415;; "locate" egg: either perform HTTP download or copy from a file-system
416;; location, also make sure it is up to date
417
418(define (locate-egg name version)
419 (let* ((cached (make-pathname cache-directory name))
420 (metadata-dir (make-pathname cache-metadata-directory name))
421 (now (current-seconds))
422 (status (make-pathname metadata-dir +status-file+))
423 (eggfile (make-pathname cached name +egg-extension+)))
424 (define (fetch lax)
425 (when (file-exists? cached)
426 (delete-directory cached #t))
427 (when (file-exists? metadata-dir)
428 (delete-directory metadata-dir #t))
429 (create-directory cached #t)
430 (create-directory metadata-dir #t)
431 (fetch-egg-sources name version cached lax))
432 (cond ((and (probe-dir cached)
433 (not (file-exists? status)))
434 ;; If for whatever reason the status file doesn't exist
435 ;; (e.g., it was renamed, as in 2f6a7221), reset the cache
436 ;; of the egg to prevent the object files in there from
437 ;; being reused.
438 (d "resetting ~a, as ~a does not exist~%" cached status)
439 (fetch #f))
440 ((or (not (probe-dir cached))
441 (not (file-exists? eggfile)))
442 (d "~a not cached~%" name)
443 (when cached-only (error "extension not cached" name))
444 (fetch #f))
445 ((and (file-exists? status)
446 (not (equal? current-status
447 (with-input-from-file status read))))
448 (d "status changed for ~a~%" name)
449 (cond (cached-only
450 (if force-install
451 (warning "cached egg does not match CHICKEN version" name)
452 (error "cached egg does not match CHICKEN version - use `-force' to install anyway" name)))
453 (else (fetch #f)))))
454 (let* ((info (validate-egg-info (load-egg-info eggfile)))
455 (vfile (make-pathname metadata-dir +version-file+))
456 (tfile (make-pathname metadata-dir +timestamp-file+))
457 (lversion (or (get-egg-property info 'version)
458 (and (file-exists? vfile)
459 (with-input-from-file vfile read)))))
460 (cond ((and (not cached-only)
461 (if (string? version)
462 (not (equal? version lversion))
463 (or (and (file-exists? tfile)
464 (> (- now (with-input-from-file tfile read)) +one-hour+))
465 (not (check-remote-version name lversion cached)))))
466 (d "version of ~a out of date~%" name)
467 (fetch #t)
468 (let* ((info (validate-egg-info (load-egg-info eggfile))) ; new egg info (fetched)
469 (lversion (or (get-egg-property info 'version)
470 (and (file-exists? vfile)
471 (with-input-from-file vfile read)))))
472 (values cached lversion)))
473 (else (values cached version))))))
474
475(define (resolve-location name)
476 (cond ((assoc name aliases) =>
477 (lambda (a)
478 (let ((new (cdr a)))
479 (d "resolving alias `~a' to: ~a~%" name new)
480 (resolve-location new))))
481 (else name)))
482
483(define (locate-local-egg-dir location egg-name version)
484 ;; Locate the directory of egg-name, considering the following
485 ;; directory layouts in order:
486 ;; * <location>/<egg-name>/<egg-name>.egg
487 ;; * <location>/<egg-name>/<version>/<egg-name>.egg
488 ;;
489 ;; Return (values <egg-dir> <version>). <egg-dir> and <version>
490 ;; will be #f in case they cannot be determined.
491 (let ((egg-dir (probe-dir (make-pathname location egg-name))))
492 (cond
493 ((not egg-dir)
494 (values #f #f))
495 ;; <location>/<egg-name>/<egg-name>.egg
496 ((file-exists? (make-pathname egg-dir egg-name +egg-extension+))
497 (values egg-dir #f))
498 (else
499 ;; <location>/<egg-name>/<version>/<egg-name>.egg
500 (if version
501 (values (probe-dir (make-pathname egg-dir (->string version)))
502 version)
503 (let ((versions (directory egg-dir)))
504 (if (null? versions)
505 (values #f #f)
506 (let ((latest (car (sort versions version>=?))))
507 (values (make-pathname egg-dir (->string latest))
508 latest)))))))))
509
510(define (write-cache-metadata egg egg-version)
511 (let ((metadata-dir (make-pathname cache-metadata-directory egg)))
512 (when egg-version
513 (with-output-to-file (make-pathname metadata-dir +version-file+)
514 (cut write egg-version)))
515 (with-output-to-file (make-pathname metadata-dir +timestamp-file+)
516 (cut write (current-seconds)))
517 (with-output-to-file (make-pathname metadata-dir +status-file+)
518 (cut write current-status))))
519
520(define (fetch-egg-sources name version dest lax)
521 (print "fetching " name)
522 (let loop ((locs default-locations))
523 (cond ((null? locs)
524 (let ((tmpdir (create-temporary-directory)))
525 (let loop ((srvs (map resolve-location default-servers)))
526 (if (null? srvs)
527 (if lax
528 (print "no connection to server or egg not found remotely - will use cached version")
529 (begin
530 (delete-directory dest)
531 (delete-directory tmpdir)
532 (error "extension or version not found" name)))
533 (begin
534 (d "trying server ~a ...~%" (car srvs))
535 (receive (dir ver)
536 (try-download name (car srvs)
537 version: version
538 destination: tmpdir
539 tests: #t ;; Always fetch tests, otherwise cached eggs can't be tested later
540 proxy-host: proxy-host
541 proxy-port: proxy-port
542 proxy-user-pass: proxy-user-pass)
543 (cond (dir
544 (copy-egg-sources tmpdir dest)
545 (delete-directory tmpdir #t)
546 (write-cache-metadata name ver))
547 (else (loop (cdr srvs))))))))))
548 (else
549 (receive (dir version-from-path)
550 (locate-local-egg-dir (car locs) name version)
551 (if dir
552 (let* ((eggfile (make-pathname dir name +egg-extension+))
553 (info (validate-egg-info (load-egg-info eggfile)))
554 (rversion
555 ;; If version-from-path is non-#f, prefer it
556 ;; over rversion, as it means the egg author
557 ;; actually tagged the egg. rversion might
558 ;; be outdated in case the egg author forgot
559 ;; to bump it in the .egg file.
560 (or version-from-path
561 (get-egg-property info 'version))))
562 (d "trying location ~a ...~%" dir)
563 (if (or (not rversion)
564 (not version)
565 (version>=? rversion version))
566 (begin
567 (copy-egg-sources dir dest)
568 (write-cache-metadata name (or rversion version)))
569 (loop (cdr locs))))
570 (loop (cdr locs))))))))
571
572
573(define (copy-egg-sources from to)
574 ;;XXX should probably be done manually, instead of calling tool
575 (let ((cmd (string-append
576 (copy-directory-command platform)
577 ;; Don't quote the globbing character!
578 " " (make-pathname (qs* from platform #t) "*")
579 " " (qs* to platform #t))))
580 (d "~a~%" cmd)
581 (system+ cmd platform)))
582
583(define (check-remote-version name lversion cached)
584 (let loop ((locs default-locations))
585 (cond ((null? locs)
586 (let loop ((srvs (map resolve-location default-servers)))
587 (and (pair? srvs)
588 (let ((versions (try-list-versions name (car srvs))))
589 (or (and versions
590 (every (cut version>=? lversion <>) versions))
591 (loop (cdr srvs)))))))
592 ;; The order of probe-dir's here is important. First try
593 ;; the path with version, then the path without version.
594 ((or (probe-dir (make-pathname (list (car locs) name)
595 (->string lversion)))
596 (probe-dir (make-pathname (car locs) name)))
597 => (lambda (dir)
598 ;; for locally available eggs, check set of files and
599 ;; timestamps
600 (compare-trees dir cached)))
601 (else (loop (cdr locs))))))
602
603(define (compare-trees there here)
604 (let walk ((there there)
605 (here here))
606 (let ((tfs (directory there))
607 (hfs (directory here)))
608 (every (lambda (f)
609 (and (member f hfs)
610 (let ((tf2 (make-pathname there f))
611 (hf2 (make-pathname here f)))
612 (and (<= (file-modification-time tf2)
613 (file-modification-time hf2))
614 (if (directory-exists? tf2)
615 (and (directory-exists? hf2)
616 (walk tf2 hf2))
617 (not (directory-exists? hf2)))))))
618 tfs))))
619
620
621;; check installed eggs for already installed files
622
623(define (matching-installed-files egg fnames)
624 (let ((eggs (glob (make-pathname (install-path) "*" +egg-info-extension+))))
625 (let loop ((eggs eggs) (same '()))
626 (cond ((null? eggs) same)
627 ((string=? egg (pathname-file (car eggs)))
628 (loop (cdr eggs) same))
629 (else
630 (let* ((info (load-egg-info (car eggs)))
631 (files (assq 'installed-files info))
632 (mfiles (and files
633 (filter (lambda (fname)
634 (and (not (member fname same))
635 (member fname files)))
636 fnames))))
637 (loop (cdr eggs) (append (or mfiles '()) same))))))))
638
639(define (check-installed-files name info)
640 (let ((bad (matching-installed-files name (cdr (assq 'installed-files info)))))
641 (unless (null? bad)
642 (flush-output)
643 (fprintf (current-error-port)
644 "\nthe extension `~a' will overwrite the following files:\n\n" name)
645 (for-each
646 (lambda (fname)
647 (fprintf (current-error-port) " ~a~%" fname))
648 bad)
649 (exit 1))))
650
651
652;; retrieve eggs, recursively (if needed)
653
654(define (retrieve-eggs eggs)
655 (for-each
656 (lambda (egg)
657 (cond ((assoc egg canonical-eggs) =>
658 (lambda (a)
659 ;; push to front
660 (set! canonical-eggs (cons a (delete a canonical-eggs eq?)))))
661 (else
662 (let ((name (if (pair? egg) (car egg) egg))
663 (version (override-version egg)))
664 (let-values (((dir ver) (locate-egg name version)))
665 (when (or (not dir)
666 (null? (directory dir)))
667 (when dir (delete-directory dir))
668 (error "extension or version not found" name))
669 (d retrieve-only "~a located at ~a~%" egg dir)
670 (set! canonical-eggs
671 (cons (list name dir ver) canonical-eggs)))))))
672 eggs)
673 (when (or (not retrieve-only) retrieve-recursive)
674 (for-each
675 (lambda (e+d+v)
676 (unless (member (car e+d+v) checked-eggs)
677 (d "checking ~a ...~%" (car e+d+v))
678 (set! checked-eggs (cons (car e+d+v) checked-eggs))
679 (let* ((fname (make-pathname (cadr e+d+v) (car e+d+v) +egg-extension+))
680 (info (validate-egg-info (load-egg-info fname))))
681 (d "checking platform for `~a'~%" (car e+d+v))
682 (check-platform (car e+d+v) info)
683 (d "checking dependencies for `~a'~%" (car e+d+v))
684 (let-values (((missing upgrade)
685 (outdated-dependencies (car e+d+v) info)))
686 (set! missing (apply-mappings missing))
687 (set! dependencies
688 (cons (cons (car e+d+v)
689 (map (lambda (mu)
690 (if (pair? mu)
691 (car mu)
692 mu))
693 (append missing upgrade)))
694 dependencies))
695 (when (pair? missing)
696 (d " missing: ~a~%" (string-intersperse missing ", "))
697 (retrieve-eggs missing))
698 (when (and (pair? upgrade)
699 (or force-install
700 (replace-extension-question e+d+v upgrade)))
701 (let ((ueggs (unzip1 upgrade)))
702 (d " upgrade: ~a~%" (string-intersperse ueggs ", "))
703 ;; XXX think about this...
704 #;(for-each
705 (lambda (e)
706 (d "removing previously installed extension `~a'" e)
707 (remove-extension e) )
708 ueggs)
709 (retrieve-eggs ueggs) ) ) ) ) ) )
710 canonical-eggs)))
711
712(define (outdated-dependencies egg info)
713 (let ((ds (get-egg-dependencies info)))
714 (for-each (lambda (h) (set! ds (h egg ds))) hacks)
715 (let loop ((deps ds) (missing '()) (upgrade '()))
716 (if (null? deps)
717 (values (reverse missing) (reverse upgrade))
718 (let-values (((m u) (check-dependency (car deps))))
719 (loop (cdr deps)
720 (if m (cons m missing) missing)
721 (if u (cons u upgrade) upgrade)))))))
722
723(define (get-egg-dependencies info)
724 (append (get-egg-property* info 'dependencies '())
725 (get-egg-property* info 'build-dependencies '())
726 (if run-tests
727 (get-egg-property* info 'test-dependencies '())
728 '())))
729
730(define (check-dependency dep)
731 (cond ((or (symbol? dep) (string? dep))
732 (values (and (not (ext-version dep)) (->string dep))
733 #f))
734 ((and (list? dep) (eq? 'or (car dep)))
735 (let scan ((ordeps (cdr dep)) (bestm #f) (bestu #f))
736 (if (null? ordeps)
737 (values (cond (bestu #f) ; upgrade overrides new
738 (bestm bestm)
739 (else #f))
740 bestu)
741 (let-values (((m u) (check-dependency (car ordeps))))
742 (if (and (not m) (not u))
743 (values #f #f)
744 (scan (cdr ordeps)
745 (if (and m (not bestm))
746 m
747 bestm)
748 (if (and u (not bestu))
749 u
750 bestu)))))))
751 ((and (list? dep) (= 2 (length dep))
752 (or (string? (car dep)) (symbol? (car dep))))
753 (let ((v (ext-version (car dep))))
754 (cond ((not v)
755 (values (->string (car dep)) #f))
756 ((not (version>=? v (->string (cadr dep))))
757 (cond ((string=? "chicken" (->string (car dep)))
758 (if force-install
759 (values #f #f)
760 (error
761 (string-append
762 "Your CHICKEN version is not recent enough to use this extension - version "
763 (cadr dep)
764 " or newer is required"))))
765 (else
766 (values #f
767 (cons (->string (car dep)) (->string (cadr dep)))))))
768 (else (values #f #f)))))
769 (else
770 (warning "invalid dependency syntax in extension meta information"
771 dep)
772 (values #f #f))))
773
774(define (ext-version x)
775 (cond ((or (eq? x 'chicken) (equal? x "chicken"))
776 (chicken-version))
777 ((let* ((sf (chicken.load#find-file
778 (make-pathname #f (->string x) +egg-info-extension+)
779 (repo-path))))
780 (and sf
781 (file-exists? sf)
782 (load-egg-info sf))) =>
783 (lambda (info)
784 (let ((a (assq 'version info)))
785 (if a
786 (->string (cadr a))
787 "0.0.0"))))
788 (else #f)))
789
790(define (check-platform name info)
791 (unless cross-chicken
792 (and-let* ((platform (get-egg-property info 'platform)))
793 (or (let loop ((p platform))
794 (cond ((symbol? p)
795 (feature? p))
796 ((not (list? p))
797 (error "invalid `platform' property" name platform))
798 ((and (eq? 'not (car p)) (pair? (cdr p)))
799 (not (loop (cadr p))))
800 ((eq? 'and (car p))
801 (every loop (cdr p)))
802 ((eq? 'or (car p))
803 (any loop (cdr p)))
804 (else (error "invalid `platform' property" name platform))))
805 (error "extension is not targeted for this system" name)))))
806
807(define (replace-extension-question e+d+v upgrade)
808 (print (string-intersperse
809 (append
810 (list "The following installed extensions are outdated, because `"
811 (car e+d+v)
812 "' requires later versions:\n\n")
813 (filter-map
814 (lambda (e)
815 (cond ((assq (string->symbol (car e)) override) =>
816 (lambda (a)
817 (when (and (pair? (cdr a))
818 (not (equal? (cadr a) (cdr e))))
819 (warning
820 (sprintf "version `~a' of extension `~a' overrides required version `~a'"
821 (cadr a) (car a) (cdr e))))
822 #f))
823 (else
824 (conc " " (car e) " ("
825 (or (ext-version (car e)) "unknown") " -> " (cdr e)
826 ")" #\newline))))
827 upgrade))
828 ""))
829 (let loop ()
830 (display "Do you want to replace the existing extensions? (yes/no/abort) ")
831 (flush-output)
832 (let ((r (trim (read-line))))
833 (cond ((string=? r "yes"))
834 ((string=? r "no") #f)
835 ((string=? r "abort") (exit 2))
836 (else (loop))))))
837
838(define (trim str)
839 (define (left lst)
840 (cond ((null? lst) '())
841 ((char-whitespace? (car lst)) (left (cdr lst)))
842 (else (cons (car lst) (left (cdr lst))))))
843 (list->string (reverse (left (reverse (left (string->list str)))))))
844
845
846;; list available egg versions on servers
847
848(define (list-egg-versions eggs)
849 (let ((srvs (map resolve-location default-servers)))
850 (let loop1 ((eggs eggs))
851 (unless (null? eggs)
852 (let* ((egg (car eggs))
853 (name (if (pair? egg) (car egg) egg)))
854 (let loop2 ((srvs srvs))
855 (and (pair? srvs)
856 (let ((versions (try-list-versions name (car srvs))))
857 (or (and versions
858 (begin
859 (printf "~a:" name)
860 (for-each (cut printf " ~a" <>) versions)
861 (newline)))
862 (loop2 (cdr srvs))))))
863 (loop1 (cdr eggs)))))))
864
865
866;; perform installation of retrieved eggs
867
868(define (install-eggs)
869 (for-each
870 (lambda (egg)
871 (let* ((name (car egg))
872 (dir (cadr egg))
873 (metadata-dir (make-pathname cache-metadata-directory name))
874 (eggfile (make-pathname dir name +egg-extension+))
875 (info (load-egg-info eggfile))
876 (vfile (make-pathname metadata-dir +version-file+))
877 (ver (and (file-exists? vfile)
878 (with-input-from-file vfile read))))
879 (when (or host-extension
880 (and (not target-extension)
881 (not host-extension)))
882 (let-values (((build install info) (compile-egg-info eggfile
883 info
884 ver
885 platform
886 'host)))
887 (let ((bscript (make-pathname dir name
888 (build-script-extension 'host platform)))
889 (iscript (make-pathname dir name
890 (install-script-extension 'host
891 platform))))
892 (generate-shell-commands platform build bscript dir
893 (build-prefix 'host name info)
894 (build-suffix 'host name info)
895 keepfiles)
896 (generate-shell-commands platform install iscript dir
897 (install-prefix 'host name info)
898 (install-suffix 'host name info)
899 keepfiles)
900 (cond (do-not-build (print bscript "\n" iscript))
901 (else
902 (print "building " name)
903 (run-script dir bscript platform)
904 (unless (if (member name requested-eggs) no-install no-install-dependencies)
905 (check-installed-files name info)
906 (print "installing " name)
907 (run-script dir iscript platform sudo: sudo-install))
908 (when (and (member name requested-eggs)
909 run-tests
910 (not (test-egg egg platform)))
911 (exit 2)))))))
912 (when target-extension
913 (let-values (((build install info) (compile-egg-info eggfile
914 info
915 ver
916 platform
917 'target)))
918 (let ((bscript (make-pathname dir name
919 (build-script-extension 'target platform)))
920 (iscript (make-pathname dir name
921 (install-script-extension 'target
922 platform))))
923 (generate-shell-commands platform build bscript dir
924 (build-prefix 'target name info)
925 (build-suffix 'target name info)
926 keepfiles)
927 (generate-shell-commands platform install iscript dir
928 (install-prefix 'target name info)
929 (install-suffix 'target name info)
930 keepfiles)
931 (cond (do-not-build (print bscript "\n" iscript))
932 (else
933 (print "building " name " (target)")
934 (run-script dir bscript platform)
935 (unless (if (member name requested-eggs) no-install no-install-dependencies)
936 (print "installing " name " (target)")
937 (run-script dir iscript platform)))))))))
938 (order-installed-eggs)))
939
940(define (order-installed-eggs)
941 (let* ((dag (reverse (sort-dependencies dependencies string=?)))
942 (ordered (filter-map (cut assoc <> canonical-eggs) dag)))
943 (unless quiet
944 (d "install order:~%")
945 (pp dag))
946 ordered))
947
948(define (test-egg egg platform)
949 (let* ((name (car egg))
950 (dir (cadr egg))
951 (version (caddr egg))
952 (testdir (make-pathname dir "tests"))
953 (tscript (make-pathname testdir "run.scm")))
954 (if (and (directory-exists? testdir)
955 (file-exists? tscript))
956 (let ((old (current-directory))
957 (cmd (string-append (qs* default-csi platform)
958 " -s " (qs* tscript platform)
959 " " (qs* name platform)
960 " " (or version ""))))
961 (change-directory testdir)
962 (d "running: ~a~%" cmd)
963 (let ((r (system+ cmd platform)))
964 (flush-output (current-error-port))
965 (cond ((zero? r)
966 (change-directory old)
967 #t)
968 (else
969 (print "test script failed with nonzero exit status")
970 #f))))
971 #t)))
972
973(define (run-script dir script platform #!key sudo (stop #t))
974 (d "running script ~a~%" script)
975 (exec (if (eq? platform 'windows)
976 script
977 (string-append
978 (if sudo
979 (string-append sudo-program " ")
980 "")
981 (let ((dyld (and (eq? (software-version) 'macosx)
982 (get-environment-variable "DYLD_LIBRARY_PATH"))))
983 (if dyld
984 (string-append "/usr/bin/env DYLD_LIBRARY_PATH="
985 (qs* dyld platform)
986 " ")
987 ""))
988 "sh " script))
989 stop))
990
991(define (exec cmd #!optional (stop #t))
992 (d "executing: ~s~%" cmd)
993 (let ((r (system+ cmd platform)))
994 (unless (zero? r)
995 (if stop
996 (error "shell command terminated with nonzero exit code" r cmd)
997 (print "shell command terminated with nonzero exit code " r ": " cmd)))
998 r))
999
1000
1001;;; update module-db
1002
1003(define (update-db)
1004 (let* ((files (glob (make-pathname (install-path) "*.import.so")
1005 (make-pathname (install-path) "*.import.scm")))
1006 (dbfile (create-temporary-file)))
1007 (print "loading import libraries ...")
1008 (fluid-let ((##sys#warnings-enabled #f))
1009 (for-each
1010 (lambda (path)
1011 (let* ((file (pathname-strip-directory path))
1012 (import-name (pathname-strip-extension file))
1013 (module-name (pathname-strip-extension import-name)))
1014 (handle-exceptions ex
1015 (print-error-message
1016 ex (current-error-port)
1017 (sprintf "Failed to import from `~a'" file))
1018 (unless quiet (print "loading " file " ..."))
1019 (eval `(import-syntax ,(string->symbol module-name))))))
1020 files))
1021 (print "generating database ...")
1022 (let ((db
1023 (sort
1024 (concatenate
1025 (filter-map
1026 (lambda (m)
1027 (and-let* ((mod (cdr m))
1028 (mname (##sys#module-name mod))
1029 ((not (memq mname +internal-modules+)))
1030 ((not (eq? mname (current-module)))))
1031 (unless quiet (print "processing " mname " ..."))
1032 (let-values (((_ ve se) (##sys#module-exports mod)))
1033 (append (map (lambda (se) (list (car se) 'syntax mname)) se)
1034 (map (lambda (ve) (list (car ve) 'value mname)) ve)))))
1035 ##sys#module-table))
1036 (lambda (e1 e2)
1037 (string<? (symbol->string (car e1)) (symbol->string (car e2)))))))
1038 (with-output-to-file dbfile
1039 (lambda ()
1040 (for-each (lambda (x) (write x) (newline)) db)))
1041 (unless quiet (print "installing " +module-db+ " ..."))
1042 (copy-file dbfile (make-pathname (install-path) +module-db+) #t)
1043 (delete-file dbfile))))
1044
1045
1046;; purge cache for given (or all) eggs
1047
1048(define (purge-cache eggs)
1049 (cond ((null? eggs)
1050 (when (file-exists? cache-directory)
1051 (d "purging complete cache at ~a~%" cache-directory)
1052 (delete-directory cache-directory #t)))
1053 (else
1054 (for-each
1055 (lambda (egg)
1056 (let* ((name (if (pair? egg) (car egg) egg))
1057 (cache-dir (make-pathname cache-directory name))
1058 (metadata-dir (make-pathname cache-metadata-directory name)))
1059 (when (file-exists? cache-dir)
1060 (d "purging ~a from cache at ~a~%" name cache-dir)
1061 (delete-directory cache-dir #t))
1062 (when (file-exists? metadata-dir)
1063 (d "purging metadata of ~a from cache at ~a~%" name metadata-dir)
1064 (delete-directory metadata-dir #t))))
1065 eggs))))
1066
1067
1068;; command line parsing and selection of operations
1069
1070(define (perform-actions eggs)
1071 (load-defaults)
1072 (cond (update-module-db (update-db))
1073 (purge-mode (purge-cache eggs))
1074 (print-repository (print (install-path)))
1075 ((null? eggs)
1076 (cond (list-versions-only
1077 (print "no eggs specified"))
1078 (else
1079 (let ((files (glob "*.egg" "chicken/*.egg")))
1080 (when (null? files) (exit 3))
1081 (set! canonical-eggs
1082 (map (lambda (fname)
1083 (list (pathname-file fname) (current-directory) #f))
1084 files))
1085 (set! requested-eggs (map car canonical-eggs))
1086 (retrieve-eggs '())
1087 (unless retrieve-only (install-eggs))))))
1088 (else
1089 (let ((eggs (apply-mappings eggs)))
1090 (cond (list-versions-only (list-egg-versions eggs))
1091 (else
1092 (set! requested-eggs (map (o car canonical) eggs))
1093 (retrieve-eggs eggs)
1094 (unless retrieve-only (install-eggs))))))))
1095
1096(define (usage code)
1097 (print #<<EOF
1098usage: chicken-install [OPTION ...] [NAME[:VERSION] ...]
1099
1100 -h -help show this message and exit
1101 -version show version and exit
1102 -force don't ask, install even if versions don't match
1103 -k -keep keep temporary files
1104 -s -sudo use external command to elevate privileges for filesystem operations
1105 -l -location DIRECTORY get egg sources from DIRECTORY. May be provided multiple times.
1106 Locations specified on the command line have precedence over the
1107 ones specified in setup.defaults.
1108 -r -retrieve only retrieve egg into cache directory, don't install (giving `-r'
1109 more than once implies `-recursive')
1110 -recursive if `-retrieve' is given, retrieve also dependencies
1111 -dry-run do not build or install, just print the locations of the generated
1112 build & install scripts
1113 -list-versions list available versions for given eggs (HTTP transport only)
1114 -n -no-install do not install, just build
1115 -no-install-dependencies do not install dependencies
1116 -purge remove cached files for given eggs (or purge cache completely)
1117 -host when cross-compiling, compile extension only for host
1118 -target when cross-compiling, compile extension only for target
1119 -test run included test-cases, if available
1120 -u -update-db update export database
1121 -repository print path used for egg installation
1122 -override FILENAME override versions for installed eggs with information from file
1123 -from-list FILENAME install eggs from list obtained by `chicken-status -list'
1124 -v -verbose be verbose
1125 -cached only install from cache
1126 -D -feature NAME define build feature
1127 -defaults FILENAME use FILENAME as defaults instead of the installed `setup.defaults'
1128 file
1129
1130chicken-install recognizes the SUDO, http_proxy and proxy_auth environment variables, if set.
1131
1132EOF
1133);|
1134 (exit code))
1135
1136(define (main args)
1137 (setup-proxy (get-environment-variable "http_proxy"))
1138 (let ((eggs '())
1139 (rx (irregex "([^:]+):(.+)")))
1140 (let loop ((args args))
1141 (if (null? args)
1142 (begin
1143 (validate-environment)
1144 (perform-actions (reverse eggs)))
1145 (let ((arg (car args)))
1146 (cond ((member arg '("-h" "-help" "--help"))
1147 (usage 0))
1148 ((equal? arg "-test")
1149 (set! run-tests #t)
1150 (loop (cdr args)))
1151 ((equal? arg "-repository")
1152 (set! print-repository #t)
1153 (loop (cdr args)))
1154 ((equal? arg "-r")
1155 (if retrieve-only
1156 (set! retrieve-recursive #t)
1157 (set! retrieve-only #t))
1158 (loop (cdr args)))
1159 ((equal? arg "-retrieve")
1160 (set! retrieve-only #t)
1161 (loop (cdr args)))
1162 ((equal? arg "-version")
1163 (print (chicken-version))
1164 (exit 0))
1165 ((member arg '("-D" "-feature"))
1166 (register-feature! (cadr args))
1167 (loop (cddr args)))
1168 ((equal? arg "-recursive")
1169 (set! retrieve-recursive #t)
1170 (loop (cdr args)))
1171 ((equal? arg "-list-versions")
1172 (set! list-versions-only #t)
1173 (loop (cdr args)))
1174 ((equal? arg "-defaults")
1175 (set! user-defaults (cadr args))
1176 (loop (cddr args)))
1177 ((equal? arg "-force")
1178 (set! force-install #t)
1179 (loop (cdr args)))
1180 ((equal? arg "-host")
1181 (set! target-extension #f)
1182 (loop (cdr args)))
1183 ((equal? arg "-target")
1184 (set! host-extension #f)
1185 (loop (cdr args)))
1186 ((member arg '("-u" "-update-db"))
1187 (set! update-module-db #t)
1188 (loop (cdr args)))
1189 ((equal? arg "-no-install-dependencies")
1190 (set! no-install-dependencies #t)
1191 (loop (cdr args)))
1192 ((equal? arg "-dry-run")
1193 (set! do-not-build #t)
1194 (loop (cdr args)))
1195 ((member arg '("-v" "-verbose"))
1196 (set! quiet #f)
1197 (loop (cdr args)))
1198 ((member arg '("-k" "-keep"))
1199 (set! keepfiles #t)
1200 (loop (cdr args)))
1201 ((member arg '("-s" "-sudo"))
1202 (set! sudo-install #t)
1203 (loop (cdr args)))
1204 ((member arg '("-l" "-location"))
1205 (when (null? (cdr args))
1206 (fprintf (current-error-port) "-l|-location: missing argument.~%")
1207 (exit 1))
1208 (set! default-locations
1209 (append (list (cadr args)) default-locations))
1210 (loop (cddr args)))
1211 ((member arg '("-n" "-no-install"))
1212 (set! no-install #t)
1213 (loop (cdr args)))
1214 ((equal? arg "-purge")
1215 (set! purge-mode #t)
1216 (loop (cdr args)))
1217 ((equal? arg "-cached")
1218 (set! cached-only #t)
1219 (loop (cdr args)))
1220 ((equal? arg "-from-list")
1221 (unless (pair? (cdr args)) (usage 1))
1222 (set! eggs
1223 (append eggs
1224 (map (lambda (p)
1225 (if (null? (cdr p))
1226 (->string (car p))
1227 (cons (->string (car p))
1228 (cadr p))))
1229 (with-input-from-file (cadr args) read-list))))
1230 (loop (cddr args)))
1231 ((equal? arg "-override")
1232 (unless (pair? (cdr args)) (usage 1))
1233 (set! override
1234 (call-with-input-file (cadr args) read-list))
1235 (loop (cddr args)))
1236
1237 ;;XXX
1238
1239 ((and (positive? (string-length arg))
1240 (char=? #\- (string-ref arg 0)))
1241 (if (> (string-length arg) 2)
1242 (let ((sos (string->list (substring arg 1))))
1243 (if (every (cut memq <> +short-options+) sos)
1244 (loop (append
1245 (map (cut string #\- <>) sos)
1246 (cdr args)))
1247 (usage 1)))
1248 (usage 1)))
1249 ((irregex-match rx arg) =>
1250 (lambda (m)
1251 (set! eggs
1252 (alist-cons
1253 (irregex-match-substring m 1)
1254 (irregex-match-substring m 2)
1255 eggs))
1256 (loop (cdr args))))
1257 (else
1258 (set! eggs (cons arg eggs))
1259 (loop (cdr args)))))))))
1260
1261(main (command-line-arguments))
1262
1263)