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.
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.
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 (or, in some cases, a subdirectory of 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.
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
Programs that can be run from a command prompt shall be placed in Comfignat.Stage_Bindir by build project files. Any executable files that aren't built with GNAT tools shall be placed in stage_bindir by makefiles.
Programs that are only to be run by other programs, not by users, shall be placed under a separate subdirectory of Comfignat.Stage_Libexecdir by build project files – or under stage_libexecdir by makefiles. Programs that need to invoke such programs shall have Comfignat.Libexecdir compiled in.
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 restructure 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 automatically puts usage project files in stage_gprdir.
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.
Locale-specific message catalogs shall be placed in the appropriate subdirectories under stage_localedir. Programs shall look for them under Comfignat.Localedir.
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.
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.
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.
It is recommended to keep intermediate files in objdir. Then Comfignat automatically knows how to delete them in “make clean”. Build project files shall use Comfignat.Objdir for Object_Dir. Makefiles may use Make_objdir.
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.
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
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.
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.
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.
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:
${Make_statedir}/example/data/db: | ${Make_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.
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”.
This is like postinstall except that it's run before the files are installed, not after. Installing users can control it with do_preinstall.
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.
“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 names of variables for use in Make rules were changed to begin with “Make_” instead of “make_”. The old names were too confusing as they looked like commands rather than variables.
The Make targets preinstall, postinstall and install_files were added.
The predefined phony targets were explicitly marked as phony so that a file with the same name won't break them.
A testsuite was introduced in this version.
A problem with spaces in pathnames was mitigated by using relative pathnames in places where Make expects a space-separated list. Thus Make doesn't see the names of parent directories, so spaces in those names don't cause any problems.
The interaction between directory variables and directories projects was corrected so that a directories project overrides the default values of some directory variables, but an explicitly set Make variable overrides the corresponding variable in the directories 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 options variable GNATPREPFLAGS was added.
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.