III. Dependency Management:

Jam maintains internally a special structure called the dependency graph. It is used to record relationships between various "things" called targets.

Targets correspond to things that must be generated, processed, or done to achieve the goals of the Jam user. For example, most Jam targets are file targets, which means that they correspond to files that must be either read or generated during the build. However, many other targets are not bound to the filesystem, but are used to describe more generic tasks (e.g. the 'clean' target, used to contain the commands used to erase all automatically generated files).

Each target is modeled by a node in the dependency graph, identified by a unique name. This name can be any string accepted by the Jam tokenizer.

A dependency between two targets is modeled by an arrow in the dependency graph. For example, we will illustrate the fact that target A depends on target B, which itself depends on target C with the following graphics:

As you would expect, dependencies are transitive, which means that if A depends on B, and B depends on C, then A indirectly depends on C as well. More on this later.


1. The Depends built-in rule:

The Depends built-in rule (also named DEPENDS for compatibility reasons with older Jam versions) is used to add a dependency in the graph. Its general syntax is:

  Depends a : b ;

which must be read as: every element of a depends on every element of b.

Note that if one of the targets listed in the Depends doesn't exist, it is automatically created in the graph. We can thus rewrite our previous examples in Jam syntax as:

  Depends A : B ;  # A depends on B
  Depends B : C ;  # B depends on C


2. Target commands & actions:

Each target can record a set of commands to be sent to the shell. This is generally used to 'build' file targets like object files or executables. Adding new commands to a given target happens according to the following scheme:

when you call a rule, the commands of the corresponding actions block are added to every element of the rule's first argument.

for example, consider the following script:

  rule Compile
  {
    Depends $(1) : $(2) ;
  }
  
  actions Compile
  {
    gcc -o $(1) $(2)
  }
  
  Compile  hello : hello.c ;

The purpose of such script is to compile a single C source file, named "hello.c", into a single executable, named "hello", with the GCC compiler. We'll see later how to deal with an '.exe' extension for Windows or OS/2 systems. For the moment, let's forget about this issue. What really happens when Jam parses this script is the following:

Note that at the end of the parsing, no commands will be sent to the command shell yet. Instead, we will have the following dependency graph built:

You will need to invoke 'jam hello' to tell Jam to rebuild the 'hello' program when needed. You can also add a dependency to a special hard-coded target named all which is used by Jam to determine the default targets to rebuild. Simply change the definition of the Compile rule as follows:

  rule Compile
  {
    Depends $(1) : $(2) ;
    Depends all  : $(1) ;
  }

You'll get the following graph:

Then, simply calling 'jam' will check if the hello program needs to be built. Note that there is nothing special about the 'all' target, except that it is used by Jam as the default target when it is invoked.



you can call an undefined rule if a corresponding action block is defined. In this case, the action's commands will simply be added to the targets listed in the first call parameter

For example, we can modify our script to strip the final executable. What we do is define a new actions block, then call the corresponding rule. The new code is in bold:

  rule Compile
  {
    Depends $(1) : $(2) ;
    Depends all  : $(1) ;
  }
  
  actions Compile
  {
    gcc -o $(1) $(2)
  }
  
  actions Strip
  {
    strip $(1) 
  }
  
  Compile  hello : hello.c ;
  Strip    hello ;

Note that we didn't need to define a 'Strip' rule. The above script builds the following graph:



3. Target-specific variables:

It is possible to refer to Jam variables within actions block. However, these are only expanded when the commands are launched, i.e. after all Jam scripts have been parsed. Consider the following example:

  CC = cc ;
  
  actions  Compile
  {
    $(CC) -o $(1) $(2) -D$(DEFINES) 
  }

  rule Compile
  {
    Depends $(1) : $(2) ;
    Depends all  : $(1) ;
  }
    
  Compile  hello : main.c ;
  
  DEFINES = DEBUG ;
  CC      = gcc ;

There are several things happening here:

In other words, the initial definition of CC as "cc" will never be used in this script. This behaviour can be a problem when we want to associate different variable values to distinct variables, though using the same actions/rules. For example, if we wanted to use a different DEFINES depending on the target.

Fortunately, Jam allows us to define so-called target-specific variables. This correspond to variable definitions that are specific to a given target, and only used when expanding that target's command. You can define such a variable with the following syntax:

  VARIABLE on target  = value ;  # set a target-specific variable
  VARIABLE on target += value ;  # append to a target-specific variable
  VARIABLE on target ?= value ;  # set a target-specific variable if it is undefined

Here's a short example:

  actions Touch
  {
    touch $(FILE) ;
  }
  
  FILE = foo ;               # global value
  FILE on target1 = bar ;    # target-specific value
  FILE on target2 = $(FILE)-2 ;  # target-specific value
  
  Touch target1 ;  # expands to "touch bar"   for "target1"
  Touch target2 ;  # expands to "touch foo-2" for "target2"
  Touch target3 ;  # expands to "touch foo"   for "target3'

Notice that when a variable is expanded on the right side of a per-target assignment, the global value will always be substituted.

  # this sets the value of FOO on target to the global
  #value of FOO followed by 'bar'
  FOO on target = $(FOO) bar ;

4. Target Bindings

Once the parsing pass is completed and that the commands for each target are fully determined, Jam begins the "binding pass" where it recursively descends the dependency graph to link each target to a specific file location.

This location is called the target's binding, or the target's bound name; it is determined through the following rules:

As a general rule, LOCATE is used to determine where to put new files (e.g. object files, libraries and executables), while SEARCH is used to find sources, especially header files.

5. Bound Variables

The arguments $(1) and $(2) are treated specially during the expansion of actions blocks. Instad of just being substituted by their content, they are always replaced by the binding of the corresponding target. For example, in:

    actions Dummy
    {
      echo dummy > $(1)
    }
    
    LOCATE on foo = dir ;  # 'foo' is bound to 'dir/foo'
    Dummy  foo ;           # expands to "echo dummy > dir/foo"

What happens here is really a double substitution: $(1) expands to foo which is itself replaced by dir/foo

this double-substitution doesn't happen for other variables expanded within actions block, except when you use the special bind modifier. Its purpose is precisely to indicate that certain variables need to be expanded with the binding name of their value.

  actions  Message1
  {
    echo $(MESSAGE) > $(1)
  }
  
  actions  bind MESSAGE Message2
  {
    echo $(MESSAGE) > $(1)
  }
  
  LOCATE on foo = dir ;   # target 'foo' is bound to 'dir/foo'
  LOCATE on bar = dir2 ;  # target 'bar' is bound to 'dir2/bar'

  MESSAGE = foo ;
  
  Message1  bar ;  # expands to "echo foo > dir2/bar"
  Message2  zoo ;  # expands to "echo dir/foo > zoo"


6. Other Dependency built-ins:

Jam defines several other built-in rules related to the management of dependencies. They can be useful when you'll begin writing your own rules instead of relying on the defaults provided by the Jambase.


6.a. The Includes built-in rule:

This built-in rule (also called INCLUDES for compatibility with older Jam versions) defines a different type of dependency within the graph. For example, the following:

Includes  a : b ;

Means that any target that depends on any element of a also depend on each element of b. This corresponds to the case where one source file includes another one. Modifying the included file should force the update of its users.

As an example, consider the following whose effect is to declare that foo.o depends on both foo.c and foo.h:

Depends  foo.o : foo.c ;
Includes foo.c : foo.h ;

Note however that you should normally never need to use this rule in your Jamfile. That's because Jam is capable of automatically computing source inclusion dependencies. While we'll describe this process later in detail, calling the Includes rule directly is only useful for the rare cases where this process doesn't work as you expect it. More on this later.


6.b. Other built-in rules:

There are also other built-in rules which affect the way Jam computes dependencies within the graph. Unless you start seriously hacking the Jambase or writing complex rules, you probably don't need to use them though:


  NotFile targets ;

This rule is used to mark targets as pseudo targets and not real files. This means that no timestamp will be checked and that actions on these targets are only executed when their dependencies are themselves updated, or when the target is also tagged with Always described below.


  Always targets ;

This rule can be used to indicate that targets should always be rebuilt independent of other conditions, though they still must be in the dependency graph. This is typically used for the clean and uninstall targets, as they have no dependencies and would otherwise appear never to need building. Best applied to targets that are also NotFile, even though it can also be used to force a real file to be updated as well.


  NoCare targets ;

This rule is used to indicate optional targets. Jam will not abort when it cannot find them on the filesystem or when no commands are defined for them; in such cases, it will dump a warning and also skip any target that depends on these.


  Temporary targets ;

This rule is used to indicate as temporary, allowing them to be removed once other targets that depend on them have been built. This is used in the Jambase to tag object files that are later moved into library archives on certain platforms. When a temporary target cannot be found on the filesystem, the timestamp of its parent target is used.


  Leaves targets ;

Makes each of targets depend only on its leaf dependees in the graph, and not on any intermediate targets. This makes it immune to its dependencies being updated, as the "leaf" dependencies are those without their own dependencies and without updating actions. This allows a target to be updated only if original source files change.



7. Other Action modifiers:

The following modifiers can be used in the definition of an actions block. They must appear after the actions keyword, and before the bind modifier, if any, or the rule name, as in:

    actions Modifiers RuleName { commands }

ignore

Used to tell Jam to ignore the error status of the commands and continue building other targets as if everything worked successfully. By default, Jam stops whenever it encounters a command that returns an error.

quietly

prevents the action's name from being sent to the standard output when it is run. Note that this doesn't prevent the commands themselves to print data. Moreover, if Jam encounters an error, it dumps the corresponding commands to let you know what happened.

existing

modifies $(1) (a.k.a. $(<)) when the commands are expanded to only include the targets whose binding exist on the filesystem. For example, this is used for the commands of the Clean target, used to remove temporary files.

updated

modifies $(2) (a.k.a. $(>) when the commands are expanded to only include the targets which need updating. For example, this is used with the Archive rule of the Jambase to add updated object files to a library.

together

piecemeal


8. Automatic Inclusion Dependencies:

When you yse the default Jambase, you don't need to list the header files included by your sources. That's because is capable of automatically compute these dependencies for you, and this section will explain how this works.

IMPORTANT: the following is only useful if you want to seriously hack the Jambase or better understand Jam internals. A typical user can safely skip to the next section.

The scheme used by Jam is both simple and powerful: