MAM (Make Abstract Machine) is a simple rule-based make language
that is implemented in just seven four-letter commands and five attributes,
yet allows unlimited flexibility as it can execute arbitrary shell code.
The program implementing MAM, mamake
,
is a portable C90 program written in a single file, mamake.c
.
This allows ksh 93u+m,
or other programs using this build system,
to be built using only a standard C compiler and utilities installation
without any other dependencies or complications.
MAM was originally designed by Glenn Fowler at AT&T and intended as an
abstraction layer for make
implementations such as AT&T nmake
.
The original documentation
for MAM specified a more extensive and slightly different language
than was actually implemented in mamake.c
.
Notably, the bind
command described there is completely different.
This file documents the MAM implementation that is actually in use.
Since fixing and maintaining AT&T nmake
proved impractical, mamake
is
used here as a full make
replacement, gradually adding some features to
the language to facilitate human maintenance of the Mamfile
s.
- General overview of the MAM language
- MAM variables
- Commands
- Appendix: Main changes from the AT&T version
MAM is a simple declarative language, easy to parse for machines and easy to
read for humans, in which targets are defined that correspond to files that
need to be generated or updated, or that are prerequisites. mamake
reads
build scripts from a file, Mamfile
by default, from start to finish. As it
encounters make
target…done
blocks, it ‘makes’ (updates) the targets
using the instructions within those blocks. Those instructions may declare
dependencies and/or specify shell command actions.
If a target is specified on the command line, mamake
will update the
actions contained within that target and ignore the rest.
By default, mamake
remains fully backward compatible with Mamfiles as
originally generated by AT&T nmake
. If the MAMAKE_STRICT
variable is
set, some backward incompatible changes and deprecation warnings are
activated to ensure correct operation and to facilitate human maintenance of
the Mamfiles. A numeric value assigned to MAMAKE_STRICT
indicates the
backward incompatibility level. The empty value is equivalent to level 1.
These are called the "strict levels" and their absence the "legacy mode",
also known as strict level 0.
Each time backward incompatible changes are introduced to mamake
that would
break previous Mamfiles, those are made subject to a MAMAKE_STRICT
value of one
higher than the previous highest one; details of those changes are documented
throughout this file and listed in the appendix below.
This makes it possible to test or backport old code using the current build
system. Current Mamfiles should use the highest strict level available.
The current highest available strict level is 3.
MAM variables are imported from the environment
or set via setv
(see below).
They are referenced using a ${
...}
syntax not dissimilar to sh
(1),
though the braces are not optional.
If an undefined variable is expanded and the variable name is valid in sh
(1)
syntax, the expansion is left in place unexpanded, otherwise it is removed.
At strict level 2 and up, it is left unexpanded even if it is not a valid
sh
(1) variable name; this allows POSIX shell expansions like ${foo#*bar}
.
By default, the expansion of MAM variable references is recursive,
i.e., the value may itself contain other variable references.
Beware: there is no reference loop detection; any variable referencing
itself directly or indirectly will cause mamake to crash.
At strict level 2 and up, this (mis)feature is disabled and
variables always expand to their literal values,
and variable references in setv
only work for previously defined variables.
Note that, in shell actions (see exec
below), MAM variables are expanded
before the script ever reaches the shell. Consequently, the use of single
shell quotes '
…'
does not stop their expansion as you might expect;
in fact, they ensure that only MAM variable expansion happens,
avoiding any potential conflicts with the shell expansion syntax.
In ${
variable?
str?
x?
y?}
,
if the string value of the variable is identical to str or if c is *
,
then the value x is substituted, otherwise y.
The x and y values may result from nested variable references.
The last ?
is optional.
In ${
variable-
x}
, the value of variable is substituted
if it is defined and non-empty, otherwise the value of x is substituted.
In ${
variable+
x}
, x is substituted if the value of
variable is defined and non-empty, otherwise the reference is removed.
Note that, unlike in sh
(1), no distinction is made between an undefined
variable and a defined variable with an empty value.
The following variables are set and updated automatically.
They are inspired by similar variables in make
implementations,
but since mamake
is different, so are these variables.
${@}
is the name of the rule currently being made.
${<}
is the name of the prerequisite rule (make
…done
or prev
)
that was last processed within the current rule.
${^}
is a space-separated list of names of all the current rule's
previously processed prerequisites.
${?}
is a space-separate list of the current rule's previously processed
prerequisites that have been updated by a shell action (see exec
below)
during the current mamake
run. Prequisites that were already up to date,
or prerequisites that do not contain a shell action, are not included.
MAM commands have the following basic form:
command [ argument [ operand string ] ]
The command name consists of four lower-case letters. Unrecognized commands or attributes are an error. The argument is a single word. The operand string is any arbitrary text until the end of the line.
note
is the comment command and is ignored.
In the legacy mode, info
and meta
are also ignored.
make
target [ attribute ... ]
done
[ target ]
A make
...done
block defines the rule named target using the other commands described here.
Unless the virtual
attribute is used, target names the pathname of the file generated or referenced by the rule.
mamake
processes the commands within the block if the target is out
of date or if the rule has the virtual
attribute (see below).
The target may be repeated as the operand to the done
command.
In that case, it is matched against the current make
target and
any mismatch will produce a "mismatched done statement" error.
If it is omitted, the current make
target is assumed.
Dependencies may be defined in two ways:
- By nesting
make
...done
blocks: the enclosing rule is the parent and the enclosed rules are the prerequisites. - By using the
prev
command (see Referencing previously defined rules below) to reference a previousmake
...done
block. The dependency is defined as if that block were repeated at theprev
command's location.
If the block contains one or more exec
commands (see Shell actions below),
the done
command executes the shell script defined by them.
A make
...done
block may lack any exec
action and, if it does not have any
dependencies of its own, it may even be empty; this has the effect of merely
declaring a dependency on a prerequisite file, such as a source code file that
comes with the distribution or a file generated by a previously run Mamfile.
Making a prerequisite that is currently being made, or one that has already been made, produces a warning; at strict level 3 and up, this is an error.
One or more attributes may be specified by appending them to the make
command.
(At strict levels < 2, they may also be appended to the done
command; the effect
is the same either way. At strict level 1, this produces a deprecation warning.)
Attributes apply to the current rule only and do not propagate down to nested rules.
The following attributes are available:
dontcare
: Marks files that do not need to exist. If the file exists then its last-modified timestamp is checked and propagated, otherwise it is silently ignored.ignore
: The timestamp associated with the target is ignored in dependency resolution.implicit
: Marks the current rule as an implicit prerequisite of the enclosing parent rule. An implicit prerequisite can make the parent rule out of date without triggering the parent action. Implicit prerequisites usually correspond to#include
prerequisites. For example, iffoo.o
is generated fromfoo.c
andfoo.c
includesfoo.h
, thenfoo.h
should be marked as an implicit prerequisite offoo.c
so that touchingfoo.h
does not makefoo.c
out of date while makingfoo.o
out of date.notrace
: Disables echoing (xtrace) of shell action commands. This does not disable the trace header for the containing rule (see Shell actions below).virtual
: Marks a rule that is not associated with any file. The commands within are executed every time the rule is processed. By convention, a virtual rule with targetinstall
performs pre-installation.
At strict level 1 and up, specifying the following attributes is deprecated and will produce a warning; at strict level 2 and up, specifying these is an error.
archive
: Ignored. Historically used to mark the generation of anar
(1) archive.generated
: Marks rules that produce output files generated by a shell action. The explicit assignment of this attribute is ignored at strict level 1. Theexec
command implicitly assigns this attribute. If a rule has this attribute, other rules dependent on this rule will avoid applying viewpathing based on this rule.joint
: Ignored. Historically used to mark one of a group of rules that are built by a single shell action.
prev
target [ attribute ... ]
The prev
command is used in two ways:
-
If target matches a previously defined rule,
prev
adds a dependency on that rule to the current rule. This can be used to make a rule a prerequisite of multiplemake
...done
blocks without repeating the rule. No attributes should be given for this use ofprev
, because the attributes of the referenced rule are used. Superfluous attributes are an error at strict level >= 1 and ignored in the legacy mode. -
If target does not match a previously defined rule, the following applies. In the legacy mode,
prev
creates an empty dummy rule and ignores the attributes; this is for backward compatibility. At strict level 1 and up,prev
creates a rule that declares a dependency on a prerequisite file named by target in a manner equivalent to an emptymake
...done
block, with the optional attributes applied to the new rule, and a nonexistent prerequisite is an error unless avirtual
ordontcare
attribute is given. Declaring a dependency on a prerequisite that is currently being made (i.e.: directly or indirectly within that prerequisite's block) produces a warning; at strict level 3 and up, this is an error.
setv
variable [ defaultvalue ]
Defines a new MAM variable, optionally assigning the initial defaultvalue.
If the variable already has a value, the setv
command is ignored; assigning a new value is not possible.
When mamake
starts, it imports all environment variables as MAM variables,
so any variable's default value can be overridden by exporting an environment variable by its name.
If the strict level is less than 2 and the defaultvalue begins and ends with
double quotes ("
), those quotes are discarded,
though double quotes elsewhere in the value are not treated specially.
The value is otherwise taken entirely literally.
When the variable is CC
, mamake runs the mamprobe
script
to probe the C compiler for flags and features,
or uses that script's stored results if not outdated.
The results are stored as a series of setv
commands
in a file in the directory ${INSTALLROOT}/lib/probe/C/mam
,
the file name being a hash of full path to the compiler indicated by ${CC}
.
That results file is then read and included in the current Mamfile
as if it followed the setv CC
command.
exec
-
code
One or more exec
commands within a make
...done
block
define a shell script that is run to generate the target.
The argument following exec
is ignored; by convention it is -
.
Each exec
command appends a line of code to the shell script for the current rule.
It is customary for a rule's exec
commands to be contiguous, but not necessary.
Before adding each line of code to the script,
MAM variable references (see MAM variables above)
are expanded; their literal values are inserted into the code line
(beware: no quoting is applied!).
Because variables are expanded when the line is encountered, the value
of the automatic variables for any exec
line depends on the position
of the line in the rule.
After MAM variable expansion, viewpathing is applied.
The first colon-separated element of ${VPATH}
is considered
the object code directory and the second the source code directory;
viewpathing provides the first with a vew to the second.
Viewpathing applies two transformations.
The first is prerequisite replacement.
Each word (separated by whitespace, ;
, (
, )
, `
, |
, &
or =
)
is searched for in the current rule's prerequisites,
and if it matches the name of a non-generated prerequisite,
it is replaced by the canonical path to it in the source directory,
ensuring that things like prerequisite headers are found.
The second is include flag duplication.
After every argument that looks like a compiler include directory path (i.e.,
starting with -I
) with a relative path name (i.e., a directory path that does
not start with a /
), another argument starting with -I
is inserted with
that path name prefixed by the path to the source directory. This mechanism
ensures that headers are found both in the object directory and in the source
directory. It is processed regardless of the command; for example, it also
works for compiler flags passed to iffe
(1).
Note that shell quotes are not treated specially. If an argument starting
with -I
ends in a shell quote without preceding whitespace, that trailing
quote is repeated along with the prefixed path and causes a syntax error.
When mamake
encounters the done
command,
the script is executed by the shell whose path is in the SHELL
environment variable
or, absent that, by sh
(1).
Each shell action is run in a new instance of the shell.
Before executing the script, an empty line followed by a trace header in the following format is written to standard error:
# path/to/Mamfile: startline-endline: rule
During script execution, shell action comands are traced using the
shell's xtrace option, unless the rule has the notrace
attribute.
At strict level 2 and up, mamake
turns off global pathname expansion
(globbing) using set -f
to make safer MAM variable expansion and shell
field splitting possible; this avoids unexpected pathname expansion if
a value contains ?
, *
or [
.
A shell action can override this using set +f
;
this should only be done for individual commands in a (
subshell)
.
shim
-
code
One or more shim
commands declare a ‘shim’: a common section of sh
(1)
code that will be automatically inserted in front of subsequent shell
actions upon execution. Like exec
, shim
combines multiple lines of
code into one section, with MAM variables expanded at declaration time and
viewpathing applied at execution time. The effect of shim
is global.
One use case is defining a shell function that each shell action can call. For example, such a function might invoke the compiler with a series of compiler flags common to all compiler invocations, with other flags added via arguments to the function, so the common flags do not need to be repeated in every shell action.
Only one shim is active at a time, but it can be redefined. When the next
exec
command is encountered, the shim is marked ready for use. The next
time a shim
command is encountered after that, it starts a new shim from
scratch that affects subsequently executed shell actions.
A single shim -
deactivates the shim.
bind
-l
libraryname [ dontcare
]
These commands are scanned for while sorting leaf directories for recursive building, and executed as normal commands while building the current directory.
Any leaf directories with names that start with INIT
will always be built before
all others. For all other leaf directories, the presence of any bind
command of the form bind -lfoo
anywhere in a leaf
directory's Mamfile causes the leaf directory named libfoo
(if it exists)
to be a prerequisite of that leaf directory.
The prerequisite leaf directory does not have to be in the same parent
directory, as long as it is processed as part of the same scan.
At this stage, attributes are ignored.
An argument of -l
libraryname
causes a MAM variable mam_lib
libraryname to be defined (see MAM variables above).
The variable will contain either the compiler argument for linking to the library libraryname
(either the -l
libraryname flag, or the full path in case of a static library)
or, if the dontcare
attribute is specified, possibly the empty string.
Any library dependencies are also included (see below).
This can be used both for AST libraries shipped with the distribution and for system libraries.
For each corresponding *.a library archive dependency built previously,
its time stamp is checked and the current target is marked as outdated if it is
newer, as if a prev
had been executed for it.
The variable set by bind
is global, but the marking of the target as
outdated applies to the current rule only, so it may be necessary to
repeat a bind
command when statically linking executables that depend
on a library, otherwise they may not be relinked when the library changes.
The mam_lib
libraryname variable will not be regenerated when repeating a bind
.
There is also a mechanism to communicate library dependency information across Mamfiles and mamake
invocations.
If a file named libraryname.req
in the current directory
or an ${INSTALLROOT}/lib/lib/
libraryname file
exists, mamake
processes each of the words in the form -l
libraryname in its contents
as if they were arguments to bind
commands
and the resulting values are appended to the value of mam_lib
libraryname
as dependencies separated by spaces.
mamake
does not create these dependency files;
they are expected to be generated by Mamfile shell actions (see Shell actions above).
If no such dependency file exists, and the dontcare
attribute is added,
then mamake
compiles a small test program on the fly to check if the library exists;
if this fails, the mam_lib
libraryname variable will be emptied.
Any bind
command whose argument does not start with -l
is ignored.
loop
variable word [ word ... ]
done
loop
reads the lines contained between it and the corresponding done
repeatedly with a named variable set to each of the words. The lines
are processed as part of the rule containing the loop.
The variable is restored to its previous state after the loop completes.
Note that loop
causes repeated reading and processing of Mamfile lines,
not necessarily repeated execution. For instance, a loop can be used to
consolidate repetitive make
…done
rules. However, each rule is only made
once and subsequent rules by the same name are an error at strict level 3
and up, or skipped over at strict < 3. So it only makes sense to do this
if the contained make target names are modified by the expansion of the
iteration variable.
loop
requires a seekable input file (i.e.: not a pipe).
Compared to the original AT&T version, ksh 93u+m made a number of
changes to mamake
that facilitate correct operation and make it easier to
maintain Mamfiles by hand. The following lists the important changes.
- Introduced the notion of ‘strict mode’ levels that tidy things up by activating some backward incompatible changes and deprecation warnings.
- Indentation and word separators may use any whitespace (e.g. tabs), not only spaces.
- Fixed a bug that stopped a rule marked
virtual
(not associated with any file) from being executed if a file by that rule's name exists. - Unrecognized commands and rule attributes throw an error instead of being silently ignored.
- It has been made optional to repeat the
make
target afterdone
. - The
notrace
attribute was added to disable xtrace for a rule's shell action. - The automatic variables
${@}
,${<}
,${^}
and${?}
have been added. - An iteration block command,
loop
…done
, has been added. - A command to set common code for shell actions,
shim
, has been added. - Attempting to make a rule that has already been made produces a warning.
- Attempting to declare a dependency on a rule currently being made produces a warning.
- At strict level 1 and up:
- Appending attributes to
done
instead ofmake
is deprecated and produces a warning. - The ignored
archive
andjoint
attributes are deprecated. - Explicitly specifying the
generated
attribute is deprecated. - The dummy
info
andmeta
commands are unavailable instead of ignored. - The
prev
command may be used instead of an emptymake
...done
block to declare a simple prerequisite with possible attributes. - When
prev
references a previously processed target, attributes are an error instead of being ignored. - The legacy
silent
andignore
command prefixes are unavailable.
- Appending attributes to
- At strict level 2 and up:
- Appending attributes to
done
instead ofmake
is an error. - The
archive
andjoint
attributes are unavailable. - Explicitly specifying the
generated
attribute is an error. - All variable references are expanded to their literal values without scanning the values for recursive variable references.
setv
does not remove leading and trailing"
from the value.- Shell actions have pathname expansion (globbing) disabled by default.
- Appending attributes to
- At strict level 3 and up:
- Attempting to make a rule that has already been made is an error.
- Attempting to declare a dependency on a rule currently being made is an error.