Table of Contents
While the whole guide is designed toward explaining the mechanisms and the behaviour of autotools, it was never meant, by itself, to be a tutorial. This appendix should cover those basis, by providing an easy path on writing an autotools build system for your project.
As already described in the rest of this guide, the name “Autotools”, we refer to a number of
different tools. If you have a very simple program (not hellow-simple, but still simple), you
definitely want to use at the very least two: autoconf and
automake. While you could use the former without the latter, you
really don't want to. This means that you need two files: configure.ac
and Makefile.am
.
The first of the two files is processed to produce a configure
script
which the user will be executing at build time. It is also the bane of most people because, if
you look at one for a complex project, you'll see lots of content (and logic) and next to no
comments on what things do.
Lots of it is cargo-culting and I’m afraid I cannot help but just show you a possible basic
configure.ac
file:
AC_INIT([myproject], [123], [flameeyes@flameeyes.com], [https://autotools.io/]) AM_INIT_AUTOMAKE([foreign no-dist-gz dist-xz]) AC_PROG_CC AC_OUTPUT([Makefile])
The first two lines are used to initialize autoconf and
automake respectively. The former is being told the name and
version of the project, the place to report bugs, and an URL for the package to use in
documentation. The latter is told that we’re not a GNU project (seriously, this is important —
this way you can avoid creating 0-sized files just because they are mandatory in the default
GNU layout, like NEWS
), and that we want a .tar.xz
tarball and not a .tar.gz
one (which is the default). See Section 1, “Available options” for more details.
After initializing the tools, you need to, at the very least, ask for a C compiler. You could
have asked for a C++ compiler as well, but I’ll leave that as an exercise to the
reader. Finally, you got to tell it to output Makefile
(it’ll use
Makefile.in
but we’ll create Makefile.am
instead
soon).
To build a program, you need then to create a Makefile.am
similar to
this:
bin_PROGRAMS = hellow dist_doc_DATA = README
Here we’re telling automake that we have a program called
hellow
(which sources are by default hellow.c
) which
has to be installed in the binary directory, and a README
file that has
to be distributed in the tarball and installed as a documentation piece. Yes this is really
enough as a very basic Makefile.am
.
If you were to have two programs, hellow
and hellou
,
and a convenience library between the two you could do it this way:
bin_PROGRAMS = hellow hellou hellow_SOURCES = src/hellow.c hellow_LDADD = libhello.a hellou_SOURCES = src/hellou.c hellow_LDADD = libhello.a noinst_LIBRARIES = libhello.a libhello_a_SOURCES = lib/libhello.c lib/libhello.h dist_doc_DATA = README
But then you’d have to add AC_PROG_RANLIB
to the
configure.ac
calls. My suggestion is that if you want to link things
statically and it’s just one or two files, just go for building it twice… it can actually
makes it faster to build (one less serialization step) and with the new LTO options it should
very well improve the optimization as well.
Let’s start from a fundamental rule: if you’re not going to install a library, you don’t want
to use libtool. Some projects that only ever deal with programs
still use it because that way they can rely on .la
files for static
linking. My suggestion is (very simply) not to rely on them as much as you can. Doing it this
way means that you no longer have to care about using libtool for
non-library-providing projects.
But in the case you are building any library, using libtool is important. Even if the library is internal only, trying to build it without libtool is just going to be a big headache for the packager that looks into your project. Before entering the details on how you use this tool, though, let’s look into something else: what you need to make sure you think about, in your library.
First of all, make sure to have an unique prefix to your public symbols, be them constants,
variables or functions. You might also want to have one for symbols that you use within your
library on different translation units — my suggestion in this example is going to be that
symbols starting with foo_
are public, while symbols starting with
foo__
are private to the library. You’ll soon see why this is important.
Reducing the amount of symbols that you expose is not only a good performance consideration, but it also means that you avoid the off-chance to have symbol collisions which is a big problem to debug. So do pay attention. There is another thing that you should consider when building a shared library and that’s the way the library’s ABI is versioned but it’s a topic that goes in quite deep, so just see Section 4, “Library Versioning” for further details.
Once you got these details sorted out, you should start by slightly change the configure.ac file from the previous post so that it initializes libtool as well:
AC_INIT([myproject], [123], [flameeyes@flameeyes.com], [https://autotools.io/]) AM_INIT_AUTOMAKE([foreign no-dist-gz dist-xz]) LT_INIT AC_PROG_CC AC_OUTPUT([Makefile])
Now it is possible to provide a few options to LT_INIT
for instance to
disable by default the generation of static archives. My personal recommendation is not to
touch those options in most cases. Packagers will disable static linking when it makes sense,
and if the user does not know much about static and dynamic linking, they are better off
getting everything by default on a manual install.
On the Makefile.am
side, the changes are very simple. Libraries built
with libtool have a different class than programs and static
archives, so you declare them as lib_LTLIBRARIES
with a
.la
extension (at build time this is unavoidable). The only real
difference between _LTLIBRARIES
and _PROGRAMS
is that
the former gets its additional links from _LIBADD
rather than
_LDADD
like the latter.
bin_PROGRAMS = fooutil1 fooutil2 fooutil3 lib_LTLIBRARIES = libfoo.la libfoo_la_SOURCES = lib/foo1.c lib/foo2.c lib/foo3.c libfoo_la_LIBADD = -lz libfoo_la_LDFLAGS = -export-symbols-regex '^foo_[^_]' fooutil1_LDADD = libfoo.la fooutil2_LDADD = libfoo.la fooutil3_LDADD = libfoo.la -ldl pkginclude_HEADERS = lib/foo1.h lib/foo2.h lib/foo3.h
The _HEADERS
variable is used to define which header files to install and
where. In this case, it goes into ${prefix}/include/${PACKAGE}
, as I
declared it a pkginclude
install.
The use of -export-symbols-regex (See also Section 3, “Exposing and Hiding Symbols”) ensures that only the symbols that we want to have publicly available are exported and does so in an easy way.