#!/bin/sh #|-*- mode:lisp -*-|# #|Dump image for faster startup or Make Executable exec ros -- $0 "$@" |# (eval-when (:compile-toplevel :load-toplevel :execute) (roswell:include "util-dump") (unless (find-package :uiop) #-quicklisp(require :uiop) #+quicklisp(ql:quickload "uiop" :silent t))) (defpackage :ros.script.dump (:use :cl :roswell.util :roswell.util.dump)) (in-package :ros.script.dump) (defun output (func params &optional force) (flet ((%dump (path &optional mode) (if (and (not force) (probe-file path)) ;; idea?? ;; (y-or-n-p "Output file exists. Overwrite? [Y,n]") (if (eql mode :normal) (format *error-output* "dump ~s already exists~%" (pathname-name path)) (format *error-output* "file ~s already exists~%" path)) (funcall func :output path)))) (cond ((equal params nil) (format *error-output* "Usage: ~A [OPTIONS] dump output [-f] [-o OUTPUT] NAME~%" (opt "argv0"))) ((and (equal (first params) "-f")) (output func (rest params) t)) ((and (equal (first params) "-o") (second params)) (%dump (second params))) ((and (equal (first params) "-o")) (format *error-output* "Missing the pathname for an image.~%")) (t (let ((path (merge-pathnames (format nil "~A.~A" (first params) (core-extention)) (dump-dir (if (equal (first params) "roswell") "roswell" (opt "roswellenv")))))) (%dump (ensure-directories-exist path) :normal)))))) (defun executable (func params) (if (not params) (format *error-output* "Usage: ros dump executable help~%") (let ((script (pathname (first params)))) (unless (probe-file script) (warn "The script ~a does not exist, aborting." script) (ros:quit 1)) (let* ((cmds (let ((*read-eval*)) (read-from-string (format nil "(~A)" (opt "restart"))))) (output (or (when (equal (second params) "-o") (or (third params) (warn "Missing argument to -o OUTPUT, falling back to default behavior"))) (if (string-equal (pathname-type script) "ros") (make-pathname :type #-win32 nil #+win32 "exe" :defaults script) script)))) (when (string-equal ; odd people may use uppercase extension... (pathname-type script) "ros") ;; Note: Why CMDS needs to be updated? ;; If you use -l,-r or other options, information on the ;; corresponding startup commands are stored in CMDS. ;; we just augment it with MAIN function information, because ;; we can for ros file. ;; Fixme: What is the use case when SCRIPT is not a roswell script? (let ((*package* (find-package :cl-user)) roswell:*cmd*) ;; loading script (roswell:script script)) ; <--- !!!WARNING!!! Side-effect on roswell:*main* (let ((main-list (let ((*package* (find-package :keyword))) `((:entry ,(format nil "~S" roswell:*main*)))))) (setf cmds (if (first cmds) (append cmds main-list) main-list)))) (unless cmds ;; Fixme: when STRING-EQUAL is not satisfied, cmds is something like ;; ((:entry "COMMON-LISP:NIL")), which eventually fails. ;; it is better to capture this. (warn "dumping an executable without specifing the initial behaviour.")) (funcall func :executable cmds output script))))) (defvar *subcmds* '(output executable)) (defun parse-options (args q) (let ((car (first args))) (cond ((find car '("--enable-compression" "-c") :test 'equal) (parse-options (rest args) (cons `(set *compression* t) q))) ((equal car "--disable-compression") (parse-options (rest args) (cons `(set *compression* nil) q))) ((equal car "--remove-docstrings") (parse-options (rest args) (cons (read-from-string "roswell.util.dump:remove-docstrings") q))) ((equal car "--delete-package") (parse-options (cddr args) (cons `(makunbound-symbols-and-delete-package ,(string-upcase (second args))) q))) ((equal car "--delete-all-packages") (parse-options (rest args) (cons 'delete-all-packages q))) ((equal car "--delete-packages-except") (parse-options (cddr args) `(delete-all-packages ,@q (pushnew ,(string-upcase (second args)) *package-blacklist* :test #'string=)))) ((equal car "--destroy-packages-sbcl") (parse-options (rest args) `(#+sbcl ,(read-from-string "roswell.dump.sbcl:destroy-packages-sbcl") ,@q))) ((equal car "--bundle-shared-object") (parse-options (rest args) (cons `(set *bundle-shared* t) q))) ((equal car "--purify") (parse-options (rest args) (cons `(set *purify* t) q))) ((equal car "--impurify") (parse-options (rest args) (cons `(set *impurify* t) q))) ((equal car "--no-purify") (parse-options (rest args) (cons `(set *purify* nil) q))) ((equal car "--no-impurify") (parse-options (rest args) (cons `(set *impurify* nil) q))) ((equal car "--delete-debug-info") (parse-options (rest args) `(#+sbcl ,(read-from-string "roswell.dump.sbcl:delete-debug-info") ,@q))) ((equal car "--delete-macro-definitions") (parse-options (rest args) (cons 'delete-macro-definitions q))) ((equal car "--delete-compiler-macro-definitions") (parse-options (rest args) (cons 'delete-compiler-macro-definitions q))) ((equal car "--delete-compiler-information-sbcl") (parse-options (rest args) `(#+sbcl ,(read-from-string "roswell.dump.sbcl:delete-compiler-information-sbcl") ,@q))) (t (values args q))))) (defun main (&rest r) (let ((module (module "dump" (or #+ccl "ccl" #+cmucl "cmucl" (remove #\Space (string-downcase (lisp-implementation-type))))))) (cond ((and module r) (multiple-value-bind (args *predump*) (parse-options r nil) (let* ((mode (first args)) (subcmd (find mode (funcall module :query *subcmds*) :test 'equal :key 'string-downcase)) (args (rest args)) (*features* (cons (let (*read-eval*) (read-from-string (format nil ":roswell.dump.~A" mode))) *features*))) (if subcmd (funcall subcmd module args) (format *error-output* "'~A' is not a valid command for '~A' subcommand for ~A ~%" mode (pathname-name *load-pathname*) (lisp-implementation-type)))))) (module (format *error-output* "Possible subcmd~%~%~{~(~A~)~%~}" (funcall module :query *subcmds*))) (t (format *error-output* "'~A' is not supported for ~A ~%" (pathname-name *load-pathname*) (lisp-implementation-type)))))) ;;; vim: set ft=lisp lisp: