nmake Overview

nmake(1) is the standard software construction tool at AT&T and Alcatel-Lucent. 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 nmake site (who mourns for Bell Labs?) 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:

  • Does header file name.h exist as a standard header?
    hdr	name
    
  • Does header file sys/name.h exist as a standard header?
    sys	name
    
  • Does the type type exist when sys/types.h and/or a given set of standard include files are included?
    typ	name	[ header.h ... ]
    
  • Is the function name in the standard library or one of the given libraries?
    lib	name	[ -llib ... ]
    
  • Is the data symbol name in the standard library or one of the given libraries?
    dat	name	[ -llib ... ]
    
  • Is the symbol name a member of a given structure when a given set of files is included?
    mem	struct_name.member_name	[ header.h ... ]
    
  • Is the command name found on the standard path?
    cmd	name
    
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:
nmake (AT&T Research) 5.6 2011-02-02

This is the version maintained by the original author.
Alcatel-Lucent nmake 12

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. Not all incompatibilities are caught. Compatibility checks are triggered by the .PROBE.INIT rule which is usually done early in the makefile input process. There may some instances where the --option=value form must be used, either on the command line or as
set --option=value
in a makefile or global rules file.
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.
java support

Different views on how to integrate java dependency analysis.
AT&T
Based on the ast-jmake package which provides jmake(1) and the JAVA(1M) and JAR(1M) assertion operators. Fully integrated with viewpathing and implicit prerequisite scanning in a manner similar to cc(1) and implicit includes.
Lucent

See the Alcatel-Lucent documentation.
XML build logs

A richer XML variant of MAM output.
AT&T
Not implemented, but a good idea for interopability. Would most likely be implemented by enhancing the --mam option.
Lucent

Implemented by a using the command xmakelog instead of nmake. Not clear from the documentation why a nmake option wouldn't do.


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 04, 2012