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.


With Guise and Guile

License: cc by-sa

Date: 2017-10-29 Sun 00:00

Author: Erik Edrosa

Created: 2020-05-06 Wed 09:35

Emacs 24.5.1 (Org mode 8.2.6)

Validate