Comfignat

Comfignat is common, convenient, command-line-controlled compile-time configuration of software built with the GNAT tools on Unix-like operating systems. It consists of a makefile foundation to be included by your makefile, and an abstract GNAT project file to be imported by your project files. Leveraging GNU Make and Gnatprep, Comfignat adds the flexibility that GNAT project files lack, so that programs and libraries whose build systems are built around Comfignat can easily be configured for all sorts of use cases. Comfignat also helps with configuration and installation of files that GNAT project files don't handle, so that the build system can install the whole software package, not just the compiled files. At the same time Comfignat greatly reduces the amount of Make code that needs to be written for every new project.

Features

Download

The code is available for download as a tarball, and is also browsable on Gitorious.

License

The following applies to all of Comfignat including this document:

Copyright 2013 Björn Persson, Bjorn@Rombobjörn.se

This material is provided as is, with absolutely no warranty expressed or implied. Any use is at your own risk.

Permission is hereby granted to use or copy these files for any purpose, provided the above notices are retained on all copies. Permission to modify the code and to distribute modified code is granted, provided the above notices are retained, and a notice that the code was modified is included with the above copyright notice.

Getting Started

This is the least that you have to do to use Comfignat:

Example

Here's a complete set of project files and makefile containing everything that is necessary for building an uncomplicated shared library and installing it where the user wants it:

build_example.gpr
with "comfignat.gpr";

library project Build_Example is
   for Library_Name      use "example";
   for Library_Kind      use "dynamic";
   for Library_Version   use "libexample.so.1";
   for Library_Interface use ("Example");
   for Object_Dir        use Comfignat.Objdir;
   for Library_Src_Dir   use Comfignat.Stage_Includedir & "/example";
   for Library_Dir       use Comfignat.Stage_Libdir;
   for Library_ALI_Dir   use Comfignat.Stage_Alidir & "/example";
end Build_Example;
example.gpr.gp
#if Directories_GPR'Defined then
with $Directories_GPR;
#end if;

library project Example is
   for Library_Name     use "example";
   for Library_Kind     use "dynamic";
   for Source_Dirs      use ($Includedir & "/example");
   for Library_Dir      use $Libdir;
   for Library_ALI_Dir  use $Alidir & "/example";
   for Externally_Built use "true";
end Example;
Makefile
include comfignat.mk

build_GPRs = build_example.gpr
usage_GPRs = example.gpr

How to Use Comfignat

Where to Place Files

During the build, the files that will be installed are collected in a directory structure under a staging directory whose name is held in the variable stagedir. In the installation step that whole directory structure is copied to the directory specified in DESTDIR, or to the root directory if DESTDIR is empty. Compiled programs, libraries, ALI files and needed library sources are written to the staging directory by the GNAT tools if the build-controlling project files are written correctly. Comfignat automatically stages usage project files. To get other files installed, the makefile needs to either copy them to the appropriate directory under the staging directory, or instruct the tools that generate those files to write them there.

Comfignat defines several directory variables to allow distributions and installing users to control where in the filesystem different kinds of files get installed and where applications write their files at run time. Variables whose names begin with “stage_” point to the directories under the staging directory where the files shall be written during the build. Variables without the “stage_” prefix tell where the files will be in the target system after installation, and are suitable for embedding in programs where the directory names are needed at run time, and in usage project files.

Directory variables are available as Make variables to makefiles that include comfignat.mk, as GNAT project variables to build project files that import comfignat.gpr, and as preprocessor symbols to usage project files that are preprocessed with Gnatprep. If the Make variable relocatable_package is set to “true” on the command line, then the variables for embedding will be relative to bindir in build project files, and relative to gprdir in usage project files, except that bindir will instead be relative to libexecdir, and runstatedir and lockdir are always absolute.

Directories Projects

A directories project is a GNAT project file that defines directory variables for use by other project files. It may be defined by an operating system and contain the standard directories on that system, or it may be written by a system administrator to encode local policy. Comfignat configures project files to use a directories project if the Make variable dirgpr is set on the command line.

A Comfignat-compatible directories project shall define the following variables:

Hardware_Platform
A short string, suitable for use in filenames, that identifies the current target architecture.
Bindir
The directory for programs that can be run from a command prompt.
Libexecdir
The top-level directory for programs that are intended to be run by other programs rather than by users.
Includedir
The top-level directory for (normally architecture-independent) source files to be used in the compilation of software using libraries.
Archincludedir
The parent of libraries' separate library-specific directories for architecture-specific source files to be used in the compilation of software using libraries, for any libraries that absolutely must install such files.
Libdir
The directory for binary libraries to be used by other software, and the top-level directory for other architecture-specific files.
Alidir
The parent of libraries' separate library-specific directories for Ada library information files.

Here's an example of what a directories project may look like:

abstract project System_Directories is

   type Platform_Type is ("i386", "x86_64", "ppc", "ppc64", "ia64");
   Hardware_Platform : Platform_Type := external ("HARDWARE_PLATFORM");

   case Hardware_Platform is
      when "i386" | "ppc" | "ia64" =>
         Lib := "lib";
      when "x86_64" | "ppc64" =>
         Lib := "lib64";
   end case;
   Libdir := "/usr/" & Lib;

   Bindir         := "/usr/bin";
   Libexecdir     := "/usr/libexec";
   Includedir     := "/usr/include";
   Alidir         := Libdir;
   Archincludedir := Libdir & "/include";

end System_Directories;

This directories project belongs in a multiarch operating system where libraries are kept in either /usr/lib or /usr/lib64 depending on which architecture they are compiled for. The directories project sets Libdir, Alidir and Archincludedir to the right directories for the target architecture based on an environment variable. A library project that uses this directories project will therefore automatically adapt to the current target architecture, so that 32-bit and 64-bit instances of the library can be installed in parallel and the right library will be used in every build.

Options

Your software may have optional features or properties that can be enabled or disabled at build time. Comfignat can help you define options for those. Each option is represented as a Make variable whose value can be “true” or “false”, which installing users and distributions are expected to override on the command line. The names of these variables should be listed in the variable options. Each option should also be assigned a default value, unless it shall be mandatory to always set it on the command line. Comfignat will check that the variables listed in options have valid Boolean values.

Here's a makefile fragment that defines two options:

options = enable_frobnicator atomic_doodads
enable_frobnicator = false
atomic_doodads = true

Options listed in options will be conveyed as preprocessor symbols to preprocessed files and as external variables to build project files.

Build Tools and their Arguments

There are several options variables that let installing users and distributions control which arguments the build tools are invoked with. They have names that end with “FLAGS”, and are documented in INSTALL. The value of GNATFLAGS is a combination of the other options variables and must not be modified in a way that disregards the other variables. Apart from that restriction you can assign default values to optional arguments in these variables, but be sure to do the assignments with “?=” so that environment variables can override your defaults.

The value of Gnatprep_arguments will be passed to Gnatprep when a file is preprocessed, and builder_arguments will be passed to GPRbuild or Gnatmake when a project is built. These variables are not meant to be overridden by users. They may be used for preprocessor symbols, external variables for project files or other arguments that are essential for the build to work. Global default values for optional arguments should be set in the options variables instead.

The program-name variables GNATPREP and GNAT_BUILDER allow installing users and distributions to control the commands that invoke the build tools, for example to use a specific version or a wrapper. You can set GNAT_BUILDER to “gnatmake” if you want to build with Gnatmake instead of GPRbuild by default, but again be sure to do the assignment with “?=” so that environment variables can override your default.

Persistent Configuration

Those Make variables that installing users are expected to change can be configured persistently. Run “make configure” with some variables set on the command line or in the environment. Those variables will then be saved in a file named comfignat_configuration.mk, which will be loaded in all subsequent Make invocations. Additional variables can be configured incrementally. Make variables that can be overridden by environment variables can also be configured from the environment, whereas those variables that can only be overridden on the command line can only be configured from the command line. In subsequent Make invocations environment variables override values that were configured from the environment, and variables set on the command line override all configured values. The configuration can be erased with “make unconfigure” or as a part of “make distclean”.

The command “make show_configuration” may be used to view the current configuration. It outputs the configured variables in Make assignment syntax, but easier to read than the actual configuration file. Variables that were configured from the command line are shown as ordinary assignments with “=”, and those that were configured from the environment are shown as conditional assignments with “?=”.

The variables that can be configured are listed in the variable configuration_variables. The variables listed in options are included. You can make additional variables configurable by appending their names to configuration_variables.

Separate Build Directories

Instead of building in the source tree you can use a separate build directory. All generated files will then be written under the build directory and the source tree will not be modified. You can have several build directories with different configuration files in them. To set up a new build directory, run “make configure builddir=/some/pathname”. The variable builddir will not be saved in the configuration; instead a configuration file will be written in the specified directory. A makefile will also be written in the build directory unless there is one already. This generated makefile will delegate all commands to the main makefile in the source directory so that Make can conveniently be invoked from the build directory.

If you use separate build directories, then you should do all your builds in separate build directories and not build anything in the source directory. If there are generated files with the same name both in the source directory and in the build directory, then the wrong file may be used in some cases.

If a build project file is preprocessed with Gnatprep, then the preprocessed file will be in the build directory, so it can't refer to source directories with pathnames relative to the project file or rely on the source files being in the same directory as the project file. The preprocessor symbol Srcdir must be used in the value of Source_Dirs. Its value is the directory where comfignat.mk is, which is usually the root of the source tree. Here's an example:

for Source_Dirs use ($Srcdir & "/tools");

Writing Make Rules

Building GNAT Projects

The projects that are listed in build_GPRs will be built by default. Any other project needs a rule to control when it is built. Such a rule shall use the variable build_GPR in its recipe. build_GPR contains a command that performs a build controlled by the first project file among the rule's prerequisites. The command is affected by program-name and options variables, along with builder_arguments and options. A library that comes with some demo programs might have a rule like this to build the demos only on explicit request (also ensuring that the library has been built first):

demo_programs: demos.gpr build
	${build_GPR}

Making Directories

There is no need to write rules to make directories. Comfignat has a pattern rule that matches all pathnames that end with a slash, and creates the directory and its ancestors. Just specify the directory as an order-only prerequisite and be sure to append a slash. This rule ensures that the directory data exists before the file db is written:

${stage_statedir}/example/data/db: | ${stage_statedir}/example/data/
	echo stuff > $@

Make Targets

These phony targets are defined in comfignat.mk:

build

This is what a plain “make” will do (unless your makefile defines some other target before it includes comfignat.mk). By default it builds and stages the build projects that are listed in build_GPRs, and preprocesses and stages any usage project files that are listed in usage_GPRs. You may add additional prerequisites if there are other things that should be built and installed by default, for example documentation:

build: man html pdf
install

If a user unpacks a source package and immediately runs “make install”, then the build target is built and then installed. If some files have already been staged in the staging directory, then “make install” doesn't rebuild anything but just copies the staged directory structure to the directory specified in DESTDIR, or to the root directory if DESTDIR is empty.

all

By default all is the same as build, but you may add additional prerequisites to it if you have optional components that shouldn't be built by default. Such targets will be built by “make all” but not by a plain “make”:

all: demo_programs auxiliary_tools
base

This builds and stages the build projects that are listed in build_GPRs, but does not do everything that build does. It can be used to bypass targets that you don't want to rebuild all the time when you're programming. Any targets that you do want to rebuild every time may be added as prerequisites.

preprocess

This preprocesses files that need to be preprocessed before projects are built.

configure

make configure” is used to set values in the persistent configuration and to set up a separate build directory. This is a double-colon rule so you can add your own configure recipe in case you need to configure things that can't easily be expressed as Make variables.

show_configuration

make show_configuration” outputs the configured variables in the persistent configuration. This is a double-colon rule so you can add your own show_configuration recipe to accompany your own configure recipe.

unconfigure

This deletes the configuration file that “make configure” writes. This is a double-colon rule. If you add your own configure recipe that writes additional configuration files, then you should also add an unconfigure recipe to delete those files.

clean

This deletes all files that are normally created by building the software, but preserves the configuration. It's a double-colon rule so you can add your own clean recipe to delete files that your recipes generate.

distclean

This deletes all files that are normally created by configuring or building the software. In an unpacked source tree where builds have been done but no other files have been created, “make distclean” leaves only the files that were in the source package. In a separate build directory it leaves only the delegating makefile.

Adjusting the Installation Instructions

After writing your makefile and project files, you should adapt the installation instructions in INSTALL to your project. The file will be useful to users as-is, but it will be more helpful if you edit it. Put the title of your project in the heading, add information about optional features and testing, and delete parts that don't apply to your project.

News

Noteworthy Changes in Version 1.2