ecl/examples/asdf_with_dependence
2017-07-25 08:53:28 +02:00
..
example-with-dep.asd examples: add package prefix to defsystem 2017-07-22 13:07:47 +02:00
example.lisp examples: don't intern symbols in asdf-with-deps 2017-07-22 12:29:34 +02:00
package.lisp examples: don't intern symbols in asdf-with-deps 2017-07-22 12:29:34 +02:00
readme.org readme.org: add language fixes by tomek 2017-07-25 08:53:28 +02:00
test.c example: use only one file (test.c) 2017-07-22 13:08:09 +02:00

Build an asdf system with dependencies

First, let's disregard the simple situation in which we write Lisp without depending on any other Lisp libraries. A more practical example is to build a library that depends on other asdf systems. ECL provides a useful extension for asdf called asdf:make-build, which offers an abstraction for building libraries directly from system definitions.

To download dependencies you may use Quicklisp to load your system (with dependencies defined). Make sure you can successfully load and run your library in ECL REPL (or *slime-repl*). Don't worry about other libraries loaded in your image ECL will only build and pack libraries your project depends on (that is, all dependencies you put in your .asd file, and their dependencies - nothing more, despite the fact that other libraries may be loaded).

Example code to build

We use a simple project that depends on alexandria to demonstrate the interface. The example consists of example-with-dep.asd, package.lisp and example.lisp (included in the examples/asdf_with_dependence/ directory in the ECL source tree). Before any kind of build you need to push the full path of this directory to asdf:*central-registry* (or link it in a location already recognized by ASDF).

Build it as a single executable

Use this in REPL to make an executable:

(asdf:make-build :example-with-dep
                 :type :program
                 :move-here #P"./"
                 :epilogue-code '(progn (example:test-function 5)
                                        (si:exit)))

Here the :epilogue-code is executed after loading our library; we can use arbitrary Lisp forms here. You can also put this code in your Lisp files and directly build them without this :epilogue-code option to achieve the same result. Running the program in a console will display the following and exit:

Factorial of 5 is: 120

Build it as shared library and use in C

Use this in REPL to make a shared library:

(asdf:make-build :example-with-dep
                 :type :shared-library
                 :move-here #P"./"
                 :monolithic t
                 :init-name "init_example")

Here :monolithic t means that ECL will compile the library and all its dependencies into a single library named example-with-dep--all-systems.so. The :move-here parameter is self-explanatory. :init-name sets the name of the initialization function. Each library linked from C/C++ code must be initialized, and this is a mechanism to specify the initialization function's name.

To use it, we write a simple C program:

/* test.c */
#include <ecl/ecl.h>

int main (int argc, char **argv) {
  extern void init_dll_example(cl_object);
  
  cl_boot(argc, argv);
  ecl_init_module(NULL, init_dll_example);

  /* do things with the Lisp library */
  cl_eval(c_string_to_object("(example:test-function 5)"));

  cl_shutdown();
  return 0;
}

Compile the file using a standard C compiler (note we're linking to libecl.so with -lecl, which provides the lisp runtime1):

gcc test.c example-with-dep--all-systems.so -o test -lecl

If ECL is installed in a non-standard location you may need to provide flags for the compiler and the linker. You may read them with:

ecl-config --cflags
ecl-config --libs

Since our shared object is not in the standard location, you need to provide LD_LIBRARY_PATH pointing to the current directory to run the application:

LD_LIBRARY_PATH=`pwd` ./test

This will show:

Factorial of 5 is: 120

You can also build all dependent libraries separately as a few .so files and link them together. For example, if you are building a library called complex-example, that depends on alexandria and cl-fad, you can do the following (in the REPL):

(asdf:make-build :complex-example
                 :type :shared-library
                 :move-here #P"./"
                 :init-name "init_example")

(asdf:make-build :alexandria
                 :type :shared-library
                 :move-here #P"./"
                 :init-name "init_alexandria")

(asdf:make-build :cl-fad
                 :type :shared-library
                 :move-here #P"./"
                 :init-name "init_fad")

(asdf:make-build :bordeaux-threads
                 :type :shared-library
                 :move-here #P"./"
                 :init-name "init_bt")

Note that we haven't specified :monolithic t, so we need to build bordeaux-threads as well because cl-fad depends on it. The building sequence doesn't matter and the resultant .so files can also be used in your future programs if these libraries are not modified.

We need to initialize all these modules using ecl_init_module in the correct order. (bordeaux-threads must be initialized before cl-fad; cl-fad and alexandria must be initialized before complex-ecample.)

Here is a code snippet (not a full program):

extern void init_fad(cl_object);
extern void init_alexandria(cl_object);
extern void init_bt(cl_object);
extern void init_example(cl_object);

/* call these *after* cl_boot(argc, argv); 
   if B depends on A, you should first init A then B. */
ecl_init_module(NULL, init_bt);
ecl_init_module(NULL, init_fad);
ecl_init_module(NULL, init_alexandria);
ecl_init_module(NULL, init_example);

Build it as a static library and use in C

To build a static library, use:

(asdf:make-build :example-with-dep
                 :type :static-library
                 :move-here #P"./"
                 :monolithic t
                 :init-name "init_example")

This will generate example-with-dep--all-systems.a in the current directory which we need to initialize with the init_example function. Compile it using:

gcc test.c example-with-dep--all-systems.a -o test-static -lecl

Then run it:

./test-static

This will show:

Factorial of 5 is: 120

Note we don't need to pass the current path in LD_LIBRARY_PATH here, since our Lisp library is statically bundled with the executable. The result is the same as the shared library example above. You can also build all dependent libraries separately as static libraries.

Footnotes


1

You may also link ECL runtime statically. That is not covered in this walkthrough.