Instead of separately documenting each build template (i.e. program, library, sub directory recursion, etc.) we will illustrate a complete (yet absolutely pointless) mixed C/C++ project that will let us understand how all the available build patterns get together to implement the final build tree.
Suppose we are creating a project named test which provides a main tool called prog and related man pages. The tool depends on some custom routines grouped in form of a C/C++ library and on a couple of external tool which have their own build system and needs perhaps to be patched in order to suit our needs.
Let’s start by creating a sensible file system layout, like the following,
test/
prog/ # sources for the prog(1) tool
man/ # man page(s) documenting the prog(1) tool
libx/ # coustom routines needed by prog(1)
include/ # header files for the library
autoprog/ # an external dependency (autotools based)
party/ # 3rd party dependencies handled by MaKL
The top level Makefile is nothing but a dispatcher of build actions across all the project directories. It includes the subdir.mk template and populates the SUBDIR variable with the list of directories with stuff that needs to be built.
SUBDIR = lib party prog autoprog include man include subdir.mk
All the directories listed in a SUBDIR variable must expose a Makefile that will be called with the goal requested by the user (e.g. all, clean, cleandepend, install, etc.).
NOTE: MaKL tries to enforce a neat interface, exposing a coherent set of targets which are made available throughout all its template files. Here they are (quite self-explainatory):
all and cleaninstall and uninstalldepend and cleandepend
MaKL users are clearly encouraged to use them, as they tend to be convenient in most cases. Anyway, should you need (1) to add a different target name for some special action, or (2) to give another semantics for one of the standard targets, that will be obtained quite straightforwardly: since MaKL files are plain GNUMake files, the target override capabilities are completely retained. Just mind the relative order between the template includes and target (re)definition to get the expected result.
The prog(1) Makefile can be as simple as that:
include common.mk include ../Makefile.conf PROG = prog SRCS = prog.c LDADD += ../lib/libx.a include prog.mk
Plus an optional add-ons section:
clean-hook-pre:
$(ECHO) "do something here, just before clean up"
Here - as in most MaKL Makefiles - we can clearly identify three different sections.
include common.mk # MaKL global settings include ../Makefile.conf # per-project settings (override globals)
Each MaKL Makefile shall begin with the “include common.mk” directive, which tells GNU Make to pick up MaKL global varialbles’ settings, basically: toolchain variables (CC, CFLAGS, LD and friends) together with various system paths (DESTDIR, BINDIR, etc.) and the corresponding credentials (BINOWN, LIBGRP, etc.). Since these global variables can be overwritten (the “=” assignement operator), or sometimes enriched (using the “+=” operator) on a per-project/per-module basis, this include has to be placed on top: following includes or straight variable settings will possibly override these values.
Quite usually, medium-to-large sized project will have their own top level Makefile.conf with project specific settings (in most cases it will be automatically created by an autoconfiguration script) which need to be included just after the common.mk and before any per-module setting.
# (override per-project and globals' settings) PROG = prog SRCS = prog.c LDADD += ../lib/libx.a
Each build template exposes its unique set of variables, by which the developer customizes the build action. Template specific variables usually set sensible defaults, but this is not always true, e.g. the prog.mk template needs that you give values for at least the following:
| variable | semantics |
|---|---|
PROG | the name of the executable that will be produced |
SRCS | the list of C/C++ files which will be compiled and linked to produce $(PROG) |
Other prog.mk variables that you can optionally set (depending on your needs) are:
| variable | semantics | example | default value |
|---|---|---|---|
USE_CXX | if defined (i.e. set to some non-void value) this will tell MaKL to use the C++ compiler instead of plain C compiler to drive the linking stage | USE_CXX=true | unset |
LDADD | set this with libraries (archive files) which your program depends on (linking ops) | LDADD+=../lib/libx.a | unset |
DPADD | set this with files which your program depends on (will feed the depend target) | DPADD+=../lib/libx.a | unset |
LDFLAGS | additional loader flags | LDFLAGS=-static | depends on host or target platform 1) |
CLEANFILES | add specific files that need to be removed on a clean goal invocation | CLEANFILES+=.done | $(PROG) and $(OBJS) |
CFLAGS | flags to the C compiler | CFLAGS+=-g -DDEBUG | depends on host or target platform |
CXXFLAGS | flags to the C++ compiler | CXXFLAGS+=-fdollars-in-identifiers | depends on host or target platform |
BINOWN | owner of the installed binary | BINOWN=nobody | interactive user credentials or $(DEFOWN) if set |
BINGRP | group of the installed binary | BINGRP=nobody | interactive user credentials or $(DEFGRP) if set |
BINMODE | mode of the installed binary, either an octal or symbolic value | BINMODE=0755 | 0555 (i.e. ugo=rx) |
BINDIR | installation directory | BINDIR=$(LIBEXDIR) | $(DESTDIR)/bin |
include prog.mk
Here we are telling GNU Make to load MaKL program template, i.e. a set of targets and corresponding actions that will be used to generate the executable. Note that this comes last in order to let all kind of variable settings (i.e. global, per-project and per-module) to take place before actual building.
In case you need to override or hook-up a MaKL target (i.e. one of all, clean, depend, cleandepend, install, uninstall) or define your target, this will be safely accomplished by placing relevant code just after this section.
Add-ons comes in three different flavours: hook, redefinition and new target.
# hook
clean-hook-pre:
$(ECHO) "do something here, just before clean up"
Each MaKL target has hooks that can be used to install user code before and after each MaKL action. Hooks follow this typo pattern: <action>-hook-pre for code to be inserted before action takes place, and <action>-hook-post for code that shall be executed after the specific action (one of the already mentioned MaKL targets).
# new target
test:
$(ECHO) "I feel lazy today and don't want to stress a single bit..."
In this case you just state your target and related actions.
# ... variable settings ...
# default install target inhibition
NO_INSTALL = true
include prog.mk
# install target redefinition
install:
$(ECHO) "my custom install directive"
Since GNU make does not allow overriding an already defined target, resetting MaKL default targets’ action to some user defined behaviour, needs some special trick. For each default target, MaKL has a special variable which, if given an arbitrary non-void value (e.g. true), resets the action(s) associated with that target to the null action. All MaKL template files expose the following variables: NO_CLEAN, NO_DEPEND, NO_CLEANDEPEND, NO_INSTALL, NO_UNINSTALL that come in handy in the aforementioned example, where, by setting one of them just before including the needed template, raises the possibility to override the corresponding target with user defined actions later on.
Let’s go back to our example and see how the library template works.
As we have seen before, the prog(1) program depends on some mixed C/C++ code which has been factored out and packaged into a library named libx. If we cd libx/ and cat Makefile that’s what we get:
include common.mk
include ../Makefile.conf
LIB = x
SRCS = a.c b.c x.cc
USE_CXX = true
# '+=' => in order to inherit default CFLAGS settings
CXXFLAGS += -fdollars-in-identifiers
include lib.mk
ifdef OS_DARWIN
install-hook-post:
$(RANLIB) $(LIBDIR)/lib$(LIB).a
endif
Note that the same basic structure as in the program example (e.g. preamble, variables’ settings and lib.mk inclusion) is retained. There is also an add-on section with code needed to work around an ld(1) bug on Darwin. This hook code will be executed after all the default install actions have been carried out, and just in case the OS_DARWIN variable have been set (perhaps in the top level Makefile.conf by the auto configuration script).
The lib.mk template needs that at least the two following variables are set:
| variable | semantics |
|---|---|
LIB | the name of the library (will be suffixed with lib) |
SRCS | the list of C/C++ source files with API and internal routines |
By default MaKL builds the static archive library. If we’d want to get a shared version of the x library we’d have to add the following settings before including the lib.mk template:
... SHLIB = true SHLIB_MAJOR = 1 # set this as appropriate SHLIB_MINOR = 4 # ... SHLIB_TEENY = 2 # ... ...
In the example above we’ve also set the USE_CXX variable so that the C++ compiler is used in the dynamic library assembly step. This is needed anytime the SRCS variable contains a C++ file and SHLIB is also defined.
On Darwin OS, MaKL defines two variables which shall be used in case the Mach-O bundle format needs to be produced - as opposed to the dynamic library format, which is the default:
| variable | semantics |
|---|---|
BUNDLE | if set a Mach-O bundle is created |
BUNDLE_EXT | extension for the produced bundle file (e.g. kext for kernel modules) |
Other lib.mk variables that you can optionally set (depending on your needs) are:
| variable | semantics | example | default value |
|---|---|---|---|
USE_CXX | same meaning as in the prog.mk template | USE_CXX=true | unset |
CLEANFILES | same meaning as in the prog.mk template | CLEANFILES+=.done | $(OBJS) and the library archive file |
CFLAGS | same meaning as in the prog.mk template | CFLAGS+=-g -DDEBUG | depends on host or target platform |
CPICFLAGS | shared library extra compiler flags | CPICFLAGS+=-fPIC | depends on host or target platform |
LIBOWN | owner of the installed library | LIBOWN=admin | interactive user credentials or $(DEFOWN) if set |
LIBGRP | group of the installed library | LIBGRP=admin | interactive user credentials or $(DEFGRP) if set |
LIBMODE | mode of the installed library, either an octal or symbolic value | LIBMODE=0644 | 0444 (i.e. ugo=r) |
LIBDIR | installation directory | LIBDIR=$(DESTDIR)/private/lib | $(DESTDIR)/lib |
Shared libraries use some other variables mostly related to shlib naming conventions. Those variables will not be documented here because they heavily depend on the target platform. Should you need some special behaviour, try hacking in the shlib/ directory.
Our sample project groups all its UNIX Manual pages into one directory (man/). This is a practice which MaKL tries to enforce via the man.mk template logic.
As usual, the developer communicates with MaKL through the set of variables exposed by the included template (i.e. man.mk), which provides back a set of standard actions. In this case only the install and uninstall targets have some associated action (those that you can easily imagine), while all other MaKL standard targets are available through their -pre and -post hooks.
include common.mk
include ../Makefile.conf
MANFILES = prog.1 libx.3
# MLINKS = {<file> <symlink>}i=0...n
MLINKS += prog.1 progalias.1
MLINKS += libx.3 foo1.3
MLINKS += libx.3 foo2.3
MLINKS += libx.3 foo3.3
include man.mk
What the above example does is declaring a couple of real UNIX manual pages (prog.1 and libx.3) through the MANFILES variable, and then their corresponding symlinks (progalias.1 and foo[1-3].3) through MLINKS.
MANFILES is the only mandatory variable in the man.mk template, and must hold files with a suitable extension, i.e. the manual section indicator. The MLINKS variable has special semantics. It’s a function-args variable2) whose values are used as parameters to an internal processing loop: at each iteration a couple of values are passed as arguments to the link function. This is a simple and powerful approach, however it must be handled with some caution since one single stepping error breaks the following processing chain.
Other man.mk variables that you can optionally set (depending on your needs) are:
| variable | semantics | example | default value |
|---|---|---|---|
MANOWN | owner of the installed man pages | MANOWN=man | interactive user credentials or $(DEFOWN) if set |
MANGRP | group of the installed man pages | MANGRP=man | interactive user credentials or $(DEFGRP) if set |
MANMODE | mode of the installed man pages, either an octal or symbolic value | MANMODE=0644 | 0444 (i.e. ugo=r) |
MANDIR | installation directory | MANDIR=$(DESTDIR)/private/share/man | $(DESTDIR)/share/man |
All our test project headers are kept into a separate directory (include/) that works as a common container for at least the project’s header files that need to be externalized, i.e. made public between modules and perhaps to the whole system.
If we go straight peeking at its Makefile, that’s what we get:
include common.mk include ../Makefile.conf INCS = a.h b.h x.h include incs.mk
which is indeed a very small and simple object.
The incs.mk template has two only precooked actions (install and uninstall) and a single mandatory variable (INCS) to drive the whole build mechanism. The INCS variable holds the list of the header files in the directory, but even outside it (absolute or relative path), that need to be installed in the system.
Like nearly every MaKL template we’ve been playing with, the incs.mk template too has variable that allow to customize the location, credential and permissions of the installed objects:
| variable | semantics | example | default value |
|---|---|---|---|
INCOWN | owner of the installed header files | INCOWN=admin | interactive user credentials or $(INCOWN) if set |
INCGRP | group of the installed header files | INCGRP=admin | interactive user credentials or $(INCGRP) if set |
INCMODE | mode of the installed header files, either an octal or symbolic value | INCMODE=0644 | 0444 (i.e. ugo=r) |
INCDIR | installation directory | INCDIR=$(DESTDIR)/proj | $(DESTDIR)/include |
Configuration, Compilation and Installation of External Dependencies can be handled by using the party.mk template.
Suppose that the previously mentioned test project depends on packages libfoo and libbar. One would create two subdirectories libfoo/ and libbar/, each with an appropriate Makefile which describes how the packages should be built and/or installed.
Let libfoo be a package to be downloaded from the official Internet site (www.libfoo.org) and installed. A sample Makefile for such dependency follows:
include ../Makefile.conf PARTY_NAME = libfoo PARTY_URL = http://www.libfoo.org/download PARTY_ARGS = --prefix=$(PARTY_CONF_PREFIX) include party.mk
Makefile.conf is a base level Makefile containing the common configuration for all 3rd party dependencies (in this case only PARTY_CONF_PREFIX which defines the prefix for installation of dependencies). This file may be static or generated dynamically by a configure script. Given the PARTY_URL shown above, the file named http://www.libfoo.org/download/libfoo.tar.gz will be retrieved by default. This default value can be changed by modifying the PARTY_FILE variable. PARTY_DECOMP and PARTY_DECOMP_ARGS can be used to set the command used for decompression and its arguments (if different from tar with gzip compression). The package will be then configured using the script named ./configure in the base directory of the decompressed file and installation prefix PARTY_CONF_PREFIX.
Let’s have a look at another sample dependency, libbar. The source code for this package is included within the test package and should not be downloaded remotely. Its Makefile is shown below:
include ../Makefile.conf PARTY_NAME = libbar PARTY_NO_DOWN = true PARTY_PATCH = libbar-patch.diff PARTY_CONF = ./conf.sh PARTY_ARGS = --prefix=$(PARTY_CONF_PREFIX) --enable-cool-feature include party.mk
PARTY_NO_DOWN disables package download. The file libbar.tar.gz is expected to be found in the current directory. The default variables can be overridden as mentioned in the previous paragraph. The package is decompressed and patched using the ‘diff’ file given by the value of PARTY_PATCH. The default configuration script command ./configure is overwritten by PART_CONF and an extra argument –enable-cool-feature has been added to the configuration arguments PARTY_ARGS.
In order to install both of the above packages automatically, the top-level Makefile simply needs to use the subdir.mk template as follows.
SUBDIR = libfoo libbar include subdir.mk
As a result of building the 3rd party dependencies using party.mk, a log file named party.log is generated in each directory. The path of these files can be modified by setting PARTY_LOG.