|
Introduction
nmake(1)
is the standard software construction tool at AT&T and
Lucent Technologies.
It is a descendent of the UNIX
make(1)
and is not related to the
Microsoft program
of the same name.
Some of the
nmake
features described in this overview are:
-
Portable makefiles that are OS and compiler independent.
-
Automatic dynamic dependency checking based on file type.
-
Automatic recursive make ordering.
-
Excellent performance that scales to 10K file, 10M line projects.
-
An extensible programming language.
-
Parallel execution.
-
Many common actions
(install,
clobber,
list.manifest,
etc.)
provided by default.
-
Makefile sizes typically 10 times smaller than
make.
-
State based execution.
-
Source in multiple directories.
-
Viewpathing.
-
Portable external package naming.
-
Native command, archive and shared library generation.
-
No
autothis,
configthat
or
footool
commands, no makefile generation, no
make depend,
no separate manifests;
just
nmake.
History
nmake
was created and enhanced by Glenn Fowler of AT&T Labs Research.
It began in 1984 as
make
with a makefile
cpp(1)
(C preprocessor) front end.
It soon evolved into a separate implementation with a language
rich enough to make preprocessed and generated makefiles obsolete.
The first internal release was in 1985 and the first external open source
release was in 2000.
The current release is available in the
ast-base
and
ast-open
packages at
Software Systems Research software;
there is also a
FAQ.
Although
nmake
makefiles are not compatible with UNIX
make
or Microsoft
nmake,
nmake
can generate UNIX
make
or Microsoft
nmake
style makefiles for porting and bootstrapping.
Overview
This is a brief overview of common user-level
nmake
features used by the component makefiles of the
ast-open
source package.
Although slightly out of sync with the AT&T release, the
Alcatel-Lucent (search for nmake -- who mourns for Bell Labs?)
site provides a more complete treatment of the details
(the tech writer remained with Lucent after the '95 split.)
The following assumes familiarity with the UNIX or GNU
make
program.
There is a glossary of terms in the appendix; the first reference to
each term is in italics.
Makefiles
Similar to UNIX
make,
nmake
input is a
makefile
consisting of a set of
assignments
and
assertions.
By default
nmake
searches for a file named
Nmakefile,
nmakefile,
Makefile,
and then
makefile
in that order.
A common convention is to use the
.mk
suffix when naming makefiles and invoke
nmake
with
-f name.mk.
Unlike UNIX makefiles, an
nmake
makefile is compiled by reading the makefile,
carrying out the assignments, and creating rules from the assertions.
The compiled makefile name is formed by adding a
.mo
suffix to the makefile name if it does not have a
.mk
suffix or by changing the
.mk
suffix to
.mo.
In addition to the assertions and assignments in the input makefile,
nmake
uses a precompiled
base rules
file and, if defined,
global rules
files when compiling the makefile.
The makefile is implicitly compiled whenever it,
the base rules, or any referenced global rules file changes.
Comments
nmake
supports C style comments as well as
#
style comments that appear after one or more space characters.
Earlier versions of
nmake
passed makefiles through the C preprocessor.
Although still supported, it is now deprecated
in lieu of newer language features.
(It hasn't been dropped because there are still a few legacy projects
that rely on makefile preprocessing.)
Assertions
An
nmake
assertion is a generalization of a
make
assertion.
It consists of a
target list
(the left hand side), an
operator,
and a
prerequisite list
(the right hand side.)
The operator may either be
:
as in
make,
or of the form
:name: where
name
is the null string or an alphanumeric.
Many operators of the latter form are defined in the base rules.
Additional operators can be defined in global and user makefiles.
A description of how to define new operators is beyond the scope of this
introduction.
For example, the line
prog : prog.o prog.h -lm
specifies that the target
prog
depends on the prerequisites
prog.o,
prog.h
and
-lm.
Indented lines after each assertion, if any, define an
action
block associated with the assertion.
Unlike
make,
nmake
actions are multi-line blocks that are executed as a unit.
To execute a shell action block,
nmake
expands any
nmake
variables and then passes the block to the shell for execution.
This allows action blocks to contain shell here documents and
multi-line structured statements without the need for extra quoting.
This is extremely difficult to do in
make,
where actions are executed one line at a time.
The shell action trace is done by the shell as each command is executed
using the
sh(1)
-x
option.
Tracing can be turned off for a target by specifying
set +x
as the first command in its action block.
Tracing can be turned off for an individual command by prefixing it with
silent:
test_silent :
silent echo this will not echo '"+ echo ..."'
By default a shell action block fails and is terminated when any command
returns non-0 exit status, excluding commands subject to
if,
while,
!,
||
or
&&.
The exit status can be ignored for all commands in a target action
by asserting the
.IGNORE
attribute on the target.
The exit status for an individual command can be ignored by prefixing it with
ignore:
test_ignore :
ignore false
echo the first false is ignored
false
echo but the second causes the action to fail and skip this echo
silent
and
ignore
are implemented as shell functions or aliases, depending on the shell path,
determined by searching for the following {
$COSHELL
$SHELL
ksh
sh
/bin/sh
}
in order on
$PATH.
The collection of assertions associated with a given target
together defines a
rule
for the target.
In most cases a rule is defined by a single assertion,
so the two terms are often used interchangeably.
Both prerequisites and targets can represent many different types of objects.
They may be names of files, names of
state variables,
names of rules, or
attributes.
A state variable is a variable that has both a value and a time stamp;
when the variable value changes its time stamp is also changes.
Attributes are special reserved words that are understood by
nmake
itself.
Attribute names all begin with a
.
and are all upper case.
The full list of attributes and their meaning can be found in the
Alcatel-Lucent documentation.
An example of an attribute is
.MAKE
which marks an action block to be executed by
nmake
rather than the default shell.
This is used in the base rules to define many
of the default rules and assertion operators.
An attribute that is often found in user makefiles is
.DONTCARE.
It is normally a fatal error when a prerequisite cannot be found or made;
asserting the
.DONTCARE
attribute on the target:
target : .DONTCARE
inhibits the error.
A target whose name contains the character
%
defines a special type of rule called a
metarule.
A metarule matches files using the
%
like the shell
*
wild card.
Metarules are common in the base and global rule files
and are used on occasion inside user makefiles.
Assignments and Variables
An assignment can be one of the following four types:
-
A simple assignment of the form name = value
that delays the expansion of
value
until
name
is expanded.
-
A simple assignment of the form name := value
that expands
value
before being assigned to
name.
-
A append assignment of the form name += value
that appends the expanded
value
to the current value of the variable
name.
-
A state variable assignment of the form name == value.
nmake
scans source files for state variable references and
keeps track of changes to their values.
A state variable can be used as a prerequisite or a target by
specifying (name).
Variables are referenced using the notation $(name)
where name is the name of the variable.
In addition each
name
can be followed by a
:
separated list of expansion operators
that operate on each space separated token in the result of the expansion.
For example, the operator
:N=pattern:
selects tokens that match the shell
pattern
and the operator
:T=F:
binds each token to a file and expands the token to the bound path name.
FILES = main.c stdio.h
print $(FILES:N=*.h:T=F)
would print
/usr/include/stdio.h
on most UNIX systems.
The complete list of expansion operators can be found in the
Alcatel-Lucent documentation.
nmake
uses certain conventions for naming related variables.
For example, while the variable
CC
names the C compiler as with
make,
other C compiler related variables also begin with
CC.
Thus, the variable name
CCFLAGS
is used rather than
CFLAGS.
In addition, other variables of the form
CC.name
are used to parameterize portable rules and assertion operators.
These are
probe variables,
described in the next section.
In general the user modifiable variables for
TOOL
are named
TOOLidentifier
and the the corresponding probe variables are named
TOOL.identifier.
Special variables
are readonly variables maintained by
nmake.
They are used in actions and have values relative to the current
target and prerequisites.
Each special variable name is a single non-alphanumeric character that
may be repeated to access ancestor values, e.g.,
$(<)
names the current target,
$(<<)
names the parent target of the current target.
Special variable names may also be used as a rule name prefix to access
information about that rule, e.g.,
$(*.SOURCE.h)
expands to the prerequisites of the
.SOURCE.h
rule.
Special variables are indispensable for several reasons:
-
bound file names are expanded; this is required for viewpath and
.SOURCE*
binding
-
specify once and right:
rules can specify target and prerequisite names once, avoiding errors
caused by out of sync duplication
-
portions of rules can be shared, .e.g, edit operators applied to
$(@rule)
The special variables are:
- $(<)
-
The target name.
This may be a list for
.JOINT
rules that generate multiple targets from one action.
- $(>)
-
The out of date file prerequisites.
This variable is most often used in metarule actions.
- $(*)
-
All file prerequisites.
- $(~)
-
All prerequisites.
- $(@)
-
The action block.
- $(%)
-
The metarule target stem or
.FUNCTIONAL
arguments.
- $(!)
-
Explicit and generated file prerequisites.
- $(&)
-
Explicit and generated state prerequisites.
- $(?)
-
All explicit and generated prerequisites.
- $(^)
-
The original bound name.
A generated target may bind to a file in another directory.
If the target is out of date then
$(<)
is the target name to be generated, and
$(^)
is the original name.
For example, a makefile that generates
./libfoo.a
and installs
$(LIBDIR)/libfoo.a
may bind
-lfoo
to
$(LIBDIR)/libfoo.a
before
./libfoo.a
is generated.
In this case
$(^)
is
$(LIBDIR)/libfoo.a
and
$(<)
is
libfoo.a.
- $(-)
-
The current
nmake
options, suitable for the command line.
This is a global special variable and is not target related.
$(-option)
expands to the current setting for
option.
- $(+)
-
The current
nmake
options, suitable for the
nmake
set
builtin.
This is a global special variable and is not target related.
$(+option)
expands to the current setting for
option.
- $(=)
-
The command line and
.EXPORT
variable settings, suitable for the command line.
This is a global special variable and is not target related.
- $(...)
-
The list of all rules and attributes.
This special variable is often used with attribute or pattern match
edit operators.
For example,
$(...:A=.TARGET)
expands to the names of all target rules and
$(...:N=*.o:T=F)
expands to the bound names of all rules matching the shell pattern
*.o
that bind to existing files.
Make Probe Information
The base rules conform to local OS conventions using information generated
by the
probe(1)
command.
Probe information is maintained for each compiler in directories shared
by all users.
Currently
nmake
uses
make
and
pp
(C preprocessor) probe information.
The information is automatically generated and updated when the corresponding
probe
script changes.
The make probe information is a set of variable definitions, all of the form
CC.name.
See
probe information
for details.
Typical user makefile make probe variables are:
- CC.DEBUG
-
The
cc
option that enables object file debugging information.
- CC.DLL
-
The
cc
option(s)
required for shared library object files.
- CC.HOSTTYPE
-
The target architecture host type as determined by
package(1).
- CC.OPTIMIZE
-
The
cc
option that enables optimization.
- CC.WARN
-
The
cc
option that enables verbose warning and error messages.
Assertion Operators
nmake
makefiles typically use one or more higher level operators, and more
often than not skip the primitive
:.
This section introduces some commonly used operators.
The
::
operator, whose
name
is the null string,
expects source file prerequisites rather than intermediates.
The
::
operator is defined in the
base rules.
It generates primitive assertions and actions based
on prerequisite file suffixes use base rule
metarules.
The base rules correspond to the
makerules.mo
global rules file, provided with every
nmake
installation.
The base rules are read by default before any user makefiles are read.
For example, the makefile
DEBUG == 1
prog :: prog.y helper.c -lxyz
specifies that the target program
prog
is constructed from the object files generated by
prog.y,
helper.c
and
-lxyz,
and that object file generation may depend on the variable
DEBUG.
In this case if either
prog.c
(and/or
helper.c)
contains the symbol
DEBUG
then the C compilation flags for
prog.o
(and/or
helper.o)
will contain
-DDEBUG.
In addition, if the value of
DEBUG
changes for some future invocation of
nmake
then
prog.o
(and/or
helper.o)
will be recompiled with that new value.
There are many other operators defined in the base rules.
The :LIBRARY: operator generates both
static and dynamic libraries:
CCFLAGS = $(CC.OPTIMIZE) $(CC.DLL)
xyz 4.0 :LIBRARY: xyz.c strmatch.c
$(INCLUDEDIR) :INSTALLDIR: xyz.h
The example makefile generates and installs both a static and shared library
named
xyz.
$(CC.OPTIMIZE)
generates optimized object files and
$(CC.DLL)
generates object files suitable for shared libraries, and also instructs
:LIBRARY:
to generate a shared library.
The
probe(1)
information determines the corresponding library file names.
For example, on linux these would be installed:
$(INCLUDEDIR)/xyz.h
$(LIBDIR)/libxyz.a
$(LIBDIR)/libxyz.so
$(LIBDIR)/libxyz.so.4.0
on win32 systems these would be installed:
$(INCLUDEDIR)/xyz.h
$(LIBDIR)/libxyz.a
$(LIBDIR)/xyz.lib
$(BINDIR)/xyz40.dll
and on
cygwin
systems these would be installed:
$(INCLUDEDIR)/xyz.h
$(LIBDIR)/libxyz.a
$(LIBDIR)/libxyz.dll.a
$(BINDIR)/cygxyz40.dll
The
:LIBRARY:
operator also deletes all static library object files after they have been
added to the static library;
nmake
is able to scan the libraries to determine the object file time
stamps using the
libardir(3)
library, also used by
pax(1).
libadir
mitigates the differences between vendor archive implementations.
The
:PACKAGE:
operator provides portable and consistent external package references.
It appends package include directories, if any, to
.SOURCE.h
and package library directories, if any, to
.SOURCE.a.
It also links all command and shared library targets with default
package libraries.
When a package cannot be found all other rules are disabled and
nmake
exits with a diagnostic message.
:PACKAGE: games X11
xgame :: README xgame.6 xgame.h xgame.c xutil.c -lXaw -lXmu -lXt
$(INCLUDEDIR) :INSTALLDIR: xgame.h
This example specifies that the command source files may reference the X11
headers, and the command executable links against the default X11 libraries,
along with the explicit X11 libraries
-lXaw,
-lXmu
and
-lXt.
The first prerequisite of all
:PACKAGE:
assertions is the main package; the main package modifies the header
installation directory by appending the package subdirectory.
-
as the first prerequisite of
:PACKAGE:
specifies that the package is external.
For this example the main package is
games,
and
$(INCLUDEDIR)
is
$(INSTALLROOT)/include/games.
Many rules provided by the base rules
must be explicitly asserted in UNIX
make
makefiles.
For example, the rules
clean,
clobber
and
install
are predefined in the base rules, making them
unneeded in user makefiles.
In addition, the
pax
and
tgz
rules produce
tar(1)
format archives containing source files defined explicitly or implicitly
by the makefile.
A source file is any local non-generated file that is used to generate a
target file.
To specify files that need to be part of the source archive
that are neither referenced in the Makefile or are implicit
prerequisites, use the :: operator without any targets.
For example:
:: README Install Copyright
adds these files to the generated
pax
file when
nmake
is invoked with the target
pax.
State File
A major difference between
nmake
and
make
is that
nmake
uses results from previous runs
to decide what needs to be built.
This information is stored in a file called the
state file.
The state file name is formed by adding a
.ms
suffix to the makefile name if it does not have a
.mk
suffix or by changing the
.mk
suffix to
.ms.
The state file retains the following information:
-
File and event times for all generated and terminal files.
-
Explicit prerequisite lists.
-
Implicit prerequisite lists.
-
Action blocks used (before expansion.)
-
State variable values.
-
Target attributes.
If any of these have changed for a particular target then the target
is marked out of date.
This is a fundamental departure from
make.
In particular, file time changes into the past or future are detected.
When a change is detected for a target its event time is set to
now,
and it is this time that is propagated to dependent targets.
The
implicit prerequisites
are generated by applying
scan rules
to source files.
The scan rules themselves are specified by the
.SCAN
attribute.
The
.SCAN
attribute specifies that the action block is a scan description.
A scan description describes the commenting and quoting rules
as well as the syntax for identifiers, includes and conditional compilation.
Include files found within conditional compilation are
given the
.DONTCARE
attribute.
Only
.DONTCARE
files that exist will be tracked as implicit prerequisites; non-existent
.DONTCARE
files are silently ignored.
The scan rules are typically metarules that match files with
a given suffix.
In addition to scanning for include dependencies,
the scan rules check for state variable dependencies
by checking for state variable name matches within the scanned source files.
The base rules contain the scan rules for several
languages including C and C++.
Implicit prerequisite generation is dynamic and incremental.
If a source file has changed from the last time it was scanned then
it is scanned again.
In addition,
if making a rule causes a file to be generated, and the file
that it generates matches a scan metarule, then the generated
file is scanned for dependency information.
This is in contrast with most
make
dependency generation tools that either generate this information statically
or collect it as a by-product of compilation
(requiring special compiler hooks.)
The latter technique is prone to
off-by-one
compile errors where generated include dependencies may not be noticed by
make
until after the compiler runs (and fails.)
The scan information is saved in the state file so that
a file only needs to be scanned again if it has changed.
The state file also stores the value of state variables.
The base rules define variables such as CC and
CCFLAGS to be state variables so that a change
of compiler or compile options triggers recompilation.
Binding and Making
The process of associating a target or prerequisite
with a physical object such as a file, rule or state variable is called
binding.
The handling of directory searches for binding is described in the
Multiple Directories
section below.
Names of the form (name)
are bound to state variables.
Names that contain a % are bound to metarules.
Other names may be bound to rules or to filenames.
Libraries can be specified as either -llib
or +llib.
On systems that support both dynamic libraries and static libraries,
-llib will prefer dynamic linking when both dynamic and
static versions of the library are present, whereas +llib
will prefer the static library.
The dynamic/static preference is on a per-directory basis.
The fundamental
nmake
operation is to
make
a target.
nmake
makes a target by recursively making each of its prerequisites.
To make a given prerequisite, it is first bound.
If it binds to a rule, then the targets for that rule are made.
If the prerequisite binds to a file or a state variable,
and
nmake
determines that it needs to be updated,
then the action for that file or state variable is executed.
The method that
nmake
uses to determine whether a target needs to be
updated differs from that of
make.
With
make,
if the file time of the target is older than
that of any of the prerequisites, then the action is triggered.
With
nmake,
if the time of any of the prerequisites
saved in the state file differs (older
or
newer) from the time of the file
then action is triggered.
Moreover, if the action completes successfully, the time stamp of the target
is recorded in the state file even if it has not changed.
In this way an action need not change the time stamp
of the target if no change is needed, preventing
anything that depends on this target from being remade.
Another consequence of this model is that restoring an
old source file with the old time stamp is detected as a change by
nmake.
For the first build, when there is no statefile, or
when the accept option, -A, is specified on
the command line,
nmake
uses the
make
time stamp model.
The actions for the prerequisites of a given target may be executed in any order;
they may even be executed concurrently.
When
nmake
is invoked it makes the rules
.INIT,
.MAIN
and
.DONE,
in that order.
By default the prerequisite of
.MAIN
is the first user makefile target, usually
all
or
.ALL
defined by the
::
assertion operator.
Multiple Directories
Unlike
make,
nmake
supports a multiple directory model.
There is no reason that the files specified in the
makefile need to reside in a single directory.
It is often convenient to partition the source for a single makefile into
many subdirectories.
The
nmake
.SOURCE
rule specifies the directories (and directory order) to be searched
for prerequisite source files.
For example,
.SOURCE : lib common
specifies that the
lib
and
common
directories are to be searched, in that order,
when binding names to files.
The current directory
.
is always searched first.
In addition, source rules for searching files of any suffix
can be specified by using the target .SOURCE.suffix.
Using source assertions for include files has another advantage.
The :: and the :LIBRARY: operators use the .SOURCE.h
assertion to generate -I directives
for the C or C++ compilers.
-I
directives are generated only for directories containing files used for each
compilation.
The ability to specify multiple search directories
eliminates the need for nested makefiles in many cases.
However, it is only useful for building something in
which all component names are known.
Large project management requires recursive makefiles.
Recursive
nmake
is
not
considered harmful; it is most emphatically
essential
for composing complex systems from smaller components.
The base rules supply the
:MAKE:
operator for this purpose.
With no prerequisites
:MAKE:
determines the build order for all subdirectories that contain a makefile.
Subdirectory makefiles may themselves contain
:MAKE:
operators, and these are handled recursively.
Project directory hierarchies typically have top level makefiles containing
a single
:MAKE:
assertion;
nmake
run at the top level will recursively run
nmake
in all subdirectories
in the proper order.
Subdirectory ordering is determined by
:PACKAGE:
assertions and
-llib
and command prerequisites.
The most important aspect of
:MAKE:
is that
component directories
can be added to or deleted from a directory hierarchy and the next
nmake
run will take these changes into account, without the need to modify
global project files or scripts.
Viewpathing
Keeping source and generated files separate eases
file management when multiple target architectures are involved.
There are several ways to do this:
-
Make a complete copy of the source tree for each architecture:
although easy to manage, this technique is not space efficient.
And, since there are multiple copies of the source,
it's hard to keep the separate source copies from diverging.
-
Make architecture specific subdirectories for each source tree leaf directory:
this is much more space efficient since all architectures share one
copy of the source.
However, isolating the files for one particular architecture is non-trivial,
since architecture specific directories are distributed throughout the
entire source tree.
package-root
. . .
. . .
. . .
. . .
. . .
. . .
. . .
bin lib src
. . . . . .
. . . . . .
A1 A2 A1 A2 lib cmd
. .
. .
. .
. .
libar foo
. . . .
. . . .
A1 A2 A1 A2
-
Make a directory tree copy of the source tree (just directories, no files)
for each architecture and make a symlink in the copy for each regular file
in the source tree: this is space efficient and isolates the architecture
specific files under a separate tree.
It is too easy, however, to clobber original source files from within
the architecture specific trees.
-
Make a directory tree copy of the source tree (just directories, no files)
for each architecture and viewpath the architecture tree on top of the
source tree:
this is space efficient and safely separates source from generated files.
package-root
. .
. .
. .
src arch
. . . .
. . . .
lib cmd . .
. . A1 A2
. . . .
. . . .
libar foo . .
src src
. . . .
. . . .
lib cmd lib cmd
. . . .
. . . .
libar foo libar foo
Viewpathing also allows multiple source trees to be chained together;
this means that source from separate package root directories can be shared.
This technique is useful for isolating local developer debug and enhancement
modifications from the master source.
Viewpaths are specified in the
VPATH
environment variable as a
:
separated list of root directories:
VPATH=developer-root:HOSTTYPE-root:master-root
If the local host supports DLL preload then the
3d(1)
command can be used to provide a transparent viewpath view to all
dynamically linked commands.
The
VPATH
environment variable provides the initial view, and additional views are
specified with the
ksh(1)
vpath(1)
builtin:
vpath developer-root HOSTTYPE-root
vpath HOSTTYPE-root master-root
Portability
The best way to achieve portability with
nmake
makefiles is to
-
specify as little as possible
-
use
probe(1)
abstractions, e.g.,
CC.DLL,
CC.SUFFIX.ARCHIVE,
etc.
-
use predefined base rules operators and variables
-
specify all source for all systems and select via
#ifdefs
in the source
-
avoid constructs that depend on a particular system or compiler
-
avoid makefile conditionals
-
avoid absolute pathnames
-
do not use explicit
-D
or
-I
options; define state variables for
-D
and
.SOURCE*
assertions for
-I
-
use
.DONTCARE
for libraries that may not be on all systems; better yet, make them
prerequisites in
:LIBRARY:
assertions
-
use
iffe(1)
to generate configuration dependent headers
The
nmake
base rules ensure that the
probe(1)
abstractions and default rules, operators and variables
function equivalently on all systems.
iffe
iffe(1)
(if feature exists)
is a tool that generates configuration header files based on information
probed from the current
$(CC)
and native system.
iffe
processes files that are normally stored in a
directory named
features.
The resulting header files are stored in the directory named
FEATURE
(not
the more intuitive
FEATURES
to appease case ignorant filesystems.)
iffe
enabled source includes these files
and uses the generated macros in conditional compilation tests.
iffe
is able to probe any non-interactive target system behavior through
user specified C programs and shell scripts.
However, many common precoded queries can be leveraged to produce simple
and compact
iffe
scripts:
Wherever an operator or name can be specified, a comma separated
list of names can be specified, and the test will
be performed for each operator and name in the list:
hdr,sys stdio,fcntl,socket,mman
Unlike
configure(1),
iffe
generated macro names conform to a strict and consistent naming convention.
See
iffe(1)
for more details.
For compatibility the
-C
or
--config
option can be used to coax
iffe
to generate
configure(1)
HAVE_feature style macros.
Converting from
configure
to
iffe
is worth the effort, for portability, stability, and size.
The corresponding
iffe
scripts are typically more compact and clearly delineate generated vs.
source files -- you didn't really think someone hand-coded that 10K line
ImageMagick
configure script.
Our equivalent
iffe
script is less than 100 lines.
Granted,
iffe
is implemented as a 4K line shell script, but
that
script remains constant across all projects.
We have some
iffe
scripts, still in use, that haven't changed since 1994.
The base rules provide complete support for
iffe(1)
through these metarules (action blocks omitted):
FEATURE/% : features/%
FEATURE/% : features/%.c
FEATURE/% : features/%.sh
% : %.iffe
Since
nmake
does automatic implicit prerequisite analysis, a source files need only
#include "FEATURE/foo"
and FEATURE/foo will be automatically generated/updated before
any dependent file is compiled.
nmake
is known to work on these
architectures.
Although architecture independent makefiles is a prime portability goal, some
architecture specifics are unavoidable.
For the
ast
software most of these correspond to C compiler optimizer bugs,
i.e., generated code works fine without
$(CC.OPTIMIZE)
in
CCFLAGS
but fails with it set.
The base rules
:NOOPTIMIZE:
assertion operator provides per-file control over optimization:
CCFLAGS = $(CC.OPTIMIZE)
cmd :: a.c b.c c.c
"sol?.i386|sgi.*" :NOOPTIMIZE: b.c
This makefile disables C compiler optimization for
b.c
on
$(CC.HOSTTYPE)
architectures that match
sol?.i386
or
sgi.*.
Execution
nmake
can be run in command mode or interactively.
nmake -n query
enters the query mode;
rule names, assignments and assertions may be entered at the
make>
prompt.
Entering a name with no operators lists all variable and rule information
for that name.
State rule names are indicated by a balanced parenthese prefix followed
by the rule name, and state variable names are entirely enclosed
in parenthesis.
Query mode is especially helpful for writing and debugging rules.
The -n option of
nmake
is similar to that of
make,
except that targets with the
.ALWAYS
attribute are still executed.
The
-N
option inhibits all shell action execution.
The
-n
is passed to recursive
nmake
invocations (via :MAKE: assertions) using the
$(-)
special variable.
When make begins execution, it looks for a file named
Makeargs
or
makeargs
in the current directory.
If found, each line of the file is inserted into the command line
option list.
This allows local additions/overrides without the need to modify
the underlying makefile, which may be on a different viewpath level.
For example a
Makeargs
file containing
--debug-symbols
will compile with compiler debugging flags enabled.
Before the first action is executed
nmake
starts a single shell process, called the
coshell,
to execute all action blocks.
This cuts down on
nmake
fork(2)
or
spawn(2)
overhead and allows command execution to be handled by the most
efficient program.
While an action is executing
nmake
determines the next action to execute.
The -jnproc option, or the NPROC=nproc environment
variable allows
nmake
to execute
nproc
actions simultaneously.
Internally
nmake
builds a dynamic prerequisite graph;
this graph determines target actions that may execute concurrently.
The
.SEMAPHORE
attribute provides per-target concurrency control.
The
coshell
program is determined by the
COSHELL
or
SHELL
environment variables, checked in that order,
or
/bin/sh
by default.
COSHELL
may also point to
coshell(1)
which supports concurrent action execution on separate hosts that share
the same filesystem.
Sometimes it is necessary to circumvent the
nmake
state model.
For instance, the
ast
software bootstrap build uses
mamake(1)
to build
nmake.
If
nmake
were to be run on the bootstrap generated files they would be rebuilt
since the bootstrap provided no
nmake
state.
The
--accept
or
-A
option instructs
nmake
to use the
make
time stamp model and accept files that are up to date with respect to file
time stamps only.
An up to date state file is generated so that subsequent invocations,
with no other changes, will treat all files as up to date.
--accept
is often used with the
--touch
or
-t
option that sets the time stamp of all out of date generated files
to the current time.
Variants
There are now three public variants of
make
named
nmake:
- AT&T nmake 5.0
-
This is the version maintained by the original author.
- Lucent nmake 3.5
-
The the variant that split in 1995 when AT&T spun off Lucent.
- MicroSoft NMAKE
-
This variant has some features inspired by the 1985 Portland USENIX paper,
but that's where the similarities end.
Use
nmake -n -f - . 'print $(MAKEVERSION)'
to list the installed
nmake
version.
Here are the known incompatibilities between the AT&T and Lucent
variants.
Bug fixes and backwards compatible enhancements to the AT&T variant
are not listed.
- output serialization
-
Output for each action is collected until the action completes.
Without serialization action error messages during parallel builds
may get intermingled to the point of being useless.
- AT&T
-
nmake --serialize
which sets the
CO_SERIALIZE
flag in the
coshell
library.
- Lucent
-
Implemented with 3 new commands, one of which is a replacement wrapper for
nmake
itself.
- probe file override
-
Allow a user to override the automatically generated probe files.
- AT&T
-
nmake
has always searched for probe information in the
../lib/probe/C/make/
directories on the
$PATH
environment variable.
In fact, all related files used a
$PATH-based
search: e.g.,
makefiles in
../lib/make/.
This was done to avoid a proliferation of
fooPATH
variables
that would soon become a maintenance and portability nightmare.
To provide a local override, simply create a
../lib/probe/C/make/
dir sibling to a dir on
$PATH,
and place a user writable probe file in it.
Make sure the override dir is to the left of the
nmake
bin dir in
$PATH.
The probe file name is determined by running
probe --key
C make
C-compiler-path.
See
probe --?override
for more details.
- Lucent
-
The user probe file is placed in a dir on
$PROBEPATH,
or on
$VPATH,
depending on the value of the
localprobe
variable.
- Makerules options
-
--option=value
instead of
option=value
to control optional makerules behavior.
The difference is subtle but significant.
option
in the second (makerules variable) form can conflict
with environment and makefile variables.
It also lacks the self documentation of
--?option
or
--man
from the
nmake
command line.
Makerules differences are the main point of divergence between
Lucent and AT&T
nmake:
- AT&T
-
The makerules variable form is supported for backwards compatibility.
Warnings will eventually be enabled to encourage use of the
makerules option form.
- Lucent
-
Many makerules variable additions.
- :MAKE: ordering
-
- Lucent
-
Not implemented.
- :MAKE: tracing
-
Emits a message when a
:MAKE:
directory is entered and exited.
By default the entry is listed as
directory:.
- AT&T
-
The directory name is prefixed with
--recurse-enter=text
on entering a directory and
--recurse-leave=text
on leaving a directory.
- Lucent
-
The directory name is prefixed with the makerules variable
$(recurse_begin_message)
on entering a directory and
$(recurse_end_message)
on leaving a directory.
- .ACTIONWRAP
-
$(.ACTIONWRAP)
is expanded instead of
$(@).
- AT&T
-
Not implemented, pending a more general solution.
- Lucent
-
Its not clear how to prevent
all
actions from being wrapped.
Example
The
nmake
Makefile
and
config.iffe
iffe
script for the GNU
diff,
version 2.7, 1994, are listed below.
GNU
diff
is a component of the
ast-open
package at
Software Systems Research software.
:PACKAGE: ast
VERSION == "2.7"
RELEASE == "1994-10-01"
DIFF_PROGRAM == "diff"
DEFAULT_EDITOR_PROGRAM == "ed"
PR_PROGRAM == "pr"
diff :: diff.c analyze.c dir.c io.c util.c context.c ed.c ifdef.c \
cmpbuf.c normal.c side.c
diff3:: diff3.c \
LICENSE='author="Randy Smith"'
sdiff:: sdiff.c \
LICENSE='since=1992,author="Thomas Lord"'
:: COPYING ChangeLog NEWS INSTALL README diagmeet.note \
diff.info diff.info-1 diff.info-2 diff.info-3 diff.info-4 \
diff.texi texinfo.tex \
Makefile.in config.hin
:MSGFUN: fatal message \
perror_fatal perror_with_exit perror_with_name pfatal_with_name
The
:PACKAGE:
assertion causes all programs to be compiled with the
ast
headers and the
-last
library.
nmake install
will build and install the three programs
diff,
diff3
and
sdiff
in
$(BINDIR).
The state variable
==
assignments generate properly quoted
-D
options in the compile lines for source files that reference the variables.
The
:MSGFUN:
assertion declares non-standard message function names for the base rules
msgcat
action that reaps source message strings and generates a C locale message
catalog using
msgcc(1)
and
msggen(1).
ast
message catalogs use C locale message text as lookup keys for text in
other locales.
The
:MSGFUN:
assertion and the
msgcat
action automate the process.
All of the source files include
config.h;
this is automatically generated from
config.iffe,
listed below, by the base rules
%:%.iffe
metarule.
The per-command scoped
LICENSE
variable assignments provide state variable information for the
ast
library
optget(3)
self-documenting option parser (used in place of the GNU
getopt_long(3).)
set config
hdr dirent,fcntl,limits,ndir,time,unistd,vfork
hdr float,stdarg,stdlib,string
key const =
lib dup2,memchr,sigaction,strchr,tmpnam
lib vfork = fork
sys dir,file,ndir,wait
typ pid_t = int
tst note{ signal handler return type }end compile{
#include <sys/types.h>
#include <signal.h>
#undef signal
extern void (*signal())();
}end yes{
#define RETSIGTYPE void
}end no{
#define RETSIGTYPE int
}end
tst CLOSEDIR_VOID note{ closedir() is a void function }end nocompile{
#include <dirent.h>
main()
{
return closedir(0);
}
}end
STDC_HEADERS = ( HAVE_FLOAT_H & HAVE_STDARG_H & HAVE_STDLIB_H & HAVE_STRING_H )
HAVE_ST_BLKSIZE = mem stat.st_blksize sys/stat.h
run{
echo "#if _PACKAGE_ast"
echo "#include <ast_std.h>"
echo "#endif"
case $HAVE_VFORK_H in
1) echo "#include <vfork.h>" ;;
esac
}end
set config
in this
iffe
script generates
configure(1)
compatible
HAVE_*
macro names.
The
name=value
statements translate from consistent
iffe
names to the ad-hoc
configure
names expected in the GNU source files.
The
lib vfork = fork
statement defines the macro
vfork
to be
fork
if
vfork(2)
is not in the default libraries.
GLOSSARY
- action
-
An action is part of an assertion that is specified by
indentation. In can be a shell action, an
nmake
action, or a scan action.
- assertion
-
An assertion is a statement in a makefile that describes
a rule or part of a rule.
An assertion consists of zero or more targets, an operator,
zero or more prerequisites, and is optionally followed by
an action.
- assignment
-
An assignment is a statement in a makefile the assigns
a value to a variable.
- attribute
-
An attribute is a special prerequisite or target
that is used to mark rules or targets.
- base rules
-
A set of rules that are defined in a makefile that
is shipped with
nmake.
This file contains contains default
rules for building software components.
These rules are implicitly included by
nmake.
- bind
-
binding is an action taken by
nmake
to associate a prerequisite
or target name with a file or other object.
- component directory
-
A directory (and its subdirectories) controlled by a makefile.
- global rules
-
A makefile that can be created by a project
that contains rules shared by all components
in that project.
- implicit prerequisite
-
A prerequisite that is not specified in the makefile.
- makefile
-
A file consisting of a set of assignments and assertions.
- making
-
making is the action taken by
nmake
to bring a target
up to date by binding all the prerequisites and
running the actions associated with the target if
any of the prerequisites have changed.
- metarule
-
a metarule is a type of rule in which the target and
prerequisite consists of a pattern containing the wildcard
character %.
- operator
-
An operator is part of a rule. It can be the operator :,
the operator ::,
or else a target of the form :name:.
- probe information
-
The set of probe variables corresponding to the current
$(CC).
Probe variable values are are generated by a script that is maintained by
probe(1).
The information is automatically generated and updated as probe script changes
are noticed.
Probe information updates do not explicitly depend on the time stamp of the
$(CC)
executable itself, since it is usually a wrapper program for other (private)
compiler passes.
- probe variable
-
A variable that is part of the
probe information
defined by
probe(1).
- prerequisite
-
A prerequisite is an item that needs to be checked in
order to decide whether a target needs to be updated.
- rule
-
A rule consists of a target, an operator,
zero or more prerequisites, and an action.
It is generated by combining all the assertions associated with a
target.
- scan rule
-
A scan rule rule is a type of rule that specifies how
a give source file is to be scanned for include files and
state variables.
- state file
-
A state file is a machine independent format data file that
stores information from the previous run.
It is used as the bases of deciding whether any prerequisites
of a target have changed.
- state variable
-
A state variable is a variable that is scanned for in source code
and whose value is saved in the state file.
- target
-
A target is an item that comes before an operator in an assertion.
- variable
-
A variable is an item
that can be used to hold an arbitrary string.
Variable names match the RE
[[:alpha:]_.][[:alnum:]_.]*
or one of the special variable names
[-+=%#@<>*~!?].
- viewpath
-
A viewpath is an ordered set of directories that are used
to search for files during the bind process.
REFERENCES
- The AT&T AST OpenSource software collection,
-
with David Korn and Stephen North and Phong Vo,
Proceedings of the FREENIX Track 2000 Usenix Annual Technical Conference,
pp 187-195, San Diego, CA, June 2000.
Practical Reusable UNIX Software,
chapter 11,
B. Krishnamurthy, editor, Wiley, 1995.
- Configuration Management,
-
with D. G. Korn and H. Rao and J. J. Snyder and K. P. Vo,
Practical Reusable UNIX Software,
chapter 3,
B. Krishnamurthy, editor, Wiley, 1995.
- Libraries and File System Architecture,
-
with D. G. Korn and S. C. North and H. Rao and K. P. Vo,
Practical Reusable UNIX Software,
chapter 2,
B. Krishnamurthy, editor, Wiley, 1995.
- nDFS -- The multiple Dimensional File System,
-
with D. G. Korn and H. Rao,
Configuration Management,
chapter 5,
W. Tichy, editor, Wiley, 1994.
- Feature Based Portability,
-
with J. J. Snyder and K. P. Vo,
USENIX VHLL Symposium, Santa Fe, NM, October 1994.
- The Shell as a Service,
-
USENIX 1993 Summer Conference Proceedings, Cincinnati, OH, June 1993.
- A User-Level Replicated File System,
-
with Y. Huang, D. G. Korn and H. C. Rao,
AT&T Bell Laboratories Technical Memorandum 0112670-930414-05, April 1993,
and USENIX 1993 Summer Conference Proceedings, Cincinnati, OH, June 1993.
- Tools and Techniques for Building and Testing Software Systems,
-
with J. E. Humelsine and C. H. Olson,
AT&T Technical Journal, Vol. 71 No. 6, pp. 46-61, November/December 1992.
- A Case for make,
-
Software - Practice and Experience, Vol. 20 No. S1, pp. 30-46, June 1990.
- Product Administration through SABLE and nmake,
-
with S. Cichinski,
AT&T Technical Journal, Vol. 67 No. 4, pp. 59-70, July/August 1988.
- The Fourth Generation Make,
-
USENIX 1985 Summer Conference Proceedings, pp. 159-174, Portland, OR, June 1985,
and UNIFORUM 1986 Winter Conference Proceedings, pp. 157-168,
Anaheim, CA, February 1986.
- Recursive Make Considered Harmful,
-
Peter Miller, AUUGN Journal of AUUG Inc., 19(1), pp. 14-25, 1997.
|
|
Glenn Fowler |
|
|
Information and Software Systems Research |
|
|
AT&T Labs Research |
|
|
Florham Park NJ |
|
|
January 02, 2007 |
|