Confused about ``ql:quickload`` and executable scripts in SBCL - common-lisp

I've been trying to use Quicklisp packages in an executable script of mine. A (trivial) working example is:
#!/usr/bin/sbcl --script
(eval-when (:compile-toplevel :load-toplevel :execute)
(ql:quickload "lisp-unit")) ;as explained by another question
(defpackage :test
(:use :cl :lisp-unit))
(format t "This is a test.")
After chmoding the file with this code in it (called test.lisp), I tried to execute it. However, I then got the following error message:
Unhandled SB-C::INPUT-ERROR-IN-LOAD in thread #<SB-THREAD:THREAD
"main thread" RUNNING
{1002C16923}>:
READ error during LOAD:
Package QL does not exist.
Line: 4, Column: 15, File-Position: 95
Stream: #<SB-SYS:FD-STREAM
for "file /home/koz/Documents/Programming/CL/trees/test.lisp"
{1002C19A93}>
Backtrace for: #<SB-THREAD:THREAD "main thread" RUNNING {1002C16923}>
0: ((LAMBDA NIL :IN SB-DEBUG::FUNCALL-WITH-DEBUG-IO-SYNTAX))
1: (SB-IMPL::CALL-WITH-SANE-IO-SYNTAX #<CLOSURE (LAMBDA NIL :IN SB-DEBUG::FUNCALL-WITH-DEBUG-IO-SYNTAX) {1002C2498B}>)
2: (SB-IMPL::%WITH-STANDARD-IO-SYNTAX #<CLOSURE (LAMBDA NIL :IN SB-DEBUG::FUNCALL-WITH-DEBUG-IO-SYNTAX) {1002C2495B}>)
3: (PRINT-BACKTRACE :STREAM #<SB-SYS:FD-STREAM for "standard error" {1002C14CF3}> :START 0 :FROM :INTERRUPTED-FRAME :COUNT NIL :PRINT-THREAD T :PRINT-FRAME-SOURCE NIL :METHOD-FRAME-STYLE NIL)
4: (SB-DEBUG::DEBUGGER-DISABLED-HOOK #<SB-C::INPUT-ERROR-IN-LOAD {1002C1CB93}> #<unavailable argument>)
5: (SB-DEBUG::RUN-HOOK *INVOKE-DEBUGGER-HOOK* #<SB-C::INPUT-ERROR-IN-LOAD {1002C1CB93}>)
6: (INVOKE-DEBUGGER #<SB-C::INPUT-ERROR-IN-LOAD {1002C1CB93}>)
7: (ERROR #<SB-C::INPUT-ERROR-IN-LOAD {1002C1CB93}>)
8: (SB-C:COMPILER-ERROR SB-C::INPUT-ERROR-IN-LOAD :CONDITION #<SB-INT:SIMPLE-READER-PACKAGE-ERROR "Package ~A does not exist." {1002C1CAA3}> :STREAM #<SB-SYS:FD-STREAM for "file /home/koz/Documents/Programming/CL/trees/test.lisp" {1002C19A93}>)
9: (SB-C::READ-FOR-COMPILE-FILE #<SB-SYS:FD-STREAM for "file /home/koz/Documents/Programming/CL/trees/test.lisp" {1002C19A93}> 25 SB-C::INPUT-ERROR-IN-LOAD)
10: (SB-INT:LOAD-AS-SOURCE #<SB-SYS:FD-STREAM for "file /home/koz/Documents/Programming/CL/trees/test.lisp" {1002C19A93}> :VERBOSE NIL :PRINT NIL :CONTEXT "loading")
11: ((FLET SB-FASL::LOAD-STREAM :IN LOAD) #<SB-SYS:FD-STREAM for "file /home/koz/Documents/Programming/CL/trees/test.lisp" {1002C19A93}> NIL)
12: (LOAD #<SB-SYS:FD-STREAM for "file /home/koz/Documents/Programming/CL/trees/test.lisp" {1002C19A93}> :VERBOSE NIL :PRINT NIL :IF-DOES-NOT-EXIST T :EXTERNAL-FORMAT :DEFAULT)
13: ((FLET SB-IMPL::LOAD-SCRIPT :IN SB-IMPL::PROCESS-SCRIPT) #<SB-SYS:FD-STREAM for "file /home/koz/Documents/Programming/CL/trees/test.lisp" {1002C19A93}>)
14: ((FLET #:WITHOUT-INTERRUPTS-BODY-140 :IN SB-IMPL::PROCESS-SCRIPT))
15: (SB-IMPL::PROCESS-SCRIPT "./test.lisp")
16: (SB-IMPL::TOPLEVEL-INIT)
17: ((FLET #:WITHOUT-INTERRUPTS-BODY-89 :IN SAVE-LISP-AND-DIE))
18: ((LABELS SB-IMPL::RESTART-LISP :IN SAVE-LISP-AND-DIE))
unhandled condition in --disable-debugger mode, quitting
I'm unsure what's going on here - if I try to do something similar from the toplevel (i.e., start SBCL, load lisp-unit using ql:quickload, etc), I don't get anything like this.

In SBCL, --script does not load your init files. If you want to use Quicklisp, you have to arrange to load it. That usually means something like (load "~/quicklisp/setup.lisp") before using anything related to Quicklisp.
I don't think SBCL and Quicklisp are a great fit for scripting tasks. When I write scripts, I don't normally expect them to fetch stuff from the Internet like ql:quickload does. Quicklisp is also pretty verbose. SBCL's FASL loading is pretty slow. Taken all together it does not make for a very good scripting experience.
I very strongly prefer to load everything I need to use into a Common Lisp session and then call functions to get stuff done. When I can't use that (like for running stuff from cron or from a Makefile), I often use buildapp to make executables.

You should consider using cl-launch, which is made for this purpose. You can enable Quicklisp by passing the option -Q. Your example code would be something like this:
#!/usr/bin/cl -Q -p lisp-unit
(eval-when (:compile-toplevel :load-toplevel :execute)
(ql:quickload "lisp-unit")) ;as explained by another question
(defpackage :test (:use :cl :lisp-unit))
(defun main (argv) "Code goes here")

Related

ASDF throws system-out-of-date condition

I'm using ASDF load cl-ppcre in a script file. The issue is (progn (require :asdf) (require :cl-ppcre)) is perfectly fine in a top level, but if the same codes wrapped in a handler-case, a system-out-of-date condition will be caught by handler-case and the whole evaluation stops, and required packages won't be loaded. I just confirm the same issue also happens in a REPL. No matter what library I try to load, the same issue just happen in a handler-case. The following is a complete session:
; SLIME 2.27
CL-USER> (require :asdf)
NIL
CL-USER> (find-package :cl-ppcre)
NIL
CL-USER> (handler-case (require :cl-ppcre) (t (c) (format t "~a: ~a~%" (type-of c) c)))
SYSTEM-OUT-OF-DATE: system cl-ppcre is out of date
NIL
CL-USER> (find-package :cl-ppcre)
NIL
CL-USER> (require :cl-ppcre)
NIL
CL-USER> (find-package :cl-ppcre)
#<PACKAGE "CL-PPCRE">
CL-USER> (handler-case (require :cl-ppcre) (t (c) (format t "~a: ~a~%" (type-of c) c)))
NIL
CL-USER> (list (lisp-implementation-type) (lisp-implementation-version))
("SBCL" "2.2.4")
CL-USER> (asdf:asdf-version)
"3.3.1"
CL-USER> (directory "/home/pxie/common-lisp/*" :resolve-symlinks nil)
(#P"/home/pxie/common-lisp/alexandria/" #P"/home/pxie/common-lisp/cl-ppcre/")
According to ASDF manual, I put my libraries in ~/common-lisp directory, and the libraries already compiled and saved in the ~/.cache/common-lisp directory.
Any insight of what is going on in ASDF?
If you ask handler-case to catch any condition that is signalled, as you have done, it will do that, whether or not the condition is an error. You almost never want to do that.
In particular if you look at plan.lisp in the ASDF sources you will find
;; NB: This is not an error, not a warning, but a normal expected condition,
;; to be to signaled by FIND-SYSTEM when it detects an out-of-date system,
;; *before* it tries to replace it with a new definition.
(define-condition system-out-of-date (condition)
...)

Exit without losing cached output

I am trying to add to a program I am writing, a feature whereby everything printed to the console, also gets added to a log file. This much can be done with broadcast streams. The problem is that the program may also need to abruptly exit from within a leaf function, and when I do this, the log file does not get created. This is what I have so far:
(catch 'quit
(with-open-file (log-stream "log.txt"
:direction :output
:if-exists :supersede
:if-does-not-exist :create)
(let ((*standard-output*
(make-broadcast-stream *standard-output* log-stream)))
(format t "abc~%")
(throw 'quit nil))))
When I run the above code (SBCL 1.4.2, Windows 7), the file log.txt does not get created. The same is true if I replace (throw 'quit nil) with (quit). However, if I remove that line altogether and just let the program exit by falling off the end of the file, the log file does get correctly created, which suggests it's a caching issue.
Is that the correct diagnosis? If so, is there a way to tell the compiler not to cache that file, or to exit with rather than without writing cached data?
This is the behaviour described in the standard for WITH-OPEN-FILE:
If a new output file is being
written, and control leaves abnormally, the file is aborted and the file system is left,
so far as possible, as if the file had never been opened.
The following explicitly closes the file:
(catch 'quit
(with-open-file (log-stream "/tmp/log.txt"
:direction :output
:if-exists :supersede
:if-does-not-exist :create)
(let ((*standard-output* (make-broadcast-stream *standard-output* log-stream)))
(unwind-protect (progn
(format t "abc~%")
(throw 'quit nil))
(finish-output)
(close log-stream :abort nil)))))
The :abort nil value is the default one, it is made explicit here for the sake of the answer.

Reading from device files

Is there a specific approach to reading device files in CL? I try the following code in SBCL but it does not appear to work:
(defparameter modem #p"/dev/ttyUSB2")
(defun read-modem()
(with-open-file (fd modem :direction :io :if-exists :append)
(loop while (peek-char nil fd) do
(format t "~A" (read-line fd))
(finish-output fd))))
I know there's output because cat /dev/ttyUSB2 shows it.
I guess, you need to read from them as from binary files. For instance, here's what I read from /dev/urandom:
> (with-open-file (fd "/dev/urandom" :direction :io :if-exists :append
:element-type 'unsigned-byte)
(read-byte fd))
161
I think your problem is with buffering.
I don't think you can turn it off in CL open, so I am afraid you have to use sb-unix:unix-open and sb-unix:unix-read.

how to guarantee a clean exit from sbcl

I am calling my common-lisp program via a shellscript which calls sbcl with the necessary parameters and I have to guarantee that anyhow the actual program finishes the call will end clean with some/none output.
My current solution looks like this:
sbcl --eval "(unwind-protect
(handler-case
(progn
(declaim #+sbcl(sb-ext:muffle-conditions style-warning))
(let ((*standard-output* (make-broadcast-stream)))
(ql:quickload \"module\"))
(eval (read-from-string \"(package:start)\"))) ;this starts the program
(error (err)
(FORMAT t \"Something went really wrong:~a~%\" err)
(sb-ext:exit)))
(sb-ext:exit))"
But in the following two szenarios it wont work:
sbcl --eval "(unwind-protect
(handler-case
(progn
(define-condition bad () ())
(error 'bad))
(error (err)
(FORMAT t \"Something went really wrong:~a~%\" err)
(sb-ext:exit)))
(sb-ext:exit))"
sbcl --eval "(unwind-protect
(handler-case
(progn
(labels ((rek () (rek)))
(rek)))
(error (err)
(FORMAT t \"Something went really wrong:~a~%\" err)
(sb-ext:exit)))
(sb-ext:exit))"
I am now wondering if there is another solution which will catch ANY possible outcome of a called program and will ensure that the sbcl call will exit clean?
For the first scenario a general catch which does not specify what to catch, would probably do the deal. The second scenario has to be able to cope with bugs/errors which would result in the low-level-debuger being called.
The --non-interactive switch will ensure that SBCL never enters the debugger or the REPL. It's similar to passing --disable-debugger and using --eval "(sb-ext:quit)". You can also customize sb-ext:*invoke-debugger-hook* if you don't want it to print a backtrace in the event of an error.

How to modify this code to support CCL?

It seems there is NO ANSI standard way to execute an external program and get its output as the following SBCL special code does:
(defmacro with-input-from-program ((stream program program-args environment)
&body body)
"Creates an new process of the specified by PROGRAM using
PROGRAM-ARGS as a list of the arguments to the program. Binds the
stream variable to an input stream from which the output of the
process can be read and executes body as an implicit progn."
#+sbcl
(let ((process (gensym)))
`(let ((,process (sb-ext::run-program ,program
,program-args
:output :stream
:environment ,environment
:wait nil)))
(when ,process
(unwind-protect
(let ((,stream (sb-ext:process-output ,process)))
,#body)
(sb-ext:process-wait ,process)
(sb-ext:process-close ,process))))))
The following CCL code reports "ERROR: value # is not of the expected type (AND CCL::BINARY-STREAM INPUT-STREAM)"
#+clozure
(let ((process (gensym)))
`(let ((,process (ccl:run-program "/bin/sh" (list "-c" (namestring ,program))
:input nil :output :stream :error :stream
:wait nil)))
(when ,process
(unwind-protect
(let ((,stream (ccl::external-process-output-stream ,process)))
,#body)
;(ccl:process-wait (ccl:process-whostate ,process) nil)
(close (ccl::external-process-output-stream ,process))
(close (ccl::external-process-error-stream ,process))))))
I know little CCL. I want to know how i can modify this code to support CCL ?
Any suggestion is appreciated !
Apparently trivial-shell:shell-command doesn't allow exactly what you want (it executes the external command synchronously and returns the whole output).
You could look into CCL's run-program. See:
run-program;
Does there exist standard way to run external program in Common Lisp? (this is a question that is similar to your question);
external-program (suggested in one of the answers in the question above) is supported by Quicklisp and it seems to have better support for executing external programs.
You should use trivial-shell.
Trivial shell is a simple platform independent interface to the underlying Operating System.

Resources