mirror of
https://github.com/roswell/roswell.git
synced 2026-09-10 07:16:17 -04:00
482 lines
19 KiB
Common Lisp
482 lines
19 KiB
Common Lisp
|
|
#|
|
|
|
|
The entry of all roswell commands.
|
|
The true internal entry invoked by the C binary is roswell:run.
|
|
(to see how this function is invoked, consult ros.c, cmd-run-sbcl.c etc.)
|
|
All roswell commands are dispatched from this function via the symbol lookup.
|
|
|
|
For example, -sp SP or --system-package SP will cause the roswell C binary
|
|
to invoke #'system-package.
|
|
|
|
This file also contains many functions which can be otherwise delegated to
|
|
UIOP, such as run-program, quit, getenv. This is because UIOP may not be
|
|
available in older implementations. Similarly, older implementations do not
|
|
have the latest asdf, and this file has a workaround for this.
|
|
|
|
|#
|
|
|
|
|
|
(cl:in-package :cl-user)
|
|
|
|
(let ((*standard-output* (make-broadcast-stream)))
|
|
#+sbcl (require :sb-posix))
|
|
|
|
(defpackage :roswell
|
|
(:use :cl)
|
|
(:nicknames :ros)
|
|
(:shadow :load :eval :package :restart :print :write)
|
|
(:export :run :*argv* :*main* :*load* :*cmd* :quit :script :quicklisp :getenv :opt
|
|
:ignore-shebang :asdf :include :ensure-asdf :revert-extension
|
|
:roswell :exec :setenv :unsetenv :version :swank :verbose
|
|
:*local-project-directories*)
|
|
(:documentation "Roswell backend."))
|
|
|
|
(in-package :roswell)
|
|
(defvar *latest-system* nil)
|
|
(defparameter *argv* nil)
|
|
(defparameter *ros-opts* nil)
|
|
(defparameter *main* nil)
|
|
(defvar *cmd* nil)
|
|
(defparameter *load* `((identity . cl:load)))
|
|
(defvar *local-project-directories* nil)
|
|
|
|
;; small tools
|
|
(defun getenv (x)
|
|
#+abcl(extensions:getenv x)
|
|
#+ccl(ccl:getenv x)
|
|
#+clasp(ext:getenv x)
|
|
#+clisp(ext:getenv x)
|
|
#+cmucl
|
|
(let ((f (ignore-errors (symbol-function (read-from-string "unix:unix-getenv")))))
|
|
(if f
|
|
(funcall f x)
|
|
(cdr (assoc x ext:*environment-list* :test #'string=))))
|
|
#+ecl(ext:getenv x)
|
|
#+mkcl(mkcl:getenv x)
|
|
#+sbcl(sb-posix:getenv x)
|
|
#+allegro(sys:getenv x)
|
|
#+lispworks(hcl:getenv x)
|
|
#-(or abcl ecl ccl clisp sbcl cmucl allegro clasp lispworks mkcl)
|
|
(when (find :asdf *features*)
|
|
(funcall (read-from-string "asdf::getenv") x)))
|
|
|
|
(defun ros-opts (&optional append)
|
|
(when append
|
|
(setf *ros-opts* (append append (ros-opts))))
|
|
(or *ros-opts*
|
|
(ignore-errors
|
|
(setf *ros-opts*
|
|
(let((*read-eval*))
|
|
(or (read-from-string (getenv "ROS_OPTS"))
|
|
'()))))))
|
|
|
|
(defun opt (param &key from-end reload)
|
|
(when reload
|
|
(setf *ros-opts* nil))
|
|
(second (assoc param
|
|
(funcall (if from-end #'reverse #'identity)
|
|
(ros-opts))
|
|
:test 'equal)))
|
|
|
|
(defun verbose ()
|
|
(and (opt "verbose")
|
|
(let ((ret (parse-integer (opt "verbose"))))
|
|
(if (zerop ret)
|
|
nil ret))))
|
|
|
|
(let (sentinel)
|
|
(defun ensure-asdf (&key (version (opt "asdf.version")))
|
|
(let ((*error-output* (if (verbose)
|
|
*error-output*
|
|
(make-broadcast-stream))))
|
|
(setf sentinel
|
|
(or sentinel
|
|
(when (and version
|
|
(and (find :asdf *features*)
|
|
(not (or (equal version
|
|
(funcall (read-from-string "asdf:asdf-version")))
|
|
(= (length version) 40)))))
|
|
(funcall 'asdf :no-download t))
|
|
(find :asdf *features*)
|
|
(ignore-errors (require "asdf")))
|
|
sentinel (not (not sentinel))))))
|
|
|
|
#+(and unix sbcl) ;; from swank
|
|
(progn
|
|
(sb-alien:define-alien-routine ("execvp" %execvp) sb-alien:int
|
|
(program sb-alien:c-string)
|
|
(argv (* sb-alien:c-string)))
|
|
(defun execvp (program args)
|
|
"Replace current executable with another one."
|
|
(let ((a-args (sb-alien:make-alien sb-alien:c-string
|
|
(+ 1 (length args)))))
|
|
(unwind-protect
|
|
(progn
|
|
(loop for index from 0 by 1
|
|
and item in (append args '(nil))
|
|
do (setf (sb-alien:deref a-args index)
|
|
item))
|
|
(when (minusp
|
|
(%execvp program a-args))
|
|
(let ((errno (sb-impl::get-errno)))
|
|
(case errno
|
|
(2 (error "No such file or directory: ~S" program))
|
|
(otherwise
|
|
(error "execvp(3) failed. (Code=~D)" errno))))))
|
|
(sb-alien:free-alien a-args)))))
|
|
|
|
(defun quit (&optional (return-code 0) &rest rest)
|
|
(let ((ret (or (and (numberp return-code) return-code) (first rest) 0)))
|
|
#+sbcl (ignore-errors (funcall (read-from-string "cl-user::exit") :code ret))
|
|
#+sbcl (ignore-errors (funcall (read-from-string "cl-user::quit") :unix-status ret))
|
|
#+clasp (core:quit ret)
|
|
#+clisp (ext:exit ret)
|
|
#+ccl (ccl:quit ret)
|
|
#+mkcl (mkcl:quit :exit-code ret)
|
|
#+cmucl (progn (finish-output) (finish-output *error-output*) (unix:unix-exit ret))
|
|
#+lispworks (lw:quit :status ret)
|
|
(ignore-errors
|
|
(progn (ensure-asdf)
|
|
(funcall (read-from-string "asdf::quit") ret)))
|
|
t))
|
|
|
|
(defun run-program (args &key output)
|
|
(if (ignore-errors #1=(read-from-string "uiop/run-program:run-program"))
|
|
(funcall #1# (format nil "~{~A~^ ~}" args)
|
|
:output output
|
|
#+(and sbcl win32) :force-shell #+(and sbcl win32) nil
|
|
:error-output :interactive)
|
|
(with-output-to-string (out)
|
|
#+sbcl(funcall (read-from-string "sb-ext:run-program")
|
|
(first args) (mapcar #'princ-to-string (rest args))
|
|
:output out)
|
|
#+clisp(if (eql output :string)
|
|
(format nil "~{~A~%~}"
|
|
(loop with i = (ext:run-shell-command (format nil "~{~A~^ ~}" args) :output :stream)
|
|
for line = (read-line i nil nil)
|
|
while line
|
|
collect line))
|
|
(ext:run-shell-command (format nil "~{~A~^ ~}" args))))))
|
|
|
|
(defun exec (args)
|
|
#+(and unix sbcl)
|
|
(execvp (first args) args)
|
|
#+(and unix ccl)
|
|
(ignore-errors
|
|
(ccl:with-string-vector (argv args) (ccl::%execvp argv)))
|
|
(quit (run-program args :output :interactive)))
|
|
|
|
(defun quicklisp (&key path (environment "QUICKLISP_HOME")
|
|
&aux (is-quicklisp-already-available
|
|
(find :quicklisp *features*)))
|
|
"Corresponds to -Q command. Finds and loads setup.lisp for quicklisp, and adds appropriate local-project paths."
|
|
|
|
(cond
|
|
(is-quicklisp-already-available
|
|
(unless (equal (opt "quicklisp")
|
|
(namestring (symbol-value (read-from-string "ql:*quicklisp-home*"))))
|
|
(setf *local-project-directories*
|
|
(copy-list
|
|
(symbol-value (read-from-string "ql:*local-project-directories*")))))
|
|
;; In this case we have to return nil
|
|
(values nil))
|
|
(t ;; If quicklisp is not loaded yet:
|
|
(let ((path (make-pathname
|
|
:name "setup"
|
|
:type "lisp"
|
|
:defaults (or path
|
|
(and environment (getenv environment))
|
|
(opt "quicklisp"))))
|
|
(local (ignore-errors
|
|
(truename
|
|
(merge-pathnames
|
|
".roswell/local-projects/"
|
|
*default-pathname-defaults*)))))
|
|
(when (probe-file path)
|
|
(cl:load path :verbose (verbose))
|
|
(unless (getenv environment)
|
|
(loop
|
|
;; *local-project-directories* defaults to a list of a single pathname,
|
|
;; which is <directory containing setup.lisp>/local-projects/ .
|
|
for path in `(;; Searches local-project/ in the current directory
|
|
,local
|
|
;; Searches relative path from env path
|
|
,(ignore-errors
|
|
(truename
|
|
(merge-pathnames
|
|
"../../../local-projects/"
|
|
(first (symbol-value (read-from-string "ql:*local-project-directories*"))))))
|
|
;; Searches local-project/ in e.g. ~/.roswell/
|
|
,(ensure-directories-exist (merge-pathnames "local-projects/" (opt "homedir"))))
|
|
for probe = (and path (or (ignore-errors (probe-file path))
|
|
#+clisp(ext:probe-directory path)))
|
|
when probe
|
|
do (push path *local-project-directories*)
|
|
until probe))
|
|
t)))))
|
|
|
|
(defvar *included-names* '("init"))
|
|
(defparameter *include-path* (or #.*compile-file-pathname* *load-pathname*))
|
|
|
|
(defun include (names &optional provide)
|
|
"CL:LOAD the files in the same directory as this init.lisp.
|
|
The same file never loaded twice.
|
|
NAMES is a list of lisp file name strings without extension.
|
|
PROVIDE is a string used for grouping/naming a set of included files.
|
|
PROVIDE is just a marker and does not correspond to a file.
|
|
As a hacky side effect, files with the same name as PROVIDE is not loaded.
|
|
"
|
|
(loop
|
|
for name in `(,provide
|
|
,@(if (listp names)
|
|
names
|
|
(list names)))
|
|
for path = (make-pathname
|
|
:defaults *include-path*
|
|
:name name :type "lisp")
|
|
do (when (and name (not (member name *included-names* :test 'string=)))
|
|
(push name *included-names*)
|
|
(when (and (probe-file path)
|
|
(not (equal provide name)))
|
|
(funcall 'load path)))))
|
|
|
|
(defun swank (&rest params)
|
|
(include "util-swank")
|
|
(apply (read-from-string "roswell.util.swank:swank") params))
|
|
|
|
(defun shebang-reader (stream sub-character infix-parameter)
|
|
(declare (ignore sub-character infix-parameter))
|
|
(loop for x = (read-char stream nil nil)
|
|
until (or (not x) (eq x #\newline)))
|
|
(values))
|
|
|
|
(defun ignore-shebang ()
|
|
(set-dispatch-macro-character #\# #\! #'shebang-reader))
|
|
|
|
(defun roswell (args &optional (output :string) trim)
|
|
(let* ((a0 (funcall (or #+win32(lambda (x) (substitute #\\ #\/ x)) #'identity)
|
|
(if (zerop (length (opt "wargv0")))
|
|
(opt "argv0")
|
|
(opt "wargv0"))))
|
|
(ret (run-program (cons a0 args) :output output)))
|
|
(if trim
|
|
(remove #\Newline (remove #\Return ret))
|
|
ret)))
|
|
|
|
(defvar *downloaded-asdf-loaded* nil)
|
|
(defun asdf (&key no-download)
|
|
(setf *downloaded-asdf-loaded*
|
|
(or *downloaded-asdf-loaded*
|
|
(let* ((version-installed (opt "asdf.version" :reload t))
|
|
(version (or version-installed
|
|
(unless no-download
|
|
(roswell '("install" "asdf"))
|
|
(roswell '("config" "show" "asdf.version") :string t))))
|
|
(path (merge-pathnames (format nil "lisp/asdf/~A/asdf.lisp" version) (opt "homedir"))))
|
|
(when (equal version "NIL")
|
|
(error "asdf download error?"))
|
|
(let ((fasl (make-pathname :defaults path :type (substitute #\- #\/ (substitute #\_ #\. (opt "impl"))))))
|
|
(when (verbose)
|
|
(format *error-output* "asdf fasl:~A~%" fasl))
|
|
(unless (probe-file fasl)
|
|
(roswell `(,@(when (verbose) '("-v")) "-L" ,(opt "impl") "compile-file" "-asdf" ,version) :string t))
|
|
(setf path (or (probe-file fasl) path)))
|
|
(when (probe-file path)
|
|
(ignore-errors
|
|
(locally
|
|
(declare #+sbcl(sb-ext:muffle-conditions sb-kernel:redefinition-warning))
|
|
(handler-bind
|
|
(#+sbcl(sb-kernel:redefinition-warning #'muffle-warning))
|
|
(funcall 'load path)))))))))
|
|
|
|
(let ((symbol (ignore-errors (read-from-string "asdf::*user-cache*")))
|
|
(impl (substitute #\- #\/ (opt "impl"))))
|
|
(when (opt "asdf.version")
|
|
(setq impl (format nil "~A-~A" impl (opt "asdf.version"))))
|
|
(when (and symbol (boundp symbol))
|
|
(cond ((listp (symbol-value symbol))
|
|
(set symbol (append (symbol-value symbol) (list impl))))
|
|
((pathnamep (symbol-value symbol))
|
|
(set symbol (merge-pathnames (format nil "~A/" impl) (symbol-value symbol))))
|
|
(t (cl:print "tbd.....")))))
|
|
|
|
(defparameter *version-cache* nil)
|
|
(defun version (&optional opt)
|
|
(if opt
|
|
(roswell `("roswell-internal-use" "version" ,(string-downcase (princ-to-string opt))) :string t)
|
|
(or *version-cache*
|
|
(setf *version-cache* (roswell '("roswell-internal-use" "version") :string t)))))
|
|
|
|
(defun implementation-version ()
|
|
"cut off after third? dot from lisp-implementation-version"
|
|
(let ((version (lisp-implementation-version)))
|
|
(loop with j = 0
|
|
for i from 0 to (1- (length version))
|
|
when (eql (aref version i) #\.)
|
|
do (incf j)
|
|
(when (= j 3)
|
|
(return-from implementation-version (subseq version 0 i))))
|
|
version))
|
|
|
|
|
|
#+sbcl
|
|
(when (ignore-errors (string-equal (opt "impl") "sbcl-bin" :end1 8))
|
|
(flet ((source (version)
|
|
(let ((path (merge-pathnames (format nil "src/sbcl-~A/" version) (opt "homedir"))))
|
|
(when (probe-file path)
|
|
(sb-ext:set-sbcl-source-location path)))))
|
|
(or (source (implementation-version))
|
|
(source "git"))))
|
|
|
|
(defun source-registry (arg &rest rest)
|
|
(declare (ignorable rest))
|
|
(let ((dir (format nil "~{~A~^:~}"
|
|
(loop for i = arg then (subseq i (1+ pos))
|
|
for pos = (position #\: i)
|
|
for part = (if pos (subseq i 0 pos) i)
|
|
when (and (not (zerop (length part)))
|
|
(probe-file part))
|
|
collect (namestring (probe-file part))
|
|
while pos))))
|
|
(if (zerop (length dir))
|
|
(warn "Source-registry ~S is invalid. Ignored." arg)
|
|
(funcall (read-from-string "asdf:initialize-source-registry") dir))))
|
|
|
|
(defun system (args &rest rest)
|
|
(declare (ignorable rest))
|
|
(ensure-asdf)
|
|
(unless (find :asdf *features*)
|
|
(error "Can't find asdf to load system"))
|
|
(loop for ar = args then (subseq ar (1+ p))
|
|
for p = (position #\, ar)
|
|
for arg = (if p (subseq ar 0 p) ar)
|
|
do (setf *latest-system* arg)
|
|
(if (find :quicklisp *features*)
|
|
(funcall (read-from-string "ql:quickload") arg :silent (not (verbose)))
|
|
(funcall (read-from-string "asdf:operate") (read-from-string "asdf:load-op") arg))
|
|
while p))
|
|
|
|
(setf (fdefinition 'load-system)
|
|
#'system)
|
|
|
|
(defun package (arg &rest rest)
|
|
(declare (ignorable rest))
|
|
(setq *package* (find-package (read-from-string (format nil "#:~A" arg)))))
|
|
|
|
(defun system-package (arg &rest rest)
|
|
(declare (ignorable rest))
|
|
(apply #'system arg rest)
|
|
(apply #'package arg rest))
|
|
|
|
(defun eval (arg &rest rest)
|
|
(declare (ignorable rest))
|
|
(loop with start = 0
|
|
with end = (gensym)
|
|
with exp
|
|
do (multiple-value-setq (exp start)
|
|
(read-from-string arg nil end :start start))
|
|
until (eql exp end)
|
|
do (cl:eval exp)))
|
|
|
|
(defun restart (arg &rest rest)
|
|
(declare (ignorable rest))
|
|
(funcall (read-from-string arg)))
|
|
|
|
(defun entry (arg &rest rest)
|
|
(declare (ignorable rest))
|
|
(let* ((sym (read-from-string arg))
|
|
(*package* (symbol-package sym)))
|
|
(quit (apply sym *argv*))))
|
|
|
|
(setf (fdefinition 'init) #'eval)
|
|
|
|
(defun print (arg &rest rest)
|
|
(declare (ignorable rest))
|
|
(cl:print (cl:eval (read-from-string arg))))
|
|
|
|
(defun write (arg &rest rest)
|
|
(declare (ignorable rest))
|
|
(cl:write (cl:eval (read-from-string arg))))
|
|
|
|
(defun hook (&rest argv)
|
|
(when *latest-system*
|
|
(let ((entry-point
|
|
(let (*read-eval*)
|
|
(ignore-errors (read-from-string (funcall (read-from-string "asdf/system:component-entry-point")
|
|
(funcall (read-from-string "asdf:find-system")
|
|
*latest-system*)))))))
|
|
(when (verbose)
|
|
(format *error-output* "hook:~S ~S ~S~%" *latest-system* entry-point argv)
|
|
(finish-output))
|
|
(when entry-point
|
|
(funcall entry-point argv)))))
|
|
|
|
(defun script (arg &rest rest)
|
|
"load and evaluate the script"
|
|
(setf *argv* rest)
|
|
(flet ((body (in)
|
|
(let ((line(read-line in))
|
|
*ros-opts*)
|
|
(push :ros.script *features*)
|
|
(locally
|
|
(declare #+sbcl(sb-ext:muffle-conditions sb-kernel:redefinition-warning))
|
|
(handler-bind
|
|
(#+sbcl(sb-kernel:redefinition-warning #'muffle-warning))
|
|
(funcall #+(or sbcl clisp) 'cl:load
|
|
#-(or sbcl clisp)
|
|
(progn
|
|
(ensure-asdf)
|
|
(read-from-string "asdf::eval-input"))
|
|
(make-concatenated-stream
|
|
(make-string-input-stream
|
|
(format nil "(cl:setf cl:*load-pathname* ~S cl:*load-truename* (ignore-errors (truename cl:*load-pathname*)))~A"
|
|
(ignore-errors (merge-pathnames (make-pathname :defaults arg)))
|
|
(if (equal (subseq line 0 (min (length line) 2)) "#!")
|
|
"" line)))
|
|
in
|
|
(make-string-input-stream
|
|
(if (eql *cmd* :script)
|
|
"(roswell:quit (cl:apply 'main roswell:*argv*))"
|
|
"(setf roswell:*main* 'main)"))))))
|
|
(setf *features* (remove :ros.script *features*)))))
|
|
(cond ((streamp arg) (body arg))
|
|
((and arg (probe-file arg))
|
|
(with-open-file (in arg)
|
|
(body in)))
|
|
(*latest-system* (apply 'hook (cons arg *argv*)))
|
|
(t (format t "script ~S does not exist~%" arg)
|
|
(quit 1)))))
|
|
|
|
(defun stdin (arg &rest rest)
|
|
(declare (ignorable arg rest))
|
|
(apply #'script :script *standard-input* rest))
|
|
|
|
(defun load (file &rest rest)
|
|
(let ((function (rest (find-if (lambda (x) (funcall (first x) file)) *load*))))
|
|
(when (verbose)
|
|
(format *error-output* "~A ~A~%" function file)
|
|
(finish-output))
|
|
(apply function file rest)))
|
|
|
|
(defun run (list)
|
|
"The true internal entry invoked by the C binary. All roswell commands are dispatched from this function"
|
|
(loop for elt in list
|
|
for *cmd* = (first elt)
|
|
do (apply (intern (string (first elt)) (find-package :ros)) (rest elt))))
|
|
|
|
(when (opt "roswellenv")
|
|
(pushnew :roswellenv *features*)
|
|
(pushnew (read-from-string (format nil ":roswell.env.~A" (opt "roswellenv"))) *features*))
|
|
|
|
#+clisp
|
|
(unless (find :ros.init *features*)
|
|
(push :ros.init *features*)
|
|
(loop
|
|
with *package* = (find-package :cl-user)
|
|
for i in ext:*args*
|
|
do (cl:eval (cl:read-from-string i))))
|
|
|
|
(unless (find :ros.init *features*)
|
|
(push :ros.init *features*))
|