CPMake Documentation

by Brian Hawkins
version 1.2
Jan 20, 2005


Rules and Dependencies


In this section we go over the general concept of rules and dependencies.  If you are familiar with GNU make then you may want to skip this section as the concepts are the same.  Those of you from the Ant world may want to take a look as it is different from Ant's way of looking at things

What is a Rule?

A rule defines what to do when a file is out of date and needs to be updated.  Also when defining a rule you provide a list of dependencies, this is how the make system knows when the file is out of date.  For example we define a rule that says file A is dependent on file B and the method to call for processing the rule is called "foo".  When the build is ran the make system checks the dates on the files A and B.  If B is newer then A the method "foo" will be called to updated A.

One thing to note here is that the dependencies that are defined while defining the rule are passed to the method as the second parameter.  Dependencies that are defined outside of the rule definition are not.

Lets look at a real world example.  When compiling C code the binary is linked together from the object files.  Each object file is built form a corresponding C file.  If a C file is newer then the object file then the object file needs to be rebuilt as well as the binary needs to be relinked.  In this case we can set up a rule that puts a dependency between the binary and the object files and the method to call would call out to the system linker and relink the binary.  We would also set up rules between the object files and the C files.  Because a project typically has only one binary and many C/CPP files you can create what is called a pattern rule.  This way in one rule you can say all object files depend on the corresponding C file of the same name and the method to call would call out to the system compiler to create the object file.

What are Dependencies?

A dependency lets you set up, well, dependencies between files that are being built.  You may be asking why do I need to do this when I can set this up with rules?  Because dependencies set up in rules are passed to the rule method.  Dependencies set up outside of rules are not..  A good example is the build directory.  In the above example the object files and the binary all depend on the build directory being there but you do not want the build directory being passed to either of the rules.  You would then have a rule for creating the directory.

What is a Phony Rule?

Sometimes you need a rule for something that does not exist.  Like clean.  Typically build scripts will have way to clean up the droppings of the build process so that you can begin again fresh.  Because there is no file "clean", you create a phony rule for it.  Phony means that the target of the rule does not exist nor will it.  Phony rules are a convenient way of putting human understandable tasks in a build file.

Building CPMake from Source

The first thing you need is Bean shell interpreter jar file.  Visit www.beanshell.org and download version 1.3.0 or later.  The build files expect version 1.3.0.  Place the jar file in the ext directory under cpmake.  Alternatively you can place it in the [jre]/lib/ext directory.
CPMake can be built in one of three ways.
  1. Manually
  2. Using Ant
  3. Using CPMake
All steps assume that your current directory is the root of the cpmake project
Manual steps for all platforms
Create a build directory
>mkdir build

Compile source to the build directory
>javac -classpath ext/bsh-1.3.0.jar;build -d build cpmake/*.java

Create the cpmake jar file
jar -cfm cpmake.jar manifest.txt -C build cpmake

Using Ant
>ant jar

Now that you have built it with one of the above steps you can use CPMake to build, well CPMake!
Using CPMake
>java -jar cpmake.jar

When using CPMake to build itself the resulting jar is placed in the build directory so as to not overwrite the good one if you happen to mess up the code.

Other build options when using CPMake:
This creates the javadocs
>java -jar cpmake.jar javadoc

This cleans out the project build files and generated doc directory.
>java -jar cpmake.jar clean

Have fun!

Running CPMake

Running CPMake requires that the cpmake.jar file is in your class path.  Also depending on what scripting language you have chosen to write your make files in you will need to have the following files as well
BeanShell: bsh-1.3.0.jar or later.
Jython: jython.jar
Groovy: groovy.jar  Groovy also requires the asm jar file that can be found here.
CPMake does not require all jar files to be present to run.  Only the for the scripting language you have chosen is required.
There is an option in the CPMake build file that lets you extract all of these jar files and place them in the cpmake.jar file.  This can make for easier internal distribution of cpmake.  To do this download the source from CVS and place the above jar files in the ext directory.  Then compile cpmake using 'onejar' as the target.  This will extract the jars and place them all in side cpmake.jar

Command line options

CPMake has a small command line help you can get to by the following call
>java make -?

This will be the most up to date information on what command line options are available.
Currently CPMake's command line is as follows:
[-v] [-f <build file>] [-t <thread count>] [<targets>]

-v : Verbose mode.  This will echo all calls to exec functions as well as echoing other information about the build.
-f : Build File.  This specifies the build file to be used.  If this option is not specified CPMake looks for the following files in this order "build.bsh", "build.py", "build.gvy", or "build.groovy".  The first one found is processed as the build file.
-t : Thread count.  This specifies the number of threads to use when processing the build queue.
<targets> : This is a list of targets CPMake is to try and make.  Each target is processed in order.

Environment variables

CPMake is built using java 1.4.2.  In this version there is no provided way of getting environment variables into the JVM.  To work around this CPMake uses a file called "env.properties".  On startup CPMake looks for the file env.properties and parses it for key=value pairs.  This file can be easily created by echoing the environment to the file.  This can be done on windows by calling:
>set > env.properties
and on unix platforms:
>env > env.properties
On startup CPMake takes properties from env.properties, cpmake.properties and System.getProperties() and combines them all together.  These properties are made available to the make file via the getProperty method calls.  None of the above files need to exist and for cleanliness env.properties is deleted after CPMake is done processing.  As of the 1.2 release CPMake can automatically import the environment variabls on Windows and Linux.  Other platforms may still need to use the env.properties file.

There are some properties that can effect the way CPMake runs.
cpmake.threadCount - sets the number of threads to use when running.  This is overridden by the -t option.
cpmake.debug - turns on debug messages when set to "on".
cpmake.dependencyCheck - turns off the dependency checker when set to "off".  It is on by default.
cpmake.cacheDir - sets the location of the .cpmakecache file.  it defaults to the current directory.
Because using a '.' is not valid in the unix environment the above settings can also be done with '_' such as cpmake_threadCount.

Properties files

CPMake reads properties from the env.properties and cpmake.properties files.  These properties are combined with the java system properties and are made available via the getProperties() method calls.  The env.properties and cpmake.properties files doe not need to be present to run.  Their main intent is for passing environment and other settings to the build script.

The Build File

CPMake supports three scripting languages for writing the build script in.
BeanShell, Jython and Groovy.  CPMake is fully compatible with all three and the only limitations are those of the languages.  Examples here are taken from BeanShell.

Creating rules

Rules are the fundamental part of the build file.  A rule defines how a file can be created or updated.  A rule can also define dependencies that a particular file has.

There are three kinds of rules; explicit, pattern and phony.  A phony rule is the same as the explicit rule except that the target does not exist, meaning the target is not a file in the file system but a task like "clean" or "build".  The sample area has good examples of the use of phony rules.

Explicit rules can be created by calling make.createExplicitRule(String target, String prerequisites, String scriptCall, boolean verify).
Lets look at an example
make.createExplicitRule("test.class", "test.java", "compile", true);
In this rule "test.class" is the target "test.java" is the prerequisite and "compile" is the name of the script method to call to update test.class.  The boolean "true" tells CPMake to verify that this file exists after the method "compile" has been called.  Here is the example in full context:

make.createExplicitRule("test.class", "test.java", "compile", true);
compile(String target, String[] prerequisites)
    {
    make.exec("javac test.java");
    }

The variable target in this case would contain the value "test.class" and the variable prerequisites would be an array of one and the first element would be "test.java".  These variables use will become more obvious as we talk about pattern rules.

When evaluating a target CPMake will run the rule for that target if any of the following are true
1. The target does not exists (except in the case of phony targets)
2. Any of the files the target is dependent on are newer then the target.

Creating pattern rules

The above example is a good one but not very practical.  Explicit rules are better suited for single targets.  In most java applications there are lots of java files to be compiled.  In this case you do not want to write an explicit rule for each one.  This is where pattern rules can be used.

For example lets write a pattern rule that can be used to compile any java file.

make.createPatternRule("(.*)\\.class", "$1.java", "compile", true);

Here we use regular expressions to set up a patter rule.  This rule says that for any target that ends with ".class" it depends on the file of the same name but with a ".java" and the method of this rule is "compile".  The boolean is to verify that the target is there after the method has been called.  Now to see this in full context:

make.createPatternRule("(.*)\\.class", "$1.java", "compile", true);
compile(String target, String[] prerequisites)
    {
    make.exec("javac "+prerequisites[0]);
    }

If the file to be compiled was "test.java" then the target variable would contain "test.class" and prerequisites is an array of one element and the first element would be "test.java".

Note.  As most java guru's know it would be a waist of time to call javac on every java file that needed to be compiled as javac will compile all missing class files needed in one call.  For the sake of this example just imagine that it didn't and it only compiled the file you tell it to.  For better examples of compiling java code see the samples area.

Pattern rules are second priority to explicit rules.  If you have both a pattern rule and an explicit rule defined for the same target the explicit rule will be used.  You may also have multiple pattern rules for the same target.  CPMake will however give favor to the one that has an existing prerequisite.  Take the following two rules for example

make.createPatternRule("(.*)\\.obj", "$1.c", "compile", true);
make.createPatternRule("(.*)\\.obj", "$1.cpp", "compile", true);

Now if the target is main.obj CPMake will look for first main.c and then main.cpp.  For the one that exists the appropriate rule will be used.

Built in rules

CPMake has one built in rule for creating directories.  This is useful if your project has a build directory where all the output files go.  You can create a rule for the out directory and it will be created for you.  For example your project puts its files in the "build" directory.  You can create a rule like this:

make.createDirectoryRule("build", null, true);

The first parameter is the directory to make.  The second parameter is any prerequisites this directory may have (most don't have any).  The last parameter is whether to echo the creation of this directory to the console.

Adding dependencies

Dependencies let you set relationships between files that are outside the rule definitions.  The only difference between the dependencies set up by calling createExplicitRule and createExplicitDependency is that the dependencies are not passed to the method call.
For example in a java application the jar file should be updated if the manifest file is changed, but you do not want to explicitly pass the manifest file as a prerequisite to the method that creates the jar file.  In this case  you can set up a dependency with the following call:

make.createExplicitDependency("test.jar", "manifest.txt");

This will trigger the rule for creating the test.jar file anytime the manifest.txt file is updated.

Adding pattern dependencies

Pattern dependencies let you set up dependencies for largest sets of files.  For example all the files in the project should be dependent on the build file.  If it is updated the entire project needs to be rebuilt to reflect the new changes.  This can be setup with the following line:

make.createPatternDependency("(.*)", "build.bsh");

Pattern dependencies use regular expressions for matching a potential target with the prerequisites.

Setting a default target

Setting a default target is optional.  What this does is allow you to run the build file without specifying any target at all and the default will be done.  To set the default target somewhere in the build file do the following:

make.setDefaultTarget("target");

Where target is the name of the target you wish to process.

Using the prepForTarget routine

If you declare a method in your build script that matches the following signature:

prepForTarget(String target);

It will be called before CPMake begins processing a build target.  In this method you may include additional build scripts, change properties values or add additional dependencies.

Calling external programs

CPMake provides several exec methods for use when calling external programs.  Some of the scripting languages used provide their own means of executing external programs, but there are several reasons why you may wish to use those provided by CPMake.
1. CPMake redirects all input and output to the console so you can interact and see the output from running the program.
2. CPMake executes the command synchronously.  It will wait for the process to end before proceeding with the rest of the build.
3. CPMake can echo the output of the program to a log file if one is specified.

For specifics on each API and the parameters please see the javadoc API documentation.

Auto clean

CPMake provides an automatic project cleaning target.  Because the build file specifies what to build it also specifies what to clean, if you just think about it backwards.  If you do not provide a build target 'clean' and clean is specified on the command line CPMake calls the autoClean method.  This method looks at all the build targets that are in the build file and then removes them if they exist.

Other Issues


Building with multiple threads

CPMake has the ability to process build files using multiple threads.  If you have a multi processor or hyper threaded machine this can significantly reduce your build times.

When building a target CPMake creates a queue of rules it needs to perform in order to get the job done.  The queue is ordered by dependency.  For example when compiling a C++ application the tasks for compiling the object files will come before linking together the executable because linking is dependent upon the compilation being done first.  When processing with multiple thread the compiling of the object files are done simultaneously but when a thread hits the linking task it will wait until all of the dependencies are done first.

Compiling multiple files at the same time can be problematic if the compiler tries to write to a common file.  This can be the case in compiling windows applications and writing to a pdb.  There is a way as shown in the samples area how to do this on windows and have separate pdb files written.  When the linker is ran it links the pdb files together as well as the exe.

Recursive calls to CPMake

This feature is being worked on for the 1.2 beta.