Table of Contents
The abuild interface system is the mechanism through which
abuild provides encapsulation. Its purpose is to allow build
items to provide information about the products they provide to
other build items. Build items provide their interfaces with the
Abuild.interface file. This chapter
describes the interface system and provides details about the
syntax and semantics of Abuild.interface and
other abuild interface files.
This section contains a prose description of the interface
system's functionality and presents the basic syntax of
Abuild.Interface without providing all of
the details. This material provides the basis for understanding
how the interface functionality works. In the next section, we
go over the details.
The Abuild.interface file has a fairly
simple syntax that supports variable declarations, variable
assignments, and conditionals. Interface files are rigorously
validated. Any errors detected in an interface file are
considered build failures which, as such, will prevent abuild
from attempting to build the item with the incorrect interface
and any items that depend on it. Most
Abuild.interface files will just set
existing variables to provide specific information about that
item's include and library information, classpath information, or
whatever other standard information may be needed depending upon
the type of item it is. For casual users, a full understanding
of this material is not essential, but for anyone trying to debug
interface issues or create support within abuild for more
complex cases, it will be important to understand how abuild
reads Abuild.interface files.
The basic purpose of Abuild.interface is to
set variables that are ultimately used by a build item to access
its dependencies. The basic model is that an item effectively
reads the Abuild.interface files of all its
dependencies in dependency order. (This is not exactly what
happens. For the full story, see Section 29.7, “Implementation of the Abuild Interface System”.) As each file is read,
it adds information to the lists of include paths, libraries,
library directories, compiler flags, classpath, etc. All
variables referenced by Abuild.interface are
global variables, even if they are declared inside the body of a
conditional, much as is the case with shell scripts or makefiles.
Although this is not literally what happens, the best way to
think about how abuild reads interface files is to imagine
that, for each build item, all of the interface files for its
dependencies along with its own interface file are concatenated
in dependency order and that the results of that concatenation
are processed from top to bottom, skipping over any blocks inside
of false conditional statements.
Once abuild parses the Abuild.interface
files of all of a build item's dependencies and that of the build
item itself, the names and values of the resulting variables are
passed to the backends by writing them to the abuild
dynamic output file, which is called
.ab-dynamic.mk for
make-based builds and
.ab-dynamic-ant.properties for
ant-based builds. The dynamic output
file is created in the output directory. Although users running
abuild don't even have to know this file exists, peeking at it
is a useful way to see the results of parsing all the
Abuild.interface files in a build item's
dependency chain.
The Abuild.interface file contains the
following items:
Comments
Variable declarations
Variable assignments
After-build file specifications
Target type restrictions
Conditionals
Similar to make or shell script syntax, each statement is
terminated by the end of the line. Whitespace characters (spaces
or tabs) are used to separate words. A backslash
(\) as the last character of the line may be
used to continue long statements onto the next line of the file,
in which case the newline is treated as a word delimiter like any
other whitespace.
[24]
Any line that starts with a # character
optionally preceded by whitespace is ignored entirely. Comment
lines have no effect on line continuation. In other words, if
line one ends with a continuation character and line two is a
comment, line one is continued on line three. This makes it
possible to embed comments in multiline lists of values. In this
example, the value of ODDS would be
one three:
ODDS = \ one \ # odd numbers only, please # two \ three
Characters that have special meanings (space, comma, equal, etc.) may be quoted by preceding them by a backslash. For consistency, a backslash followed by any character is treated as that character. This way, the semantics of backslash quoting won't change if additional special characters are added in the future.
All variables must be declared, though most
Abuild.interface files will be assigning to
variables that have already been declared in other interface
files. There are no variable scoping rules: all variables are
global, even if declared inside a conditional block. Variable
names may contain alphanumeric characters, dash, underscore, and
period. By convention, make-based rules use all uppercase
letters in variable names. This convention also has the
advantage of avoiding potential conflict with reserved
statements. Java-based rules typically use lower-case
period-separated properties. Ultimately abuild interface
variables become make variables or
ant properties, which is the basis for
these conventions. Note, however, that variables of both naming
styles may be used by either backend, and some of abuild's
predefined interface variables that are available to both
make and
ant are of the all upper-case variety.
Once declared, a variable may be assigned to or referenced. A
variable is referenced by enclosing its name with parentheses and
preceding it by a dollar sign (as in
$(VARIABLE)), much like with standard make
syntax, except that there is no special case for single-character
variable names. Other than using the backslash character to
quote single characters, there is no quoting syntax: the single
and double quote characters are treated as ordinary characters
with no special meanings.
Environment variables may be referenced using the syntax
$(ENV:VARIABLE). Unlike many other systems
which treat undefined environment variables as the empty string,
abuild will trigger an error condition if the environment
variable does not exist. This can be useful for having abuild
interface files initialize interface variables from the
environment. Use this feature sparingly as it is possible to
make a build become overly dependent on the environment in this
way. (Even without this feature, there are other ways to fall
into this trap that are even worse.) Note that environment
variables are not abuild variables. They are expanded as
strings and can be used in the interface file wherever ordinary
strings can be used.
Variables may contain single scalar values or they may contain
lists of values of one of the three supported types:
boolean, string, or
filename.
Boolean variables are simple true/false values. The values
1 and true are
interpreted interchangeably as true, and the values
0 and false are
interpreted interchangeably as false. Regardless of whether the
word or numeric value is used to assign to boolean variables, the
normalized values of 0 and
1 are passed to the backend build system.
String variables just contain arbitrary text. It is possible to
embed spaces in string variables by quoting them with a
backslash, but keep in mind that not all backends handle spaces
in single-word variable values cleanly. For example, dealing
with embedded spaces in variable names in GNU Make is impractical
since it uses space as a word delimiter and offers no specific
quoting mechanisms. The values of filename variables are
interpreted to be path names. Path names may be specified with
either forward slashes or backslashes on any platform. Relative
paths (those that do not start with a path separator character
or, on Windows, also a drive letter) are interpreted as
relative to the file in which they are
assigned, not the file in which they are referenced as
is the case with make. This means
that build items can export information about their local files
using relative paths without having to use any special variables
that point to their own local directories. Although this is
different from how make works, it is the only sensible semantic
for files that are referenced from multiple locations.
List variables may contain multiple space-separated words.
Assignments to list variables may span multiple lines by using a
trailing backslash to indicate continuation to the next line.
Each element of a list must be the same type. Lists can be made
of any of the supported scalar types. (Lists of boolean values
are supported, though they are essentially useless.) List
variables must be declared as either append or
prepend, depending upon whether successive
assignments are appended or prepended to the value of the list.
This is described in more depth when we discuss variable
assignment below.
Scalar variables may be assigned to in one of three ways: normal, override, and fallback. A normal assignment to a scalar variable fails if the variable already has a value. An override assignment initializes a previously uninitialized variable and replaces any previously assigned value. A fallback assignment sets the value of the variable only if it has not previously been initialized. Uninitialized variables are passed to the backend as empty strings. It is legal to initialize a string variable to the empty string, and doing this is distinct from not initializing it.
List variables work differently from anything you're likely to
have encountered in other environments, but they offer
functionality that is particularly useful when building software.
List variables may be assigned to multiple times. The value in
each individual assignment may contain zero or more words.
Depending on whether the variable was declared as
append or prepend, the
values are appended to or prepended to the list in the order in
which they appear in the specific assignment. An example is
provided below.
Scalar and list variables can both be reset using the
reset statement. This resets the variable back
to its initial state, which is uninitialized for scalars and
empty for lists.
Any variable assignment statement can be made conditional upon the presence of a given interface flag. Interface flags are introduced in Chapter 20, Interface Flags, and the details of how to use them in interface files are discussed later in this chapter.
Abuild supports nested conditionals, each of which may contain
an if clause, zero or more
elseif clauses, and an optional
else clause. The abuild interface syntax
supports no relational operators: all conditionals are expressed
in terms of function calls, the details of which are provided
below.
In addition to supporting variables and conditionals, it is
possible to specify that certain variables are relevant only to
build items of a specific target type. A target type restriction
applies until the next target-type directive
or until the end of the current file and all the files it loads
as after-build files. By default,
declarations in an Abuild.interface file
apply to all target types. The vast majority of interface files
will not have to include any target type restrictions.
It is possible for a build item to contain interface information
that is intended for items that depend on it but not intended for
the item itself. Typical uses cases would include when some of
this information is a product of the build or when a build item
needs to modify interface information provided by a dependency
after it has finished using the information itself. To support
this, an Abuild.interface file may specify
additional interface files that are not to be read until after
the item is built. The values in any such files are not
available to the build item itself, but they are available to any
items that depend on the build item that exports this interface.
Such files may be dynamically generated (such as with
autoconf; see Section 15.3, “Autoconf Example”), or they may be hand-generated
files that are just intended not to apply to the build of the
current build item (see Section 25.1, “Opaque Wrapper Example”).
[24] In this way, abuild's handles line continuation like GNU Make and the C shell. This is different from how the Bourne shell and the C programming language treat line continuation characters: in those environments, a quoted newline disappears entirely. The only time this matters is if there are no spaces at the beginning of a line following a line continuation character. For abuild, make, and the C shell it doesn't matter whether or not space is present at the beginning of a line following a line continuation character, but for C and the Bourne shell, it does.