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.

License

The following applies to all of Comfignat including this document:

Copyright 2013 - 2016 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

Directory Variables

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.

Comfignat defines several directory variables that control where in the target filesystem different kinds of files will get installed and where applications write their files at run time. Distributions and installing users are expected to override these variables according to their filesystem layout. They 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. The build project variables are suitable for embedding in programs where the directory names are needed at run time. In project files most of these variables are relative pathnames when a relocatable package is built. Otherwise they are absolute. In makefiles they are always absolute.

Another set of directory variables, which are derived from the first set but have the prefix “stage_” in their names, specify the directories under the staging directory where the files shall be written during the build. These variables are available to makefiles and build project files. Build projects shall use them in the attributes that control where generated files are placed. They are always absolute pathnames.

A third set, with the prefix “Make_”, are relative versions of the staging directory variables. These are meant for use in Make targets, prerequisites and other places where Make expects space-separated lists. This mitigates the problem that Make has with spaces in filenames. Using a relative pathname hides the pathname of the parent directory from Make so that spaces in that part of the pathname don't cause Make to read it as a list of multiple filenames. (In recipes it's usually possible to use quotes around a pathname to ensure that it won't be read as a space-separated list, so there the “stage_” variables should work.) These variables are only available to makefiles.

The directory objdir is meant for intermediate files, and srcdir and builddir are used to support separate build directories. For Make targets and prerequisites there are Make_stagedir, Make_objdir, Make_srcdir and Make_builddir

Where to Place Files

Programs
Libraries
Data
Documentation
System State
Intermediate Files

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 parent of applications' separate application-specific directories 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.

Shared and Static Libraries

Many libraries can be built either as a shared library or as a static library. To enable this, use the variable Comfignat.Library_Type for Library_Kind in the build-controlling project file, and the preprocessor symbol Library_Type in the usage project file. The default will then be to build a shared library, but installing users and distributions can build a static library instead by setting library_type to “static” on the Make command line. The possible values of Library_Type are the same as for Library_Kind: “dynamic”, “static”, and “relocatable” which is an alias for “dynamic”.

It may be necessary to also use Comfignat.Library_Type in a case construction to set certain attributes only for one kind of library. It may for example be desirable to set Library_Interface for a shared library but not for a static library, to get the best automatic elaboration in both cases. Here's a project file fragment that sets Library_Version and Library_Interface only when building a shared library:

   for Library_Kind      use Comfignat.Library_Type;
   case Comfignat.Library_Type is
      when "dynamic" | "relocatable" =>
         for Library_Version   use "libexample.so.1";
         for Library_Interface use ("Example");
      when "static" =>
         null;
   end case;

It is also possible to build both a shared and a static library in the same Make invocation by overriding LIBRARY_TYPE on the builder command line:

base: build_example.gpr
	${build_GPR} -margs -XLIBRARY_TYPE=dynamic
	${build_GPR} -margs -XLIBRARY_TYPE=static

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.

Relocatable Packages

A relocatable software package is a directory tree with programs and/or libraries that can be installed anywhere in a filesystem and function from there without recompilation. Such software can't use embedded absolute pathnames to refer to files in the package. Pathnames embedded in the files must be relative to some directory in the relocatable package.

If the Make variable relocatable_package is set to “true” on the command line, then Comfignat will use relative pathnames in preprocessor symbols and GNAT project variables for embedding. In usage project files the pathnames will be relative to gprdir so that the project file will reference the right directories regardless of how the package is relocated (as long as the package is moved as a whole and not rearranged internally). In build project files most directory variables will be relative to bindir. Provided that a program can find out the pathname of its own executable file, it can get bindir as the directory part of that, and then find the other directories relative to that directory. bindir will instead be relative to libexecdir so that a helper program in libexecdir can find bindir and then find the other directories from there.

Only runstatedir and lockdir are always absolute pathnames. Those directories belong to the operating system and are used for communication between subsystems. It wouldn't make sense for an application to have its own runstatedir.

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. (Essential arguments may of course also be specified in build project files.) 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:

${Make_statedir}/example/data/db: | ${Make_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.

postinstall

You may add a recipe to postinstall if there are commands that need to be run on the target system after the files have been installed. This will typically be commands that modify existing files on the target system. Normally “make install” runs postinstall after installing the files when DESTDIR is empty, but skips it when a directory is specified in DESTDIR, because such commands need to be run on the target system, not on a build server. Installing users can override this default behaviour by setting do_postinstall to “true” or “false”.

preinstall

This is like postinstall except that it's run before the files are installed, not after. Installing users can control it with do_preinstall.

install_files

You may add a recipe to install_files if any files have to be written, deleted or moved after the staged directory tree has been copied. Avoid using this if possible. It's better to stage all the files correctly under the staging directory in the build phase, but this hook exists if you really need it for some workaround.

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.5

Noteworthy Changes in Version 1.4

Noteworthy Changes in Version 1.3

Noteworthy Changes in Version 1.2