(roswell:include '("locations" "util") "util-install") (defpackage :roswell.install (:use :cl :roswell.util :roswell.locations) (:export :*build-hook* :install-impl :read-call :*ros-path* :install-system-script :install-impl-if-probed :install-script-if-probed :install-system-if-probed :mingw-namestring :install-github :*checkout-default* :install :decide-version :decide-download :require-system-package :start :setup :date :github-version :version :install-script :count-line-stream :get-elements-by-tag-name)) (in-package :roswell.install) (defvar *ros-path* nil) (defvar *env* "ROSINSTALL") (defvar *checkout-default* '(checkout-github)) (defun install-impl (impl version argv cmds) "See install-impl-if-probed." (when cmds (let ((param `(t :target ,impl :version ,version :version-not-specified nil :argv ,argv))) (handler-case (loop for call in cmds when (roswell:verbose) do (format *error-output* "~&:" call (rest param)) do (setq param (funcall call (rest param))) while (first param)) #+sbcl (sb-sys:interactive-interrupt (condition) (declare (ignore condition)) (format t "SIGINT detected, cleaning up the partially installed files~%") (roswell:roswell `(,(format nil "deleteing ~A/~A" (getf (cdr param) :target) (getf (cdr param) :version))) :string t)))))) (defun install-impl-if-probed (imp version argv) "Install the implementation when there is a file named install-XXXX (e.g. install-sbcl) in the roswell directory. These files contain the `recipes` for download/build the binaries/sources of these implementations." (values (let ((fun (module "install" imp))) (when fun (install-impl imp version argv (funcall fun :install)) (setf argv nil) t)) argv)) (defun install-script-if-probed (impl/version) "Install a .ros script (ros install XXX.ros or just ros install XXX if it is in the current directory)" (let* (sub (result (and (pathname-name impl/version) (probe-file (setf sub (make-pathname :defaults impl/version :type "ros")))))) (when result (funcall 'install-ros sub) result))) (defun install-system-if-probed (imp) "Install the quicklisp system." (let ((result (or (read-call "ql-dist:find-system" imp) (read-call "ql:where-is-system" imp)))) (when (and result (= 1 (count #\, (ros:getenv *env*)))) (funcall 'install-system-script imp) result))) (defun install-localpath-if-probed (namestring) "To install a system by local path. NAMESTRING should be a path to an asdf file. To differentiate it from the system with the same name in quicklisp, the path should start with '.', contain at least one '/'." (when (and (eql #\. (aref namestring 0)) (find #\/ namestring)) (let* ((path (truename namestring)) (system (or (pathname-name path) (first (last (pathname-directory path))) (error "system not specified!"))) (dir (make-pathname :defaults path :name nil :type nil)) (dest (ensure-directories-exist (merge-pathnames (format nil "local-projects/local/~A/" system) (homedir))))) (format *error-output* "installing ~A~%" (truename namestring)) (copy-dir dir dest) (install-system-if-probed system)))) (defun github-version (uri project filter) (let ((elts (let ((file (merge-pathnames (format nil "tmp/~A.html" project) (homedir)))) (download uri file :interval (* 60 60)) (read-call "plump:parse" file)))) (nreverse (loop for link in (read-call "roswell.install:get-elements-by-tag-name" elts "link") for href = (read-call "plump:get-attribute" link "href") when (funcall filter href) collect it)))) (defun checkout-github (impl version tag) "Install a system from github." (if (head (format nil "https://github.com/~A/~A" impl version)) (clone-github impl version :path "local-projects" :branch tag) (format *error-output* "github: ~A/~A not exists~%" impl version))) (defun install (argv) "Install an implementation or a system. How this function parses it's arguments. It takes each argument and makes following: 1. Splits the argument by first '/' occurence. 2. Everything before / becomes the name of an implementation or a system. 3. Everything after / (if any), again splitted by '/'. 4. If step 3 was successful, first part of the string becomes a 'version' and second part (if any) becomes a 'tag'. After the parsing, roswell sequentially tries: 1. To install 'implementation' of the given 'version'. 2. To install a something.ros script, if argument is a path to a file. 3. To install an asdf system with same name as exact argument's value. 4. To install a system by local path. Path should start with '.', contain at least one '/' and point to an asd file. System is installed into \"${roswell-homedir}/local-projects/local/${system-name}/\". 5. If nothing of above matched, it tries to clone system from the github, like: git clone -b 'tag' 'system'/'version' For example, if you did: ros install some/repo/the-feature, it will do git clone -b the-feature \\ https://github.com/some/repo \\ ${roswell-home}/local-projects/some/repo " (read-call "roswell.util:local-project-build-hash" :rebuild t) (loop with *ros-path* = (make-pathname :defaults (opt "argv0")) with changed with envold = (ros:getenv *env*) for impl/version/tag = (first argv) for _ = (remove "" (split-sequence #\/ impl/version/tag) :test 'equal) for impl = (first _) for version = (second _) for tag = (and (cddr _) (format nil "~{~A~^/~}" (cddr _))) for version/tag = (if tag (format nil "~A/~A" version tag) version) do (setf argv (rest argv)) (setenv *env* (format nil "~A,~A" impl/version/tag envold)) (cond ;;registerd implementations like sbcl ccl-bin abcl etc ((setf (values _ argv) (install-impl-if-probed impl version/tag argv))) ;;local ros file like tool.ros ((install-script-if-probed impl/version/tag)) ;;asd/quicklisp registered systems which contain "roswell" directory ((install-system-if-probed impl/version/tag)) ;;local relative pathname ((install-localpath-if-probed impl/version/tag)) ;;github registerd system like "fukamachi/sblint" checkout (version (loop for f in *checkout-default* until (funcall f impl version tag)) (read-call "roswell.util:local-project-build-hash" :rebuild t) (or (let ((project (merge-pathnames (format nil "local-projects/~A/~A/project.lisp" impl version) (roswell.util:checkoutdir)))) (when (probe-file project) (ignore-errors (let ((system (with-open-file (i project) (second (assoc "asd" (second (second (first (nth 1 (read i))))) :test #'equal))))) (install-system-if-probed system))))) (and (install-impl-if-probed version nil argv) (or (setf argv nil) t)) (install-system-if-probed version))) (t (format *error-output* "'~A' is not a valid target for 'install' -- It should be a name of either: + a quicklisp-installable system + a common lisp installation ~%" impl) (roswell:quit 1))) (setf changed t) while argv finally (when changed (roswell:exec `(,(opt "argv0") "setup"))))) #+win32 (defun mingw-namestring (path) (string-right-trim (format nil "~%") (uiop:run-program `(,(sh) "-lc" ,(format nil "cd ~S;pwd" (uiop:native-namestring path))) :output :string))) (defvar *package-assoc* '(("dpkg" . (("zlib" . "zlib1g-dev"))))) (defun require-system-package (&rest packages) "check installation of system library like 'zlib' and show how to install." (declare (ignorable packages)) (loop with result with (mgr . assoc) = (find-if #'roswell.util:which *package-assoc* :key #'first) for package in packages for to-install = (cdr (assoc package assoc :test 'equal)) do (cond ((null to-install)) ((equal mgr "dpkg") (or (ignore-errors (uiop:run-program `(,mgr "-l" ,to-install)) t) (push to-install result)))) finally (return (if result (cond ((equal mgr "dpkg") (format *error-output* "might cause error 'apt-get install ~{~A~^ ~}' would help~%" result) t)) ;; Don't have confidence. t)))) (defun setup (argv) (unless (equal (getf argv :target) "sbcl-bin") (setf (config "default.lisp") (getf argv :target) (config (format nil "~A.version" (getf argv :target))) (opt "as"))) (setf (config "setup.time") (format nil "~A" (get-universal-time))) (cons t argv))