#common-lisp #asdf #cffi
Вопрос:
Я создал библиотеку, которая содержит пользовательский код на языке Си, и думаю о том, как лучше всего создать общую библиотеку в рамках загрузки ASDF. Файл makefile обусловлен для различных операционных систем, поэтому он может быть таким же простым , как uiop:run-program ...
, но я подумал, что спрошу здесь, есть ли для этого более стандартная идиома.
Поскольку код C специфичен для этого приложения, он не будет доступен через менеджер пакетов и должен быть создан специально для каждого компьютера пользователя. Я в порядке с документированием ручной сборки, но если я смогу сгладить ситуацию для пользователя, я это сделаю. Я замечаю, что у Python, похоже, есть какой-то автоматизированный способ создания библиотек для их CFFI, и задаюсь вопросом, есть ли что-то для CL.
Ответ №1:
Для ответа на мой собственный вопрос: похоже, что нет ни фактического способа сделать это (основанного на поиске файлов asd на github), ни определенного метода в лучших практиках ASDF, хотя из этого документа можно почерпнуть некоторые идеи.
Я представлю свою реализацию в качестве предложенной идиомы для этого варианта использования вместе с некоторыми возможными альтернативами. Надеюсь, некоторые из экспертов ASDF здесь исправят любые недоразумения.
;; Define a makefile as a type of source file for the system
(defclass makefile (source-file) ((type :initform "m")))
;; tell ASDF how to compile it
(defmethod perform ((o load-op) (c makefile)) t)
(defmethod perform ((o compile-op) (c makefile))
(let* ((lib-dir (system-relative-pathname "cephes" "scipy-cephes"))
(lib (make-pathname :directory `(:relative ,(namestring lib-dir))
:name "libmd"
:type # unix "so" # (or windows win32) "dll"))
(built (probe-file (namestring lib))))
(if built
(format *error-output* "Library ~S exists, skipping build" lib)
(format *error-output* "Building ~S~%" lib))
(unless built
(run-program (format nil "cd ~S amp;amp; make" (namestring lib-dir)) :output t))))
(defsystem "cephes"
:description "Wrapper for the Cephes Mathematical Library"
:version (:read-file-form "version.sexp")
:license "MS-PL"
:depends-on ("cffi")
:serial t
:components ((:module "libmd"
:components ((:makefile "makefile")))
(:file "package")
(:file "init")
(:file "cephes")))
Это прекрасно работает как в MS Windows, так и в UNIX. Добавление метода в perform
, по — видимому, является наиболее распространенным методом на github.
Альтернативой может быть использование a build-op
, как описано в разделе построение системы. Описание
Некоторые системы предлагают операции, которые не являются ни загрузкой в текущем образе, ни тестированием. С какой бы операцией ни предназначалась система, вы можете использовать ее с:
(asdf:make :foobar)
Это вызовет операцию сборки, которая, в свою очередь, будет зависеть от операции сборки для системы, если она определена, или операции загрузки, если нет. Таким образом, для обычных систем Lisp, которые хотят, чтобы вы их загрузили, вышеуказанное будет эквивалентно (asdf:load-system :foobar), но для других систем Lisp, например, для тех, которые создают исполняемый файл командной строки оболочки (asdf:make …), будет делать правильные вещи™, что бы это ни было.
предложите мне, чтобы это было довольно близко к идее создания библиотеки C, и это хорошо соответствовало бы ментальной модели использования файла makefile и asdf:make
команды. Я не нашел слишком много примеров использования этого в дикой природе, хотя технически мы загружаем библиотеку C в существующий образ.
Еще один момент, который можно было бы пересмотреть, — это обнаружение существующей общей библиотеки, чтобы избежать перестройки. make
позволит избежать перекомпиляции, если общая библиотека существует, но все равно снова вызовет компоновщик. Это приводит к ошибкам, поскольку он не может выполнять запись в общую библиотеку, когда она используется, по крайней мере, в MS Windows. В примере ASDF использовался код Lisp для обнаружения существования библиотеки и предотвращения перекомпиляции, но альтернативой может быть использование output-files
.
Документы ASDF немного запутаны в целях output-files
, и нет примеров, которые проясняют их намерения, но в разделе руководства по созданию новых операций у нас есть:
выходные файлы Если ваш метод выполнения имеет какие-либо выходные данные, вы должны определить метод для этой функции. для ASDF, чтобы определить, где находятся результаты выполнения операции.
что предполагает, что определение общей библиотеки (libmd.so или libmd.dll) — рекомендуемый способ избежать перекомпиляции, если output-files
она уже существует.
Наконец, в данном случае библиотеку C можно было бы считать вторичной системой cephes/libmd
и добавить в :depends-on
предложение в основной системе. В разделе, посвященном другим вторичным системам, демонстрируется создание исполняемого файла таким образом, с build-op
помощью . За исключением того факта, что это создание исполняемого файла и жестких кодов «.exe», похоже, хорошо согласуется с вариантом использования:
Чтобы создать исполняемый файл, определите систему следующим образом (в данном случае это вторичная система, но она также может быть основной системой). Вы сможете создать исполняемый файл foobar-команды, выполнив оценку (asdf:make :foobar/исполняемый файл):
(defsystem "foobar/executable"
:build-operation program-op
:build-pathname "foobar-command" ;; shell name
:entry-point "foobar::start-foobar" ;; thunk
:depends-on ("foobar")
:components ((:file "main")))
Путь к сборке указывает имя исполняемого файла; тип .exe
будет автоматически добавлен в Windows.
Я не использовал этот метод, потому что вторичная система выглядела бы почти так же, как сейчас первичная, но была бы немного менее понятной.