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.
- Manually
- Using Ant
- 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.