551 lines
26 KiB
HTML
551 lines
26 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta name="generator" content=
|
||
"HTML Tidy for HTML5 for Linux version 5.2.0">
|
||
<title>Building Dynamic Libraries</title>
|
||
<meta charset="utf-8">
|
||
<meta name="description" content="A collection of examples of using Common Lisp">
|
||
<meta name="viewport" content=
|
||
"width=device-width, initial-scale=1">
|
||
<link rel="icon" href=
|
||
"assets/cl-logo-blue.png"/>
|
||
<link rel="stylesheet" href=
|
||
"assets/style.css">
|
||
<script type="text/javascript" src=
|
||
"assets/highlight-lisp.js">
|
||
</script>
|
||
<script type="text/javascript" src=
|
||
"assets/jquery-3.2.1.min.js">
|
||
</script>
|
||
<script type="text/javascript" src=
|
||
"assets/jquery.toc/jquery.toc.min.js">
|
||
</script>
|
||
<script type="text/javascript" src=
|
||
"assets/toggle-toc.js">
|
||
</script>
|
||
|
||
<link rel="stylesheet" href=
|
||
"assets/github.css">
|
||
|
||
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
|
||
</head>
|
||
<body>
|
||
<h1 id="title-xs"><a href="index.html">The Common Lisp Cookbook</a> – Building Dynamic Libraries</h1>
|
||
<div id="logo-container">
|
||
<a href="index.html">
|
||
<img id="logo" src="assets/cl-logo-blue.png"/>
|
||
</a>
|
||
|
||
<div id="searchform-container">
|
||
<form onsubmit="duckSearch()" action="javascript:void(0)">
|
||
<input id="searchField" type="text" value="" placeholder="Search...">
|
||
</form>
|
||
</div>
|
||
|
||
<div id="toc-container" class="toc-close">
|
||
<div id="toc-title">Table of Contents</div>
|
||
<ul id="toc" class="list-unstyled"></ul>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="content-container">
|
||
<h1 id="title-non-xs"><a href="index.html">The Common Lisp Cookbook</a> – Building Dynamic Libraries</h1>
|
||
|
||
<!-- Announcement we can keep for 1 month or more. I remove it and re-add it from time to time. -->
|
||
<!-- <p class="announce"> -->
|
||
<!-- 📢 🤶 ⭐ -->
|
||
<!-- <a style="font-size: 120%" href="https://www.udemy.com/course/common-lisp-programming/?couponCode=LISPY-XMAS2023" title="This course is under a paywall on the Udemy platform. Several videos are freely available so you can judge before diving in. vindarel is (I am) the main contributor to this Cookbook."> Discover our contributor's Lisp course with this Christmas coupon.</a> -->
|
||
<!-- <strong> -->
|
||
<!-- Recently added: 18 videos on MACROS. -->
|
||
<!-- </strong> -->
|
||
<!-- <a style="font-size: 90%" href="https://github.com/vindarel/common-lisp-course-in-videos/">Learn more</a>. -->
|
||
<!-- </p> -->
|
||
|
||
<p class="announce">
|
||
📢 New videos: <a href="https://www.youtube.com/watch?v=h_noB1sI_e8">web dev demo part 1</a>, <a href="https://www.youtube.com/watch?v=xnwc7irnc8k">dynamic page with HTMX</a>, <a href="https://www.youtube.com/watch?v=Zpn86AQRVN8">Weblocks demo</a>
|
||
</p>
|
||
|
||
<p class="announce-neutral">
|
||
📕 <a href="index.html#download-in-epub">Get the EPUB and PDF</a>
|
||
</p>
|
||
|
||
|
||
<div id="content"
|
||
<p>Although the vast majority of Common Lisp implementations have
|
||
some kind of <a href="ffi.html">foreign function interface</a> which allows you to
|
||
call functions from libraries which use C ABI, the other way around,
|
||
i.e. compiling your CL library as a library callable via C ABI from
|
||
other languages, might be rare.</p>
|
||
|
||
<p>Commercial implementations like LispWorks and Allegro CL usually
|
||
offer this functionality, and they are well documented <sup id="fnref:1" role="doc-noteref"><a href="dynamic-libraries.html#fn:1" class="footnote" rel="footnote">1</a></sup>.</p>
|
||
|
||
<p>This chapter describes a project called <a href="https://github.com/quil-lang/sbcl-librarian">SBCL-Librarian</a>,
|
||
an opinionated way to create libraries callable from C (anything which has C FFI) and Python using
|
||
the excellent open-source and free-to-use implementation <a href="https://www.sbcl.org">SBCL (Steel Bank Common Lisp)</a>.</p>
|
||
|
||
<p>SBCL-Librarian does support callbacks so you can integrate your Lisp library with any code,
|
||
including Python code which might use its great machine learning and statistical libraries.</p>
|
||
|
||
<p>The way SBCL-Librarian works is that it generates C source files, a C header and a Python module.</p>
|
||
|
||
<p>The C source file is compiled first into a dynamic library which is (using the provided header file)
|
||
loadable from any C project or by any project in a language which supports loading C libraries.</p>
|
||
|
||
<p>The generated Python module loads the compiled library into the Python process. This means that the C
|
||
library needs to be compiled before your Lisp library is used from Python code. This fact has two
|
||
main consequences:</p>
|
||
|
||
<ul>
|
||
<li>on one hand the Lisp library is all efficient native code, which is great. The Python interpreter can be quite slow and many libraries, especially the ones for machine learning
|
||
and statistics, are all compiled to native code. You can achieve the same efficiency with Common Lisp.</li>
|
||
<li>on the other hand, your library can only use the C interface to communicate with Python - primitive data
|
||
types from C, structures, functions and pointers (including pointers to functions). Some basic knowledge
|
||
of C is required.</li>
|
||
</ul>
|
||
|
||
<div class="info" style="background-color: #e7f3fe; border-left: 6px solid #2196F3; padding: 17px;">
|
||
<!-- if inside a <p> then bootstrap adds 10px padding to the bottom -->
|
||
<strong>NOTE:</strong> The team behind SBCL-Librarian works on quantum computing in the industry. More precisely on a programming language for quantum computing called Quil, and its ecosystem.
|
||
</div>
|
||
|
||
<h2 id="preparing-the-environment">Preparing the Environment</h2>
|
||
|
||
<h3 id="build-sbcl-with-shared-library-support">Build SBCL with Shared Library Support</h3>
|
||
|
||
<p>Binary distributions of SBCL usually do not come with SBCL built as a shared library, which is necessary for SBCL-Librarian.
|
||
You can download it either from the <a href="https://github.com/sbcl/sbcl">SBCL git repository</a>
|
||
or by <a href="getting-started.html#with-roswell">using Roswell</a> and running the command <code>ros install sbcl-source</code>.</p>
|
||
|
||
<p>SBCL also requires a working Common Lisp system to bootstrap the compilation process. An easy trick is to download a binary installation from Roswell and add it to your <code>PATH</code> variable.</p>
|
||
|
||
<p>SBCL depends on the <code>zstd</code> library. On Linux-based systems, you can obtain both the library and its header files from the package manager, where it is usually named <code>libzstd-dev</code>. On Windows, the recommended approach is to use
|
||
<a href="https://www.msys2.org">MSYS2</a> which includes Roswell, <code>zstd</code>, and its headers.</p>
|
||
|
||
<p>Navigate to the directory with the sources and run:</p>
|
||
|
||
<pre><code class="language-bash"># Bash
|
||
|
||
# (assuming the version of your SBCL installed via Roswell is 2.4.1)
|
||
export PATH=~/.roswell/impls/x86-64/linux/sbcl-bin/2.4.1/bin/:$PATH
|
||
|
||
./make-config.sh --fancy
|
||
./make.sh --fancy
|
||
./make-shared-library.sh --fancy
|
||
</code></pre>
|
||
|
||
<p>Note that the shared library has a <code>.so</code> extension even on Windows and Mac, but it seems to work just fine. If you use Roswell in MSYS2, it can use your Windows home directory rather than your MSYS2 home directory, which are different paths. Therefore, the path to Roswell might be <code>$USERPROFILE/.roswell</code> (or <code>/C/Users/<username>/.roswell</code>),
|
||
not <code>~/.roswell/</code>.</p>
|
||
|
||
<h3 id="download-and-setup-sbcl-librarian">Download and Setup SBCL-Librarian</h3>
|
||
|
||
<p>Clone the SBCL-Librarian repostiory:</p>
|
||
|
||
<pre><code class="language-bash">git clone https://github.com/quil-lang/sbcl-librarian.git
|
||
</code></pre>
|
||
|
||
<h2 id="hello-world-from-lisp">Hello World from Lisp</h2>
|
||
|
||
<p>Although SBCL-Librarian comes with some documentation and a couple of examples, it doesn’t really have anything like a basic tutorial. In this chapter we’ll make a basic function which adds two numbers and we’ll call it from Python.</p>
|
||
|
||
<p>Let’s set a couple of environment variables for convenience:</p>
|
||
|
||
<pre><code class="language-bash"># Directory with SBCL sources
|
||
export SBCL_SRC=~/.roswell/src/sbcl-2.4.1
|
||
# Directory with this project, don't forget the double slash at the end
|
||
# or it might not work
|
||
export CL_SOURCE_REGISTRY="~/prg/sbcl-librarian//"
|
||
</code></pre>
|
||
|
||
<p>Libraries are usually not searched for in the current directory on more modern Linux-based systems. The paths which Python searches libraries on are usually not set to the current working directory either. Let’s set them this way for convenience.</p>
|
||
|
||
<pre><code class="language-bash">export LD_LIBRARY_PATH=.:
|
||
export PATH=.:$PATH
|
||
</code></pre>
|
||
|
||
<p>Now we can create a file <code>helloworld.lisp</code> with the following content:</p>
|
||
|
||
<pre><code class="language-lisp">(require '#:asdf)
|
||
(asdf:load-system :sbcl-librarian)
|
||
|
||
(defpackage libhelloworld
|
||
(:use :cl :sbcl-librarian))
|
||
|
||
(in-package libhelloworld)
|
||
|
||
;; will be called from Python
|
||
(defun hello-world (a b)
|
||
(+ a b))
|
||
|
||
|
||
;; error enum to be used in C/Python code for error handling
|
||
(define-enum-type error-type "err_t"
|
||
("ERR_SUCCESS" 0)
|
||
("ERR_FAIL" 1))
|
||
|
||
;; mapping Common Lisp conditions to C enums
|
||
;; in this simple example, all conditions are mapped to number 1
|
||
;; which is "ERR_FAIL" in `error-type` enum
|
||
(define-error-map error-map error-type 0
|
||
((t (lambda (condition)
|
||
(declare (ignore condition))
|
||
(return-from error-map 1)))))
|
||
|
||
;; structure of the generated C source file
|
||
(define-api libhelloworld-api (:error-map error-map ; error enum
|
||
:function-prefix "helloworld_") ; prefix for all function names (C doesn't have namespaces)
|
||
(:literal "/* types */") ; just a comment (whatever is there will be printed as-is)
|
||
(:type error-type) ; outputs the error enum
|
||
(:literal "/* functions */")
|
||
(:function ; function declaration - name, return type, argument types
|
||
(hello-world :int ((a :int) (b :int)))))
|
||
|
||
;; definition of the whole library - what is there
|
||
(define-aggregate-library libhelloworld (:function-linkage "LIBHELLOWORLD_API")
|
||
sbcl-librarian:handles sbcl-librarian:environment libhelloworld-api)
|
||
|
||
;; builds the bindings
|
||
(build-bindings libhelloworld ".")
|
||
(build-python-bindings libhelloworld ".")
|
||
|
||
;; outputs the Lisp core
|
||
(build-core-and-die libhelloworld "." :compression t)
|
||
</code></pre>
|
||
|
||
<p>The macro <code>define-enum-type</code> creates a mapping between conditions signaled by Common Lisp functions and a return type for the wrapping C functions. If a condition is signaled from Common Lisp, it is translated into a number — a C function return value — within <code>define-error-map</code>. The enumeration type adds a C <code>enum</code>, so instead of:</p>
|
||
|
||
<pre><code class="language-C">if (1 == cl_function()) {
|
||
</code></pre>
|
||
|
||
<p>you can write:</p>
|
||
|
||
<pre><code class="language-C">if (ERR_FAIL == cl_function()) {
|
||
</code></pre>
|
||
|
||
<p>which is more readable.</p>
|
||
|
||
<p><code>define-api</code> outlines the structure of the library code to be created, specifying the error map, types, functions, and their order (<code>:literal</code> is used for comments in this case).</p>
|
||
|
||
<p><code>define-aggregate-library</code> defines the entire library, specifying what should be included and in what order.</p>
|
||
|
||
<p>You can compile the file with the following commands:</p>
|
||
|
||
<pre><code class="language-bash">$SBCL_SRC/run-sbcl.sh --script "helloworld.lisp"
|
||
cc -shared -fpic -o libhelloworld.so libhelloworld.c -L$SBCL_SRC/src/runtime -lsbcl
|
||
cp $SBCL_SRC/src/runtime/libsbcl.so .
|
||
</code></pre>
|
||
|
||
<p>You can run a Python console and check that the <code>helloworld</code> module was created successfully:</p>
|
||
|
||
<pre><code class="language-python">import helloworld
|
||
|
||
dir(helloworld)
|
||
</code></pre>
|
||
|
||
<p>The function <code>helloworld_hello_world</code> should be present in the printed dictionary.</p>
|
||
|
||
<p>This function follows a C standard that the return value of the function is its error code
|
||
(0 is for success, other numbers should be defined in <code>err_t</code> class which follows the <code>error-map</code> definitions),
|
||
the last parameter of the function is its return value. Since this is a pointer to integer in this case,
|
||
an integer needs to be created using <code>ctypes</code> library and <code>helloworld_hello_world</code> has to be called with
|
||
a pointer to the result value.</p>
|
||
|
||
<p>The following program should print 11:</p>
|
||
|
||
<pre><code class="language-python">import helloworld
|
||
import ctypes
|
||
|
||
rv = ctypes.c_int(0)
|
||
helloworld.helloworld_hello_world(5, 6, ctypes.pointer(rv))
|
||
print(rv.value)
|
||
</code></pre>
|
||
|
||
<p>There are two common problems which can occur, depending on your system.</p>
|
||
|
||
<p>First is a rather cryptic error from Python:</p>
|
||
|
||
<pre><code>>>> import helloworld
|
||
Traceback (most recent call last):
|
||
File "<stdin>", line 1, in <module>
|
||
ImportError: dynamic module does not define module export function (PyInit_helloworld)
|
||
</code></pre>
|
||
|
||
<p>This means that Python tries to open <code>helloworld.so</code> as a Python module rather than <code>helloworld.py</code>. Since
|
||
<code>helloworld.so</code> is just an ordinary dynamic library and not a natively-compiled Python module, it
|
||
will not work.</p>
|
||
|
||
<pre><code class="language-bash">cp ./helloworld.py ./py_helloworld.py
|
||
</code></pre>
|
||
|
||
<p>and in Python <code>import py_helloworld</code>.</p>
|
||
|
||
<p>If you experience a following exception being raised:</p>
|
||
|
||
<pre><code>Traceback (most recent call last):
|
||
...
|
||
raise Exception('Unable to locate libhelloworld') from e
|
||
Exception: Unable to locate libhelloworld
|
||
</code></pre>
|
||
|
||
<p>the first, check that all required dependencies - <code>libsbcl</code> and <code>libzstd</code> in this case - are either copied
|
||
to the output directory or are in the path which your operating system loads libraries from. If it still doesn’t
|
||
work, it might be a problem with the mechanism Python locates libraries on your particular system.</p>
|
||
|
||
<p>Open <code>helloworld.py</code> (or <code>py_helloworld.py</code> if you renamed it as suggested earlier) and change the line</p>
|
||
|
||
<pre><code class="language-Python">libpath = Path(find_library('libcallback')).resolve()
|
||
</code></pre>
|
||
|
||
<p>to a path for your operating system, e.g.</p>
|
||
|
||
<pre><code class="language-Python">libpath = Path('./libhelloworld.so').resolve()
|
||
</code></pre>
|
||
|
||
<h2 id="more-complex-example-callback-example">More Complex Example: Callback Example</h2>
|
||
|
||
<p>SBCL-Librarian includes several examples, one of which is a simple callback to Python code. This example comes with a <code>Makefile</code> and with a properly defined system using <code>asdf</code>.</p>
|
||
|
||
<h3 id="asdf-system-definition">ASDF system definition</h3>
|
||
|
||
<p>The system definition in <code>libcallback.asd</code> declares a dependency on SBCL-Librarian:</p>
|
||
|
||
<pre><code class="language-lisp">(asdf:defsystem #:libcallback
|
||
:defsystem-depends-on (#:sbcl-librarian)
|
||
:depends-on (#:sbcl-librarian)
|
||
</code></pre>
|
||
|
||
<p>The ASDF system needs to know where to find the SBCL-Librarian sources. One way to specify this is by setting the <code>CL_SOURCE_REGISTRY</code> environment variable to include its directory, as seen above, or to clone the project in a location whene ASDF can find it (<code>~/common-lisp/</code>, <code>~/quicklisp/local-projects/</code>).</p>
|
||
|
||
<h3 id="bindingslisp">Bindings.lisp</h3>
|
||
|
||
<p><code>bindings.lisp</code> contains the crucial elements for generating the C bindings:</p>
|
||
|
||
<pre><code class="language-lisp">(defun call-callback (callback outbuffer)
|
||
(sb-alien:with-alien ((str sb-alien:c-string "I guess "))
|
||
(sb-alien:alien-funcall callback str outbuffer)))
|
||
</code></pre>
|
||
|
||
<p>This function is key to the example; it is invoked from Python code and calls back a Python method (the<code>callback</code> parameter). As SBCL-Librarian generates both a C library and a Python module that wraps it, this function can be called from either C or Python. This example focuses on Python.</p>
|
||
|
||
<p>SBCL-Librarian utilizes <code>sb-alien</code>, an SBCL package for interfacing with C functions. <code>with-alien</code> creates a resource (here it is <code>str</code> of type <code>c-string</code>) that is valid within its scope and is automatically disposed of afterward, preventing memory leaks. <code>alien-funcall</code> is used to call a C function, in this case <code>callback</code>, with a newly created string and a string buffer passed in as arguments.</p>
|
||
|
||
<pre><code class="language-lisp">(sbcl-librarian::define-type :callback
|
||
:c-type "void*"
|
||
:alien-type (sb-alien:* (sb-alien:function sb-alien:void sb-alien:c-string (sb-alien:* sb-alien:char)))
|
||
:python-type "c_void_p")
|
||
|
||
(sbcl-librarian::define-type :char-buffer
|
||
:c-type "char*"
|
||
:alien-type (sb-alien:* sb-alien:char)
|
||
:python-type "c_char_p")
|
||
</code></pre>
|
||
|
||
<p>This section defines the types <code>callback</code> and <code>char-buffer</code> in C, Python, and Common Lisp. The C and Python types for both are <code>void*</code> and <code>char*</code>, respectively. The Common Lisp type for callback specifies a function prototype: a pointer to a function that returns <code>void</code> and takes two parameters, a <code>c-string</code> and a pointer to a <code>char</code>. The <code>sb-alien:*</code> indicates a pointer, so <code>:callback</code> is a pointer to a function. The <code>:char-buffer</code> type represents a <code>char*</code> in all three languages.</p>
|
||
|
||
<p>The rest of this file is similar to what was described in the <code>Hello World</code> section.</p>
|
||
|
||
<h3 id="compile-lisp-code">Compile LISP Code</h3>
|
||
|
||
<p><code>script.lisp</code> is a straightforward Lisp script for compiling the Lisp sources and outputting the wrapper code and the Lisp core.</p>
|
||
|
||
<pre><code class="language-lisp">(require '#:asdf)
|
||
|
||
(asdf:load-system '#:libcallback)
|
||
|
||
(in-package #:sbcl-librarian/example/libcallback)
|
||
|
||
(build-bindings libcallback ".")
|
||
(build-python-bindings libcallback ".")
|
||
(build-core-and-die libcallback "." :compression t)
|
||
</code></pre>
|
||
|
||
<p>Now you have a couple of new files.</p>
|
||
|
||
<p><code>libcallback.c</code> is the source code for the library:</p>
|
||
|
||
<pre><code class="language-c">#define CALLBACKING_API_BUILD
|
||
|
||
#include "libcallback.h"
|
||
|
||
void (*lisp_release_handle)(void* handle);
|
||
int (*lisp_handle_eq)(void* a, void* b);
|
||
void (*lisp_enable_debugger)();
|
||
void (*lisp_disable_debugger)();
|
||
void (*lisp_gc)();
|
||
err_t (*callback_call_callback)(void* fn, char* out_buffer);
|
||
|
||
extern int initialize_lisp(int argc, char **argv);
|
||
|
||
CALLBACKING_API int init(char* core) {
|
||
static int initialized = 0;
|
||
char *init_args[] = {"", "--core", core, "--noinform", };
|
||
if (initialized) return 1;
|
||
if (initialize_lisp(4, init_args) != 0) return -1;
|
||
initialized = 1;
|
||
return 0; }
|
||
</code></pre>
|
||
|
||
<p>At the top, you’ll find several SBCL-related functions, such as <code>lisp_gc</code>, which signals to the Lisp garbage collector that it is a good time to run. Then there is a pointer to the <code>callback_call_callback</code> function. Finally, the <code>init</code> function, which should be run before executing any Lisp code.</p>
|
||
|
||
<p>SBCL (as of version 2.4.2) didn’t support de-initialize the Lisp core so there are no functions for doing so.</p>
|
||
|
||
<p><code>libcallback.h </code> is a header file that should be included in both <code>lispcallback.c</code> and any calling C code. It contains prototypes of functions and function pointers in <code>lispcallback.c</code>, includes the error <code>enum</code>, and any comments added in <code>bindings.lisp</code>:</p>
|
||
|
||
<pre><code class="language-C">typedef enum { ERR_SUCCESS = 0, ERR_FAIL = 1, } err_t;
|
||
</code></pre>
|
||
|
||
<p>The last file, <code>lispcallback.py</code>, is a Python wrapper around the library. The most notable part is this:</p>
|
||
|
||
<pre><code class="language-Python">from ctypes import *
|
||
from ctypes.util import find_library
|
||
|
||
try:
|
||
libpath = Path(find_library('libcallback')).resolve()
|
||
except TypeError as e:
|
||
raise Exception('Unable to locate libcallback') from e
|
||
</code></pre>
|
||
|
||
<p>The rest of the file is similar to the C header file.</p>
|
||
|
||
<p>This setup loads a compiled C library (shared object, DLL, dylib) and informs the Python interpreter about the functions and types included in the library. It also initializes the Lisp core when loaded by the Python interpreter. The initialization needs to be called manually when the generated library is called from C.</p>
|
||
|
||
<h3 id="compile-c-code">Compile C Code</h3>
|
||
|
||
<pre><code class="language-bash">cc -shared -fpic -o libcallback.so libcallback.c -L$SBCL_SRC/src/runtime -lsbcl
|
||
</code></pre>
|
||
|
||
<p>On Mac OS the command might be a bit different:</p>
|
||
|
||
<pre><code class="language-bash">cc -dynamiclib -o libcallback.dylib libcallback.c -L$SBCL_SRC/src/runtime -lsbcl
|
||
</code></pre>
|
||
|
||
<p>If you do not have <code>$SBCL_SRC/src/runtime</code> in your <code>$PATH</code>, you should copy the <code>$SBCL_SRC/src/runtime/libsbcl.so</code> file to the current directory.</p>
|
||
|
||
<h3 id="run">Run</h3>
|
||
|
||
<p>Now that everything is set up, you can run the example code using the following command:</p>
|
||
|
||
<pre><code class="language-bash">$ python3 ./example.py
|
||
</code></pre>
|
||
|
||
<p>If it’s successful, you should see the output:</p>
|
||
|
||
<pre><code class="language-bash">I guess it works!
|
||
</code></pre>
|
||
|
||
<h2 id="makefile">Makefile</h2>
|
||
|
||
<p>Each example comes with a Makefile designed for building on Mac. It even automatically builds the <code>libsbcl.so</code> library and copies it into the current directory. However, the command for building the project (e.g., <code>libcallback</code>) needs to be modified to work on Linux-based operating systems and on Windows (with MSYS2).</p>
|
||
|
||
<h2 id="cmake">CMake</h2>
|
||
|
||
<p>Using CMake is relatively straightforward. Unfortunately, there is currently no CMake-aware library or a <code>vcpkg</code>/<code>conan</code> package, so you’ll need to use <code>HINTS</code> with <code>find_library</code> to locate the necessary libraries.</p>
|
||
|
||
<p>Assuming you would like to compile a project named <code>my_project</code> and would like to add a LISP library, you could proceed as follows:</p>
|
||
|
||
<pre><code class="language-CMake"># If there is a better way, let me know.
|
||
if(WIN32)
|
||
set(DIR_SEPARATOR ";")
|
||
else()
|
||
set(DIR_SEPARATOR ":")
|
||
endif()
|
||
|
||
# Set the ENV Vars for building the LISP part
|
||
set(SBCL_SRC "$ENV{SBCL_SRC}" CACHE PATH "Path to SBCL sources directory.")
|
||
set(SBCL_LIBRARIAN_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../sbcl-librarian" CACHE PATH "Source codes of SBCL-LIBRARIAN project.")
|
||
set(CL_SOURCE_REGISTRY "${CMAKE_CURRENT_SOURCE_DIR}${DIR_SEPARATOR}${SBCL_LIBRARIAN_DIR}" CACHE PATH "ASDF registry for building of the libray.")
|
||
|
||
# Find the SBCL library
|
||
find_library(libsbcl NAMES sbcl HINTS ${SBCL_SRC}/src/runtime/)
|
||
|
||
# Link the library to the C project
|
||
target_link_libraries(my_project ${libsbcl})
|
||
|
||
# Build LISP part of the project
|
||
add_custom_command(OUTPUT my_project-lisp.core my_project-lisp.c my_project-lisp.h my_project-lisp.py
|
||
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
|
||
COMMAND ${CMAKE_COMMAND} -E env CL_SOURCE_REGISTRY="${CL_SOURCE_REGISTRY}"
|
||
${SBCL_SRC}/run-sbcl.sh ARGS --script script.lisp
|
||
COMMAND ${CMAKE_COMMAND} -E copy_if_different my_project-lisp.core $<TARGET_FILE_DIR:my_project>
|
||
COMMAND ${CMAKE_COMMAND} -E copy_if_different my_project-lisp.c $<TARGET_FILE_DIR:my_project>
|
||
COMMAND ${CMAKE_COMMAND} -E copy_if_different my_project-lisp.h $<TARGET_FILE_DIR:my_project>
|
||
COMMAND ${CMAKE_COMMAND} -E copy_if_different my_project-lisp.py $<TARGET_FILE_DIR:my_project>
|
||
COMMAND ${CMAKE_COMMAND} -E rm my_project-lisp.core my_project-lisp.c my_project-lisp.h my_project-lisp.py
|
||
|
||
# Copy SBCL library if newer
|
||
add_custom_command(TARGET my_project POST_BUILD
|
||
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
||
"${libsbcl}"
|
||
$<TARGET_FILE_DIR:my_project>)
|
||
</code></pre>
|
||
|
||
<p>This concludes our tutorial on getting started with SBCL-librarian. We hope it expands your imagination in what you can build with Common Lisp and that it put you in the right tracks.</p>
|
||
|
||
<div class="footnotes" role="doc-endnotes">
|
||
<ol>
|
||
<li id="fn:1" role="doc-endnote">
|
||
<p><a href="https://www.lispworks.com/documentation/lw70/DV/html/delivery-20.htm">on LispWorks</a>, <a href="https://www.lispworks.com/documentation/lw80/lw/lw-lw-ji-88.htm">on LispWorks for Java</a>, <a href="https://franz.com/support/documentation/10.1/doc/dll.htm">on AllegroCL</a>. <a href="dynamic-libraries.html#fnref:1" class="reversefootnote" role="doc-backlink">↩</a></p>
|
||
</li>
|
||
</ol>
|
||
</div>
|
||
|
||
|
||
<p class="page-source">
|
||
Page source: <a href="https://github.com/LispCookbook/cl-cookbook/blob/master/dynamic-libraries.md">dynamic-libraries.md</a>
|
||
</p>
|
||
</div>
|
||
|
||
<script type="text/javascript">
|
||
|
||
// Don't write the TOC on the index.
|
||
if (window.location.pathname != "/cl-cookbook/") {
|
||
$("#toc").toc({
|
||
content: "#content", // will ignore the first h1 with the site+page title.
|
||
headings: "h1,h2,h3,h4"});
|
||
}
|
||
|
||
$("#two-cols + ul").css({
|
||
"column-count": "2",
|
||
});
|
||
$("#contributors + ul").css({
|
||
"column-count": "4",
|
||
});
|
||
</script>
|
||
|
||
|
||
|
||
<div>
|
||
<footer class="footer">
|
||
<hr/>
|
||
© 2002–2023 the Common Lisp Cookbook Project
|
||
<div>
|
||
📹 Discover <a style="color: darkgrey; text-decoration: underline", href="https://www.udemy.com/course/common-lisp-programming/?referralCode=2F3D698BBC4326F94358">our contributor's Common Lisp video course on Udemy</a>
|
||
</div>
|
||
</footer>
|
||
|
||
</div>
|
||
<div id="toc-btn">T<br>O<br>C</div>
|
||
</div>
|
||
|
||
<script text="javascript">
|
||
HighlightLisp.highlight_auto({className: null});
|
||
</script>
|
||
|
||
<script type="text/javascript">
|
||
function duckSearch() {
|
||
var searchField = document.getElementById("searchField");
|
||
if (searchField && searchField.value) {
|
||
var query = escape("site:lispcookbook.github.io/cl-cookbook/ " + searchField.value);
|
||
window.location.href = "https://duckduckgo.com/?kj=b2&kf=-1&ko=1&q=" + query;
|
||
// https://duckduckgo.com/params
|
||
// kj=b2: blue header in results page
|
||
// kf=-1: no favicons
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<script async defer data-domain="lispcookbook.github.io/cl-cookbook" src="https://plausible.io/js/plausible.js"></script>
|
||
|
||
</body>
|
||
</html>
|