Gentoo Logo
Gentoo Logo Side

Gentoo Linux Jam Guide

Contents:

1. Introduction

What is Jam?

Jam is a software build tool. It is an alternative to GNU make. Jam was written by Christopher Seiwald of Perforce Software, and has been released as open source.

Jam includes a case sensitive interpreted procedural language to define the rules and actions processed by Jam. Jam uses these rules (from a Jamfile or a group of Jamfiles) to define how Jam should build a set of targets, which can be object files, libraries, or executables, or other files.

Jam's language features include functions, procedures, local variables, standard conditional and iterative constructs, lists, and target-specific variables. Jam processes dependencies rapidly, and its automatic generation of C & C++ header dependencies eliminates the necessity of declaring header or object files in the Jamfile.

Jam's operation consists of separate start-up, parsing, binding, and updating phases. During the start-up phase Jam obtains system information from environment variables, which is stored in Jam variables of the same name. Parsing involves setting variable values and dependencies based on user defined rules and rule invokations. In the binding phase Jam uses dependence information to bind variables to target files or other filesystem locations, and checks modification times before commencing the updating phase in which actions are executed for targets which require updating.

Since shell commands executed during the updating phase will always preceed the processing of rules during the parsing phase, a Jamfile cannot ordinarily set a variable value based on filesystem data. However, Jam can invoke itself recursively to cope with that unusual case.

A table which briefly summarizes the standard Jam documentation is available here .

Prerequisites

While no prior knowledge of Jam is assumed, a basic understanding of the C++ language and the GNU toolchain, and a working knowledge of the bash shell is recommended. XML is used as well as C++ to illustrate Jam usage, but detailed knowledge of these languages is not necessary in order to understand the examples. However, it is advisable that the reader have a prior knowledge of the relative advantages of the various build tools.

2. Installing Jam

First of all Jam needs to be installed on your system. For Gentoo Linux systems, the following is sufficient:

Code listing 2.1: Building Jam

# emerge dev-util/ftjam

The following commands enable environment variables for optimized compilation to be visible to Jam on Gentoo systems. These commands can go into your ~/.bash_profile.

Code listing 2.2: Exporting build variables

source /etc/make.conf
export CFLAGS="${CFLAGS}"
export CXXFLAGS="${CXXFLAGS}"

Jam can be built on the following operating systems: AIX, BSD/386 1.0, COHERENT/386, DGUX 5.4, FreeBSD, HPUX 9.0, IRIX 5.0, Linux, NEXTSTEP 3.2, OSF/1, PTX V2.1.0, Solaris 2, SunOS4.1, Ultrix 4.2, BeOS, Windows NT, and OS/2.

On Linux the following commands are sufficient to build Jam:

Code listing 2.3: Building Jam by hand

$ wget ftp://ftp.perforce.com/pub/jam/jam-2.5.tar
$ tar xf jam-2.5.tar
$ cd jam-2.5
$ make
$ cp bin.linuxx86/jam /usr/local/bin

Notes on building for other operating systems can be found in the README file.

3. Jam Language

Jam's data type

Jam variables are lists which can contain zero or more items. The items can be iterated with for or accessed by index.

Code listing 3.1: Defining and examining variables

servers = mail sql web ;

Echo "The server names are:" $(servers) ;

for i in $(servers) {

        Echo "  " server $(i) ;
}

Echo "Server 2 is" $(servers[2]) ;

When a list is declated the items are separated by whitespace. An item which includes whitespace is declared by enclosing it in double quotes. The following two list declarations are equivalent.

Code listing 3.2: List declarations

servers = "mail" sql web ;

servers =
    mail
    sql
    web
;

Variable expansion

Jam variables can be expanded by referencing other variables. The expression for a value obtained by reference is the name of the referenced variable enclosed within the $( and ) characters. The expansion of a variable which contains references to more than one other variable results in the set of all combinations of each of the items of the lists which comprise each referenced variable.

Jam provides several predefined variables. One of these, called JAMUNAME contains data about the system than Jam is running on.

Code listing 3.3: Variable expansion

servers = mail sql web ;

ip_mail = 192.168.0.2 ;
ip_sql = 192.168.0.4 ;
ip_web = 192.168.0.4 ;

host = $(JAMUNAME[4]) ;

Echo "The ip address of" $(host) "is" $(ip_$(host)) ;
Echo "The ip addresses are " $(ip_$(servers)) ;

Conditionals

Jam can test variables for the existance of values by making the variable itself the expression which is tested by if. Jam uses the C operators !=, !, &&, and !! for inequality, inversion, and, and or respectively. Jam uses the operator = to test for equality, and along with <, >, <=, and >= the operator applies to lists rather than to singular values.

4. Hello World

Here a small set of files is created in order to test the build process.

Creating a source file

Code listing 4.1: hello.cc

#include <iostream>

using namespace std;

int main (int argc, char *argv[]) {

	cout << "hello world" << endl;
}

Creating a Jamfile

Note: In a Jamfile the colons and semicolons are tokens and must be separated by whitespace.

Code listing 4.2: Jamfile

C++ = g++ ;
LINK = $(C++) ;

Main hello : hello.cc ;

Making, testing, and removing the executable

Code listing 4.3: Make the executable

$ jam
...found 11 target(s)...
...updating 2 target(s)...
Link hello 
Chmod1 hello 
...updated 2 target(s)...

Code listing 4.4: Test the executable

$ ./hello
hello world

You can remove the object and executable files by typing jam clean.

Code listing 4.5: Remove the executable and object files.

$ jam clean
...found 1 target(s)...
...updating 1 target(s)...
Clean clean 
...updated 1 target(s)...

5. Linking a user-built object file and library

Creating the header file

Code listing 5.1: mytools.h

#ifndef _MYTOOLS_
#define _MYTOOLS_

#include <string>

namespace std {

	void whatever (string s);
}

#endif

Creating the source file

Code listing 5.2: mytools.cc

#include <iostream>
#include "mytools.h"

using namespace std;

void std::whatever (string s) {

	cout << "whatever [" << s << ']' << endl;
}

Modifying hello.cc

The new header file needs to be included, and the procedure whatever needs to be called by main.

Code listing 5.3: hello.cc

#include <iostream>
#include "mytools.h"

using namespace std;

int main (int argc, char *argv[]) {

	cout << "hello world" << endl;
	whatever ("test");
}

Linking an object file

The file mytools.cc needs to be added to the main dependency list.

Code listing 5.4: Jamfile

C++ = g++ ;
LINK = $(C++) ;

Main hello : hello.cc mytools.cc ;

Linking multiple executables

Using the Main rule more than once in a Jamfile can result in repeated dependencies. When a Jamfile is used to build multiple executables, the Objects and MainfromObjects rules should be used instead of Main.

Code listing 5.5: Multiple executables

C++ = g++ ;
LINK = $(C++) ;

Objects hello.cc mytools.cc ;
MainFromObjects hello : hello.o mytools.o ;

Linking a library

The Library rule is used to build the mytools library, and the LinkLibraries rule is used to add the library to the target program.

Code listing 5.6: Jamfile

C++ = g++ ;
LINK = $(C++) ;

Main hello : hello.cc ;
Library mytools : mytools.cc ;
LinkLibraries hello : mytools ;

6. Linking a pre-compiled library

Modifying the source file

The header file math.h should be included along with the other headers. To test the library the arctangent of 1 is calculated.

Code listing 6.1: mytools.cc

#include <iostream>
#include <math.h>
#include "mytools.h"

using namespace std;

void whatever (string s) {

	cout << "whatever [" << s << ']' << endl;
	cout << "atan (1.0) = " << atan (1.0) << endl;
}

Modifying the Jamfile

The math library libm needs to be added to library list.

Code listing 6.2: Jamfile

C++ = g++ ;
LINK = $(C++) ;

LINKLIBS += -lm ;

Main hello : hello.cc mytools.cc ;

The += operator causes the identifiers following the operator to be added to the list of identifiers which was previously bound to the variable, instead of replacing them as in the case of the = operator.

Examining the build command

Run Jam with the -an flags to see how the math library is added to the build command.

Code listing 6.3: Dry-run

$ jam -an
...found 29 target(s)...
...updating 3 target(s)...
C++ hello.o 

g++ -c -o hello.o -O   hello.cc

C++ mytools.o 

g++ -c -o mytools.o -O   mytools.cc

Link hello 

g++  -o hello  hello.o mytools.o  -lm 

Chmod1 hello 

chmod 711 hello

...updated 3 target(s)...

7. Target specific variables and user defined actions

Target specific variables

Target specific variables enable the attachment of diverse information to specific build targets.

Code listing 7.1: Assigning a non-specific variable

LINKLIBS = -lm ;

In this example the math library will be linked for all targets built by the Jamfile.

Code listing 7.2: Assigning a target-specific variable

LINKLIBS on hello = -lm ;

In this second example the math library will only be linked on the hello target.

Target specific variables can be referenced from within Jam by using the on keyword.

Code listing 7.3

on hello Echo "The libraries for target hello are $(LINKLIBS)" ;

Here the identifer hello which follows the on keyword specifies that variable references will be specific to that target. A target specific variable cannot be assigned a value using this syntax, but non-specific variables can still be referenced.

Another variable which has a predefined function is LINKFLAGS, which causes flags to be added to the start of the linker argument list. Also, multiple targets can be specified for target-specific examples.

Code listing 7.4: Multiple targets

LINKFLAGS on hello myproject test = -pg ;

In this example the flag which activates profiling for g++ will be set for all three targets.

Enabling profiling in g++ requires that the -pg flag is set in both the compile and link phases if the two phases are executed separately. Unfortunately the C++FLAGS variable does not work as expected for the compile phase in stock Jam. An alternative method of setting C++ compile flags is the use of the ObjectC++Flags rule.

Code listing 7.5: Setting C++ compiler flags

ObjectC++Flags hello : -pg ;

Target specific variables are not limited to variables which have a predefined meaning within Jam. An arbitrary variable may be attached to a target and then referenced by the on keyword (as mentioned earliear in this section), or by user defined actions which act on that target.

User defined actions

User defined actions enable the specification of the shell commands which produce build targets. A target may be a file, which typically will be an object file or an executable built by a compiler. Pseudotargets may also be defined in Jam. Jam does not check any file status (i.e. the most recent time of modification of the file) that is assocated with a pseudotarget as occurs with an ordinary target. Two predefined pseudotargets are clean and all. The psuedotarget all is built when no target was explicitly named when Jam was executed from the command shell. In the following example the pseudotarget prof controls the generation of profile data for hello.

Code listing 7.6: Actions

actions Profile {
echo "The target is $(1)"
gprof -b hello $(PROFILE_DATA)
}

PROFILE_DATA on prof = gmon.out ;
NOTFILE prof ;
ALWAYS prof ;

Profile prof ;

Here a set of actions are defined for the identifier Profile. NOTFILE identifies prof as a pseudotarget, and ALWAYS is used for targets that have no dependencies to trigger the build process. The last line of the example code associates the actions with the target.

Jam actions are associated with the binding of file targets to variables. Binding only ever binds a variable to a single file once when Jam is run, and uses the last value that was assigned to that variable during the parsing phase of Jam's execution. The first argument of the action is bound to the target (or targets) of the action, and the second argument is bound to any dependencies.

Binding is the main behaviour which sets a build system like Jam apart from a general purpose procedural language. The binding process is what enables target dependencies to conditionally trigger build actions.

8. Rules

Overview

Rules are primarily used to define the dependecies between targets, and can also be used for factoring common build patterns in the Jamfile so as to increase readability and to decrease opportunities for bugs to occur. Rules take zero to nine arguments (each a list) and may return a list. Rules may declare local variables and may be invoked recursively.

Code listing 8.1: A hash function

rule hash key : source : target  {

    local result ;

    if $(source) {
        if $(key) = $(source[1]) {
            result = "\"$(target[1])\"" ;
            return $(result) ;
        } else {
            return [ hash $(key) : $(source[2-]) : $(target[2-]) ] ;
        }
    }
}

colours = red green blue ;
letters = alpha beta gamma ;

colour = blue ;
result = [ hash $(colour) : $(colours) : $(letters) ] ;

Echo The letter for $(colour) is $(result) ;

9. Other languages

Overview

Jam is not exclusively for C and C++ programs. It can be used to build executables for any language, so long as the appropriate rules and actions are defined.

Example

This new rule defines build dependencies for a set of actions and ensures that the output file will be removed when jam clean is invoked. The first two arguments will only be bound to targets if a set of actions with the same name as the rule exists.

Code listing 9.1: Rules

rule MakeHtml {

	Depends $(1) : $(2) ;
	Depends all : $(1) ;
	Clean clean : $(1) ;
}

Descriptive names for arguments can be used in a rule.

Code listing 9.2: Rules

rule MakeHtml html : xml_xsl {

	Depends $(html) : $(xml_xsl) ;
	Depends all : $(html) ;
	Clean clean : $(html) ;
}

The target for a set of actions is the first argument for that set of actions, which is referenced as $(1). An action can only have a single input argument, referenced as $(2), but this argument may consist of a multiple file targets. The actions have reduced whitespace so that the build command appears in Jam's output the same way as Jam's built-in build commands do.

Note: The command jam -dd is very useful for solving dependency problems.

Code listing 9.3: Action and invocation

actions MakeHtml {
Xalan -v $(2) >$(1) ;
}

MakeHtml jamref.html : jamref.xml jamref.xsl ;

Depends all : jamref.html ;

A rule that references the dependencies must be invoked before Jam will attempt to build anything. Making the all pseudotarget dependant upon a specific target causes that target to be built automatically when Jam is invoked without any arguments. Is this example the dependency has been repeated; once within the rule and again after the rule is invoked.

Arguments to Jam rules take the form of parameter lists. The rule MakeHtml takes two arguments: the input list, and the output list. The colon is the delimiter which separates the two lists.

Improvements

A rule that invokes MakeHtml can make the main invocation more succinct:

Code listing 9.4: Factoring out repeated text

rule Html {

	local h = $(1).html ;
	local m = $(1).xml ;
	local s = $(1).xsl ;

	MakeHtml $(h) : $(m) $(s) ;
}

Html jamref ;

The declaration of local variables in the previous example was unnecessary; a shorter form of the rule is:

Code listing 9.5: A terse rule

rule Html { MakeHtml $(1).html : $(1).xml $(1).xsl ; }

Parameters can be passed to the stylesheet by adding an extra argument to the Html rule: a name list which refers to globally declared data. The list is expanded by the rule to conform with the syntax required by Xalan and the resulting parameter list is passed to the action as a target specific variable.

Code listing 9.6: Adding XSL parameters

rule MakeHtml {

	Depends $(1) : $(2) ;
	Clean clean : $(1) ;
}

actions MakeHtml { 
Xalan -v $(parms) $(2) >$(1) ; 
} 
 
rule Html { 
 
	local target = $(1).html ;
        local names = $(2) ; 
        local i value ; 
 
        for i in $(names) { 
                value = $($(1)_$(i)) ; 
                parms on $(target) += -p $(i) "\"'$(value)'\"" ; 
        } 

        MakeHtml $(target) : $(1).xml $(1).xsl ;
} 
 
jamref_date = "1 Jan 2007" ; 
jamref_revision = 1.1 ; 
 
Html jamref : date revision ;

Depends all : jamref.html ;

The files used in this example are available as jamref.xml, jamref.xsl, and jamref.html.

10. Directories

Overview

Two rules, SubDir and SubInclude, are used to manage subdirectories.

A SubDir rule identifies the directory which contains a Jamfile. The first argument in a SubDir rule is the name of the variable which refers to the the directory root (in this guide this variable is named JAMR). The other arguments for the SubDir rule are not variable names, but are instead literal tokens which identify the directories in the path from the root.

At the root of the tree containing the Jamfiles there should be a Jamrules file, which contains any default rules for the Jamfiles. A side effect of the SubDir rule is that the Jamrules file is read. The Jamrules file is only ever read once, at the first occurrence of the SubDir rule. The SubDir rule should precede any rules which refer to the contents of the directory.

A SubInclude rule causes another Jamfile to be read in. Like the SubDir rule, the first argument is the name of a variable, but the other arguments are literal directory names. Typically the Jamfile in the root directory (where the Jamrules file is) will contain a SubInclude rule for each subdirectory one level down. Multiple levels of subdirectories may be managed by writing SubInclude rules for a particular directory in the Jamfile of the parent directory, rather that putting all of the SubInclude rules in the Jamfile located in the root directory.

An environment variable may be used to define the root of the directory tree which contains Jamfiles. The SubDir rule will compute a value for this variable if it is not already defined. The SubDir rule sets $(SUBDIR) to the full path name of the directory identifies by the rule.

Example

The environment variable may be included within the .bash_profile file in your home directory.

Code listing 10.1: ~/.bash_profile

export JAMR=~/jam

An example directory structure may be set up with the following commands:

Code listing 10.2

$ mkdir $JAMR
$ cd $JAMR
$ mkdir main tools

The files mytools.h & mytools.cc should go into the tools directory. The file hello.c should go into the main directory.

The Jamfile in the JAMR directory should be as follows:

Code listing 10.3: Jamfile

SubDir JAMR ;
SubInclude JAMR tools ;
SubInclude JAMR main ;

The Jamrules file belongs in the JAMR directory. Here are the rules for variables which are commonly referenced by the individual Jamfiles. The Jam language assigns values to strings in a fashion similar to that of the bash shell. Here the string variable TOOLS is set to the path of the tools subdirectory.

Code listing 10.4: Jamrules

Echo "Parsing Jamrules" ;

C++ = g++ ;
LINK = $(C++) ;
TOOLS = $(JAMR)/tools ;

The Jamfile in the tools subdirectory uses the Objects rule to specify that only the compilation of the object file is desired.

Code listing 10.5: tools/Jamfile

SubDir JAMR tools ;

Objects mytools.cc ;

The Jamfile in the main directory refers to the tools directory for a dependency. The SubDirHdrs rule modifies the list of directories that the compiler will search for header files, and must include the tools subdirectory in this case.

Code listing 10.6: main/Jamfile

SubDir JAMR main ;

SubDirHdrs $(TOOLS) ;

Main hello : hello.cc $(TOOLS)/mytools.cc ;

11. Debugging

Pre-empive strategies

One of the advantages of Jam is its terse syntax, which enables production patterns to be written without a slew of extra punctuation characters. The downside of this feature is that Jam cannot readily distinguish between an badly formed expression and a list with items which necessarily contain semicolons or colons. This means that typos may not be reliably identified by Jam as such, and therefore measures should be taken when writing a Jamfile to assist in debugging, particulaly when multiple files are included.

There are several coding techniques which can make life easier when it comes to tracking down errors. The techniques of modularisation is particularly relevant since build systems do not usually have the benefit of top-down specification of function. Debugging rules which involve readable descriptions of the variables that they output are also beneficial.

Modularisation can be effected by minimising the number of expressions that do not exist within rules, and by using local variables rather than global ones wherever possible. When a rule sets a global variable there should ideally be a corresponding debugging rule which prints a description of the variable and its value. These rules can be commented out or renamed as non-functional variants in order to avoid being overwhelmed by debugging output when a Jamfile is run.

Another modularisation technique is the use of milestone targets. Milestone targets are simple file targets which are updated when a set of dependencies have been met. They enable a large build to be broken up into a set of chunks which can be independently tested, and they also enable dependencies to be filtered out of Jam's debug output in order to focus on a particular set of targets.

The multiple target trap

Jam's behaviour is not intuitive when more than one build target is produced from a single set of actions. This is because the dependency graph is constructed from the targets of actions and not from the actions themselves. If there are two or more build targets from a set of actions, and those targets are not dependant upon the same set of source targets, then the actions may be executed when all of the dependencies of the source targets have not been met, since the actions will be executed based on the dependencies of any of the build targets, not the dependencies of all of them.

This trap can be avoided by ensuring that actions which update multiple targets only ever have a single source target, and by ensuring that all of the build targets are dependant upon it. The source target could have a set of actions which simply write a debugging tag to a file, and would be dependant upon all of the source targets of the original action.

12. Recursive Invocation

Overview

Recursive invocation of Jam is a technique which enables Jam to set variable values based on the execution of arbitrary commands.

Example

Code listing 12.1: Recursive invocation

actions GetDistroPath {
source /etc/make.conf
export DISTDIR
jam make_distro
}

actions MakeDistro {
tar czf $(DISTDIR)/demo.tgz $(JAMR)
}

GetDistroPath distro ;
NOTFILE distro ;
ALWAYS distro ;

MakeDistro make_distro ;
NOTFILE make_distro ;
ALWAYS make_distro ;

In this example calling Jam with the pseudotarget distro results in Jam invoking itself recursively, with the environment variable DISTDIR set to the value defined in make.conf.



line
Updated 2008-01-26
line
Anon
Author

line
Summary: This document explains the Jam build system to a first-time user.
line

Donate to support our development efforts.

line
The Gentoo Linux Store
line
php|architect

php|architect is the monthly magazine for PHP professionals, available worldwide in print and electronic format. A percentage of all the sales will be donated back into the Gentoo project.

line
Tek Alchemy

Tek Alchemy offers dedicated servers and other hosting solutions running Gentoo Linux.

line
DDR Memory at Crucial.com

Purchase RAM from Crucial.com and a percentage of your sale will go towards further Gentoo Linux development.

line
Win4Lin at NeTraverse

Win4Lin from NeTraverse lets you run Windows applications under Gentoo Linux at native speeds.

line
Copyright 2001-2003 Gentoo Technologies, Inc. Questions, Comments, Corrections? Email docs@solder.ath.cx.