source: branches/0.20.x/abcl/contrib/asdf-install/installer.lisp

Last change on this file was 12657, checked in by Mark Evenson, 15 years ago

Adjust ASDF interface to match ASDF2 definition.

File size: 20.2 KB
Line 
1(in-package #:asdf-install)
2
3(pushnew :asdf-install *features*)
4
5(defun installer-msg (stream format-control &rest format-arguments)
6  (apply #'format stream "~&;;; ASDF-INSTALL: ~@?~%"
7   format-control format-arguments))
8
9(defun verify-gpg-signatures-p (url)
10  (labels ((prefixp (prefix string)
11       (let ((m (mismatch prefix string)))
12         (or (not m) (>= m (length prefix))))))
13    (case *verify-gpg-signatures*
14      ((nil) nil)
15      ((:unknown-locations)
16       (notany
17  (lambda (x) (prefixp x url))
18  *safe-url-prefixes*))
19      (t t))))
20   
21(defun same-central-registry-entry-p (a b)
22  (flet ((ensure-string (x)
23           (typecase x
24             (string x)
25             (pathname (namestring (translate-logical-pathname x)))
26             (t nil))))
27    (and (setf a (ensure-string a))
28         (setf b (ensure-string b))
29         a b (string-equal a b))))
30
31(defun add-registry-location (location)
32  (let ((location-directory (pathname-sans-name+type location)))
33    #+asdf
34    (pushnew location-directory
35       asdf:*central-registry*
36       :test #'same-central-registry-entry-p)
37 
38    #+mk-defsystem
39    (mk:add-registry-location location-directory)))
40
41;;; Fixing the handling of *LOCATIONS*
42
43(defun add-locations (loc-name site system-site)
44  (declare (type string loc-name)
45           (type pathname site system-site))
46  #+asdf
47  (progn
48    (pushnew site asdf:*central-registry* :test #'equal)
49    (pushnew system-site asdf:*central-registry* :test #'equal))
50
51  #+mk-defsystem
52  (progn
53    (mk:add-registry-location site)
54    (mk:add-registry-location system-site))
55  (setf *locations*
56        (append *locations* (list (list site system-site loc-name)))))
57
58;;;---------------------------------------------------------------------------
59;;; URL handling.
60
61(defun url-host (url)
62  (assert (string-equal url "http://" :end1 7))
63  (let* ((port-start (position #\: url :start 7))
64   (host-end (min (or (position #\/ url :start 7) (length url))
65      (or port-start (length url)))))
66    (subseq url 7 host-end)))
67
68(defun url-port (url)
69  (assert (string-equal url "http://" :end1 7))
70  (let ((port-start (position #\: url :start 7)))
71    (if port-start 
72  (parse-integer url :start (1+ port-start) :junk-allowed t) 80)))
73
74; This is from Juri Pakaste's <juri@iki.fi> base64.lisp
75(defparameter *encode-table*
76  "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=")
77
78(defun base64-encode (string)
79  (let ((result (make-array
80                 (list (* 4 (truncate (/ (+ 2 (length string)) 3))))
81                 :element-type 'base-char)))
82    (do ((sidx 0 (+ sidx 3))
83         (didx 0 (+ didx 4))
84         (chars 2 2)
85         (value nil nil))
86        ((>= sidx (length string)) t)
87      (setf value (ash (logand #xFF (char-code (char string sidx))) 8))
88      (dotimes (n 2)
89        (when (< (+ sidx n 1) (length string))
90          (setf value
91                (logior value
92                        (logand #xFF (char-code (char string (+ sidx n 1))))))
93          (incf chars))
94        (when (= n 0)
95          (setf value (ash value 8))))
96      (setf (elt result (+ didx 3))
97            (elt *encode-table* (if (> chars 3) (logand value #x3F) 64)))
98      (setf value (ash value -6))
99      (setf (elt result (+ didx 2))
100            (elt *encode-table* (if (> chars 2) (logand value #x3F) 64)))
101      (setf value (ash value -6))
102      (setf (elt result (+ didx 1))
103            (elt *encode-table* (logand value #x3F)))
104      (setf value (ash value -6))
105      (setf (elt result didx)
106            (elt *encode-table* (logand value #x3F))))
107    result))
108
109(defun request-uri (url)
110  (assert (string-equal url "http://" :end1 7))
111  (if *proxy*
112      url
113      (let ((path-start (position #\/ url :start 7)))
114  (assert (and path-start) nil "url does not specify a file.")
115        (subseq url path-start))))
116
117(defun url-connection (url)
118  (let ((stream (make-stream-from-url (or *proxy* url)))
119        (host (url-host url)))
120    (format stream "GET ~A HTTP/1.0~C~CHost: ~A~C~CCookie: CCLAN-SITE=~A~C~C"
121            (request-uri url) #\Return #\Linefeed
122            host #\Return #\Linefeed
123            *cclan-mirror* #\Return #\Linefeed)
124    (when (and *proxy-passwd* *proxy-user*)
125      (format stream "Proxy-Authorization: Basic ~A~C~C"
126              (base64-encode (format nil "~A:~A" *proxy-user* *proxy-passwd*))
127              #\Return #\Linefeed))
128    (format stream "~C~C" #\Return #\Linefeed)
129    (force-output stream)
130    (list
131     (let* ((l (read-header-line stream))
132            (space (position #\Space l)))
133       (parse-integer l :start (1+ space) :junk-allowed t))
134     (loop for line = (read-header-line stream)
135           until (or (null line)
136                     (zerop (length line))
137                     (eql (elt line 0) (code-char 13)))
138           collect
139           (let ((colon (position #\: line)))
140             (cons (intern (string-upcase (subseq line 0 colon)) :keyword)
141                   (string-trim (list #\Space (code-char 13))
142                                (subseq line (1+ colon))))))
143     stream)))
144
145(defun download-link-for-package (package-name-or-url)
146  (if (= (mismatch package-name-or-url "http://") 7)
147    package-name-or-url
148    (format nil "http://www.cliki.net/~A?download"
149            package-name-or-url)))
150
151(defun download-link-for-signature (url)
152  (concatenate 'string url ".asc"))
153
154(defun download-files-for-package (package-name-or-url)
155  (multiple-value-bind (package-url package-file) 
156      (download-url-to-temporary-file
157       (download-link-for-package package-name-or-url))
158    (if (verify-gpg-signatures-p package-name-or-url)
159  (multiple-value-bind (signature-url signature-file) 
160      (download-url-to-temporary-file
161       (download-link-for-signature package-url))
162    (declare (ignore signature-url))
163    (values package-file signature-file))
164  (values package-file nil))))
165 
166(defun verify-gpg-signature (file-name signature-name)
167  (block verify
168    (loop
169      (restart-case
170    (let ((tags (gpg-results file-name signature-name)))
171      ;; test that command returned something
172      (unless tags
173        (error 'gpg-shell-error))
174      ;; test for obvious key/sig problems
175      (let ((errsig (header-value :errsig tags)))
176        (and errsig (error 'key-not-found :key-id errsig)))
177      (let ((badsig (header-value :badsig tags)))
178        (and badsig (error 'key-not-found :key-id badsig)))
179      (let* ((good (header-value :goodsig tags))
180       (id (first good))
181       (name (format nil "~{~A~^ ~}" (rest good))))
182        ;; good signature, but perhaps not trusted
183        (restart-case
184      (let ((trusted? (or (header-pair :trust_ultimate tags)
185              (header-pair :trust_fully tags)))
186      (in-list? (assoc id *trusted-uids* :test #'equal)))
187        (cond ((or trusted? in-list?)
188         ;; ok
189         )
190        ((not trusted?)
191         (error 'key-not-trusted 
192          :key-user-name name :key-id id))
193        ((not in-list?)
194         (error 'author-not-trusted
195          :key-user-name name :key-id id))))
196    (add-key (&rest rest)
197      :report "Add to package supplier list"
198      (declare (ignore rest))
199      (pushnew (list id name) *trusted-uids*))))
200      (return-from verify t))
201        (install-anyways
202      (&rest rest)
203    :report "Don't check GPG signature for this package"
204    (declare (ignore rest))
205    (return-from verify t))
206        (retry-gpg-check
207      (&rest args)
208    :report "Retry GPG check \(e.g., after downloading the key\)"
209    (declare (ignore args))
210    nil)))))
211
212(defun header-value (name headers)
213  "Searchers headers for name _without_ case sensitivity. Headers should be an alist mapping symbols to values; name a symbol. Returns the value if name is found or nil if it is not."
214  (cdr (header-pair name headers)))
215
216(defun header-pair (name headers)
217  "Searchers headers for name _without_ case sensitivity. Headers should be an alist mapping symbols to values; name a symbol. Returns the \(name value\) pair if name is found or nil if it is not."
218  (assoc name headers 
219         :test (lambda (a b) 
220                 (string-equal (symbol-name a) (symbol-name b)))))
221
222(defun validate-preferred-location ()
223  (typecase *preferred-location*
224    (null t)
225    ((integer 0) 
226     (assert (<= 1 *preferred-location* (length *locations*)) 
227       (*preferred-location*)
228       'invalid-preferred-location-number-error
229       :preferred-location *preferred-location*))
230    ((or symbol string) 
231     (assert (find *preferred-location* *locations* 
232       :test (if (typep *preferred-location* 'symbol)
233           #'eq #'string-equal) :key #'third)
234       (*preferred-location*)
235       'invalid-preferred-location-name-error 
236       :preferred-location *preferred-location*))
237    (t
238     (assert nil 
239       (*preferred-location*)
240       'invalid-preferred-location-error 
241       :preferred-location *preferred-location*)))
242  *preferred-location*)
243
244(defun select-location ()
245  (loop with n-locations = (length *locations*)
246     for response = (progn
247          (format t "Install where?~%")
248          (loop for (source system name) in *locations*
249       for i from 1
250       do (format t "~A) ~A: ~%   System in ~A~%   Files in ~A ~%"
251            i name system source))
252          (format t "0) Abort installation.~% --> ")
253          (force-output)
254          (read))
255     when (and (numberp response)
256         (<= 1 response n-locations))
257     return response
258     when (and (numberp response)
259         (zerop response))
260     do (abort (make-condition 'installation-abort))))
261
262(defun install-location ()
263  (validate-preferred-location)
264  (let ((location-selection (or *preferred-location*
265        (select-location))))
266    (etypecase location-selection
267      (integer 
268       (elt *locations* (1- location-selection)))
269      ((or symbol string)
270       (find location-selection *locations* :key #'third
271       :test (if (typep location-selection 'string) 
272          #'string-equal #'eq))))))
273
274
275;;; install-package --
276
277(defun find-shell-command (command)
278  (loop for directory in *shell-search-paths* do
279       (let ((target (make-pathname :name command :type nil
280            :directory directory)))
281   (when (probe-file target)
282     (return-from find-shell-command (namestring target)))))
283  (values nil))
284
285(defun tar-command ()
286  #-(or :win32 :mswindows)
287  (find-shell-command *gnu-tar-program*)
288  #+(or :win32 :mswindows)
289  *cygwin-bash-program*)
290
291(defun tar-arguments (source packagename)
292  #-(or :win32 :mswindows :scl)
293  (list "-C" (namestring (truename source))
294  "-xzvf" (namestring (truename packagename)))
295  #+(or :win32 :mswindows)
296  (list "-l"
297  "-c"
298  (format nil "\"tar -C \\\"`cygpath '~A'`\\\" -xzvf \\\"`cygpath '~A'`\\\"\""
299    (namestring (truename source))
300    (namestring (truename packagename))))
301  #+scl
302  (list "-C" (ext:unix-namestring (truename source))
303  "-xzvf" (ext:unix-namestring (truename packagename))))
304
305(defun extract-using-tar (to-dir tarball)
306  (let ((tar-command (tar-command)))
307    (if (and tar-command (probe-file tar-command))
308  (return-output-from-program tar-command
309            (tar-arguments to-dir tarball))
310  (warn "Cannot find tar command ~S." tar-command))))
311
312(defun extract (to-dir tarball)
313  (or (some #'(lambda (extractor) (funcall extractor to-dir tarball))
314            *tar-extractors*)
315      (error "Unable to extract tarball ~A." tarball)))
316
317(defun install-package (source system packagename)
318  "Returns a list of system names (ASDF or MK:DEFSYSTEM) for installed systems."
319  (ensure-directories-exist source)
320  (ensure-directories-exist system)
321  (let* ((tar (extract source packagename))
322         ;; Some tar programs (OSX) list entries with preceeding "x "
323         ;; as in "x entry/file.asd"
324         (pos-begin (if (= (search "x " tar) 0)
325                        2
326                        0))
327   (pos-slash (or (position #\/ tar)
328                        (position #\Return tar)
329                        (position #\Linefeed tar)))
330   (*default-pathname-defaults*
331    (merge-pathnames
332     (make-pathname :directory
333        `(:relative ,(subseq tar pos-begin pos-slash)))
334     source)))
335    ;(princ tar)
336    (loop for sysfile in (append
337                          (directory
338               (make-pathname :defaults *default-pathname-defaults*
339                                          :name :wild
340                                          :type "asd"))
341                          (directory
342               (make-pathname :defaults *default-pathname-defaults*
343                                          :name :wild
344                                          :type "system")))
345       do (maybe-symlink-sysfile system sysfile)
346       do (installer-msg t "Found system definition: ~A" sysfile)
347       do (maybe-update-central-registry sysfile)
348       collect sysfile)))
349
350(defun maybe-update-central-registry (sysfile)
351  ;; make sure that the systems we install are accessible in case
352  ;; asdf-install:*locations* and asdf:*central-registry* are out
353  ;; of sync
354  (add-registry-location sysfile))
355
356(defun temp-file-name (p)
357  (declare (ignore p))
358  (let ((pathname nil))
359    (loop for i = 0 then (1+ i) do
360   (setf pathname 
361         (merge-pathnames
362    (make-pathname
363     :name (format nil "asdf-install-~d" i)
364     :type "asdf-install-tmp")
365    *temporary-directory*))
366   (unless (probe-file pathname)
367     (return-from temp-file-name pathname)))))
368
369
370;;; install
371;;; This is the external entry point.
372
373(defun install (packages &key (propagate nil) (where *preferred-location*))
374  (let* ((*preferred-location* where)
375   (*temporary-files* nil)
376         (trusted-uid-file 
377          (merge-pathnames "trusted-uids.lisp" *private-asdf-install-dirs*))
378   (*trusted-uids*
379          (when (probe-file trusted-uid-file)
380            (with-open-file (f trusted-uid-file) (read f))))
381         (old-uids (copy-list *trusted-uids*))
382         #+asdf
383         (*defined-systems* (if propagate 
384                              (make-hash-table :test 'equal)
385                              *defined-systems*))
386         (packages (if (atom packages) (list packages) packages))
387         (*propagate-installation* propagate)
388         (*systems-installed-this-time* nil))
389    (unwind-protect
390      (destructuring-bind (source system name) (install-location)
391        (declare (ignore name))
392        (labels 
393      ((one-iter (packages)
394         (let ((packages-to-install nil))
395     (loop for p in (mapcar #'string packages) do
396          (cond ((local-archive-p p)
397           (setf packages-to-install
398           (append packages-to-install 
399             (install-package source system p))))
400          (t
401           (multiple-value-bind (package signature)
402         (download-files-for-package p)
403             (when (verify-gpg-signatures-p p)
404         (verify-gpg-signature package signature))
405             (installer-msg t "Installing ~A in ~A, ~A"
406                p source system)
407             (install-package source system package))
408           (setf packages-to-install
409           (append packages-to-install 
410             (list p))))))
411     (dolist (package packages-to-install)
412       (setf package
413       (etypecase package
414         (symbol package)
415         (string (intern package :asdf-install))
416         (pathname (intern
417              (namestring (pathname-name package))
418              :asdf-install))))
419       (handler-bind
420           (
421      #+asdf
422      (asdf:missing-dependency
423       (lambda (c) 
424         (installer-msg
425          t
426          "Downloading package ~A, required by ~A~%"
427          (asdf::missing-requires c)
428          (asdf:component-name
429           (asdf::missing-required-by c)))
430         (one-iter 
431          (list (asdf::coerce-name 
432           (asdf::missing-requires c))))
433         (invoke-restart 'retry)))
434      #+mk-defsystem
435      (make:missing-component
436       (lambda (c) 
437         (installer-msg 
438          t
439          "Downloading package ~A, required by ~A~%"
440          (make:missing-component-name c)
441          package)
442         (one-iter (list (make:missing-component-name c)))
443         (invoke-restart 'retry))))
444         (loop (multiple-value-bind (ret restart-p)
445             (with-simple-restart
446           (retry "Retry installation")
447         (push package *systems-installed-this-time*)
448         (load-package package))
449           (declare (ignore ret))
450           (unless restart-p (return)))))))))
451    (one-iter packages)))
452      ;;; cleanup
453      (unless (equal old-uids *trusted-uids*)
454        (let ((create-file-p nil))
455    (unless (probe-file trusted-uid-file)
456      (installer-msg t "Trusted UID file ~A does not exist"
457         (namestring trusted-uid-file))
458      (setf create-file-p
459      (y-or-n-p "Do you want to create the file?")))
460          (when (or create-file-p (probe-file trusted-uid-file))
461      (ensure-directories-exist trusted-uid-file)
462      (with-open-file (out trusted-uid-file
463                                 :direction :output
464                                 :if-exists :supersede)
465        (with-standard-io-syntax
466          (prin1 *trusted-uids* out))))))
467      (dolist (l *temporary-files* t)
468  (when (probe-file l) (delete-file l))))
469    (nreverse *systems-installed-this-time*)))
470
471(defun local-archive-p (package)
472  #+(or :sbcl :allegro) (probe-file package)
473  #-(or :sbcl :allegro) (and (/= (mismatch package "http://") 7)
474         (probe-file package)))
475
476(defun load-package (package)
477  #+asdf
478  (progn
479    (installer-msg t "Loading system ~S via ASDF." package)
480    (asdf:operate 'asdf:load-op package))
481  #+mk-defsystem
482  (progn
483    (installer-msg t "Loading system ~S via MK:DEFSYSTEM." package)
484    (mk:load-system package)))
485
486;;; uninstall --
487
488(defun uninstall (system &optional (prompt t))
489  #+asdf
490  (let* ((asd (asdf:system-definition-pathname system))
491   (system (asdf:find-system system))
492   (dir (pathname-sans-name+type
493         (asdf::resolve-symlinks asd))))
494    (when (or (not prompt)
495        (y-or-n-p
496         "Delete system ~A~%asd file: ~A~%sources: ~A~%Are you sure?"
497         system asd dir))
498      #-(or :win32 :mswindows)
499      (delete-file asd)
500      (let ((dir (#-scl namestring #+scl ext:unix-namestring (truename dir))))
501  (when dir
502    (asdf:run-shell-command "rm -r '~A'" dir)))))
503
504  #+mk-defsystem
505  (multiple-value-bind (sysfile sysfile-exists-p)
506      (mk:system-definition-pathname system)
507    (when sysfile-exists-p
508      (let ((system (ignore-errors (mk:find-system system :error))))
509        (when system
510          (when (or (not prompt)
511              (y-or-n-p
512               "Delete system ~A.~%system file: ~A~%Are you sure?"
513               system
514                     sysfile))
515            (mk:clean-system system)
516            (delete-file sysfile)
517            (dolist (f (mk:files-in-system system))
518              (delete-file f)))
519          ))
520      )))
521
522     
523;;; some day we will also do UPGRADE, but we need to sort out version
524;;; numbering a bit better first
525
526#+(and :asdf (or :win32 :mswindows))
527(defun sysdef-source-dir-search (system)
528  (let ((name (asdf::coerce-name system)))
529    (dolist (location *locations*)
530      (let* ((dir (first location))
531             (files (directory (merge-pathnames
532                                (make-pathname :name name
533                                               :type "asd"
534                                               :version :newest
535                                               :directory '(:relative :wild)
536                                               :host nil
537                                               :device nil)
538                                dir))))
539        (dolist (file files)
540          (when (probe-file file)
541            (return-from sysdef-source-dir-search file)))))))
542
543(defmethod asdf:find-component :around 
544    ((module (eql nil)) name)
545  (when (or (not *propagate-installation*) 
546            (member name *systems-installed-this-time* 
547                    :test (lambda (a b)
548                            (flet ((ensure-string (x)
549                                     (etypecase x
550                                       (symbol (symbol-name x))
551                                       (string x))))
552                              (string-equal (ensure-string a) (ensure-string b))))))
553    (call-next-method)))
554
555(defun show-version-information ()
556  (let ((version (asdf-install-version)))
557    (if version
558      (format *standard-output* "~&;;; ASDF-Install version ~A"
559              version)
560      (format *standard-output* "~&;;; ASDF-Install version unknown; unable to find ASDF system definition."))
561  (values)))
562
563(defun asdf-install-version ()
564  "Returns the ASDf-Install version information as a string or nil if it cannot be determined."
565  (let ((system (asdf:find-system 'asdf-install)))
566    (when system (asdf:component-version system))))
567
568;; load customizations if any
569(eval-when (:load-toplevel :execute)
570  (let* ((*package* (find-package :asdf-install-customize))
571         (file (probe-file (merge-pathnames
572          (make-pathname :name ".asdf-install")
573          (truename (user-homedir-pathname))))))
574    (when file (load file))))
575
576;;; end of file -- install.lisp --
Note: See TracBrowser for help on using the repository browser.