Writing a C++ Build File

In this tutorial I will use Jython (python) script to write a build file for a Linux application.  If you wish to use one of the other scripting languages the concepts show here still apply.  The samples area have build files written in each scripting language for comparison

If your like me you will spend time up front making a good build file and then forget about it for 6 months until you have to do another one.  Then you will copy the one you already did and modify it for the new project.  Because of this pattern I find it best to keep the build file as generic as possible.  Keeping with this philosophy I set variables at the top of the build file that are specific to my project.  I set the build directory, the output file, the source directory and the include directory as variables.

blddir = "build"
progbin = blddir+"/totorial"
srcdir = "src"
incdir = "include"

This is the only part of the build file that is specific to my project.  The next thing to do is create a list of source files and object files.  CPMake provides some convenience functions to help with this.  The createFileList method can recursively search directories returning a list of files that match a given regular expression.  In my case I want a list of all ".cpp" files.

srcfiles = make.createFileList(srcdir, "(.*)\\.cpp")

To create the list of object files CPMake offers the substitute method that will perform a regular expression pattern substitution on an array of strings.

objfiles = make.substitute("(.*)\\.cpp", blddir+"/$1.o", srcfiles)

Here I substitute the .cpp name for a corresponding .o file in the build directory.

Now I need to tell CPMake where to find my files.  This is so CPMake can locate the files for dependency checking purposes.

make.addSearchPath(".*\\.h.*", incdir)
make.addSearchPath(".*\\.cpp", srcdir)

This sets up the search path for both header files and source files.

Now for some rules.  The first rule is one to create the build directory.  Because making a directory is a pretty common task CPMake again provides a convenience routine for it.

make.createDirectoryRule(blddir, None, 0)

The first param is the directory to create.  The second is any dependencies this directory has (none or null in this case).  The last parameter is if you want CPMake to echo a message when this directory is being created.

The next rule on my hit list is the one to compile the object files from the source.  Here it is best suited to create a pattern rule.  The pattern rule lets me create a rule for files that match a regular expression.  In my case I want a rule for all .o files.

make.createPatternRule(blddir+"/(.*)\\.o", "$1.cpp", "compile", 1)

This line does a pattern match that says any build directory .o file depends on the cpp file of the same name.  The method to call when performing this rule is "compile" and the last parameter tells CPMake to verify that the target file was created after the rule was called.  The compile method for the above rule looks like this:

def compile(target, prereqs):
    print(prereqs[0])
    make.exec("g++ -I"+incdir+" -Wall -Werror -c "+prereqs[0]+" -o "+target)

All rule methods have two parameters.  The first is the target file and the second is an array of prerequisites that were defined in the rule.  Note. Only the dependencies that were defined in the call to create the rule are passed. If dependenies are defined outside of the create rule call they will not be passed to the method.

In order for my directory to get created before I try and put files in it I need to setup a dependency between the .o files and the build directory.  I can do this by calling createPatternDependency.

make.createPatternDependency(blddir+"/(.*)\\.o", blddir)

This ensures the directory will be there when I start compiling the source code.

The last rule I need to add is the one to link the binary.  In this case because there is only one binary I will use the createExplicitRule method

make.createExplicitRule(progbin, objfiles, "link", 1)

This says the program binary file is dependent upon the object files and the method to call when performing this rule is "link".  The last parameter again tells CPMake to verify that the target was created.  The "link" method looks like this:

def link(target, prereqs):
    print("Linking "+target)
    make.exec("g++ "+make.arrayToString(prereqs)+" -o "+target)

There are a couple of other things I can do to this build file to make it more usable; set a default target and create a rule to test the program.  To set a default target I do the following:

make.setDefaultTarget(progbin)

And here is a phony rule for testing the program

make.createPhonyRule("test", progbin, "test")
def test(target, prereqs):
    print("Running "+prereqs[0])
    make.exec(blddir, make.fullPath(progbin), 1)

And there you have it.  The only part of this build file that is platform specific is the line to compile and the one to link.  These can be easily abstracted by calling methods that are included from platform specific build files.  See the build files in the samples area for examples of how this can be done.

For your viewing pleasure here is the entire make file


   1:# setup project variables
2:
3:blddir = "build"
4:progbin = blddir+"/tutorial"
5:srcdir = "src"
6:incdir = "include"
7:
8:# create a list of source files
9:
10:srcfiles = make.createFileList(srcdir, "(.*)\\.cpp")
11:
12:# create a list of output files
13:
14:objfiles = make.substitute("(.*)\\.cpp", blddir+"/$1.o", srcfiles)
15:
16:# set search paths
17:
18:make.addSearchPath(".*\\.h.*", incdir)
19:make.addSearchPath(".*\\.cpp", srcdir)
20:
21:# create a rule for the build directory
22:
23:make.createDirectoryRule(blddir, None, 0)
24:
25:# create a rule for compiling source files into object files
26:
27:make.createPatternDependency(blddir+"/(.*)\\.o", blddir)
28:make.createPatternRule(blddir+"/(.*)\\.o", "$1.cpp", "compile", 1)
29:def compile(target, prereqs):
30: print(prereqs[0])
31: make.exec("g++ -I"+incdir+" -Wall -Werror -c "+prereqs[0]+" -o "+target)
32:
33:# create a rule for the linker
34:
35:make.createExplicitRule(progbin, objfiles, "link", 1)
36:def link(target, prereqs):
37: print("Linking "+target)
38: make.exec("g++ "+make.arrayToString(prereqs)+" -o "+target);
39:
40:# create a phony rule to run the program
41:
42:make.createPhonyRule("test", progbin, "test")
43:def test(target, prereqs):
44: print("Running "+prereqs[0])
45: make.exec(blddir, make.fullPath(progbin), 1)
46:
47:# set default target
48:
49:make.setDefaultTarget(progbin)
50: