The make file to end all make files
17 July 2011
One of the things I do for living is fiddling with make files with GNU Make . Usually this leaves me with the vague impression that there must be a better way to do this and that. And so it’s time to do it right, even if it is only for my own stuff. So each piece of software should start with a list of requirements, here is my list for the make file
- Support make/test/install/clean/cleanall targets.
- Make script runs on multiple operating systems (Linux and Cygwin for a start). It is possible to override options per OS / OS version / processor type.
- All build results (objects, executables, libraries) are placed under one root directory. Root directory name contains OS name + OS version + processor type.
- Multiple targets of different type can be declared in one make file; if one has forgotten declaration required for target, a human readable error is displayed.
- The object files of each target are placed in separate object directory, this way we can set separate compiler option for intersecting source file sets.
- A build target can be of type exe/shared library/static library.
- A build target either includes subset of files or all files in directory.
- A build target can add its specific compiler/linker settings.
- Track source file dependencies; use trick that avoids reparsing of make file.
- A target can declare dependent libraries;
- Each make file has optional pre build and post build steps that can do code generation for instance.
- Display banner on building of each target; (what is the target, type of target + directory)
- Ability to add compiler and linker options from environment to each target (environment variables EXTERNAL_CFLAGS , EXTERNAL_CXXFLAGS , EXTERNAL_LDFLAGS ).
- Test target: all test programs and test libraries are put into separate directory $(BIN_ROOT_DIR)/test
- Try to minimize number of shells invoked; try to minimize number of make file rereads/re reads. This is important for Cygwin support + compilation speed.
- Like all software, the build script has a set of tests.
The make system magic works by reusing rule definition files all over each Makefile. Each directory that builds a target has its own Makefile, this file has to includes the skeleton rule file.
The skeleton rule files are placed in the root directory and those are
- rules.make
- rules-GNU-Linux.make
- rules-Cygwin.make You also get it with my GIT repository for stuff in c
Basic usage + defining build targets
1: TOPDIR=../..
2:
3: # - declare build targets. (built with make)
4: TARGETS:=shlib slibuser
5:
6: # - slib target is a static library -
7: shlib_TYPE=lib
8: shlib_SRC=slib.c
9:
10:
11: # - slibuser target is a executable using slib -
12: slibuser_TYPE=exe
13: slibuser_SRC=slibuser.c slibuser2.c slibuser3.c
14: slibuser_LIBS=shlib
15:
16: include $(TOPDIR)/rules.make
Each Makefile has the TOPDIR variable that points to the root directory, in the root directory we have the skeleton rule files. Line 16 includes the rule.make - the skeleton rule file. The List of build targets is declared in line 4; the TARGETS variable lists the name of each target; in this case targets shlib and slibuser are declared. Each build target defines its type slibuser_TYPE=exe This means that build target slibuser builds a executable. slibuser_TYPE=shlib This means that build target slibuser builds a shared library. slibuser_TYPE=lib This means that build target slibuser builds a static link library. Each target is built from sources, so either it lists all the source files: slibuser_SRC=slibuser.c slibuser2.c slibuser3.c Says that build target slibuser is built from the source files slibuser.c slibuser2.c and slibuser3.c
1: TOPDIR=../..
2:
3: # - declare build targets. (built with make)
4: TARGETS:=shlib
5:
6: shlib_TYPE=shlib
7: shlib_SRC_EXTENSION=c
8:
9
10: include $(TOPDIR)/rules.make
shlib_SRC_EXTENSION=c This declaration says that the build target shlib is built from all files with file extension *.c in the current directory.
build results
The build result directory is a sub directory of the root directory (a sub directory of $(TOPDIR); The name of the build result directory is made up of the following components
- Operating system name (based on uname
o, sanitized by replacing / ( ) characters with) - Kernel release (based on uname
r, sanitized by replacing / ( ) characters with) - Machine architecture (based on uname
m, sanitized by replacing / ( ) characters with) For my current system the name is GNU-Linux-2.6.38-8-generic-i686 ;
For each executable build target the result is placed into $(BUILD_RESULT_DIRECTORY)/bin, static or shared libraries are placed into $(BUILD_RESULT_DIRECTORY)/lib Test programs and libraries are placed into $(BUILD_RESULT_DIRECTORY)/test
adding specific compiler / linker options to a project.
1: TOPDIR=../..
2:
3: # - declare build targets. (built with make)
4: TARGETS:=shlib
5:
6: # - slib target is a static library -
7: shlib_TYPE=shlib
8: shlib_SRC_EXTENSION=c
9: shlib_CFLAGS=-finstrument-functions -O2 -fno-omit-frame-pointer
10: shlib_LIBS=pthread cutils
11:
12: include $(TOPDIR)/rules.make
shlib_CFLAGS=-finstrument-functions This declares that compiler option -finstrument-functions is added specifically to target shlib; the option is specifically added to compilation of C files. In order to add options to C** command line one should have set shlib_CXXFLAGS variable. shlib_LIBS=pthread cutils This declares that the shlib target is linked with libraries pthread and cutils; the -lpthread and -lcutils is added to the linker options for this target.
Recursing into subdirectories
1: TOPDIR=../..
2:
3: PREBUILD_SUBDIRS=dir-a dir-b dir-c
4:
5: # - declare build targets. (built with make)
6: TARGETS:=shlib
7:
8: # - slib target is a static library -
9: shlib_TYPE=shlib
10: shlib_SRC_EXTENSION=c
11: shlib_CFLAGS=-finstrument-functions -O2 -fno-omit-frame-pointer
12: shlib_LIBS=pthread cutils
13:
14: include $(TOPDIR)/rules.make
PREBUILD_SUBDIRS declares that prior to building target shlib, the make process would recurse into sub directories and invoke Makeefile in directories dir-a dir-b and dir-c POSTBUILD_SUBDIRS declares that after building target shlib, the make process would recurse into sub directories and invoke Makeefile in directories dir-a dir-b and dir-c
Specifying test targets invoked by ‘make test’
1: TOPDIR=../..
2: TESTS=cutilstest
3: cutilstest_TYPE=exe
4: cutilstest_SRC_EXTENSION=c
5: cutilstest_LIBS=cutils vtest
6: include $(TOPDIR)/rules.make
The TESTS variable lists name of test programs; these programs compiled and run by ‘make test’ command.
Adding custom pre build step
Each target can execute a pre build step - a script/program that is run before making any source files. This can be used either to
- Generation of source code
- A self configuration script that tests current environment and writes a header file that defines all sort of options.
The pre build test program receives the names of all source files of this target as command line parameters.
A pre build step is defined by the following declaration
\_PREBUILD= h3. Adding a custom post build step Each target can execute a post build step - a script/program that is run after making any source files. This can be used either to - custom packaging
- Instrument the binary of the executable
- Run the target binary yas part of complicated test script.
The first argument of the post build script is always the path name of the file that is built by the target. A post build step is defined by the following declaration