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: