Russ Allbery > Technical Notes > Coding Style | Package Documentation Style > |
I use the following coding style and best practices for the build system of any software project that requires compilation. Currently, this page only documents software composed of C source; Perl packages work differently. Documentation for Perl build system coding style will be added when I have a chance.
General Guidelines
Autoconf
Automake
Shared Libraries
Creating a Distribution
All C software should use Autoconf and Automake as its build system. Details about how to use Autoconf and Automake, including which Automake options to use, is the topic of much of the rest of this document. Packages that build shared libraries should use Libtool to build and install those shared libraries. That's the current best way to maintain portable shared libraries. This includes loadable modules such as PAM modules.
All C software must support a separate build directory from the source
directory, configured by creating an empty build directory and then
invoking configure
with the full path to the script in the source
directory. In this mode, no rule should attempt to write to the source
directory. This can be verified using the Automake distcheck
target, mentioned under creating a distribution. Since the
build systems for Perl, Python, and PHP add-on modules do not support this
sort of separate build environment, the surrounding build system generally
has to copy the relevant files for those add-ons into the build directory
before recursing into the language-specific build infrastructure.
All C software must support installation into a different root directory
from that configured via the configure
script by installing all
files relative to the DESTDIR variable, which can be set on the
make
command line. Automake by and large handles this
automatically, but be careful of writing custom installation rules that
don't support DESTDIR. This too can normally be verified with the
Automake distcheck
target.
When using Autoconf and Automake, put an executable shell script at the
top of the source tree named autogen
. This should start with:
#!/bin/sh set -e autoreconf -i --force rm -rf autom4te.cache
to rebuild all the Autoconf and Automake files. It should also use
pod2man
to generate any manual pages, generate any protocol
documentation, and do anything else required to go from the contents of
the revision control repository to what's distributed as part of the
end-user-ready package.
Software configuration and portability should be done using GNU Autoconf. I feel free to require the version available from current Debian unstable when I'm working on the package if there are any new features that are useful for that package. Do not bother reinventing wheels or patching around Autoconf bugs in order to remain compatible with old versions of Autoconf. The end-user distribution contains pre-built scripts, and life is too short to resolve problems already solved by others.
For details on writing good configure scripts, review the Autoconf manual (available via info autoconf). Reading the manual through once is highly recommended, to get an idea fo the tests that are already available. In addition to Autoconf best practices from the manual, I also follow these rules:
Always name the source of the main configure script
configure.ac
, not configure.in
. Elsewhere in an
Autoconf source tree, .in
means that the base file is generated
by performing variable substitution on the .in
file via
config.status
. This is not the case for configure
, so
configure.ac
is clearer.
Always indicate the required version of Autoconf in the
configure.ac
file by using AC_PREREQ. Always use the
three-argument form of AC_INIT, and maintain the package version
number there, just using that value in any other build files that need
to know it. The version number should only be maintained in three
places in the source tree: configure.ac
, README
, and
NEWS
.
Always use build-aux
as the directory for supporting files,
portable
as the directory for AC_REPLACE_FUNCS replacements,
and m4
as the directory for macro files. This means the
following macros in configure.ac
:
AC_CONFIG_AUX_DIR([build-aux]) AC_CONFIG_LIBOBJ_DIR([portable]) AC_CONFIG_MACRO_DIR([m4])
Do not probe for portability issues in configure unless the source
code is prepared to deal with the results. In other words, it's
pointless to check for the existence of, say, unistd.h
if the
source code isn't prepared to do something differently if
unistd.h
isn't available. Unless you are very familiar with
the reasons for such tests, don't use them unless you're able to test
the failing case on some platform. As a quick rule of thumb, unless
the HAVE_ macro defined by Autoconf is used somewhere in the source
code, the Autoconf test should probably be omitted.
Assume ANSI C and a reasonably modern C library. In particular, Id do
not support SunOS and it's not necessary to check for problems (like
broken memcmp or a missing string.h
header) that only occur on
SunOS.
Use AC_REPLACE_FUNC where possible to handle portability issues where a standard system function is missing or broken on some platforms. This allows one to code to the standard and then conditionally compile replacements of those functions only on those platforms that need them. There is a library of replacement functions already available in rra-c-util, and I add any new ones to that package as I encounter a need for them.
When writing macros to probe for libraries, don't put the flags for
those libraries into the top-level AM_CPPFLAGS, etc., variables.
Someone may want to only use that library for a small part of the code
base. Always store any discovered flags in LIBRARY_CPPFLAGS,
_LDFLAGS, and _LIBS, where LIBRARY is some short name for the library
being probed. -I
flags to specify include paths go into
CPPFLAGS, -L
flags to specify library locations go into
LDFLAGS, and the actual libraries go into LIBS. (In rare cases, you
may have other compiler flags, which go into CFLAGS for that library.)
Always use a configuration header (AC_CONFIG_HEADERS) rather than using DEFS and passing all of the Autoconf-defined symbols to the compiler on the command line. It makes the build look a lot cleaner. Everything which can be set in the configuration header rather than stuck into the Makefile should be. In particular, try not to ever add -D flags to CFLAGS; instead, put the appropriate define into a configuration header.
Build-time options should ideally be avoided (can it be made a
command-line switch or run-time configuration option instead?), but if
necessary, should be controlled with an --enable
switch to
configure (AC_ARG_ENABLE) if they are internal options. Use
--with
(AC_ARG_WITH) instead if they are optional features
enabled by the presence of other libraries. Don't use C header files
that the user has to edit. Optional features enabled by other
libraries should follow the pattern of failing configure
if the
--with
flag was given and the library wasn't found, neither
checking for nor enabling use of the library if --without
was
given (even if the library is installed), and probing for the library
and using it only if it exists if neither flag was given.
For any --with
flag specifying a library location, the argument
to the basic --with-foo
flag should specify the prefix where
foo was installed, expecting include
and lib
subdirectories. There should be separate --with-foo-include
and --with-foo-lib
flags that can set the library and include
paths separately if necessary. Use RRA_SET_LIBDIR from
rra-c-util to handle lib32
and lib64
directories required on some platforms.
Significant self-contained tests should be kept as separate m4 files
in a subdirectory named m4
, which Automake is configured to
include (see below). I then include any generally
useful m4 file in the next version of
rra-c-util.
Always use non-recursive Automake with a single Makefile.am
at the
top level that builds the entire source tree. This means referring to all
files using relative paths and following the Automake manual documentation
on how to use non-recursive Automake. It also requires adding
subdir-objects
to AUTOMAKE_OPTIONS in Makefile.am
and adding
AM_PROG_CC_C_O to configure.ac
. There are many reasons to favor
non-recursive Automake, summed up by "Recursive Make
Considered Harmful".
Always use Automake in foreign mode (adding foreign
to
AUTOMAKE_OPTIONS in Makefile.am
). Otherwise, it requires too many
boilerplate files from the GNU coding standards that aren't useful to us.
Always use the following rules in configure.ac
:
AM_INIT_AUTOMAKE([1.11 check-news foreign silent-rules subdir-objects -Wall -Werror]) AM_MAINTAINER_MODE
check-news
is an additional check during distcheck
that
ensures that the NEWS file is updated with the current release version
before the release. foreign
suppresses various checks for files
that don't follow naming conventions I use. silent-rules
allows
people to select silent (Linux-kernel-style) build output, which makes
it much easier to see warnings. Silent rules requires Automake 1.11 or
later. subdir-objects
forces use of non-recursive make.
Finally, enabling maintainer mode support allows disabling the automated rebuild of build system files when timestamps differ, which is often undesireable for automated builds (such as in Debian package builds).
For project source organized into subdirectories, also add:
AM_PROG_CC_C_O
which is required for proper non-recursive make support. (Automake will warn that this is required.)
Makefile.am
Best Practices
Start any Makefile.am
with:
ACLOCAL_AMFLAGS = -I m4
and then the setting of EXTRA_DIST, which lists all files not involved in rules or Autoconf templates that should be included in the distribution (such as documentation files).
The GNU coding standards doesn't remove the results of running Autoconf
and Automake even on make maintainer-clean
, which I think is a
bug. That target should reverse all the results of running
./autogen
. This means something like:
MAINTAINERCLEANFILES = Makefile.in aclocal.m4 build-aux/compile \ build-aux/config.guess build-aux/config.sub build-aux/depcomp \ build-aux/install-sh build-aux/ltmain.sh build-aux/missing \ config.h.in config.h.in~ configure m4/libtool.m4 m4/ltoptions.m4 \ m4/ltsugar.m4 m4/ltversion.m4 m4/lt~obsolete.m4
if you're also using Libtool with this package. Without Libtool, there will be fewer files.
Include a warnings
target that builds the source tree with
aggressive warnings enabled. For the latest version of this target, see
rra-c-util's Makefile.am
. I
also enable quiet rules (V=0
) for this target to make it easier to
see warnings.
Support libraries internal to the package should not be built as shared, but all libraries intended for consumption by users outside of the package should be built as shared and static libraries by default, using the standard Libtool support for building libraries. All shared libraries should be built using Libtool, including loadable modules such as PAM modules.
Follow the library versioning instructions in the Libtool documentation, only changing library versions at most once per release. Any changes to library interfaces that result in an ABI change must result in a change to the SONAME, which is handled automatically by Libtool if one follows the versioning instructions. Changes in functionality that do not change the ABI need not change the SONAME unless they're likely to break programs written following the previous assumptions.
All objects that are included in shared libraries, including helper utility libraries, must be built both shared and non-shared. This means they need to be included in a helper libtool library that's then linked with the final library. The Automake documentation explains how to do this.
All shared libraries should use visibility control where possible. There are three ways to do this: during compilation, using shared library versioning, and using the libtool symbol export list. The first should always be used where possible. The third is a poor substitute for the second so should only be used when using real shared library versioning isn't possible. For information on compile-time visibility control, see the C coding style.
To use shared library versioning, first probe for whether it is supported
by including m4/ld-version.m4
from
rra-c-util in the package and calling
it in configure.ac with:
RRA_LD_VERSION_SCRIPT
This will set an Automake conditional indicating whether ld supports version scripts. Then, use code like the following in Makefile.am:
if HAVE_LD_VERSION_SCRIPT VERSION_LDFLAGS = -Wl,--version-script=${srcdir}/lib/<library>.map else VERSION_LDFLAGS = -export-symbols ${srcdir}/lib/<library>.sym endif
where <library> is the name of the library, and use
$(VERSION_LDFLAGS)
on the link line of the shared library (or do
something similar but with separate variables or conditionalizing the
LDFLAGS setting if there are multiple shared libraries).
This uses two files, which unfortunately have to be kept in sync. The
file matching the name of the library but with the .map
extension
should be a version script, which looks like this:
LIBRARY_1.0 { global: library_func1; library_func2; local: *; };
There are other, more complex things one can do with versioning scripts, but this covers nearly every need. LIBRARY should be the short name of the library, and the following version number the interface. The version here is used to ensure that if both the old and new version of the shared library are loaded at the same time, the appropriate function is called. It's therefore essential to change the version number if the calling parameters of functions changed (such as via changes to the internal structure of a private struct so that structs returned by the new library would cause problems when passed to the old library or vice versa).
All public symbols provided by the library, functions or variables, must
be listed in the global
section. (Public variables are generally a
bad idea and should normally be avoided.) The local
section then
hides all other symbols.
The .sym
file is much simpler. It's used by Libtool to hide
non-public symbols on platforms that don't provide symbol versioning and
should be a simple list of all public interfaces in the library, one per
line. Be sure to update both this file and the .map
file whenever
adding or removing an interface.
pkg-config is a tool to provide the flags required to
compile or link against a shared library. It's a replacement for complex
Autoconf probes and for all of the incompatible and occasionally buggy
*-config
scripts. All shared libraries should install a pkg-config
configuration file.
To do this, provide a template pkg-config configuration in a file named
after the library but with a .pc.in
extension. Here is an example:
prefix=@prefix@ exec_prefix=@exec_prefix@ includedir=@includedir@ libdir=@libdir@ Name: <library> Description: <short description> URL: <upstream url> Version: @PACKAGE_VERSION@ Cflags: -I${includedir} Libs: -L${libdir} -l<library> Libs.private: @KRB5_LDFLAGS@ @KRB5_LIBS@
Replace <library> with the official name of the library (which isn't
necessarily the library name with lib
removed). This is only
informational, may contain spaces, and isn't used to locate the file. Its
filename is used for that.
Replace <short description> with a short description of the purpose of the library. Often the short description of the package from the start of the README file works well here. Replace <upstream url> with the URL of the package's distribution page.
Normally, all Cflags
needs to contain is what's indicated above,
which lets the compiler find the header files if installed in a
non-standard location. For Libs
, note that pkg-config
distinguishes between the shared libraries that must be linked against on
a system with proper transitive dependency support and the libraries that
must be linked for a static build or on a system with poor support for
shared libraries. For a well-designed shared library, one should rarely
need anything in Libs
other than what's listed above.
Libs.private
, on the other hand, needs all the LDFLAGS and LIBS of
everything the library itself links with external to this package. In
this example, the shared library is linked with the Kerberos libraries and
therefore needs the Kerberos LDFLAGS and LIBS; modify as needed for your
example.
If the dependencies of the shared library also use pkg-config, just list
the other pkg-config file names in Requires
or
Requires.private
instead of using Libs
or
Libs.private
for them.
Finally, add the Automake code to build and install the pkg-config configuration file. It should look like this:
pkgconfigdir = $(libdir)/pkgconfig nodist_pkgconfig_DATA = lib/libfoo.pc lib/libfoo.pc: $(srcdir)/lib/libfoo.pc.in sed -e 's![@]prefix[@]!$(prefix)!g' \ -e 's![@]exec_prefix[@]!$(exec_prefix)!g' \ -e 's![@]includedir[@]!$(includedir)!g' \ -e 's![@]libdir[@]!$(libdir)!g' \ -e 's![@]PACKAGE_VERSION[@]!$(PACKAGE_VERSION)!g' \ -e 's![@]KRB5_LDFLAGS[@]!$(KRB5_LDFLAGS)!g' \ -e 's![@]KRB5_LIBS[@]!$(KRB5_LIBS)!g' \ $(srcdir)/lib/libfoo.pc.in > $@
Replace libfoo
with the actual name of the library and change its
path in the source package as needed. Note the pattern above and use it
for any Autoconf substitution variables that you need to replace in the
pkg-config template. The above example adds KRB5_LDFLAGS and KRB5_LIBS to
the variables; you may have other ones you need, particularly to construct
Libs.private
.
The above installs the pkg-config configuration file under
$(libdir)/pkgconfig
. $(datadir)/pkgconfig
is appropriate
instead if one is certain that the flags required to link with this
library will never vary by architecture, but that's not the case for most
libraries due to Libs.private
.
Note that the Automake documentation recommends using comma as the sed delimiter for doing this sort of trick, but LDFLAGS frequently contains a comma since it's the separator for parts of a linker-specific option. I use an exclamation point instead.
Rather than using this sort of sed script approach, one can instead just have Autoconf generate the pkg-config configuration (via AC_OUTPUT). However, this has two problems:
Autoconf variables may depend on other Autoconf variables, and Autoconf won't expand all those variables. This means that you need to list each variable and expand it in the pkg-config configuration file so that pkg-config can expand all the variables, but which variables you need isn't well-defined and is to some extent an implementation detail (and has changed in the past). The above solution using a Makefile.am fragment will cause make to expand all the variables for you.
Running configure
with one prefix setting and then overriding
that setting when running make
should build software using the
overridden prefix. This isn't necessary very often, but there are
times when it's very useful, particularly when rebuilding parts of the
tree. Generating the pkg-config configuration file from the Makefile
ensures that this works properly.
You may wish to substitute PACKAGE_NAME and PACKAGE_URL as well instead of hard-coding them in the pkg-config template. I usually hard-code them since they rarely change and have to be searched out and changed in multiple places in the source tree if they change anyway.
Using Automake means that most of the work in creating a distribution and
doing some basic testing is already handled for you. To generate the
distribution tarball, run make distcheck
. This will create a
subdirectory, copy the distributed files into it, and then test builds
outside the source directory, make check
, make install
, and
make uninstall
, and only if all that works, create the distribution
tarball.
Most problems with a distribution are from omitting files that Automake
isn't aware of since they're not involved in creating any compiled
software, or aren't mentioned in the expected rules. Such files should be
listed in EXTRA_DIST in Makefile.am
.
Packages that install Perl modules by default won't be able to use the
uninstallation check, since the Perl build system doesn't provide
make uninstall
. For those packages, add a rule like:
# Alas, we have to disable this check because there's no way to do an # uninstall from Perl. distuninstallcheck: @:
to Makefile.am
to skip that check.
Russ Allbery > Technical Notes > Coding Style | Package Documentation Style > |