Chapter 26. Recipe Syntax and Semantics

Recipe syntax definition

This defines the recipe syntax (more or less). NOTE: this may still change quite a bit.

Table 26-1. Notation

|separates alternatives
()grouping a sequence of items or alternatives
[]optional items (also does grouping)
""contains literal text; """ is one double quote
...indicates the preceding item or group can be repeated
EOLan end-of-line, optionally preceded by a comment
INDENT  an amount of white space, at least one space
INDENT2  an amount of white space, at least one space more than INDENT

A comment starts with "#" and continues until the end-of-line. It may appear in many places.

White space may be inserted in between items. It is often ignored, but does have a meaning in a few places.

Line continuation is done with a backslash immediately before an end-of-line. It's not needed for an assignment and other commands that don't have a build_block: the item continues if the following line has more indent than the first line.

For items that have a build_block, the start of the build block is the line with the smallest indent that is larger than the indent of the line that started the command. The lines before this are continuation lines. Example:
	mytarget : source1
		      source2	# continuation line
	    :print $source	# first build line of build block

aapfile::=( [ aap_item ] EOL ) ...
aap_item::=dependency | rule | variant | otherfile | build_command
   
dependency::=targets ":" [ dependencies ] [ EOL build_block ]
targets::=item_list
dependencies::=item_list
build_block::=INDENT build_command [ EOL [ INDENT build_command ] ] ...
   
rule::=":rule" pattern ... ":" pattern ... [ EOL build_block ]
   
variant::=":variant" name ( EOL variant_item ) ...
variant_item::=INDENT varvalue EOL INDENT2 build_block
   
otherfile::=includefile | childfile
includefile::=":include" item_list
childfile::=":child" item_list
   
build_command::=assignment | script_item | generic_command | message
   
assignment::=assign_set | assign_append | assign_cond
assign_set::=name "=" item_list
assign_append::=name "+=" item_list
assign_cond::=name "?=" item_list
   
script_item::=script_line | script_block
script_line::="@" script_command
script_block::=":python" EOL ( script_command EOL ) ... ":end"
   
generic_command::=":" command_name [ command_argument ]
   
message::=info_msg | error_msg
info_msg::=":print" expression ...
error_msg::=":error" expression ...
   
# A list can contain white-separated items and Python style expressions.
   
item_list::=item [ white_space item ] ...
item::=( simple_item | "(" item_list ")" ) [ attribute ... ]
simple_item::=( expression | non_white_item ) ...
attribute::="{" name [ "=" expression ] "}"
expression::=string_expr | python_expr
string_expr::=""" text """ | "'" text "'"
python_expr::="`" python-expression "`"
non_white_item::=non-white-text | "$" [ "(" ] name [ ")" ]


A backslash at the end of the line is used for line continuation.
- Can't have white space after it!
- Also for comments.
- Also for Python commands; A leading @ char and white space before it is
  removed.
- Lines are joined before inspecting indents.

	# comment \
	continued comment

	@ python command \
	@   continued python command \
	       still continued


When lines are joined because a command continues with a line with more indent,
the line break and leading white space of the next line are replaced with a
sinble space.  An exception is when the line ends in "$br": The $br is removed,
the line break is inserted in the text and leading white space of the next line
is removed.


VARIABLES:

When concatenating variables and using rc-style expansion, the attributes of
the last variable overrule the identical attributes of a previous one.

	v1 = foo {check = 1}
	v2 = bar {check = 2}
	vv = $*v1$v2

	-> vv = foobar{check = 1}{check = 2}

When using rc-style expansion, quotes will not be kept as they are, but
removed and re-inserted where used or necessary.  Example:

	foo: "file 1.c" foo.c
		:print "dir/$*source"

Results in:  "dir/file 1.c" "dir/foo.c"

Be careful with using "$\" and quotes, you may not always get what you wanted.

To get one item out of a variable that is a list of items, use an index number
in square brackets.  Parenthesis or curly braces must be used around the
variable name and the index.  The first item is indexed with zero.  Example:

	BAR = beer coffee cola
	:print $(BAR[0])
beer
	BAR_ONE = $(BAR[2])
	:print $BAR_ONE
cola

Using an index for which no item exists gives an empty result.  When $MESSAGE
includes "warning" a message is printed about this.

Normally using $VAR gets what you want.  A-A-P will use the kind of quoting
expected and add attributes when needed.  However, when you want something
else, this can be specified:
	$var	depends on where it's used

	$?var	when the variable is not set or defined use an empty string
		instead of generating an error

	$-var	without attributes (may collapse white space)
	$+var	with attributes
	
	$*var	use rc-style expansion (may collapse white space)

	$=var	no quotes or backslashes
	$'var	aap quoted (using ' and/or " where required, no backslashes)
	$"var	quoted with " (doubled for a literal ")
	$\var	special characters escaped with a backslash
  	$!var	depends on the shell, either like $'var or $"var

In most places $var is expanded as $+'var (with attributes, using ' and " for
quoting).  The exceptions are:
	:sys			$-!var		no attributes, shell quoting
	$n in $(v[$n])		$-=var		no attributes, no quoting
	:del			$-'var		no attributes, normal quoting

The quoted variables don't handle the backslash as a special character.  This
is useful for MS-Windows file names.  Example:

	prog : "dir\file 1.c"
		:print $'source

Results in:  "dir\file 1.c"



ASSIGNMENT:

overview:
   var = value	    assign
   var += value	    append (assign if not set yet)
   var ?= value	    only assign when not set yet
   var $= value	    evaluate when used
   var $+= value    append, evaluate when used
   var $?= value    only when not set, evaluate when used

Assignment with "+=" or "$+=" appends the argument as a separate item.  This
is actually done by inserting a space.  But when the variable wasn't set yet
it works like a normal assignment:

	VAR += something

is equal to:

	@if globals().has_key("VAR"):
	@   VAR = VAR + " " + "something"
	@else:
	@   VAR = "something"

Assignment with "?=" only does the assignment when the variable wasn't set
yet.  A variable that was set to an empty string also counts as being set.
Thus when using "aap VAR=" the empty value overrules the value set with "?=".

	VAR ?= something

is equal to:

	@if not globals().has_key("VAR"):
	    VAR = something

When using "$=", "$+=" or "$?=" variables in the argument are not evaluated at
the time of assignment, but this is done when the variable is used.

	VAR = 1
	TT $= $VAR
	VAR = 2
	:print $TT

prints "2".

When first setting a variable with "$=" and later appending with "+=" the
evaluation is done before the new value is appended:

	VAR = 1
	TT $= $VAR
	TT += 2
	VAR = 3
	:print $TT

prints "1 2"

Note that evaluating a python expressions in `` is not postponed.


BLOCK ASSIGNMENT

The normal assignment command uses a single line of text.  When broken into
several lines they are joined together, just like with other commands.  $br can
be used to insert a line break.  Example:

	foo = first line$br
		second line$br
		third line $br

The block assignment keeps the line breaks as they are.  The same example but
using a block assignment:

	foo << EOF
	  first line
	  second line
	  third line 
	    EOF

The generic format is:

	{var} << {term}
	line1
	...
	{term}

{term} can be any string without white space.  The block ends when {term} is
found in a line by itself, optionally preceded by white space and followed by
white space and a comment.

The amount of indent to be removed from all the lines is set by the first
line.  When the first line should start with white space use $empty.

All the variations of the assignment command can be used:

   var << term	    assign
   var +<< term	    append (assign if not set yet)
   var ?<< term	    only assign when not set yet
   var $<< term	    evaluate when used
   var $+<< term    append, evaluate when used
   var $?<< term    only when not set, evaluate when used


ATTRIBUTES

Items can be given attributes.  The form is:

	{name = value}

"value" is expanded like other items, with the addition that "}" cannot appear
outside of quotes.

This form is also possible and uses the default value of 1:

	{name}

Examples:

	bar : thatfile {check = $MYCHECK}
	foo {virtual} : somefile

The "virtual" attribute is used for targets that don't exist (as file or
directory) but are used for selecting the dependency to be build.  These
targets have the "virtual" attribute set by default:

	TARGET		COMMONLY USED FOR
	all		build the default targets
	clean		remove intermediate build files
	distclean	remove all generated files

	test		run tests
	check		same as "test"
	install		build and install for use
	tryout		build and install for trying out

	reference	generate or update the cross-reference database

	fetch		obtain the latest version of each file
	update		fetch and build the default targets

	checkout	checkout (and lock) from version control system
	commit		commit changes to VCS without unlocking
	checkin		checkin and unlock to VCS
	unlock		unlock files from a VCS
	add		add new files to VCS
	remove		remove deleted files from VCS
	revise		like checkin + remove
	tag		add a tag to the current version

	prepare		prepare for publishing (generated docs but no exe) 
	publish		distribute all files for the current version

	finally		always executed last (using "aap finally" is uncommon)

These specific targets may have multiple build commands.  They are all
executed to update the virtual target.  Normally there is up to one target in
each (child) recipe.

Note that virtual targets are not related to a specific directory.  Make sure
no other item in this recipe or any child recipe has the same name as the
virtual target to avoid confusion.  Specifically using a directory "test"
while there also is a virtual target "test".  Name the directory "testdir"
to avoid confusion.

The "comment" attribute can be used for targets that are to be specified at
the command line.  "aap comment" will show them.

	% aap comment
	target "all": build everything
	target "foo": link the program


EXECUTING COMMANDS

A dependency and a rule can have a list of commands.  For these commands the
following variables are available:

	$source		The list of input files as a string.
	$source_list	The list of input files as a Python list.
	$source_dl	Only for use in Python commands: A list of
			dictionaries, each input item is one entry.
	$depend		The list of dependencies (source files plus virtual
			dependencies) as a string.
	$depend_list	The list of dependencies (source files plus virtual
			dependencies) as a Python list.
	$depend_dl	Only for use in Python commands: A list of
			dictionaries, each dependency item is one entry.
	$target		The list of output files as a string.
	$target_list	The list of output files as a Python list.
	$target_dl	Only for use in Python commands: A list of
			dictionaries, each output item is one entry.
	$buildtarget	The name of the target for which the commands are
			executed.  It is one of the items in $target.
	$match		For a rule: the string that matched with %

Example:

	doit {virtual}:
		:print building $target
	prog : "main file.c" doit
		:print building $target from $source

Results in:
	  building doit{virtual=1}
	  building prog from "main file.c"

Note that quoting of expanded $var depends on the command used.

The Python lists $source_list and $target_list can be used to loop over each
item.  Example:
	$OUT : foo.txt
		@for item in target_list:
			:print $source > $item

Note the difference between $source and $depend: $source only contains real
files, $depend also contains virtual dependencies.

The list of dictionaires can be used to access the attributes of each item.
Each dictionary has an entry "name", which is the (file) name of the item.
Other entries are attributes.  Example:

	prog : file.c {check = md5}
		@print sourcelist[0]["name"], sourcelist[0]["check"]

Results in:  file.c  md5


RATIONALE

In an assignment and other places a Python expression can be used in
backticks.  Expanding this is done before expanding $VAR items, because this
allows the possibility to use the Python expression to result in the name of a
variable.  Example:

	foovaridx = 5
	FOO = $SRC`foovaridx`
Equal to:
	FOO = $SRC5

In the result of the Python expression $ characters are doubled, to avoid it
being interpreted as the start of a variable reference.  Otherwise Python
expressions with arbitrary results would always have to be filtered explicitly.
Variables can still be obtained without using the $, although none of the
modifiers like $+ and $* are available.  Example:

	FOO = foo/`glob("*.tmp")`

May result in:

	FOO = foo/one.tmp two.tmp

Note that "foo/" is only prepended to the whole result, not each
white-separated item.  If you do want rc-style expansion, use two commands:

	TT = `glob("*.tmp")`
	FOO = foo/$*TT

Result:
	FOO = foo/one.tmp foo/two.tmp

Watch out for unexpected results when rc-style expansion is done for $*VAR.
Example:

	VAR = one two
	FOO = $*VAR/`glob("*.tmp")`

Would result in:

	FOO = one/one.tmp two/one.tmp two.tmp

because the `` part is expanded first, thus the assignment is executed like:

	FOO = $*VAR/one.tmp two.tmp

The backticks for a Python expression are also recognized inside quotes,
because this makes the rule for doubling backticks consistent.  Example:

	FOO = "this``file" that``file

Doubling the backtick to avoid it being recognized as a Python expression
makes it impossible to have an empty Python expression.  There appears to be
no reason to use an empty Python expression.