root/trunk/thirdparty/asdf/asdf-install.lisp

Revision 2236, 9.8 kB (checked in by hhubner, 1 year ago)

update asdf from cvs

Line 
1 #|| sh asdf-install.lisp will compile this file to an exe called asdf-install
2 sbcl <<EOF
3 (require 'sb-executable)
4 (compile-file "asdf-install.lisp")
5 (sb-executable:make-executable "asdf-install" *)
6 EOF
7 exit 0
8 ||#
9
10 ;;; Install an ASDF system or anything else that looks convincingly
11 ;;; like one, including updating symlink for all the toplevel .asd files it
12 ;;; contains
13
14 ;;; If the file $HOME/.asdf-install exists, it is loaded.  This can be
15 ;;; used to override the default values of exported special variables
16 ;;; (see the defpackage form for details) - however, most of them are
17 ;;; sensible and/or taken from the environment anyway
18
19 #||
20 TODO:
21 a) gpg signature checking would be better if it actually checked against
22 a list of "trusted to write Lisp" keys, instead of just "trusted to be
23 who they say they are"
24
25 d) in sbcl 0.8.1 we'll have a run-program that knows about $PATH and so
26 won't need to hardcode gpgpgpgp and tar locations.
27
28 e) nice to have: resume half-done downloads instead of starting from scratch
29 every time.  but right now we're dealing in fairly small packages, this is not
30 an immediate concern
31
32 ||#
33 (in-package :cl-user)
34 (eval-when (:compile-toplevel :load-toplevel :execute)
35   (require 'asdf)
36   (require 'sb-posix)
37   (require 'sb-executable)
38   (require 'sb-bsd-sockets))
39
40 (defpackage :asdf-install
41   (:use "CL" "SB-EXT"  "SB-BSD-SOCKETS")
42   (:export #:*proxy* #:*cclan-mirror* #:*sbcl-home*
43            #:*verify-gpg-signatures* #:*locations*))
44
45 (defpackage :asdf-install-customize
46   (:use "CL" "SB-EXT"  "SB-BSD-SOCKETS" "ASDF-INSTALL"))
47
48 (in-package :asdf-install)
49
50 (defvar *proxy* (posix-getenv "http_proxy"))
51 (defvar *cclan-mirror*
52   (or (posix-getenv "CCLAN_MIRROR")
53       "http://ftp.linux.org.uk/pub/lisp/cclan/"))
54
55 (defun directorify (name)
56   ;; input name may or may not have a training #\/, but we know we
57   ;; want a directory
58   (let ((path (pathname name)))
59     (if (pathname-name path)
60         (merge-pathnames
61          (make-pathname :directory `(:relative ,(pathname-name path))
62                         :name "")
63          path)
64         path)))
65
66 (defvar *sbcl-home* (directorify (posix-getenv "SBCL_HOME")))
67 (defvar *dot-sbcl*
68   (merge-pathnames (make-pathname :directory '(:relative ".sbcl"))
69                    (user-homedir-pathname)))
70
71 (defvar *verify-gpg-signatures* t)
72
73 (defvar *locations*
74   `((,(merge-pathnames "site/" *sbcl-home*)
75      ,(merge-pathnames "site-systems/" *sbcl-home*)
76      "System-wide install")
77     (,(merge-pathnames "site/" *dot-sbcl*)
78      ,(merge-pathnames "systems/" *dot-sbcl*)
79      "Personal installation")))
80
81 (let* ((*package* (find-package :asdf-install-customize))
82        (file (probe-file (merge-pathnames
83                           (make-pathname :name ".asdf-install")
84                           (user-homedir-pathname)))))
85   (when file (load file)))
86
87 (define-condition download-error (error)
88   ((url :initarg :url :reader download-url)
89    (response :initarg :response :reader download-response))
90   (:report (lambda (c s)
91              (format s "Server responded ~A for GET ~A"
92                      (download-response c) (download-url c)))))
93
94 (define-condition signature-error (error)
95   ((cause :initarg :cause :reader signature-error-cause))
96   (:report (lambda (c s)
97              (format s "Cannot verify package signature:  ~A"
98                      (signature-error-cause c)))))
99              
100 (defun url-host (url)
101   (assert (string-equal url "http://" :end1 7))
102   (let* ((port-start (position #\: url :start 7))
103          (host-end (min (or (position #\/ url :start 7) (length url))
104                         (or port-start (length url)))))
105     (subseq url 7 host-end)))
106
107 (defun url-port (url)
108   (assert (string-equal url "http://" :end1 7))
109   (let ((port-start (position #\: url :start 7)))
110     (if port-start (parse-integer url :start port-start :junk-allowed t) 80)))
111
112 (defun url-connection (url)
113   (let ((s (make-instance 'inet-socket :type :stream :protocol :tcp))
114         (host (url-host url))
115         (port (url-port url)))
116     (socket-connect
117      s (car (host-ent-addresses (get-host-by-name (url-host (or *proxy* url)))))
118      (url-port (or  *proxy* url)))
119     (let ((stream (socket-make-stream s :input t :output t :buffering :full)))
120       ;; we are exceedingly unportable about proper line-endings here.
121       ;; Anyone wishing to run this under non-SBCL should take especial care
122       (format stream "GET ~A HTTP/1.0~%Host: ~A~%Cookie: CCLAN-SITE=~A~%~%"
123               url host *cclan-mirror*)
124       (force-output stream)
125       (list
126        (let* ((l (read-line stream))
127               (space (position #\Space l)))
128          (parse-integer l :start (1+ space) :junk-allowed t))
129        (loop for line = (read-line stream nil nil)
130              until (or (null line) (eql (elt line 0) (code-char 13)))
131              collect
132              (let ((colon (position #\: line)))
133                (cons (intern (string-upcase (subseq line 0 colon)) :keyword)
134                      (string-trim (list #\Space (code-char 13))
135                                   (subseq line (1+ colon))))))
136        stream))))
137
138 (defun download (package-name-or-url file-name)
139   (let ((url
140          (if (= (mismatch package-name-or-url "http://") 7)
141              package-name-or-url
142              (format nil "http://www.cliki.net/~A?download"
143                      package-name-or-url))))
144     (destructuring-bind (response headers stream)
145         (block got
146           (loop
147            (destructuring-bind (response headers stream) (url-connection url)
148              (unless (member response '(301 302))             
149                (return-from got (list response headers stream)))
150              (close stream)
151              (setf url (cdr (assoc :location headers))))))
152       (if (>= response 400)
153         (error 'download-error :url url :response response))
154       (let ((length (parse-integer
155                      (or (cdr (assoc :content-length headers)) "")
156                      :junk-allowed t)))
157         (format t "Downloading ~A bytes from ~A ..."
158                 (if length length "some unknown number of") url)
159         (force-output)
160         (with-open-file (o file-name :direction :output)
161           (if length
162               (let ((buf (make-array length
163                                      :element-type
164                                      (stream-element-type stream)  )))
165                 (read-sequence buf stream)
166                 (write-sequence buf o))
167               (sb-executable:copy-stream stream o))))
168       (close stream)
169       (terpri)
170       ;; seems to have worked.  let's try for a detached gpg signature too
171       (when *verify-gpg-signatures*
172         (verify-gpg-signature url file-name)))))
173
174 (defun verify-gpg-signature (url file-name)
175   (destructuring-bind (response headers stream)
176       (url-connection (concatenate 'string url ".asc"))
177     (declare (ignore headers))
178     (unwind-protect
179          (if (= response 200)
180              ;; sadly, we can't pass the stream directly to run-program,
181              ;; because (at least in sbcl 0.8) that ignores existing buffered
182              ;; data and only reads new fresh data direct from the file
183              ;; descriptor
184              (let ((data (make-string (parse-integer
185                                        (cdr (assoc :content-length headers))
186                                        :junk-allowed t))))
187                (read-sequence data stream)
188                (let ((ret
189                       (process-exit-code
190                        (sb-ext:run-program "/usr/bin/gpg"
191                                            (list "--verify" "-"
192                                                  (namestring file-name))
193                                            :output t
194                                            :input (make-string-input-stream data)
195                                            :wait t))))
196                  (unless (zerop ret)
197                    (error 'signature-error
198                           :cause (make-condition
199                                   'simple-error
200                                   :format-control "GPG returned exit status ~A"
201                                   :format-arguments (list ret))))))
202              (error 'signature-error
203                     :cause
204                     (make-condition
205                      'download-error :url  (concatenate 'string url ".asc")
206                      :response response)))
207       (close stream))))
208        
209    
210
211
212 (defun where () 
213   (format t "Install where?~%")
214   (loop for (source system name) in *locations*
215         for i from 1
216         do (format t "~A) ~A: ~%   System in ~A~%   Files in ~A ~%"
217                    i name system source))
218   (format t " --> ") (force-output)
219   (let ((response (read)))
220     (when (> response 0)
221       (elt *locations* (1- response)))))
222
223 (defun install (source system packagename)
224   "Returns a list of asdf system names for installed asdf systems"
225   (ensure-directories-exist source )
226     (ensure-directories-exist system )
227   (let* ((tar
228           (with-output-to-string (o)
229             (or
230              (sb-ext:run-program "/bin/tar"
231                                  (list "-C" (namestring source)
232                                        "-xzvf" (namestring packagename))
233                                  :output o
234                                  :wait t)
235              (error "can't untar"))))
236          (dummy (princ tar))
237          (pos-slash (position #\/ tar))
238          (*default-pathname-defaults*
239           (merge-pathnames
240            (make-pathname :directory
241                           `(:relative ,(subseq tar 0 pos-slash)))
242            source)))
243     (loop for asd in (directory
244                       (make-pathname :name :wild :type "asd"))
245           do (let ((target (merge-pathnames
246                             (make-pathname :name (pathname-name asd)
247                                            :type (pathname-type asd))
248                             system)))
249                (when (probe-file target)
250                  (sb-posix:unlink target))
251                (sb-posix:symlink asd target))
252           collect (pathname-name asd))))
253
254 (defvar *temporary-files*)
255 (defun temp-file-name (p)
256   (let* ((pos-slash (position #\/ p :from-end t))
257          (pos-dot (position #\. p :start (or pos-slash 0))))
258     (merge-pathnames
259      (make-pathname
260       :name (subseq p (if pos-slash (1+ pos-slash) 0) pos-dot)
261       :type "asdf-install-tmp"))))
262                      
263
264
265 (defun run (&optional (packages (cdr *posix-argv*)))
266   (destructuring-bind (source system name) (where)
267     (labels ((one-iter (packages)
268                (dolist (asd
269                          (loop for p in packages
270                                unless (probe-file p)
271                                do (let ((tmp (temp-file-name p)))
272                                     (pushnew tmp *temporary-files*)
273                                     (download p tmp)
274                                     (setf p tmp))
275                                end
276                                do (format t "Installing ~A in ~A,~A~%" p source system)
277                                append (install source system p)))
278                  (handler-case
279                      (asdf:operate 'asdf:load-op asd)
280                    (asdf:missing-dependency (c)
281                      (format t "Downloading package ~A, required by ~A~%"
282                              (asdf::missing-requires c)
283                              (asdf:component-name (asdf::missing-required-by c)))
284                      (one-iter (list
285                                 (symbol-name (asdf::missing-requires c)))))))))
286       (one-iter packages))))
287
288 (handler-case
289     (let ((*temporary-files* nil))
290       (unwind-protect
291            (run)
292         (dolist (l *temporary-files*)
293           (when (probe-file l) (delete-file l)))))
294   (error (c)
295     (princ "Install failed due to error:") (terpri)
296     (princ c) (terpri)
297     (quit :unix-status 1)))
298
299 ;(quit)
Note: See TracBrowser for help on using the browser.