(defvar *proxy* (posix-getenv "http_proxy"))
(defvar *cclan-mirror*
- (or (posix-getenv "CCLAN_MIRROR")
- "http://ftp.linux.org.uk/pub/lisp/cclan/"))
+ (let ((mirror (posix-getenv "CCLAN_MIRROR")))
+ (or (and (not (string= mirror "")) mirror)
+ "http://ftp.linux.org.uk/pub/lisp/cclan/")))
(defun directorify (name)
;; input name may or may not have a training #\/, but we know we
;; want a directory
(let ((path (pathname name)))
(if (pathname-name path)
- (merge-pathnames
- (make-pathname :directory `(:relative ,(pathname-name path)))
- (make-pathname :directory (pathname-directory path)
- :host (pathname-host path)))
- path)))
+ (merge-pathnames
+ (make-pathname :directory `(:relative ,(pathname-name path)))
+ (make-pathname :directory (pathname-directory path)
+ :host (pathname-host path)
+ :device (pathname-device path)))
+ path)))
(defvar *sbcl-home* (directorify (posix-getenv "SBCL_HOME")))
(defvar *dot-sbcl*
(merge-pathnames (make-pathname :directory '(:relative ".sbcl"))
- (user-homedir-pathname)))
+ (user-homedir-pathname)))
(defparameter *trusted-uids* nil)
-
-(defun verify-gpg-signatures-p (url)
- (labels ((prefixp (prefix string)
- (let ((m (mismatch prefix string)))
- (or (not m) (>= m (length prefix))))))
- (case *verify-gpg-signatures*
- (nil nil)
- (:unknown-locations
- (notany
- (lambda (x) (prefixp x url))
- (cons *cclan-mirror* *safe-url-prefixes*)))
- (t t))))
-
(defvar *locations*
`((,(merge-pathnames "site/" *sbcl-home*)
,(merge-pathnames "site-systems/" *sbcl-home*)
,(merge-pathnames "systems/" *dot-sbcl*)
"Personal installation")))
-(let* ((*package* (find-package :asdf-install-customize))
- (file (probe-file (merge-pathnames
- (make-pathname :name ".asdf-install")
- (user-homedir-pathname)))))
- (when file (load file)))
+(unless (sb-ext:posix-getenv "SBCL_BUILDING_CONTRIB")
+ ;; Not during build, thanks.
+ (let* ((*package* (find-package :asdf-install-customize))
+ (file (probe-file (merge-pathnames
+ (make-pathname :name ".asdf-install")
+ (user-homedir-pathname)))))
+ (when file (load file))))
(define-condition download-error (error)
((url :initarg :url :reader download-url)
(response :initarg :response :reader download-response))
(:report (lambda (c s)
- (format s "Server responded ~A for GET ~A"
- (download-response c) (download-url c)))))
+ (format s "Server responded ~A for GET ~A"
+ (download-response c) (download-url c)))))
(define-condition signature-error (error)
((cause :initarg :cause :reader signature-error-cause))
(:report (lambda (c s)
- (format s "Cannot verify package signature: ~A"
- (signature-error-cause c)))))
+ (format s "Cannot verify package signature: ~A"
+ (signature-error-cause c)))))
(define-condition gpg-error (error)
((message :initarg :message :reader gpg-error-message))
(:report (lambda (c s)
- (format t "GPG failed with error status:~%~S"
- (gpg-error-message c)))))
+ (format s "GPG failed with error status:~%~S"
+ (gpg-error-message c)))))
(define-condition no-signature (gpg-error) ())
(define-condition key-not-found (gpg-error)
((key-id :initarg :key-id :reader key-id))
(:report (lambda (c s)
- (format s "No key found for key id 0x~A. Try some command like ~% gpg --recv-keys 0x~A"
- (key-id c) (key-id c)))))
+ (format s "No key found for key id 0x~A. Try some command like ~% gpg --recv-keys 0x~A"
+ (key-id c) (key-id c)))))
(define-condition key-not-trusted (gpg-error)
((key-id :initarg :key-id :reader key-id)
(key-user-name :initarg :key-user-name :reader key-user-name))
(:report (lambda (c s)
- (format s "GPG warns that the key id 0x~A (~A) is not fully trusted"
- (key-id c) (key-user-name c)))))
+ (format s "GPG warns that the key id 0x~A (~A) is not fully trusted"
+ (key-id c) (key-user-name c)))))
(define-condition author-not-trusted (gpg-error)
((key-id :initarg :key-id :reader key-id)
(key-user-name :initarg :key-user-name :reader key-user-name))
(:report (lambda (c s)
- (format s "~A (key id ~A) is not on your package supplier list"
- (key-user-name c) (key-id c)))))
-
+ (format s "~A (key id ~A) is not on your package supplier list"
+ (key-user-name c) (key-id c)))))
+
(defun url-host (url)
(assert (string-equal url "http://" :end1 7))
(let* ((port-start (position #\: url :start 7))
- (host-end (min (or (position #\/ url :start 7) (length url))
- (or port-start (length url)))))
+ (host-end (min (or (position #\/ url :start 7) (length url))
+ (or port-start (length url)))))
(subseq url 7 host-end)))
(defun url-port (url)
(let ((port-start (position #\: url :start 7)))
(if port-start (parse-integer url :start (1+ port-start) :junk-allowed t) 80)))
+(defun request-uri (url)
+ (assert (string-equal url "http://" :end1 7))
+ (if *proxy*
+ url
+ (let ((path-start (position #\/ url :start 7)))
+ (subseq url path-start))))
+
(defun url-connection (url)
(let ((s (make-instance 'inet-socket :type :stream :protocol :tcp))
- (host (url-host url))
- (port (url-port url)))
+ (host (url-host url))
+ (port (url-port url))
+ result)
(declare (ignore port))
- (socket-connect
- s (car (host-ent-addresses (get-host-by-name (url-host (or *proxy* url)))))
- (url-port (or *proxy* url)))
- (let ((stream (socket-make-stream s :input t :output t :buffering :full :external-format :iso-8859-1)))
- ;; we are exceedingly unportable about proper line-endings here.
- ;; Anyone wishing to run this under non-SBCL should take especial care
- (format stream "GET ~A HTTP/1.0~%Host: ~A~%Cookie: CCLAN-SITE=~A~%~%"
- url host *cclan-mirror*)
- (force-output stream)
- (list
- (let* ((l (read-line stream))
- (space (position #\Space l)))
- (parse-integer l :start (1+ space) :junk-allowed t))
- (loop for line = (read-line stream nil nil)
- until (or (null line) (eql (elt line 0) (code-char 13)))
- collect
- (let ((colon (position #\: line)))
- (cons (intern (string-upcase (subseq line 0 colon)) :keyword)
- (string-trim (list #\Space (code-char 13))
- (subseq line (1+ colon))))))
- stream))))
+ (unwind-protect
+ (progn
+ (socket-connect
+ s (car (host-ent-addresses (get-host-by-name (url-host (or *proxy* url)))))
+ (url-port (or *proxy* url)))
+ (let ((stream (socket-make-stream s :input t :output t :buffering :full
+ :element-type :default :external-format :iso-8859-1)))
+ ;; we are exceedingly unportable about proper line-endings here.
+ ;; Anyone wishing to run this under non-SBCL should take especial care
+ (format stream "GET ~A HTTP/1.0~c~%~
+ Host: ~A~c~%~
+ Cookie: CCLAN-SITE=~A~c~%~c~%"
+ (request-uri url) #\Return
+ host #\Return
+ *cclan-mirror* #\Return #\Return)
+ (force-output stream)
+ (setf result
+ (list
+ (let* ((l (read-line stream))
+ (space (position #\Space l)))
+ (parse-integer l :start (1+ space) :junk-allowed t))
+ (loop for line = (read-line stream nil nil)
+ until (or (null line) (eql (elt line 0) (code-char 13)))
+ collect
+ (let ((colon (position #\: line)))
+ (cons (intern (string-upcase (subseq line 0 colon)) :keyword)
+ (string-trim (list #\Space (code-char 13))
+ (subseq line (1+ colon))))))
+ stream))))
+ (when (and (null result)
+ (socket-open-p s))
+ (socket-close s)))))
+
+
+(defun copy-stream (in out)
+ (let ((buf (make-array 8192 :element-type (stream-element-type out))))
+ (loop for pos = (read-sequence buf in)
+ until (zerop pos)
+ do (write-sequence buf out :end pos))))
(defun download-files-for-package (package-name-or-url file-name)
(let ((url
- (if (= (mismatch package-name-or-url "http://") 7)
- package-name-or-url
- (format nil "http://www.cliki.net/~A?download"
- package-name-or-url))))
+ (if (= (mismatch package-name-or-url "http://") 7)
+ package-name-or-url
+ (format nil "http://www.cliki.net/~A?download"
+ package-name-or-url))))
(destructuring-bind (response headers stream)
- (block got
- (loop
- (destructuring-bind (response headers stream) (url-connection url)
- (unless (member response '(301 302))
- (return-from got (list response headers stream)))
- (close stream)
- (setf url (cdr (assoc :location headers))))))
+ (block got
+ (loop
+ (destructuring-bind (response headers stream) (url-connection url)
+ (unless (member response '(301 302))
+ (return-from got (list response headers stream)))
+ (close stream)
+ (setf url (cdr (assoc :location headers))))))
(if (>= response 400)
- (error 'download-error :url url :response response))
+ (error 'download-error :url url :response response))
(let ((length (parse-integer
- (or (cdr (assoc :content-length headers)) "")
- :junk-allowed t)))
- (format t "Downloading ~A bytes from ~A ..."
- (if length length "some unknown number of") url)
- (force-output)
- (with-open-file (o file-name :direction :output :external-format :iso-8859-1)
- (if length
- (let ((buf (make-array length
- :element-type
- (stream-element-type stream))))
- (read-sequence buf stream)
- (write-sequence buf o))
- (sb-executable:copy-stream stream o))))
+ (or (cdr (assoc :content-length headers)) "")
+ :junk-allowed t)))
+ (format t "Downloading ~A bytes from ~A ..."
+ (if length length "some unknown number of") url)
+ (force-output)
+ (with-open-file (out file-name :direction :output
+ :element-type '(unsigned-byte 8))
+ (if length
+ (let ((buf (make-array length :element-type '(unsigned-byte 8))))
+ (read-sequence buf stream)
+ (write-sequence buf out))
+ (copy-stream stream out))))
(close stream)
(terpri)
- (restart-case
- (verify-gpg-signature/url url file-name)
- (skip-gpg-check (&rest rest)
- :report "Don't check GPG signature for this package"
- nil)))))
+ (restart-case
+ (verify-gpg-signature/url url file-name)
+ (skip-gpg-check ()
+ :report "Don't check GPG signature for this package"
+ nil)))))
(defun read-until-eof (stream)
(with-output-to-string (o)
- (sb-executable:copy-stream stream o)))
-
+ (copy-stream stream o)))
+
(defun verify-gpg-signature/string (string file-name)
(let* ((proc
- (sb-ext:run-program
- "gpg"
- (list
- "--status-fd" "1" "--verify" "-"
- (namestring file-name))
- :output :stream :error :stream :search t
- :input (make-string-input-stream string) :wait t))
- (ret (process-exit-code proc))
- (err (read-until-eof (process-error proc)))
- tags)
+ (sb-ext:run-program
+ "gpg"
+ (list
+ "--status-fd" "1" "--verify" "-"
+ (namestring file-name))
+ :output :stream :error :stream :search t
+ :input (make-string-input-stream string) :wait t))
+ (err (read-until-eof (process-error proc)))
+ tags)
(loop for l = (read-line (process-output proc) nil nil)
- while l
- when (> (mismatch l "[GNUPG:]") 6)
- do (destructuring-bind (_ tag &rest data) (asdf::split l)
- (pushnew (cons (intern tag :keyword)
- data) tags)))
+ while l
+ when (> (mismatch l "[GNUPG:]") 6)
+ do (destructuring-bind (_ tag &rest data) (asdf::split-string l)
+ (declare (ignore _))
+ (pushnew (cons (intern tag :keyword)
+ data) tags)))
;; test for obvious key/sig problems
(let ((errsig (assoc :errsig tags)))
(and errsig (error 'key-not-found :key-id (second errsig) :gpg-err err)))
(let ((badsig (assoc :badsig tags)))
(and badsig (error 'key-not-found :key-id (second badsig) :gpg-err err)))
(let* ((good (assoc :goodsig tags))
- (id (second good))
- (name (format nil "~{~A~^ ~}" (nthcdr 2 good))))
+ (id (second good))
+ (name (format nil "~{~A~^ ~}" (nthcdr 2 good))))
;; good signature, but perhaps not trusted
(unless (or (assoc :trust_ultimate tags)
- (assoc :trust_fully tags))
- (cerror "Install the package anyway"
- 'key-not-trusted
- :key-user-name name
- :key-id id :gpg-err err))
+ (assoc :trust_fully tags))
+ (cerror "Install the package anyway"
+ 'key-not-trusted
+ :key-user-name name
+ :key-id id :gpg-err err))
(loop
(when
- (restart-case
- (or (assoc id *trusted-uids* :test #'equal)
- (error 'author-not-trusted
- :key-user-name name
- :key-id id :gpg-err nil))
- (add-key (&rest rest)
- :report "Add to package supplier list"
- (pushnew (list id name) *trusted-uids*)))
- (return))))))
+ (restart-case
+ (or (assoc id *trusted-uids* :test #'equal)
+ (error 'author-not-trusted
+ :key-user-name name
+ :key-id id :gpg-err nil))
+ (add-key ()
+ :report "Add to package supplier list"
+ (pushnew (list id name) *trusted-uids*)))
+ (return))))))
(destructuring-bind (response headers stream)
(url-connection (concatenate 'string url ".asc"))
(unwind-protect
- (if (= response 200)
- (let ((data (make-string (parse-integer
- (cdr (assoc :content-length headers))
- :junk-allowed t))))
- (read-sequence data stream)
- (verify-gpg-signature/string data file-name))
- (error 'download-error :url (concatenate 'string url ".asc")
- :response response))
+ (if (= response 200)
+ (let ((data (make-string (parse-integer
+ (cdr (assoc :content-length headers))
+ :junk-allowed t))))
+ (read-sequence data stream)
+ (verify-gpg-signature/string data file-name))
+ (error 'download-error :url (concatenate 'string url ".asc")
+ :response response))
(close stream))))
-(defun where ()
+(defun where ()
(format t "Install where?~%")
(loop for (source system name) in *locations*
- for i from 1
- do (format t "~A) ~A: ~% System in ~A~% Files in ~A ~%"
- i name system source))
+ for i from 1
+ do (format t "~A) ~A: ~% System in ~A~% Files in ~A ~%"
+ i name system source))
(format t " --> ") (force-output)
(let ((response (read)))
(when (> response 0)
(elt *locations* (1- response)))))
+(defparameter *tar-program*
+ ;; Please do not "clean this up" by using a bunch of #+'s and one
+ ;; #-. When the conditional is written this way, adding a new
+ ;; special case only involves one change. If #- is used, two changes
+ ;; are needed. -- JES, 2007-02-12
+ (progn
+ "tar"
+ #+darwin "gnutar"
+ #+(or sunos netbsd) "gtar"))
+
+(defun get-tar-directory (packagename)
+ (let* ((tar (with-output-to-string (o)
+ (or
+ (sb-ext:run-program *tar-program*
+ (list "-tzf" (namestring packagename))
+ :output o
+ :search t
+ :wait t)
+ (error "can't list archive"))))
+ (first-line (subseq tar 0 (position #\newline tar))))
+ (if (find #\/ first-line)
+ (subseq first-line 0 (position #\/ first-line))
+ first-line)))
+
+(defun untar-package (source packagename)
+ (with-output-to-string (o)
+ (or
+ (sb-ext:run-program *tar-program*
+ (list "-C" (namestring source)
+ "-xzvf" (namestring packagename))
+ :output o
+ :search t
+ :wait t)
+ (error "can't untar"))))
+
(defun install-package (source system packagename)
"Returns a list of asdf system names for installed asdf systems"
- (ensure-directories-exist source )
- (ensure-directories-exist system )
- (let* ((tar
- (with-output-to-string (o)
- (or
- (sb-ext:run-program #-darwin "tar"
- #+darwin "gnutar"
- (list "-C" (namestring source)
- "-xzvf" (namestring packagename))
- :output o
- :search t
- :wait t)
- (error "can't untar"))))
- (dummy (princ tar))
- (pos-slash (position #\/ tar))
- (*default-pathname-defaults*
- (merge-pathnames
- (make-pathname :directory
- `(:relative ,(subseq tar 0 pos-slash)))
- source)))
- (declare (ignore dummy))
+ (ensure-directories-exist source)
+ (ensure-directories-exist system)
+ (let* ((tdir (get-tar-directory packagename))
+ (*default-pathname-defaults*
+ (merge-pathnames (make-pathname :directory `(:relative ,tdir))
+ source)))
+ (princ (untar-package source packagename))
(loop for asd in (directory
- (make-pathname :name :wild :type "asd"))
- do (let ((target (merge-pathnames
- (make-pathname :name (pathname-name asd)
- :type (pathname-type asd))
- system)))
- (when (probe-file target)
- (sb-posix:unlink target))
- (sb-posix:symlink asd target))
- collect (pathname-name asd))))
+ (make-pathname :name :wild :type "asd"))
+ do (let ((target (merge-pathnames
+ (make-pathname :name (pathname-name asd)
+ :type (pathname-type asd))
+ system)))
+ (when (probe-file target)
+ (sb-posix:unlink target))
+ #-win32
+ (sb-posix:symlink asd target))
+ collect (pathname-name asd))))
(defvar *temporary-files*)
(defun temp-file-name (p)
(let* ((pos-slash (position #\/ p :from-end t))
- (pos-dot (position #\. p :start (or pos-slash 0))))
+ (pos-dot (position #\. p :start (or pos-slash 0))))
(merge-pathnames
(make-pathname
:name (subseq p (if pos-slash (1+ pos-slash) 0) pos-dot)
:type "asdf-install-tmp"))))
-
+
;; this is the external entry point
(defun install (&rest packages)
(let ((*temporary-files* nil)
- (*trusted-uids*
- (let ((p (merge-pathnames "trusted-uids.lisp" *dot-sbcl*)))
- (when (probe-file p)
- (with-open-file (f p) (read f))))))
+ (*trusted-uids*
+ (let ((p (merge-pathnames "trusted-uids.lisp" *dot-sbcl*)))
+ (when (probe-file p)
+ (with-open-file (f p) (read f))))))
(unwind-protect
- (destructuring-bind (source system name) (where)
- (labels ((one-iter (packages)
- (dolist (asd
- (loop for p in (mapcar 'string packages)
- unless (probe-file p)
- do (let ((tmp (temp-file-name p)))
- (pushnew tmp *temporary-files*)
- (download-files-for-package p tmp)
- (setf p tmp))
- end
- do (format t "Installing ~A in ~A,~A~%"
- p source system)
- append (install-package source system p)))
- (handler-bind
- ((asdf:missing-dependency
- (lambda (c)
- (format t
- "Downloading package ~A, required by ~A~%"
- (asdf::missing-requires c)
- (asdf:component-name
- (asdf::missing-required-by c)))
- (one-iter (list
- (symbol-name
- (asdf::missing-requires c))))
- (invoke-restart 'retry))))
- (loop
- (multiple-value-bind (ret restart-p)
- (with-simple-restart
- (retry "Retry installation")
- (asdf:operate 'asdf:load-op asd))
- (unless restart-p (return))))))))
- (one-iter packages)))
+ (destructuring-bind (source system name) (where)
+ (declare (ignore name))
+ (labels ((one-iter (packages)
+ (dolist (asd
+ (loop for p in (mapcar 'string packages)
+ unless (probe-file p)
+ do (let ((tmp (temp-file-name p)))
+ (pushnew tmp *temporary-files*)
+ (download-files-for-package p tmp)
+ (setf p tmp))
+ end
+ do (format t "Installing ~A in ~A,~A~%"
+ p source system)
+ append (install-package source system p)))
+ (handler-bind
+ ((asdf:missing-dependency
+ (lambda (c)
+ (format t
+ "Downloading package ~A, required by ~A~%"
+ (asdf::missing-requires c)
+ (asdf:component-name
+ (asdf::missing-required-by c)))
+ (one-iter (list
+ (symbol-name
+ (asdf::missing-requires c))))
+ (invoke-restart 'retry))))
+ (loop
+ (multiple-value-bind (ret restart-p)
+ (with-simple-restart
+ (retry "Retry installation")
+ (asdf:operate 'asdf:load-op asd))
+ (declare (ignore ret))
+ (unless restart-p (return))))))))
+ (one-iter packages)))
(let ((p (merge-pathnames "trusted-uids.lisp" *dot-sbcl*)))
- (ensure-directories-exist p)
- (with-open-file (out p :direction :output :if-exists :supersede)
- (with-standard-io-syntax
- (prin1 *trusted-uids* out))))
+ (ensure-directories-exist p)
+ (with-open-file (out p :direction :output :if-exists :supersede)
+ (with-standard-io-syntax
+ (prin1 *trusted-uids* out))))
(dolist (l *temporary-files*)
- (when (probe-file l) (delete-file l))))))
+ (when (probe-file l) (delete-file l))))))
(defun uninstall (system &optional (prompt t))
(let* ((asd (asdf:system-definition-pathname system))
- (system (asdf:find-system system))
- (dir (asdf::pathname-sans-name+type
- (asdf::resolve-symlinks asd))))
+ (system (asdf:find-system system))
+ (dir (asdf::pathname-sans-name+type
+ (asdf::resolve-symlinks asd))))
(when (or (not prompt)
- (y-or-n-p
- "Delete system ~A~%asd file: ~A~%sources: ~A~%Are you sure?"
- system asd dir))
+ (y-or-n-p
+ "Delete system ~A~%asd file: ~A~%sources: ~A~%Are you sure?"
+ system asd dir))
(delete-file asd)
(asdf:run-shell-command "rm -r ~A" (namestring dir)))))
-
+
;;; some day we will also do UPGRADE, but we need to sort out version
;;; numbering a bit better first