The Jam/MR Language

Jam/MR 2.2

This document describes the Jam/MR language, used to defines rules and dependencies for jam, the Jam/MR executable program. For additional information, please see:

Also, the Jambase file provided in the Jam/MR distribution is an excellent source of Jam/MR language examples. Documentation and source are available from www.perforce.com/jam/jam.html


Lexical Features

Jam/MR treats its input files as whitespace-separated tokens, with two exceptions: double quotes (") can enclose whitespace to embed it into a token, and everything between the matching curly braces ({}) in the definition of a rule action is treated as a single string. A backslash (\) can escape a double quote.

Jam/MR requires whitespace (blanks, tabs, or newlines) to surround all tokens, including the colon (:) and semicolon (;) tokens. This is because jam runs on many platforms and no characters, save whitespace, are uncommon in the file names on all of those platforms.

Targets

The essential Jam/MR data entity is a target. Built targets are files to be updated. Source targets are the files used in updating those targets. Pseudotargets are symbols which represent dependencies on other targets. Built targets and source targets are collectively referred to as file targets; pseudotargets are not file targets.

Each target has a unique identifier, which, in the case of file targets, is its filename optionally qualified with grist (a string value used to assure uniqueness) and/or a pathname, either rooted or relative to the directory of jam's invocation. A file target with an identifier of the form file(member) is an an ar(1) library member.

Rules

Jam/MR's basic language entity is called a rule, which is used primarily to relate built targets to their source targets. A rule is defined in two parts: the Jam/MR statements to execute when the rule invocation is parsed, and the actions (shell commands) to execute when updating the built targets of the rule. A rule may have a procedure definition, an action definition, or both.

The Jam/MR statements for defining and invoking rules are as follows:

	rule rulename { statements }

	actions [ modifiers ] rulename { commands }

	rulename field1 : field2 : ... : fieldN ;
	

The first form defines a rule's procedure; the second defines the rule's updating actions; the third invokes the rule. Redefining a rule's procedure or actions replaces the previous definition.

A rule is invoked with values in field1 through fieldN, which are referenced in the procedure definition statements as variables named $(1) through $(N). A rule defined with a procedure definition and no action definition is just a procedure call.

When a rule is invoked, its action definition, if any, is automatically the first updating action to be associated with targets. Any other actions invoked from a rule's procedure definition statements will be executing during updating in the order in which they were invoked. Updating actions are associated with the targets in field1.

Built targets and source targets are passed to an updating action through field1 and field2, and are refenced in the action's commands as $(1) and $(2) respectively. $(1) and $(2) may also be referenced as $(<) and $(>) in action commands and procedure definition statements. During updating, commands are passed to the OS, with $(1) and $(2) replaced by bound targets. (See "Binding" in the The Jam/MR Executable Program.)

Action Modifiers

The following action modifiers are understood:

              actions bind vars
                     $(vars) will be replaced with bound values.
			
              actions existing
                     $(>) includes only source targets currently  existing.

              actions ignore
                     The  return  status of the commands is
                     ignored.

              actions piecemeal
                     commands  are  repeatedly  invoked
                     with a subset of $(>) small enough to fit in
                     the command buffer on this OS.

              actions quietly
                     The action is not  echoed  to  the  standard
                     output.

              actions together
                     The $(>) from multiple invocations of the same
                     action  on  the  same  built target  are   glommed
                     together.

              actions updated
                     $(>) includes only source targets themselves
		     marked for updating.

Built-in Rules

Jam/MR has ten built-in rules, none of which have updating actions:


              ALWAYS targets ;
                     Rebuilds  targets, even if they are up-to-date.

              DEPENDS targets1 : targets2 ;
                     Makes targets1 dependencies of targets2.

              ECHO args ;
                     Blurts out the message args to stdout.

              EXIT args ;
                     Blurts out the message args to stdout  and
                     then exits with a failure status.

              INCLUDES targets1 : targets2 ;
                     Makes  targets2 dependencies of anything of which 
		     targets1 are dependencies.

              LEAVES targets ;
                     Makes each of targets depend only  on  its leaf  
		     sources,  and  not on any intermediate targets. Its leaf 
		     sources are those  source targets without any 
		     dependencies themselves.

              NOCARE targets ;
                     Marks targets as possibly being not needed. If a 
		     target is present, its timestamp is used to compute 
		     dependencies; if it is not present, it is not considered a
		     dependency.

              NOTFILE targets ;
                     Marks targets as pseudotargets, not files.

              NOUPDATE targets ;
                     Causes the timestamps  of  targets  to  be ignored: 
		     either  target exists  or it doesn't. If it  exists,
		     it is  considered eternally old.

              TEMPORARY targets ;
                     Marks targets as temporary; causes targets to be removed 
		     when all their dependencies have been updated.

The ALWAYS, LEAVES, NOCARE, NOTFILE, NOUPDATE, and TEMPORARY affect only the binding phase (q.v.).

Flow-of-Control

Jam/MR has several simple flow-of-control statements:

	      local vars [ = values ] ;

              include file ;

              for var in list { statements }

              switch value 
			{ case pattern1 : statements ; 
			  case pattern2 : statements ; 
			  ... 
			}

              if  cond 
			{ statements } 
	 	 [ else { statements } ]

Variables

Jam/MR variables are lists of zero or more elements, with each element being a string value. An undefined variable is indistinguishable from a variable with an empty list, however, a defined variable may have one more elements which are null strings.

Variables are either global or target-specific. All variables are referenced as $(VARIABLE).

A variable is defined with:

              variable = elements ;

              variable += elements ;

              variable on targets = elements ;

              variable on targets += elements ;

              variable default = elements ;
              variable        ?= elements ;

The first two forms set variable globally. The third and forth forms set a target-specific variable, where variable takes on a value only during the binding and updating targets. The = operator replaces any previous elements of variable with elements; the += operation adds elements to variable's list of elements. The final two forms do the same thing, set variable globally, but only if it was previously unset.

On program start-up, jam imports environment variable settings into Jam/MR variables. Environment variables are split at blanks with each word becoming an element in the variable's list of values. Environment variables whose names end in PATH are split at $(SPLITPATH) characters (e.g., ":" for Unix). Environment variable values can be overridden on the command line with the -s flag. Jam/MR variables are not re-exported to the shell that executes the updating actions, but the updating actions can reference Jam/MR variables with $(variable).

Variables referenced in updating commands will be replaced with their values; target-specific values take precedence over global values. Variables passed as arguments to actions are replaced with their bound values; the "bind" modifier can be used on actions to cause other variables to be replaced with bound values.

Variable Expansion

During parsing, Jam/MR performs variable expansion on each token that is not a keyword or rule name. Such tokens with embedded variable references are replaced with zero or more tokens. Variable references are of the form $(v) or $(vm), where v is the variable name, and m are optional modifiers.

Variable expansion in a rule's actions is similar to variable expansion in statements, except that the action string is tokenized at whitespace regardless of quoting.

The result of a token after variable expansion is the product of the components of the token, where each component is a literal substring or a list substituting a variable reference. For example:

              $(X)      -> a b c
              t$(X)     -> ta tb tc
              $(X)z     -> az bz cz
              $(X)-$(X) -> a-a a-b a-c b-a b-b b-c c-a c-b c-c

The variable name and modifiers can themselves contain a variable reference, and this partakes of the product as well:

              $(X)      -> a b c
              $(Y)      -> 1 2
              $(Z)      -> X Y
              $($(Z))   -> a b c 1 2

Because of this product expansion, if any variable reference in a token is undefined, the result of the expansion is an empty list. If any variable element is a null string, the result propagates the non-null elements:

              $(X)        -> a ""
              $(Y)        -> "" 1
	      $(Z)	  -> 
	      *$(X)$(Y)*  -> *a* *a1* ** *1*
	      *$(X)$(Z)*  ->

A variable element's string value can be parsed into grist and filename-related components. Modifiers to a variable are used to select elements, select components, and replace components. The modifiers are:

[n] Select element number n (starting at 1). If the variable contains fewer than n elements, the result is a zero-element list.
[n-m] Select elements number n through m.
[n-] Select elements number n through the last.
:B Select filename base.
:S Select (last) filename suffix.
:M Select archive member name.
:D Select directory path.
:P Select parent directory.
:G Select grist.
:U Replace lowercase characters with uppercase.
:L Replace uppercase characters with lowercase.
:chars Select the components listed in chars.
:G=grist Replace grist with grist.
:D=path Replace directory with path.
:B=base Replace the base part of file name with base.
:S=suf Replace the suffix of file name with suf.
:M=mem Replace the archive member name with mem.
:R=root Prepend root to the whole file name, if not already rooted.

On VMS, $(var:P) is the parent directory of $(var:D); on Unix and NT, $(var:P) and $(var:D) are the same.

Built-in Rules and Variables

This section discusses Jam/MR's built-in rules and variables. These built-in rules, along with the other Jam/MR syntax for manipulating variables, provide the foundation upon which the Jambase is built. A Jamfile, or (more likely) a Jamrules (q.v.), can make use of these built-in rules and variables as well.

DEPENDS, INCLUDES Rules

Two rules build the dependency graph. DEPENDS simply makes its sources dependencies of its targets. INCLUDES makes its sources dependencies of anything of which its targets are dependencies. This reflects the dependencies that arise when one source file includes another: the object built from the source file depends both on the original and included source file, but the two sources files don't depend on each other. For example:
              DEPENDS foo.o : foo.c ;
              INCLUDES foo.c : foo.h ;
Both "foo.c" and "foo.h" become dependencies of "foo.o" in this example.

ALWAYS, LEAVES, NOCARE, NOTFILE, NOUPDATE, TEMPORARY Rules

Six rules mark targets so that jam treats them differently during its target binding and updating phase. Normally, jam updates a target if it is missing, if its filesystem modification time is older than any of its dependencies (recursively), or if any of its dependencies are being updated. This basic behavior can be changed by invoking the following rules with the target name as the rule's target:

The ALWAYS rule causes its targets to be always updated. This is used for the clean and uninstall targets, as they have no dependencies and would otherwise appear never to need building. It is best applied to targets that are also NOTFILE targets, but it can also be used to force a real file to be updated as well.

The NOCARE rule causes jam to ignore its targets if they can't be found and have no updating actions. Normally, jam issues a warning about a target that can't be built and then refuses to build anything that depends on that target. The HdrRule in Jambase uses NOCARE on the header file names found during header file scanning, to let jam know that the included files may not exist. For example, if a #include is within an #ifdef, the included file may not actually be around.

The NOTFILE rule marks its targets as being pseudotargets, that is, targets that aren't really files. The actions on such a target are only executed if the target's dependencies are updated, or if the target is also marked with ALWAYS. The default jam target "all" is a pseudotarget. In Jambase, NOTFILE is used to define several addition convenient pseudotargets.

The NOUPDATE rule causes jam to ignore the modification time of the target. This has two effects: first, once the target has been created it will never be updated; second, manually updating target will not cause other targets to be updated. In Jambase, for example, this rule is applied to directories by the MkDir rule, because MkDir only cares that the target directory exists, not when it has last been updated.

The TEMPORARY rule allows for targets to be deleted after they are generated. If jam sees that a temporary target is missing, it will use the target's parent's time when determining if the target needs updating. Jambase uses TEMPORARY to mark object files that are archived in a library after they are built, so that they can be deleted after they are archived.

The LEAVES rule makes each of the targets depend only on its "leaf" dependencies. 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.

ECHO, EXIT Rules

These two rules are are used only in jam's parsing phase. The ECHO rule just echoes its targets to the standard output. The EXIT rule does the same and then does a brutal, fatal exit of jam.

SEARCH, LOCATE Variables

These two variables control the binding of target names to real files: they indicate what path name is to be prepended to the target name when substituting values for variables referenced in action definitions. (See "Binding" in The Jam/MR Executable Program.)

$(SEARCH) provides a list of directories along which jam scans looking for a target. $(LOCATE) overrides $(SEARCH), indicating the directory where the target must be. Normally, $(SEARCH) is set for existing targets while $(LOCATE) is set for the targets which jam must build. Only the first element in $(LOCATE) is used for binding. If neither $(SEARCH) nor $(LOCATE) are set, or if the name of the target is a rooted file name (e.g., on UNIX beginning with "/"), then the file name is assumed to be the target name.

Both $(SEARCH) and $(LOCATE) should be set target-specific and not globally. If they were set globally, jam would use the same paths for all file binding, which is not likely to produce sane results. When writing your own rules, especially ones not built upon those in Jambase, you may need to set $(SEARCH) or $(LOCATE) directly. Almost all of the rules defined in Jambase set $(SEARCH) and $(LOCATE) to sensible values for sources they are looking for and targets they create, respectively.

HDRSCAN, HDRRULE Variables

These two variable control header file scanning. The first is an egrep(1) pattern, with ()'s surrounding the file name, used to find file inclusion statements in source files. The second is the name of a rule to invoke with the results of the scan: the scanned file is the target, the found files are the sources. This is the only place where jam invokes a rule through a variable setting.

Both $(HDRSCAN) and $(HDRRULE) must be set for header file scanning to take place, and they should be set target-specific and not globally. If they were set globally, all files, including executables and libraries, would be scanned for header file include statements.

The scanning for header file inclusions is not exact, but it is at least dynamic, so there is no need to run something like makedepend(GNU) to create a static dependency file. The scanning mechanism errs on the side of robustness (i.e., it is more likely to return filenames that are not actually used by the compiler than to miss include files) because it can't tell if #include lines are inside #ifdefs or other conditional logic. In Jambase, HdrRule applies the NOCARE rule to each header file found during scanning so that if the file isn't present and isn't needed during compilation, jam doesn't skip its dependencies.

Also, scanning for regular expressions only works where the included file name is literally in the source file. It can't handle languages that allow including files using variable names (as the Jam/MR language itself does).

Platform Identifier Variables

A number of Jam/MR built-in variables can be used to identify runtime platform:

UNIXtrue on Unix platforms
NTtrue on NT platforms
VMStrue on VMS platforms
MACtrue on MAC platforms
OS2true on OS2 platforms
OSOS identifier string
OSVEROS version, when applicable
OSPLATUnderlying architecture, when applicable
JAMVERSIONjam version, currently "2.2"

JAMDATE Variable

Initialized to time and date at jam startup.

JAMSHELL Variable

When jam executes a rule's action block, it forks and execs a shell, passing the action block as an argument to the shell. The invocation of the shell can be controlled by $(JAMSHELL). On Unix, for example:
              JAMSHELL = /bin/sh -c % ;
The % is replaced with the text of the action block.

jam can build targets in parallel, as long as the dependencies among files are properly spelled out and actions don't create fixed named files in the current directory. (If either of those two provisions are violated, jam can trip over itself when building in parallel things which just happen to build OK sequentially.) When building in parallel, jam simply forks off more than one shell at a time.

jam does not directly support building in parallel across multiple hosts, since that is heavily dependent on the local environment. To build in parallel across multiple hosts, you need to write your own shell that provides access to the multiple hosts. You then reset $(JAMSHELL) to reference it.

Just as jam expands a % to be the text of the rule's action block, it expands a ! to be the multi-process slot number. The slot number varies between 1 and the number of concurrent jobs permitted by the -j flag given on the command line. Armed with this, it is possible to write a multiple host shell. For example:

	  #!/bin/sh

	  # This sample JAMSHELL uses the SunOS on(1) command to execute
	  # a command string with an identical environment on another host.
	  #
	  # Set JAMSHELL = jamshell ! %
	  #
	  # where jamshell is the name of this shell file.
	  #
	  # This version handles up to -j6; after that they get executed
	  # locally.

	  case $1 in
	  1|4) on winken sh -c "$2";;
	  2|5) on blinken sh -c "$2";;
	  3|6) on nod sh -c "$2";;
	  *)   eval "$2";;
	  esac

Back to top.

Copyright 1997 Perforce Software, Inc.
Comments to info@perforce.com
Last updated: Oct 19, 1997