Shared build system between C++ projects, using makefiles.
Maintainer: GabrielNumworks
Run make help (or simply make) for a list of the main targets.
There are two types of targets:
- executable targets: combine one of the named goals with an extension, along with optional flavors, e.g.
make kernel.A.allow3rdparty.elf - miscellaneous targets: do not necessarily resolve to an executable file, e.g.
make format,make epsilon.dfu
Build is controlled by variables that can be supplied on the command line:
DEBUG: 0 or 1ASSERTIONS: optional, defaults toDEBUG. 0 or 1 otherwise.PLATFORM: one of the supported platforms (e.g.n0110,macos...). The special valuessimulatorandhostwill cause PLATFORM to become the name of the host environment.ARCHS: optional, platform-specific. A list of architectures to build for.VERBOSE: 0 makes the build completely silent, 1 shows building target, 2 prints the commands as they are executed. (defaults to 1)
By default, files will be built in a customizable output directory, with sub-directories depending on build parameters.
- First sub-directory depends on the
DEBUGandASSERTIONSvariables:releaseforDEBUG=0andASSERTIONS=0debugforDEBUG=1andASSERTIONS=1debug/no_assertforDEBUG=1andASSERTIONS=0debug/optimizedforDEBUG=0andASSERTIONS=1
- Second sub-directory is the name of the
PLATFORM. - Third sub-directory is the name of the architecture, if any.
- Fourth sub-directory is optional and specified by the goal.
In your makefile, define the following variables:
PATH_haussmann: the path of the haussmann directory relative to the projectOUTPUT_ROOT: where to place the built filesAPP_NAMEAPP_VERSION- you might give default values to
DEBUGandPLATFORM
... then include $(PATH_haussmann)/Makefile.
The function create_goal is used to generate rules for making an executable target.
Its arguments are:
- the name of the goal, must be letters, numbers, and underscores
- a list of modules
- (optional) a directory to append to the output path
- (optional) a description
$(call create_goal,demo, \
database \
gui \
render \
,, \
A simple demo of the app
)
This will create a goal called demo, with 3 modules, no subdirectory, and a description. If .bin is an extension on the current platform, make demo.bin is an acceptable target.
When building a goal, the list of modules indicate which source files and which compilation flags will be used (see modules below for more explanations). Source files can be further configured using flavors :
- Invoking
make demo.test.binwill forward thetestflavor to all modules. - In the module list, writing
gui.lightwill forward thelightflavor to theguimodule every build.
Caution
Because of some limitations, the subdirectory argument must be declared by adding its name to the variable ALL_SPECIAL_SUBDIRECTORIES prior to including Haussmann.
Tip
If you need to apply some pre- or post-processing to the linker's output, you can redefine the LD_WRAPPER_<goal> variable. Inside, $1 will stand for the default recipe.
A module is a collection of files and flags, grouped into several variables:
PATH_<module>VERSION_<module>SOURCES_<module>: the list of source files for this module, with optional flags. The paths may be relative to the root of the project, or the output directory.SFLAGS_<module>: those flags are used when building all objects of goals involving this module.PRIVATE_SFLAGS_<module>: on the other hand, those flags are only used when building the objects of this module.LDFLAGS_<module>: flags used for linking executables using this module.LDDEPS_<module>: linked executables using this module will depend on those non-sources files (e.g. linker scripts).
Modules are typically created in their own subdirectory, using a file named module.mak. The function import_module takes the path of the module and includes the module.mak file if found.
$(call import_module,database,src/database)
The function create_module is used to define the basics of a new module: its version, some sources, and the flag -I$(PATH_<module>/include.
Its arguments are:
- the name of the module
- the version, an integer
- a list of sources, with optional tastes. The paths are relative to the module's path.
$(call create_module,database,3, \
query.cpp \
transaction.cpp \
postgresql.cpp:-lite \
sqlite:+lite \
)
The other variables of the module need to be edited by hand.
Tip
A module does not always require a module.mak file, but make sure import_module is called before create_module, as the prior will define PATH_<module> used by the latter.
Note
When creating a shared module in its own repository, don't forget to give Read access to user ShellyAnnNumworks, in order to allow CI to fetch the submodule.
Tastes modify the sources that are built depending on the flavors passed to the module at compilation, e.g.:
a.cpp:+f1means a.cpp is only compiled if the flavorf1is provided (e.g.make goal.f1.exeor$(call create_goal,goal,module.f1)b.cpp:-f1is only compiled if the flavorf1is not provided.
If several tastes qualify the same file, they combine as an AND i.e. the file is only compiled if all its flavors are satified, e.g.:
c.cpp:+f1:+f2:-f3is compiled only if bothf1andf2are provided andf3is not.
To combine tastes as an OR, you can specify the file several times:
$(call create_module,module,1, \
d.cpp:+f1 \
d.cpp:+f2 \
)
d.cpp will be compiled if either f1 or f2 is provided.
A module might depend on some other modules. To prevent conflicts, the version of the dependencies can be listed in a lock file; if someone attempts to build with versions not listed in the lock, the compilation will fail.
e.g. Say a module main_mod depends on depA and depB.
- In the repository for
main_mod, there should be a goal calledmain_modrequiringdepAanddepB. - Calling
make main_mod.lockwill create the filelock.mak, listing the dependencies and their current versions. - In another project, when building a goal requiring
main_mod, the build system will look forlock.makat the root ofmain_modand compare the listed versions with the ones provided.
Caution
Lock files should only be re-generated once the module has been tested against the new versions of its dependencies.
If a project uses module modA at version 2, but modB has only been tested
against version 1 of modA, the compilation will fail (provided there is a
lock.mak file in modB).
The project can either:
- downgrade to
modAversion 1, if possible. - if
modAversion 2 is required, themodBmaintainers will need to release a new version that uses the newmodAversion.
Tip
Compilation will fail as soon as a conflict is encountered.
To list all conflicts at once for a specific goal, use make goal.modules.
This will display a list of all modules required by goal (with the version provided), their respective dependencies (as specified in their lock.mak, with the version required), and will highlight any discrepancy.
Goals are automatically added to make help along with their description.
The functions document_extension and document_other_target add new entries to their respective sections.
$(call document_extension,exe,A windows executable)
$(call document_other_target,format,Format modified .cpp and .py files)
A reusable library:
SomeLib
├── src
│ ├── core.c
│ ├── dangerous.c
│ └── safe.c
├── test
│ ├── haussmann
│ └── SomeOtherLib
├── Makefile
│ └── Define PATH_haussmann, DEBUG, PLATFORM, OUTPUT_ROOT...
│ include haussmann/Makefile
│ $(call import_module,some_lib,.)
│ $(call import_module,some_other_lib,SomeOtherLib)
│ $(call create_goal,test_runner,some_lib some_other_lib)
└── module.mak
└── $(call create_module,some_lib,2, \
src/core.c \
src/dangerous.c:+unsafe \
src/safe.c:-unsafe \
)
An application using the previous library and some other private modules:
SomeApp
├── haussmann
├── sources
│ ├── external
│ │ └── SomeLib
│ ├── FirstModule
│ └── SecondModule
└── Makefile
└── Define PATH_haussmann, DEBUG, PLATFORM, OUTPUT_ROOT
include haussmann/Makefile
$(call import_module,some_lib,sources/external/SomeLib)
$(call import_module,first_module,sources/FirstModule)
$(call import_module,second_module,sources/SecondModule)
$(call create_goal,app,some_lib.unsafe first_module second_module)
Commit messages follow the Angular Guidelines. See conventional-commit-types for a description of each type.
