lispx-proxy наконец вошел в фазу "как было задумано", поэтому, спешу представить: http://lispx-proxy.sourceforge.net/
Вкратце, он упрощает запуск лисп файлов как скриптов в Windows и позволяет создавать "исполняемые дистрибутивы в исходных кодах", содержащие все необходимые зависимости, которые можно выполнять без какой-либо дополнительной конфигурации. Для этого на машине нужно иметь Лисп, доступный через PATH, и lispx-proxy. Можно встраивать Лисп в дистрибутив, и тогда он будет полностью портабельным. Дистрибутивы можно упаковывать, распространяя и выполняя их отдельным файлом, можно создавать скомпилированные быстро-загружаемые версии приложений (однако, они все равно требуют lispx-proxy для запуска).
LispCabinet со встроенным lispx-proxy: http://lispcabinet.sourceforge.net/
Отдельный дистрибутив для Windows: http://sourceforge.net/projects/lispx-proxy/files/lispx-proxy-0.1.0/lispx-proxy-setup-0.1.0.exe
Примеры: http://sourceforge.net/projects/lispx-proxy/files/lispx-proxy-0.1.0/lispx-proxy-examples-0.1.0.zip
Бинарников для Linux пока нет, а компиляция требует установки Boost 1.44 и выше, что само по себе довольно невесело, хотя если уже установлена какая-нибудь из серии 1.4x, то можно попробовать скомпилировать с ней, но я не пробовал.
На Linux пока поддерживается только Clozure CL, SBCL при загрузке через execve не желает обрабатывать toplevel опции командной строки, пока я докопал только до процедуры его холодного старта, и если кто-нибудь знает, почему это происходит, то буду признателен. Теоретически, SBCL можно запустить передавая execve промежуточный шелл-скрипт, но тут возникает вопрос, как через скрипт передать замысловатые параметры командной строки с кавычками и пробелами, на которых и основана вся работа.
> SBCL при загрузке через execve не желает обрабатывать toplevel опции командной строки
имеется в виду отсутствие в *posix-argv* стандартных опций?
$ /usr/bin/sbcl --noinform --no-userinit --no-sysinit --eval "(format t \"~% type sb-ext:*posix-argv* to see command line arguments~%~%\")"
type sb-ext:*posix-argv* to see command line arguments
* sb-ext:*posix-argv*
("/usr/bin/sbcl")
просто он их не учитывает, только нестандартные:
$ /usr/bin/sbcl --noinform --no-userinit --no-sysinit --eval "(format t \"~% type sb-ext:*posix-argv* to see command line arguments~%~%\")" --my-param "(foo \"1 2 3\")"
type sb-ext:*posix-argv* to see command line arguments
* sb-ext:*posix-argv*
("/usr/bin/sbcl" "--my-param" "(foo \"1 2 3\")")
аналогично с execve:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int
main
(int argc, char *argv[]) {
char *environment[] = { NULL };
if (argc < 2) {
fprintf(stderr, "Usage: %s <file-to-exec> <options>\n", argv[0]);
exit(EXIT_FAILURE);
}
execve(argv[1], &argv[1], environment);
perror("execve");
exit(EXIT_FAILURE);
}$ gcc exec.c -o exec
$ ./exec `which sbcl` --core /usr/lib/sbcl/sbcl.core --noinform --no-userinit --no-sysinit --eval "(format t \"~% type sb-ext:*posix-argv* to see command line arguments~%~%\")" --my-param "(foo \"1 2 3\")"
type sb-ext:*posix-argv* to see command line arguments
* sb-ext:*posix-argv*
("/usr/bin/sbcl" "--my-param" "(foo \"1 2 3\")")
Т.е. там такая схема (
http://www.sbcl.org/manual/#Command-Line-Options):
sbcl runtime-option* --end-runtime-options toplevel-option* --end-toplevel-options user-options*
и чтобы они были видны в *posix-argv* их нужно повторить после --end-runtime-options и --end-toplevel-options:
$ ./exec `which sbcl` --core /usr/lib/sbcl/sbcl.core --noinform --end-runtime-options --no-userinit --no-sysinit --eval "(format t \"~% type sb-ext:*posix-argv* to see command line arguments~%~%\")" --end-toplevel-options --core /usr/lib/sbcl/sbcl.core --noinform --no-userinit --no-sysinit --eval "(format t \"~% type sb-ext:*posix-argv* to see command line arguments~%~%\")" --my-param "(foo \"1 2 3\")"
type sb-ext:*posix-argv* to see command line arguments
* sb-ext:*posix-argv*
("/usr/bin/sbcl" "--core" "/usr/lib/sbcl/sbcl.core" "--noinform"
"--no-userinit" "--no-sysinit" "--eval"
"(format t \"~% type sb-ext:*posix-argv* to see command line arguments~%~%\")"
"--my-param" "(foo \"1 2 3\")")
вот для такого повторения можно скрипт сделать (придётся различать runtime, toplevel и пользовательские опции).
Оказалось, что я передавал первым параметром лишний пустой параметр, который SBCL видимо считал концом toplevel опций и как раз наоборот, перекидывал все опции в *posix-argv*, все заработало, спасибо.
Завтра выложу фикс.
Поддержка SBCL под Linux: http://sourceforge.net/projects/lispx-proxy/files/lispx-proxy-0.1.1/lispx-proxy-source-0.1.1.zip
И наконец, бинарники для Linux:
http://sourceforge.net/projects/lispx-proxy/files/lispx-proxy-0.1.1/lispx-proxy-linux-binaries-0.1.1.tar.gz
И пример реального приложения в виде lispx-proxy дистрибутива:
https://github.com/GChristensen/sn-explorer
А где можно подробнее прочесть про принцип работы? Там проксирование выполняемого кода осуществляется через сокеты, вроде того как оно в swank? Например, можно с помощью lispx-proxy запустить swank, hunchentoot и stumpwm в рамках одного образа, но как три разных приложения?
Нет, все намного проще, это просто лончер лиспа (а ля cl-launch), предполагаемый как средство для запуска скриптов в Windows, и теоретически способный помочь от изобретения велосипедов при создании приложений, исполняемых вне среды разработки. Использует пайпы для связи с дочерним процессом (на основе Boost.Process).
> Нет, все намного проще, это просто лончер лиспа (а ля cl-launch), предполагаемый как средство для запуска скриптов в Windows, и теоретически способный помочь от изобретения велосипедов при создании приложений, исполняемых вне среды разработки.
Основной вопрос в том запускаются ли эти скрипты (при одновременном запуске) в рамках одного образа, или на каждый скрипт (архив скриптов, как я понял там возможны jar-like архивы) lispx-proxy вызывает `sbcl ... --load "script.lisp"` ?
Допустим Вася написал приложение A, а Петя - приложение B. И они друг от друга не зависят, но могут использовать одни и те же библиотеки (предполагается, что версии используемых библиотек не важны). Какие могут быть способы их дистрибуции? Например:
- Наиболее привычный (для самих разработчиков) - запускается реализация CL, с помощью quicklisp (к примеру) A и B выкачиваются со всеми зависимостями, с помощью ASDF (без вариантов) загружаются. В рамках образа оказываются определены функции (пакеты, классы и т.п.) всех нужных библиотек и сами A и B - далее логично A запускать в рамках одного потока, B - в рамках другого, а из REPL-а (из SLIME) ими как-то управлять.
- Но это не удобно при дистрибуции (заставлять пользователя запускать всё это добро для разработчика - emacs, slime, quicklisp, asdf) - проще всего сохранить образ (save-lisp-and-die) назначив toplevel функцию (и executable режим). Но при этом если пользователь запустит и A и B - в памяти будут две копии стандартного образа (~30MB), в каждом из которых могут быть загружены одни и те же библиотеки. К примеру если образы и A и B используют cl-gtk2, то overkill наступит достаточно быстро (больше 50MB на каждое такое приложение, а на десктопе, обычно именно GUI приложения). Говорят, в коммерческих реализациях этого оверхеда можно избежать (есть там такие режимы компиляции, в т.ч. поддержка чего-то вроде shared libraries в LispWorks). Но у свободных реализаций ничего такого нет.
- Запускать на каждый файл исходного кода или архив с исходным кодом команду `sbcl ... --load ...` - будет аналогичный эффект (с расходом памяти).
- Сделать приложение lisp-server, которое запускает реализацию со стандартным образом в режиме демона (сервиса) но имеет функцией старта не запуск REPL-а, а открытие passive socket-а с некоторым обработчиком. В принципе, swank может играть такую роль. И приложение lisp-client которое принимает аргументом *.lisp файлы и отправляет их серверу на обработку, или *.архивы - распаковывает их и в некоторой последовательности отправляет на обработку. Очевидно что и lisp-server и lisp-client не могут быть написаны на самом CL - они должны быть компактными (С или С++, как у вас). lisp-server можно бы было демонизировать с lisp-side, но если такой демон отвалится - кто-то должен его перезапускать.
> и отправляет их серверу на обработку
т.е. (load <file>) отправляет.
В данном случае, полная аналогия с Java - одна виртуальная машина (т.е. инстанс лиспа) на одно приложение (т.о. проблема перерасхода памяти существует).
Если используется lispx-proxy, встроенный в Lisp Cabinet, то все зависимости можно хранить в одном из репозиториев Lisp Cabinet-а, поэтому, для локального использования можно создавать приложения, не содержащие явно копии зависимых ASDF-систем.
Идея с Lisp-сервером определенно заслуживает внимания, но пока это только утилита для создания самодостаточных приложений, которые могут быть запущены на разных системах без необходимости в какой-либо конфигурации. В Java - это серверы приложений, и я боюсь, что идея с Lisp-сервером может выродиться в нечто похожее.
>3. Запускать на каждый файл исходного кода или архив с исходным кодом
команду `sbcl ... --load ...` - будет аналогичный эффект (с расходом
памяти).
Тут все немного хитрее. Создается список параметров, содержащий код загрузки файла инициализации, инициализации ASDF-репоизториев, загрузки ASDF-систем, загрузки файла application.lispx, выполнения точки входа и т.п. и через цепочку параметров --eval передается одному инстансу лиспа. Т.е. фактически загружаются только файл инициализации, если он указан и application.lispx, остальное подхватывается через ASDF.
>2.
Если точка входа указана в метаданных, то можно сделать образ приложения с помощью опции --dump (если указать опцию --executable он будет исполнимым, но так как часто возникает необходимость использовать внешние библиотеки, то последняя опция мало полезна, так как в случае ее использования придется устанавливать библиотеки в общедоступное место на целевой системе, однако необходимость в lispx-proxy при этом пропадает). Эта опция предназначена для ускорения загрузки на целевой системе (т.о. можно создавать кросплатформенные приложения, которые компилировать на месте, если это допустимо).