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.
Users and distributions can build and install the software with the traditional commands “make” and “make install”.
All aspects of the build are fully configurable through directory variables, program-name variables and options variables. These configuration variables are compatible with the GNU Coding Standards.
DESTDIR is supported, so installation can be done to a staging directory or directly to the target system.
Configuration variables can be saved in a file and will then be used in every subsequent Make invocation. This is convenient in particular for developers who can configure debugging options or nonstandard paths in their development environments.
The build can be controlled entirely from a command prompt, which means that it can be scripted. There is no need to edit or patch configuration files. This is important in particular for distributions with automated build systems.
Users who build and install from source do not need to run a configuration script. They can do any configuration they need directly in the Make command.
Directories projects are supported. A directories project is a GNAT project file that defines directory variables for use by other project files. If a directories project is provided, then Comfignat will configure the project files to use the directory variables it defines.
Out-of-tree building is supported. There can be several separate build directories with different configurations.
Multiarch operating systems are supported. Library instances built for different architectures can coexist in a computer system if the system provides a directories project that refers to different directories depending on the target architecture.
Relocatable binary packages can be built. Comfignat can be instructed to convert the directory variables into relative pathnames and configure the project files with those, so that the installed directory tree as a whole can be moved to another location in the filesystem without breaking the project files.
Gnatmake and GPRbuild are both supported, which makes Comfignat suitable for both mixed-language projects and pure Ada projects.
The Make code that is needed to do all of this is generic, so that only a minimal amount of Make code needs to be written for each project. An uncomplicated project may need only two or three lines of Make code to build and install the software itself. Test suites, documentation et cetera add to this.
Make is not a hard requirement. Comfignat uses some advanced features of GNU Make that may not be supported by other clones and forks of Make. If a system's native Make doesn't have those features and GNU Make isn't available, then it's possible to bypass Make and run first Gnatprep and then Gnatmake or GPRbuild manually. It's less convenient to build that way though, files that the GNAT tools don't handle must then be installed in some other way, and some of the features listed here are lost.
The code is available for download as a tarball, and is also browsable on Gitorious.
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.
This is the least that you have to do to use Comfignat:
Copy the files comfignat.gpr.gp, comfignat.mk and INSTALL to your source tree.
Write a GNAT project file to control the build. Import “comfignat.gpr” (without “.gp”) and use the variables that the project Comfignat defines.
Use Comfignat.Objdir for Object_Dir.
If your project file builds a program, then use Comfignat.Stage_Bindir for Exec_Dir, unless the program is only intended to be executed by other programs and not run manually from a command prompt, in which case it should be placed under Comfignat.Stage_Libexecdir.
If your project file builds a library, then use a subdirectory of Comfignat.Stage_Includedir for Library_Src_Dir, Comfignat.Stage_Libdir for Library_Dir, and a subdirectory of Comfignat.Stage_Alidir for Library_ALI_Dir.
If your project is a library, then write a project file for other projects to import to use the library. Give it a filename that ends with “.gpr.gp”. It will be run through Gnatprep to generate the actual project file without the “.gp” suffix. Make it import the project file that the preprocessor symbol Directories_GPR specifies, but only if Directories_GPR is defined. Do not import comfignat.gpr. Use the symbol Includedir in the value of Source_Dirs, use Libdir for Library_Dir, and use Alidir as part of Library_ALI_Dir.
Write a makefile that includes comfignat.mk. The makefile shall set the variable build_GPRs to the filename of the build-controlling project file. For a library it shall also set the variable usage_GPRs to the filename that the usage project file will have after preprocessing. That's all that is required of the makefile. Everything else is optional.
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
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.
Programs that can be run from a command prompt shall be placed in stage_bindir. Build project files shall use Comfignat.Stage_Bindir.
Programs that are only to be run by other programs, not by users, shall be placed under stage_libexecdir, Comfignat.Stage_Libexecdir in build project files. If there are several such programs they should probably be under a separate subdirectory. Programs that need to invoke such programs shall have Comfignat.Libexecdir compiled in.
Idiosyncratic read-only architecture-independent data files shall be placed under a separate subdirectory of stage_datadir. Programs shall look for them under Comfignat.Datadir.
Configuration files – host-specific data files that aren't modified in the normal course of their use but may be modified by system administrators – shall be placed under stage_sysconfdir. If there are several they should probably be under a separate subdirectory. Programs shall look for them under Comfignat.Sysconfdir.
Idiosyncratic variable data files shall be placed under a separate subdirectory of stage_statedir. Programs shall read and write them under Comfignat.Statedir.
If your program keeps cached data files that it can regenerate if they are deleted, then those files shall be kept under a separate subdirectory of Comfignat.Cachedir. You won't install cached files but you may want to create the subdirectory under stage_cachedir.
Log files shall be written under Comfignat.Logdir. You won't install log files but you may want to create a separate subdirectory under stage_logdir if your program writes its own log files.
Small files that take part in describing the state of the system and that exist only while the program is running, such as process identifier files and transient Unix-domain sockets, shall be sought and created under Comfignat.Runstatedir. (This is not the place for temporary files in general.)
Lock files that are used to prevent multiple programs from trying to access a device or other resource at the same time shall be sought and created under Comfignat.Lockdir.
Ada specifications, C headers and other source files that are needed for compilation of other software that uses your libraries shall be placed under Comfignat.Stage_Includedir by build project files, usually under a separate subdirectory. Usage project files shall get the directory from the preprocessor symbol Includedir. These files should normally be architecture-independent.
If you have architecture-specific source files that you need to make available to other software that uses your libraries, then you should refactor your code to encapsulate the architecture-specific bits in the compiled code and keep the API clean. But if you really must install such files, then you may put them under a separate subdirectory of stage_archincludedir. Usage project files shall get the directory from the preprocessor symbol Archincludedir.
Binary libraries shall be placed in Comfignat.Stage_Libdir by build project files. Usage project files shall get the directory from the preprocessor symbol Libdir.
Ada library information (ALI) files shall be placed under a separate subdirectory of Comfignat.Stage_Alidir by build project files. Usage project files shall get the directory from the preprocessor symbol Alidir.
Other architecture-specific files shall usually be placed under a separate subdirectory of stage_libdir. (It will be the same subdirectory that ALI files are placed in when alidir = libdir.) Programs shall look for them under Comfignat.Libdir.
Comfignat puts usage project files in stage_gprdir.
Locale-specific message catalogs shall be placed in the appropriate subdirectories under stage_localedir. Programs shall look for them under Comfignat.Localedir.
Documentation in the Man format shall be placed in the appropriate subdirectories under stage_mandir.
Documentation in the Info format shall be placed in stage_infodir.
Other documentation files shall be placed under a separate subdirectory of stage_miscdocdir.
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:
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.
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.
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.
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.
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");
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}
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 > $@
These phony targets are defined in comfignat.mk:
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
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.
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
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.
This preprocesses files that need to be preprocessed before projects are built.
“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.
“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.
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.
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.
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.
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.
The directory variables alidir and archincludedir were added.
The directory variable runtimedir was renamed to runstatedir to keep Comfignat compatible with the GNU Coding Standards.
Comfignat.Bindir was made relative to Comfignat.Libexecdir when relocatable_package is true.
Comfignat's behaviour in sub-Makes was fixed so that subprocesses working in subdirectories use the right build and staging directories.
The ability to save environment variables in the persistent configuration was added.
The pattern rule for making directories was added.
The Make targets all, base and show_configuration were added.