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_Libdir & "/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 $Libdir & "/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.

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 source files to be used in the compilation of software using libraries.
Libdir
The directory for binary libraries to be used by other software, and the top-level directory for other architecture-specific 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";

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 to the right directory 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. 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 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");

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.