четверг, 7 июня 2012 г.

Как запускать программы на Common Lisp как консольные скрипты

Допустим, у вас есть классная маленькая программка на Common Lisp под названием filter. Она занимает всего один файл исходного кода под названием filter.lisp примерно в 200 строк длиной. В этом файле вот такой заголовок:


(defpackage :localhost.me.filter
  (:use :common-lisp)
  (:export :run))
(in-package :localhost.me.filter)

И дальше собственно сама программа. Допустим также, что вы пользуетесь своей программой часто. Загвоздка в том, что для того, чтобы её запустить, вам необходимо сделать слишком много действий:


$ cd $PROGRAMDIR
$ sbcl
CL-USER> (load "filter.lisp")
CL-USER> (:localhost.me.filter:run)

Рассмотрим два решения, одно простое, но не всегда поможет, другое посложнее и поможет уже в большем числе случаев.

Решение первое

SBCL умеет запускать программы на CL в режиме скриптов командной строки, для этого используется специальный флаг --script. Более того, благодаря этому можно написать лисп-программу, добавить к ней типичный юниксовый шебанг, вызывающий SBCL с этим флагом, и она будет работать как любой другой шелл-скрипт (за исключением того, что рантайм SBCL весит 50MB бгг). Так что напишем этот скрипт:


#!/usr/bin/sbcl --script
(load "filter.lisp")
(:localhost.me.filter:run)

Теперь положим этот скрипт в ту же директорию, что и filter.lisp, в файл с именем run и можно будет запускать нашу программу так:


$ ./run

Что и требовалось.

Решение второе

Однако, у флага --script есть одна особенность: SBCL не загружает никакие пользовательские скрипты инициализации при старте, так что скрипт будет выполняться в 100% дефолтном рантайме. Это плохо, если, допустим, вы используете Quicklisp и у вас в filter.lisp есть такой вызов:


...
(ql:quickload :CL-FAD)
(use-package :CL-FAD)
...

Ну или вызов любой другой библиотеки, неважно. Если запустить такой скрипт первым способом, то SBCL будет грязно ругаться по поводу того, что он не знает, что такое ql:quickload.

Для решения этой проблемы мы смухлюем, подменив бинарник SBCL таким бинарником, у которого все нужные библиотеки уже загружены. В SBCL есть функция save-lisp-and-die, которая выгружает текущее состояние рантайма в файл с указанным именем. Дальше этот файл можно использовать как обычный бинарник SBCL, в том числе, и для вызовов с использованием --script. Поэтому если запустить SBCL и сразу выгрузить его в файл, то полученный бинарник будет содержать всё, что SBCL подключил при загрузке, основываясь на пользовательских конфигах.


(save-lisp-and-die "/абсолютный/путь/до/файла")

Теперь в скрипте run, который написан по первому варианту, заменяем путь до системного SBCL путём до выгруженного бинарника, и скрипт начнёт работать как должен.