The build system uses a non-recursive approach with GNU make, similar to the one documented in the paper Recursive Make Considered Harmful by Peter Miller, and adapting some Makefile code from here.
The paper describes many advantages to non-recursive Make; in short, it results in a cleaner and more robust dependency tree, and it means that the code can quite easily be compiled in parallel.
The system used here adds two key conveniences to the aforementioned system:
- Improved readability by moving standard code, especially the recursion, into template files wihin the .make directory. The recursion is located in stack.mk.
- The ability to call make from any child directory and have it behave the way you would expect it to if that child directory were the root.
Global configuration settings are set in the root Makefile, and then individual modules can each be configured via the Makefile in their own subdirectory (e.g. src/Makefile).
The subdirectory Makefiles should work more or less like normal Makefiles, although it is processed relative to the root directory, which means that any paths should be relative to the root - the variable $(d) is provided as shorthand for the relative path of the current module w.r.t. the root.
Each module is also required to define two variables that are used for the recursive processing:
MODULE_NAME
: The module name; should be a unique identifier. This is used for generating intermediate targets for each specific module.CHILD_DIRS
: The subdirectories that are required to be processed.
However, in order to mainain consistency with the rest of the build system, particularly with regards to build modes (release vs. debug) and with regards to the structure of the build output directories (e.g. builds/release and builds/debug), it is best to use the provided templates to automatically generate most of the rules. Refer to the Makefile in the src directory for a simple example, or read the next section for greater depth.
The key rules that do most of the work are located in template.mk, which detects .cpp source files and includes a default .cpp -> .o recipe, as well as automatic dependency handling.
To provide rules on how to build the module as a whole rather than implicitly via other modules that depend on it, you need to add the targets you want as prerequisites for the phony build targets. For example, build-solver is the target that builds the entire "solver" module.
The simplest way to define the build pattern for a module is to simply build every individual object file - the rule for this is given in build.mk.
The solver code is currently set to build into an archive called "libtapir.a";
the rules to do this are defined in the src/solver Makefile.
As you can see there, the rule defines all of the files that make up this
library in terms of the automatic OBJS_[module]
variables generated by
the template code.
Alternatively, if you set CFG=shared, the core solver code is instead built into a shared library "libtapir.so"
A more complex example is the build template used for each individual problem, which is located in problem-template.mk. Its purpose is to defines recipes for the "solve" and "simulate" executables, which are the key executables for running TAPIR on a specific problem.
The core of this template is defining all of the dependencies for each problem. The default dependencies are the other object files in the same directory as solve.cpp and simulate.cpp, the solver archive libtapir.a / libtapir.so, and the external library libspatialindex.
If your problem needs to be linked to additional libraries, you can specify
this by adding those dependencies to the variable
EXTRA_LINKER_ARGS_[module]
.