#linker #shared-libraries #common-lisp #dlopen #quicklisp
#компоновщик #общие библиотеки #common-lisp #dlopen #quicklisp
Вопрос:
У меня есть простая серверная программа common lisp, которая использует библиотеку osicat для взаимодействия с файловой системой posix. Мне нужно это сделать, потому что система создает символические ссылки на файлы и использует метаданные POSIX stat, и ни одна из этих вещей не является простой для выполнения в portable lisp.
Я управляю зависимостями с помощью quicklisp, и все это у меня закреплено в рабочем дистрибутиве. Приложение переносимо между CCL и SBCL, и я склонен создавать его в первом и развертывать с использованием последнего. Я объявляю зависимости для приложения с помощью asdf defsystem
, и я могу использовать quicklisp для загрузки его для упрощения разработки из локальных проектов.
Для развертывания я просто использовал несколько сборников ansible playbook, которые реплицировали среду разработчика на удаленном компьютере (например, настраивали quicklisp, загружали код в локальные проекты, запускали из домашнего каталога пользователя), что было непросто, но в основном нормально. В последнее время, поскольку он становится более стабильным, я компилировал его с помощью sb-ext:save-lisp-and-die
, используя простой скрипт компиляции. Это означает, что я получаю исполняемый файл, который я могу запускать скорее как сервер, со сценариями управления службами и анонимной учетной записью пользователя.
Это работает очень хорошо, и поэтому я недавно перенес этот шаг на следующий уровень, и я создаю пакеты .deb с помощью моего скрипта компиляции, чтобы я мог объединить все в перемещаемый двоичный файл. Это тоже вроде как работает, но результирующие двоичные файлы невозможно переместить с исходного узла сборки. Они отказываются запускаться, и кажется, что они пытаются динамически загрузить общую библиотеку для osicat
Unhandled SIMPLE-ERROR in thread #<SB-THREAD:THREAD "main thread" RUNNING
Mar 15 12:47:14 annie [479]: {10005C05B3}>:
Mar 15 12:47:14 annie [479]: Error opening shared object "libosicat.so":
Mar 15 12:47:14 annie [479]: libosicat.so: cannot open shared object file: No such file or directory.
похоже, что изображение ожидает найти это в архивах quicklisp исходного дерева сборки
(ERROR "Error opening ~:[runtime~;shared object ~:*~S~]:~% ~A." "/home/builder/buil...quicklisp/dists/quicklisp/software/osicat-20180228-git/posix/libosicat.so
(SB-SYS:DLOPEN-OR-LOSE #S(SB-ALIEN::SHARED-OBJECT :PATHNAME #P"
итак, копаясь в исходном коде, я понимаю, что когда quicklisp извлекает osicat и выполняет свою операцию сборки, он компилирует эту DLL, чтобы перенести ее интерфейс с системными библиотеками, а не просто напрямую ffi к ним — возможно, потому, что он использует cffi groveller, я не очень много знаю о cffi (пока). Это нормально, но вместо ссылки на a . таким образом, используя системный компоновщик, он пытается dlopen
получить его по фиксированному пути, что не очень переносимо и отчасти снижает полезность save-image
На данный момент я немного озадачен, но прежде чем я углублюсь в сборки QL и cffi, я подумал, не хватает ли мне какой-нибудь конфигурации сборки или компиляции, которая сделала бы ее загрузку более «статичной» или повлияла бы на создание обернутой библиотеки. В идеале мне нужен просто один большой двоичный объект, который я мог бы поместить в установщик и связать его с системными библиотеками, но если мне придется развернуть некоторые дополнительные артефакты, это, вероятно, нормально. Я не знаю, как сделать так, чтобы автоматически созданные общие объекты появлялись по более контролируемому пути.
Однако на этом этапе я могу также написать a .so для своих вызовов posix и распространить это вместе с приложением и попытаться выполнить FFI для него более напрямую. Это было бы немного затруднительно, поэтому я бы предпочел этого не делать.
Ответ №1:
Вы правы, когда загруженный образ запускается, он пытается перезагрузить разделяемые библиотеки. Который, как вы видите, не работает, если образ не запускается на компьютере, на который он был сброшен.
Это почти то, что static-program-op намеревался решить. Простое системное определение, подобное этому, должно помочь вам скомпилировать статическую программу:
(defsystem "foo"
:defsystem-depends-on ("cffi-grovel")
:build-operation "static-program-op" ; "asdf" package is implied
:build-pathname "foo" ; path of the generated binary
:entry-point "foo:main" ; function to use as the entry point
;; ... everything else ...
)
Если ваша система зависит от файлов grovel (определенных с помощью :cffi-wrapper-file
, :c-file
или :o-file
), таких как файлы, предоставляемые osicat, то она статически свяжет их с вашим выгруженным изображением.
Однако это не идеально.
По сути, все еще остаются некоторые проблемы. Некоторые исправляются вверх по потоку самим CFFI (например, не перезагружаются разделяемые библиотеки статически встроенных библиотек), другие немного сложнее. (Например, параметры компиляции SBCL по умолчанию не позволяют использовать static-program-op
по умолчанию. Это исправлено в сборках SBCL Debian, но другие дистрибутивы менее отзывчивы.)
Очевидно, что это проблема, с которой столкнулось сообщество в целом, и есть несколько библиотек, которые готовы помочь:
- Первый, который существует уже некоторое время, — это Deploy. Используемый им подход заключается в том, что он встраивает созданный образ и библиотеки в архив и перестраивает двоичный файл, чтобы загружать их оттуда, куда он извлекается.
- Второй вариант, к которому я отношусь с предубеждением, потому что я его создал, — это linux-упаковка. Он использует подход исправления
static-program-op
путем его расширения, но требует от вас создания пользовательского SBCL. Однако он генерирует дистрибутивные пакеты, такие как.deb
и.rpm
, чтобы иметь возможность указывать зависимости для системных разделяемых библиотек (например, если вы зависите от sqlite, он определит, какой пакет его предоставляет, и добавит его в качестве зависимости в.deb
). Я настоятельно рекомендую посмотреть на.gitlab-ci.yml
для примеров.
Я рекомендую ознакомиться с веб-страницами обеих этих библиотек, чтобы сделать свой выбор, у них обеих есть свои преимущества и недостатки. <joke>
Очевидно, что linux-упаковка превосходна. </joke>
Ответ №2:
Возможно, вместо этого вы можете использовать sb-posix:symlink
и sb-posix:fstat
в SBCL, удалив зависимость osicat переключением функций.
Комментарии:
1. вау, я понятия не имел о sb-posix. это хороший совет. Я бы потерял переносимость для ccl, но я использую это только в локальной разработке, поэтому я мог бы перевернуть его с помощью условий reader. Я думаю, это также сработает для зависимостей в
defsystem
2. было довольно просто заставить приложение работать с этим изменением, и его можно перемещать, так что это полезный обходной путь. Жаль, что он не переносим, хотя в сценариях сборки уже происходит сбой для обработки различий.