Setting up a GNU Guile project with Autotools
Contributed article by Erik Edrosa, originally published on erikedrosa.com.
Lisp, specifically Scheme, has captivated me for about three years now. My Scheme implementation of choice is GNU Guile because it is the official extension language of the GNU project. One issue I faced early with when trying to develop for Guile was how to set up and organize my project. There doesn't seem to be a single recommended way to set up a Guile project but several projects do follow a similar project structure I will describe below. You can find a git repository with the example project here.
Simple Project Structure
The project template I created for new projects is based on several GNU
Guile projects I have examined. These projects follow the traditional
GNU Build System using the familiar commands
./configure && make && sudo make install for building and installing
software. To help generate these files, we will use the collection of
software known as autotools which include the software
Autoconf and
Automake. Unfortunately,
autotools can be quite complex for developers with its esoteric
languages like m4 being used to magically create and configure all the
necessary build files for your project. Good news for us, not much magic
is needed for us to conjure the build files for a simple Guile project.
. ├── bootstrap ├── configure.ac ├── COPYING ├── COPYING.LESSER ├── guile.am ├── m4 │ └── guile.m4 ├── Makefile.am ├── skeleton │ └── hello.scm ├── pre-inst-env.in ├── README └── skeleton.scm
Above is the directory structure of the project. bootstrap is a simple
shell script which a developer can regenerate all the GNU Build System
files. configure.ac is a template file which Autoconf uses to generate
the familiar configure script. m4/guile.m4 is a recent copy of
Guile's m4 macros, may not be needed if you prefer to use the macro from
your Guile distribution, but it is recommended to keep your own copy.
COPYING and COPYING.LESSER are just the GPL and LGPL licenses.
Makefile.am and guile.am are Automake files used to generate the
Makefile.in which configure will configure. skeleton.scm and
skeleton/hello.scm are some initial source code files, where
skeleton.scm represents the Guile module (skeleton) and
skeleton/hello.scm is the (skeleton hello) module, change these file
and directory names to what you want to name your modules as.
pre-inst-env.in is a shell script which set up environment variables
to be able to use your code before installing it.
Bootstrapping the Project
#! /bin/sh autoreconf --verbose --install --force
This is the bootstrap script, it just calls autoreconf, which uses
Autoconf and Automake, to generate the configure script from
configure.ac and Makefile.in file from Makefile.am. The
bootstrap script is sometimes also named autogen.sh in projects but
seems to no longer be preferred to avoid confusion with the
GNU AutoGen project. The
command will also generate a bunch of other files needed by the build
process. This script is only used when building from a checkout of the
project's repository, because a user will only need configure and
Makefile.in. Whenever you might be having an issue with the configure
script or made a change to it, doing ./bootstrap will regenerate the
files for you.
Generating the Configure Script
AC_INIT([guile-skeleton], [0.1]) AC_CONFIG_SRCDIR([skeleton.scm]) AC_CONFIG_AUX_DIR([build-aux]) AC_CONFIG_MACRO_DIR([m4]) AM_INIT_AUTOMAKE([-Wall -Werror foreign]) GUILE_PKG([2.2 2.0]) GUILE_PROGS if test "x$GUILD" = "x"; then AC_MSG_ERROR(['guild' binary not found; please check your guile-2.x installation.]) fi AC_CONFIG_FILES([Makefile]) AC_CONFIG_FILES([pre-inst-env], [chmod +x pre-inst-env]) AC_OUTPUT
Above is the configure.ac file used by
GNU Autoconf to
generate the configure script. The first line is the AC_INIT macro,
the first argument is the package name and the second argument is the
version. There is a couple of other optional arguments which you can
learn more about
here.
The AC_CONFIG_SRCDIR macro adds a check to configure for the
existence of a unique file in the source directory, useful as a safety
check to make sure a user is configuring the correct project.
AC_CONFIG_AUX_DIR macro is where auxiliary builds tools are found,
build-aux is the the most commonly used directory, we use this so we
don't litter the source directory with build tools. The next macro,
AC_CONFIG_MACRO_DIR is where additional macros can be found, we add
this to include the m4/guile.m4 file.
GNU Automake options are
part of the next macro, AM_INIT_AUTOMAKE, where -Wall turns all
warnings, -Werror turns those warnings into errors, and finally
foreign will turn the strictness to a standard less than the GNU
standard. More automake options can be found
here.
The GUILE_PKG and GUILE_PROGS macro is part of the m4/guile.m4
file. This macro will substitute various variables that will be used in
the Makefile. The GUILE_PKG macro will use the pkg-config program to
find the development files for Guile and substitute the
GUILE_EFFECTIVE_VERSION variable in the Makefile. The GUILE_PROGS
macro finds the various Guile programs we will need to compile our
program. This macro substitutes the variables GUILE and GUILD with
the path to the guile and guild programs. By default, these Guile
macros will check for the latest version of Guile first, which is
currently 2.2. If you have multiple versions of Guile installed, a user
of the configure script may override the above Guile variables. For
example if you have Guile 2.2 and 2.0 installed and you want to install
the package for 2.0, you can run ./configure GUILE=/path/to/guile2.0.
There is also a check to ensure the GUILD variable was set by
GUILE_PROGS and displayed an error if it could not be found.
The next portion of this file involves the files that will actually be
configured when a user runs the configure script. The
AC_CONFIG_FILES macro's first argument is files that will be created
by substituting the variables found in the file of the same name with
.in appended to the end. In the first macro, it creates a Makefile
by substituting the variables in Makefile.in. The second argument of
this macro will be commands to run after the file is created, in the
second macro, it uses chmod to make the pre-inst-env script
executable. The last macro in this script is AC_OUTPUT and must be the
final macro in configure.ac. This macro generates config.status and
then uses it to do all the configuration.
Generating the Project Makefile
For this project we use GNU
Automake to help us generate the Makefile. When Automake is ran, it
will produce a Makefile.in file which will then be configured by the
configure script. We divide the Makefile into two files, Makefile.am
and guile.am. The first file is where we will put code specific for
this project, that includes source code and any other files to be
distributed to users. guile.am file is where we have all the code that
can be shared between any other Guile project.
include guile.am SOURCES = \ skeleton/hello.scm \ skeleton.scm EXTRA_DIST = \ README \ bootstrap \ pre-inst-env.in
This Makefile.am file is pretty small for now. The Automake script
first includes the Guile specific automake script guile.am. The next
part is the variable SOURCES which is a list of the project's source
code that will be compiled and installed. The next variable,
EXTRA_DIST is a list of other files that should be included in the
tarball used to distribute this project.
moddir=$(datadir)/guile/site/$(GUILE_EFFECTIVE_VERSION) godir=$(libdir)/guile/$(GUILE_EFFECTIVE_VERSION)/site-ccache GOBJECTS = $(SOURCES:%.scm=%.go) nobase_dist_mod_DATA = $(SOURCES) $(NOCOMP_SOURCES) nobase_go_DATA = $(GOBJECTS) # Make sure source files are installed first, so that the mtime of # installed compiled files is greater than that of installed source # files. See # <http://lists.gnu.org/archive/html/guile-devel/2010-07/msg00125.html> # for details. guile_install_go_files = install-nobase_goDATA $(guile_install_go_files): install-nobase_dist_modDATA CLEANFILES = $(GOBJECTS) GUILE_WARNINGS = -Wunbound-variable -Warity-mismatch -Wformat SUFFIXES = .scm .go .scm.go: $(AM_V_GEN)$(top_builddir)/pre-inst-env $(GUILD) compile $(GUILE_WARNINGS) -o "$@" "$<"
Now for guile.am, this file has all of the Guile specific code used in
our Automake scripts. The first two variables, moddir and godir, are
the paths where we will install our Guile modules and compiled modules.
The next variable is the GOBJECTS variable which has some code that
creates a list of Guile object files from our SOURCE variable. The
next two variables declared are special DATA variables using some of
Automake's features to indicate which files should be installed and
where. The first portion of the variable, the nobase_ prefix is used
to tell Automake to not strip the path of these files when installing
them. dist_ tells Automake that these files must be distributed in the
tarball. The next part, mod_ or go_, tell which directory these
files should be installed, they refer to the above moddir and godir
variables. The files in SOURCES and NOCOMP_SOURCES are installed in
the moddir, where SOURCES are the scheme files that we want to be
compiled and the NOCOMP_SOURCES are scheme files which should not be
compiled. The compiled Guile source code, GOBJECTS are installed in
the godir. The next two lines of code are some special magic to ensure
the files are installed in the right order by Automake.
The CLEANFILES variable is an Automake variable with files which
should be deleted when a user runs make clean. The compiled Guile
modules are just the files we need to delete, so we assign GOBJECTS.
GUILE_WARNINGS are warnings we want to pass to Guile when it compiles
or executes the code. SUFFIXES allows us to add Guile's .scm and
.go file extensions to be handled by Automake and we define a suffix
rule on how to compile the source code using GUILD.
GNU Guile Project Source Files
We now get to the actual Guile code for this project. A Guile project
may be divided into several modules and organized in various ways. In
this skeleton project, the main module is the skeleton module and is
found in skeleton.scm file. Sub-modules of skeleton are found in the
skeleton/ directory, where we currently have the skeleton hello
module found in the skeleton/hello.scm file.
(define-module (skeleton) #:use-module (skeleton hello)) (hello-world)
This is the skeleton module, it defines the module with the
define-module form. We also import the skeleton hello module using
the #:use-module option of define-module. All this file does is call
hello-world procedure defined in the skeleton hello module.
(define-module (skeleton hello) #:export (hello-world)) (define (hello-world) (display "Hello, World!"))
The final module is the skeleton hello module. This module defines the
hello-world procedure used in the previous module and then exports it
using the #:exports option in the define-module form.
Putting It All Together
Now how does this all come together for development? With all these
files in place in the project, executing the command ./bootstrap will
use Autoconf and Automake to generate the configure, Makefile.in,
and some other files. Then executing ./configure will configure
Makefile.in, pre-inst-env.in. Running the program make should now
compile your source code.
#!/bin/sh abs_top_srcdir="`cd "@abs_top_srcdir@" > /dev/null; pwd`" abs_top_builddir="`cd "@abs_top_builddir@" > /dev/null; pwd`" GUILE_LOAD_COMPILED_PATH="$abs_top_builddir${GUILE_LOAD_COMPILED_PATH:+:}$GUILE_LOAD_COMPILED_PATH" GUILE_LOAD_PATH="$abs_top_builddir:$abs_top_srcdir${GUILE_LOAD_PATH:+:}:$GUILE_LOAD_PATH" export GUILE_LOAD_COMPILED_PATH GUILE_LOAD_PATH PATH="$abs_top_builddir:$PATH" export PATH exec "$@"
Above is the pre-inst-env.in file which is configured by the configure
script. The variables between '@' characters are variables that will be
replaced by the configure script. abs_top_srcdir and
abs_top_builddir are Autoconf variables which gives the absolute
source directory and build directory. Then we add these directories to
Guile's GUILE_LOAD_COMPILED_PATH and GUILE_LOAD_PATH.
GUILE_LOAD_COMPILED_PATH is an environment variable that has the
search path for compiled Guile code which have the .go extension.
GUILE_LOAD_PATH is the search path for Guile source code files. When
the configure script configures this file, it then allows you to run
Guile and use the modules of the project before installing them. This
can be done with this command ./pre-inst-env guile. The script also
does the same for the PATH variable, to allow you to execute any
scripts in the project's directory. Finally, the script executes the
rest of the command passed into this script.
Distributing the Project
So the project is now complete and you want to distribute it to other
people so they can build and install it. One of the great features of
autotools is it generates everything you need to distribute your
project. From the Makefile that is generated by GNU Automake, you run
make dist and it will generate a tar.gz file of your project. This
will be the file you will then give to your users and they will just
extract the contents and run ./configure && make && sudo make install
to build and install your project. The files that are included in the
distribution are figured out by Automake and can be added to in your
Makefile.am script file using the EXTRA_DIST variable. One other
helpful feature that GNU Automake will generate is the command
make distcheck. This command will check to ensure the distribution
actually works, it will first create a distribution and then proceed to
open the distribution, build the project, run tests, install the
project, and uninstall the project all in a temporary directory. You can
learn more about GNU Automake distribution in the
manual.
One more note about installing your GNU Guile project. By default, the
GNU Build System installs your project in the /usr/local directory.
GNU Guile installations generally do not have this directory on their
load path. There are several options on how to resolve this issue. You
can add /usr/local/share/guile/site/$(GUILE_EFFECTIVE_VERSION) to the
GUILE_LOAD_PATH variable as well as
/usr/local/lib/guile/$(GUILE_EFFECTIVE_VERSION)/site-ccache to the
GUILE_COMPILED_LOAD_PATH variable, where $(GUILE_EFFECTIVE_VERSION)
is the GNU Guile version you are using, like 2.0 or 2.2. You can add
these variables to your .profile or .bash_profile in your home
directory like so:
export GUILE_LOAD_PATH="/usr/local/share/guile/site/2.2${GUILE_LOAD_PATH:+:}$GUILE_LOAD_PATH" export GUILE_LOAD_COMPILED_PATH="/usr/local/lib/guile/2.2/site-ccache${GUILE_LOAD_COMPILED_PATH:+:}$GUILE_COMPILED_LOAD_PATH"
The alternative is to install your project in the current load path of
your GNU Guile installation which is often /usr. You can easily do
this by changing the prefix variable in the configure script like
./configure --prefix=/usr. Now when you run make install it will
install everything in /usr instead of /usr/local. With the GNU Build
System, you have full control of where you install your Guile files so
you have the possibility of installing it anywhere you want like your
home directory, just be sure to add that location to your load paths for
Guile. There are several other variables you can modify to change the
installation location of various files, you can learn more in the
GNU
Autoconf manual.
Conclusion
The GNU Build System provides a common interface for configuring, building, and installing software. The autotools project, although a bit complex, helps us achieve this. This should be enough for a basic GNU Guile library that can be compiled and distributed to users using the GNU Build System. You can find the example project on GitLab here. The project can be extended to include tests and documentation that I hope to cover in other blog posts.
License: cc by-sa