Если б я знал толком, я рассказал бы чётко и ясно. Был бы туториал. А я в первый раз разбираюсь, поэтому будет мноого-мноого букаф. Размышления, соображения, предложения для обсуждения... В конце есть пара примеров, но сначала - большое словоблудное вступление.
Если говорить о скриптовании, то чего мы хотим:
Х1) исполняемый файл, который при необходимости может рабтать с аргументами командной строки и стандартным вводом/выводом, может возвращать код выхода;
Х2) быстрый запуск скрипта и, как написано в Википедии, маленький отпечаток памяти (не знаю, что это такое). При этом мы в принципе непротив "курирующей" программы-интерпретатора и малого размера файла;
Х3) работа с системой (файлы, переменные, внешние программы...);
Х4) переносимость - между ОС с минимальными требованиями к администрированию (желательно обходиться установкой пакетов из репов), а также переносимость между лисперами и нелисперами;
Х5) чтобы Х1-Х4 было просто и удобно.
Что мы при этом имеем:
И1) реализации с быстрым запуском и малым размером отпечатка памяти - SBCL, CLISP;
И2) возможность использования кода в виде чтения из файла, сохранения образа лиспа или создания самодостаточного приложения;
И3) ASDF 3 с UIOP и quicklisp;
И4) cl-launch.
Подумаем, что из Х1-Х5 можно осуществить И1-И4 (заодно разберёмся, что такое И2-И4). Начнём с самого простого.
Х3) В моде UIOP. По-другому не надо (в частности, cl-fad отправляется на заслуженный отдых). Потому что ASDF 3 - это не только
легкоусвояемое мясо менеджер исходников (ASDF 2 был заново переписан для избавления от фундаментальных багов), но и UIOP - большая система утилит в том числе для работы с ОС, работающая с разными реализациями лиспа и операционными системами.
Подвох: ASDF
три. Проверить его наличие можно в *features*, и свежие дистрибутивы должны его содержать. И если так, то всё нормально. А если у вас Debian Wheezy, будем грызть кактус вместе. Попозже.
Ещё подвох: документация к UIOP в основном существует в виде докстрингов... Но зато там очень много докстрингов.
Х2) Согласно И1, CLISP и SBCL вполне годятся. Поэтому я буду рассматривать именно их, когда речь о вещах, зависящих от реализации.
Подвох: при загрузке библиотек время запуска проседает на порядок.
Естественное решение - не грузить каждый раз библиотеки, а просто не выгружать их. Это стандартный и классический приём: сохранить образ лиспа и грузить его на следующем старте. Это быстро.
Вообще, образы довольно большие (мой дебиан: у SBCL - мегабайт на тридцать, у CLISPа поменьше, около шести), но сейчас мы говорим о создании одного образа для скриптования (и вообще для жизни). Насчёт свободного места в образе беспокоиться не надо, его хватит. Это под быстрый запуск лисп (исходно) не затачивался, а под серьёзную работу с большими системами - очень даже.
Пример создания образа для CLISPа:
Код:
#!/usr/bin/clisp --quiet -norc
#|
Dump a CLISP memory image with ASDF3, cl-launch, quicklisp and selected
librares. All the locations are specified as parameters.
The script defines the BUILD-MAIN-CLISP-IMAGE package and a dummy ASDF system "build-main-clisp-image", the only purpose of which is to list and load dependencies.
For scripting purposes the package SCRIPT is defined. It inherits symbols from CL-USER and a few selected packages.
|#
(in-package #:cl-user)
(defpackage #:build-main-clisp-image
(:use #:cl))
(in-package #:build-main-clisp-image)
(defparameter *asdf-path* (merge-pathnames #p".local/share/common-lisp/asdf/asdf.lisp"
(user-homedir-pathname)))
(defparameter *launcher-path*
(merge-pathnames #p".local/share/common-lisp/cl-launch/cl-launch/launcher.lisp"
(user-homedir-pathname)))
(defparameter *quicklisp-path*
(merge-pathnames #p"quicklisp/setup.lisp" (user-homedir-pathname)))
(defparameter *image-path*
(merge-pathnames #p".local/share/common-lisp/images/clisp/main"
(user-homedir-pathname)))
(load *asdf-path*)
(load *launcher-path*)
(load *quicklisp-path*)
(asdf:defsystem "build-main-clisp-image"
:depends-on ("cl-ppcre" "inferior-shell" "iterate"))
(ql:quickload "build-main-clisp-image")
(defpackage #:script
(:use #:cl #:cl-ppcre #:uiop #:inferior-shell #:iterate))
(ext:saveinitmem *image-path* :start-package "CL-USER" :norc t)
Файл, как видим, даже исполняемый. Первым делом грузится ASDF 3, который я вручную скачал, потом грузится лисповая часть cl-launch (тогда она не будет загружаться при запуске скриптов) и quicklisp. Потом хочу загрузить сколько-то систем. Для этого ставлю их зависимостями от "метасистемы", и пускай quicklisp разбирается, что в каком порядке ставить. Также в интересах скриптования определяю пакет SCRIPT, наследующий от регулярных выражений, двух пакетов для работы с системой и библиотеки циклов. Потом ещё чего-нибудь накидаю... Принцип ясен, менять и добавлять легко.
Про SBCL потом напишу.
Х1) Загрузить файл - это на самом деле дёшево и сердито. Если же хочется многофайлового проекта с зависимостями, можно сохранить его в образ. Короче, при скриптовании я за загрузку файлов. Сохранение отдельного образа для скрипта - это из пушки по воробьям, учитывая калибр, то бишь размер образа. Сохранение же самодостаточного бинарника - вообще непонятно, из каких соображений (а размер будет ещё больше, чем у образа, потому что в бинарник зашит весь лисп).
Единственное "но" - при использовании сохранённых образов шебанг теряет всю ценность. В него же надо прописывать полный путь образа, который лежит (надо полагать) в пользовательской директории. Другому пользователю придётся всё править.
Выход: использовать обёртки на sh. Их можно генерировать с помощью cl-launch.
Также cl-launch замечательно работает с аргументами скрипта.
Ещё cl-launch можно указывать в шебанге! Я пока не пробовал, потому что не устанавливал пакет.
Х4) Между собственными компами переносить несложно. Из репозиториев можно обойтись минимумом - лиспом. Можно и cl-launch поставить, если он четвёртый. Локально ставится quicklisp и можно что-нибудь написать в .cl-launchrc. При желании ASDF 3 и cl-launch можно установить и в своей директории. Пример создания образа CLISPа с нужным ASDF был выше. cl-launch я себе скачал в ~/.local/share/common-lisp/cl-launch/ и создал линки ~/bin/cl-launch и ~/bin/cl. Написал простой скриптик, который проверяет наличие cl-launch.tar.gz в текущей директории, скачивает, если не нашёл, распаковывает куда надо и создаёт симлинки.
Код:
#!/bin/bash
CL_LAUNCH_DIR="$HOME/.local/share/common-lisp/cl-launch"
SYMLINK_DIR="$HOME/bin"
ARCHIVE="cl-launch.tar.gz"
ARCHIVE_URL="http://common-lisp.net/project/xcvb/cl-launch/cl-launch.tar.gz"
# If cl-launch.tar.gz is not in cwd, try and download it or exit in the case of
# failure.
[ -a "$ARCHIVE" ] \
|| wget "$ARCHIVE_URL" \
|| { echo "ERROR: cannot get $ARCHIVE"; exit 1; }
# Get the name of the real directory containing cl-launch (includes version)
CL_LAUNCH_DIRNAME=$(tar -tf $ARCHIVE | head -n 1)
CL_LAUNCH_DIRNAME="${CL_LAUNCH_DIRNAME%?}"
# Create directories, untar, and create symlinks
mkdir -p "$CL_LAUNCH_DIR"
rm -f "$SYMLINK_DIR/cl-launch" "$SYMLINK_DIR/cl" "$CL_LAUNCH_DIR/cl-launch"
tar -zxf "$ARCHIVE" -C "$CL_LAUNCH_DIR"
ln -s "$CL_LAUNCH_DIR/$CL_LAUNCH_DIRNAME" "$CL_LAUNCH_DIR/cl-launch"
ln -s "$CL_LAUNCH_DIR/$CL_LAUNCH_DIRNAME/cl-launch.sh" "$SYMLINK_DIR/cl-launch"
ln -s "$CL_LAUNCH_DIR/$CL_LAUNCH_DIRNAME/cl-launch.sh" "$SYMLINK_DIR/cl"
Насчёт переноса от лиспера к нелисперу - надо думать.
Во-первых, библиотеки. В первый-то раз нужный образ сохранить можно. А если потом другой скрипт потребует новых библиотек, надо образ как-то заменять... Или, к примеру, обновлять. Хорошо хоть quicklisp всё ставить без проблем и можно закопать его в недрах ~/.local. Со скриптом на питоне хлопот было бы меньше, наверно, даже при использовании нестандартных библиотек.
Во-вторых, скрипт "на экспорт" нехорошо оформлять в виде куска кода с шапкой (in-package #:script). Наверно, лучше завернуть в ASDF-систему ну или по крайней мере назвать пакет более развёрнуто и выписать его определение.