Guile & Autoconf

I use Autotools to organize my projects (yes, I know; but to paraphrase Churchill it's the worst solution, except for all the others). While working on a Guile Scheme project recently, I became frustrated trying to figure out how to install my compiled Scheme files (i.e. .go files).

This is the best discussion I've found, but it concerns the details of authoring an Automake file that will install compiled Scheme files. Their install destination is simply assumed to be ~/.cache/guile/ccache, but that is no longer the case, at least on my system. Of course, given that this discussion took place in 2010 and pertains to Guile 1.9, that's not surprising.

This post sets it to $(libdir)/guile/$(GUILE_EFFECTIVE_VERSION)/site-ccache, which is not a bad guess, but not always correct. For instance, on one of my systems, the site-ccache is /usr/lib/x86_64-linux-gnu/guile/2.2/cache.

The Autoconf macro GUILE_SITE_DIRS (see here) will set the variable GUILE_SITE_CCACHE based on pkgconfig, which seems correct when installing to the default location, but that's not always what you want. Suppose someone configures your package with a prefix of, say, $HOME? They would then reasonably expect to be able to install without escalating privileges, and will be quite surprised when the installation fails for your .go files (unless they run the install as root).

Worse, when making the distcheck target, Automake will try to install the package into a temporary location as the maintainer running make. If you have Makefile rules trying to install .go files to GUILE_SITE_CCACHE, which will typically be under /usr/lib, or /usr/local/lib, make distcheck will fail.

Since I can't know a priori what the user will want to do, I fell back to having them tell me. Autoconf doesn't permit maintainers to add new command-line options (other than enabling or disabling optional packages), but it does allow us to add environment variables which will be declared precious and described in the output of configure --help (see here for more details).

I introduced such an environment variable (MY_PKG_GUILE_SITE, in this example). The rules for how it will be used are as follows:

This seems to handle all the cases I can think of:

Here's a snippet from configure.ac that demonstrates the technique:

AC_ARG_VAR([MY_PKG_GUILE_SITE], [The directory under which compiled Scheme
files shall be installed.  Unset or empty means install into
$(libdir)/guile/$GUILE_EFFECTIVE_VERSION/site-ccache.])

if test -n "${MY_PKG_GUILE_SITE}"; then
    _MY_PKG_GUILE_SITE="${MY_PKG_GUILE_SITE}"
elif test "${prefix}" == "NONE"; then
    if test -n "${GUILE_SITE_CCACHE}"; then
        _MY_PKG_GUILE_SITE="${GUILE_SITE_CCACHE}"
    else
        _MY_PKG_GUILE_SITE="${ac_default_prefix}/lib/guile/${GUILE_EFFECTIVE_VERSION}/site-ccache"
    fi
else
    _MY_PKG_GUILE_SITE="${prefix}/lib/guile/${GUILE_EFFECTIVE_VERSION}/site-ccache"
fi

AC_MSG_NOTICE([Compiled Scheme files will be installed under ${_MY_PKG_GUILE_SITE}])
AC_SUBST([_MY_PKG_GUILE_SITE])    

I wind up substituting a variable, _MY_PKG_GUILE_SITE with the final location to which .go files shall be installed– this can be output to a file via AC_OUTPUT, but the variable will be available to make, as well. In my Makefile.am, I can follow the approach laid out in the first reference above (a guile-devel thread) and set ccachedir to $(_MY_PKG_GUILE_SITE) if I'm going to install my .go files directly into the Guile site directory, or to $(_MY_PKG_GUILE_SITE/my-package-directory if I want to setup a sub-directory for my package.

But wait– all of the arguments above apply to installing the Scheme source for my modules, as well: I can get the system Guile site directory (/usr/local/share/guile/site, e.g.) in the variable ${GUILE_SITE} from the same GUILE_SITE_DIRS macro above, but that, like GUILE_SITE_CCACHE, doesn't respect the prefix argument to configure, so once again neither make distcheck nor un-privileged installations would work. I wound up taking the same approach– introduce an environment variable to my configure script that the user can use to explicitly say where they want the module source installed.

Here's a complete solution, assuming I want to install my module under directory my-package, and that my package consists of two files: foo.scm and bar.scm.

configure.ac:

GUILE_SITE_DIR

dnl This logic decides where Scheme modules go. The GUILE_SITE_DIR
dnl call, above, retrieves the location as recorded by `pkgconfig',
dnl but that's not appropriate for an installation to a non-standard
dnl place via the --prefix option. The rules are as follows:
dnl   1. if ${MY_PKG_GUILE_SITE} is non-empty, use that
dnl   2. if ${prefix} is "NONE"
dnl      - if GUILE_SITE is not empty, use GUILE_SITE
dnl      - use ${ac_default_prefix}/share/guile/site
dnl   3. use ${prefix}/share/guile/site

AC_ARG_VAR([MY_PKG_GUILE_SITE], [The directory under which Scheme modules
shall be installed.  Unset or empty means install into
${datadir}/guile/site.])

if test -n "${MY_PKG_GUILE_SITE}"; then
    _MY_PKG_GUILE_SITE="${MY_PKG_GUILE_SITE}"
elif test "${prefix}" == "NONE"; then
    if test -n "${GUILE_SITE}"; then
        _MY_PKG_GUILE_SITE="${GUILE_SITE}"
    else
        _MY_PKG_GUILE_SITE="${ac_default_prefix}/share/guile/site"
    fi
else
    _MY_PKG_GUILE_SITE="${prefix}/share/guile/site"
fi

AC_MSG_NOTICE([Scheme modules will be installed under ${_MY_PKG_GUILE_SITE}])
AC_SUBST([_MY_PKG_GUILE_SITE])

dnl This logic decides where compiled Schmee files (`.go' files) shall be installed.
dnl The rules are as follows:
dnl   1. if ${MY_PKG_SITE_CCACHE} is non-empty, use that
dnl   2. if ${prefix} is "NONE"
dnl      - if GUILE_SITE_CCACHE is not empty, use GUILE_SITE_CCACHE
dnl      - use ${ac_default_prefix}/lib/guile/${GUILE_EFFECTIVE_VERSION}/site-ccache
dnl   3. use ${prefix}/lib/guile/${GUILE_EFFECTIVE_VERSION}/site-ccache

AC_ARG_VAR([MY_PKG_GUILE_CCACHE], [The directory under which compiled Scheme
files shall be installed.  Unset or empty means install into
$(libdir)/guile/$GUILE_EFFECTIVE_VERSION/site-ccache.])

if test -n "${MY_PKG_GUILE_CCACHE}"; then
    _MY_PKG_GUILE_CCACHE="${MY_PKG_GUILE_CCACHE}"
elif test "${prefix}" == "NONE"; then
    if test -n "${GUILE_SITE_CCACHE}"; then
        _MY_PKG_GUILE_CCACHE="${GUILE_SITE_CCACHE}"
    else
        _MY_PKG_GUILE_CCACHE="${ac_default_prefix}/lib/guile/${GUILE_EFFECTIVE_VERSION}/site-ccache"
    fi
else
    _MY_PKG_GUILE_CCACHE="${prefix}/lib/guile/${GUILE_EFFECTIVE_VERSION}/site-ccache"
fi

AC_MSG_NOTICE([Compiled Scheme files will be installed under ${_MY_PKG_GUILE_CCACHE}])
AC_SUBST([_MY_PKG_GUILE_CCACHE])    

my-package/Makefile.am:

moddir = $(_MY_PKG_GUILE_SITE)/my-package

ccachedir = $(_MY_PKG_GUILE_CCACHE)/my-package

SOURCES = foo.scm bar.scm

EXTRA_DIST = $(SOURCES)

GOBJECTS = $(SOURCES:%.scm=%.go)

# Per the Automake manual "If make built it, and it is commonly something that one would want to 
# rebuild (for instance, a .o file), then mostlyclean should delete it."-- that's why I put the 
# `.go' objects here.
MOSTLYCLEANFILES = $(GOBJECTS)

mod_DATA = $(SOURCES)

ccache_DATA = $(GOBJECTS)

# Here https://lists.gnu.org/archive/html/guile-devel/2010-07/msg00125.html is the famous e-mail
# (I've seen this one e-mail message cited in multiple Guile projects at which I've looked) explaining
# how to install the script first, and then their compiled `.go' counterparts, so that the compiled
# versions will have a later timestamp.
guile_install_go_files = install-ccacheDATA

$(guile_install_go_files): install-modDATA

GUILD_WARNINGS = -Wunbound-variable -Warity-mismatch -Wformat

SUFFIXES = .scm .go

.scm.go:
        $(GUILD) compile -L $(srcdir)/.. $(GUILD_WARNINGS) -o "$@" "$<"

I've been using this in my Guile Scheme projects for some time now, and it has worked well for my needs. Still, it feels inelegant, not in keeping with the intent of GNU Autotools. Until Automake adds native support for Scheme projects, we will have to author our own rules. But using environment variables to control install destinations seems wrong, somehow. For instance, even with this solution, if GUILE_SITE_CCACHE on a given system is /usr/local/lib/x86_64-linux-gnu/guile/2.2/cache, and the user says configure --prefix=/usr/local, the compiled Scheme files will wind up in the wrong location (with my scheme, they would end up in /usr/local/lib/guile/$(GUILE_EFFECTIVE_VERSION)/site-ccache). Yes, if the user is careful, runs sudo make -n install and notices, they can override that with my environment variable, but still. Anyone else dealt with this?

03/19/20 11:30