Alcatel-Lucent nmake Product Builder

Tutorial: A Little Help With Alcatel-Lucent nmake

[Table of Contents] [Previous Section] [Next Section]

2. An Example

Let's start with some simple instructions that tell nmake to write a message on our screen. Anything will do, so we'll be traditional and put

     hello :
          echo "hello, world"

in a file named hello.mk. Get the positioning right when you type it in - hello starts in column 1 and echo is indented by a tab. Blank lines and extra tabs or spaces won't usually matter; you can even get rid of white space around the colon.

2.1 Background

We need some common ground, mostly simple definitions and a few words about nmake, before we can go much farther. Don't worry, we'll keep it short so you'll hardly notice.

2.1.1 Makefiles

A file like hello.mk, with stuff in it that means something to nmake, is called a makefile. The name can be anything you want. Popular choices are Makefile, makefile, or a name that ends in .mk. Makefile and makefile are the defaults, so they'll save you a few keystrokes when you run nmake; more than one makefile in a directory is a good reason to use a suffix.

2.1.2 Assertions

The two lines in hello.mk constitute an assertion. The components of the assertion in our example are shown below:

[assertion diagram]

It's easy to parse assertions, even in complicated makefiles. The target list is a single line that starts in the first column and ends at a colon; prerequisites follow the colon, usually on the same line. Indented lines after the prerequisites are called the action block; the end of the makefile or the first line that's not indented (blank lines and comments don't count) ends the action block. Names that appear as targets or prerequisites are collectively called atoms.

All three components are optional. An empty prerequisite list, as in our example, or a missing action block, is not unusual. But an assertion with no target list isn't particularly useful, and if you find one you may be looking at a mistake. Don't be surprised to see the same target in several assertions: normally one assertion will have an action block and the others just add prerequisites to the target.

The assertions in a makefile usually describe the components of a software project. Targets often refer to programs or libraries, and have source files as prerequisites and shell commands that build the target from the source files as action blocks. Obviously our example is an exception: hello is the target, but it's not the name of a program, there aren't any prerequisites, and the action block doesn't build anything.

You'll often use words like "the target depends on the prerequisites," when you're describing an assertion. For example, if you were reading

      prog : a.c b.c

you would probably say "prog depends on a.c and b.c."

2.1.3 Looks Can Be Deceiving

Programs like make and nmake read makefiles and try to build targets as correctly and efficiently as possible. "Correctly" means they follow instructions, build everything that's needed, and stop if something goes wrong. "Efficiently" means they try to avoid unnecessary work, so both programs deal with prerequisites before targets, and in most cases only build a target, usually by handing an action block to the shell, when they find a prerequisite that's newer (i.e., younger) than the target.

But the two programs are very different, and their treatment of prerequisites is a prime example. They're usually files, but nmake gives you an easy way to include things, like compiler options, as prerequisites. It's important, but it means extra work for nmake. The information needs to be preserved between runs, but it's not necessarily in the makefile or directly available from a permanent resource, like the file system. But with make there's no easy way to associate abstract things, like compiler options, with programs or object files.

nmake also knows how to look through source files to find implicit prerequisites, such as header files in C programs. If a source file includes a header file and that header file changes, then most people would agree the source file should be recompiled. Header files often include other files, so dependencies can get complicated, but nmake figures them all out automatically.

make, on the other hand, only knows what it reads in your makefile, and that means dependencies encoded as #include directives in source files also need to appear as assertions in makefiles. The duplication can be a source of errors. Change a source file and you may also have to update a makefile; but header files are often shared, so it's not just a matter of one source file affecting one makefile.

2.2 Getting Started

Setting things up is easy - if nmake is in your PATH you're ready to go.[1] nmake can be installed anywhere, so check with your system administrator if you can't find it. What's in your environment is also important, but right now there aren't any magic shell variables you'll need to define and export.

We want you to participate, so set your PATH up and follow along. You'll get the most out of the paper if you type the examples in, run nmake, experiment a bit, and make mistakes. As you read along, either here or in the manual, think about eliminating duplication. It's an important theme and keeping it in mind will help you appreciate this tool.

2.3 Running nmake

We can run our example by typing

     nmake -f hello.mk hello

and we get:

     + echo hello, world
     hello, world

That's lots of work for a simple greeting and we ended up with more than we really wanted. We'll talk about the noise and how to get rid of it shortly, but first a few words about the command line.

2.3.1 The Option

We used the -f option to point at our makefile. The white space separating -f and hello.mk isn't needed [2] but the option is. If we leave it out

     nmake hello

we get:

     make: a makefile must be specified when Makefile,makefile omitted

When you don't choose a makefile nmake looks for Makefile and then makefile, and complains if it can't find either file. By the way, you can get the same error message if you're using viewpathing, which is something we'll talk about later in the paper, and your VPATH shell variable is wrong or just not exported.

2.3.2 The Argument

The hello argument tells nmake what target to build. Move it left

     nmake hello -f hello.mk

or leave it out

     nmake -f hello.mk

and nothing changes. nmake reads your makefile before it builds targets, and usually picks the first target in your makefile when you don't tell it what to do. The real story is more involved - we'll come back to it when we talk about .MAIN later in the paper.

2.4 The Noise

We can quiet things down from the command line using the -s option

     nmake -f hello.mk -s

or by sending standard error to /dev/null:

     nmake -f hello.mk  2>/dev/null

Either way we get,

     hello, world

which is what we originally wanted. If you're familiar with make you may recognize the -s option, but redirecting standard error is new, because make's noise shows up on standard output.

2.4.1 silent

We can also control the noise from a makefile. Putting silent in front of a simple shell command [3] stops the noise, but only for that command. For example, we could put

     hello :
          silent echo "hello, world"

in a file named silent.mk, type

     nmake -f silent.mk

and end up with:

     hello, world

That's the right answer again, but this time we got it without doing anything special on the command line. We'll use silent in many of our examples, mostly to avoid cluttering command lines with -s options or file redirection.

We should also mention there's a related command named ignore, that tells nmake to keep going when a shell command fails. Learn to use silent and you'll know how to use ignore, and if you're familiar with make's @ and - special characters you'll probably already appreciate silent and ignore.

2.5 Another Assertion

Let's add a second assertion to our example. We don't need to get fancy, so another simple message will do. If we put

     goodbye :
        silent echo "goodbye, world"
                                 
     hello :
        silent echo "hello, world"

in a file named goodbye.mk, then we can type

     nmake -f goodbye.mk goodbye

when we want to say goodbye. Name two different targets on the command line

     nmake -f goodbye.mk hello goodbye

and we get two messages:

     hello, world
     goodbye, world

Order makes a difference - rearrange the command line and see for yourself. Try more than one hello (or goodbye)

     nmake -f goodbye.mk hello hello

and you may be surprised by what happens. nmake usually only builds a target once per invocation, but in this case that's not the real explanation. We'll give you the full story later when we talk about .ARGS.

2.6 Some Easy Mistakes

Making mistakes is an important part of learning, particularly when you're trying to master a complicated subject like nmake. We've already talked about one mistake (forgetting the -f option); there are a few others that deserve a brief mention.

2.6.1 A Missing Makefile

Point nmake at a makefile that doesn't exist

     nmake -f missing.mk

and we get:

     make: missing.mk: cannot read

2.6.2 No Targets

If we create an empty file and try use it as a makefile

     >empty.mk
     nmake -f empty.mk

we get,

     make: empty.mk: a main target must be specified

because nmake usually complains, no matter what's in the makefile, if it doesn't find at least one assertion.

2.6.3 A Missing Target

Try to build a target that's not in a makefile

     nmake -f hello.mk goodbye

and nmake complains with:

     make: don't know how to make goodbye

2.6.4 A Missing Prerequisite

We can get the same kind of error message when there's a mistake in a makefile. For example, put

     hello : greeting
        echo "hello, world"

in a file named mistake.mk and type

     nmake -f mistake.mk hello

and we get:

     make: don't know how to make hello : greeting

nmake builds prerequisites before targets, but there's nothing in mistake.mk that explains how to build greeting, and that's why nmake complained.

Try to understand the error message, because you'll see it again and again. The colon-separated list is how nmake tells you what went wrong, and it's not just a copy of the first line of the assertion. The list can get long, but it always describes how nmake got from the target it was trying to build (the first name) to the prerequisite that caused the problem (the last name) [4]

2.6.5 An Action Block Failure

Commands that return a non-zero exit status usually stop nmake. For example, put

     failed :
        cat /xxx/yyy

in failed.mk and type

     nmake -f failed.mk failed

and we get:

     + cat /xxx/yyy
     cat: cannot open /xxx/yyy: No such file or directory
     make: *** exit code 2 making failed

nmake quit because cat exited with a non-zero status. Use ignore when you don't care about errors or when you run commands, like grep, that can return a non-zero exit status when there aren't any errors.

2.7 Variables

We've talked a little about assertions; now it's time to introduce variables [5] Put

     AUDIENCE = world
                                       
     goodbye :
        silent echo "goodbye, $(AUDIENCE)"
                                       
     hello :
        silent echo "hello, $(AUDIENCE)"

in variable.mk, type

     nmake -f variable.mk goodbye

and we get,

     goodbye, world

which is exactly what happened in the last makefile. That's good news, but there's lots to explain.

2.7.1 Names

Names are usually made up of letters, digits, underscores, and periods. They can be as long as you want and all characters are significant [6] Be careful with periods, particularly at the beginning or end of upper case names - nmake has claimed some of them. Variable and target names are completely independent, so confusing makefiles like

     hello = world
                        
     hello :
        echo hello $(hello)

are allowed, but overloading names is bad style. Instead, we recommend you pick a convention that visually separates the name spaces, say upper case variables and lower case targets, and stick with it as long as possible. But remember, targets can refer to programs or files that you don't control, so you won't always be able to follow strict rules.

2.7.2 Not Quite Reserved

A few words mean something special to nmake [7] - at this point the important ones are:

     break      else       eval       include    print      rules
     continue   end        for        let        read       set
     elif       error      if         local      return     while

They're not officially reserved, but you'll have trouble using them as variable or target names. Double quotes around any of the special words, as in

     "print" :
        echo "the target must be quoted"

is one solution. Forget the quotes and nmake usually complains, sometimes in a way that depends on the unquoted word, and other times in a way that depends on the context near the mistake. These errors are confusing, so get some experience. See what happens when you remove the quotes; then try substituting other special words, like error and include, for print.

2.7.3 Assignment Operators

nmake supports string and integer variables, and five different assignment operators. String variables and three assignment operators will get us through most of this paper.

The = and := operators assign a new value to a variable; the += operator appends a space and a value to whatever's currently stored in a variable. The := and += operators share an important property that we'll talk about when we get to the section on variable expansion. Until then, we'll stick with = when we want to assign a new value to a variable.

The strings picked up by assignment operators start right after the operator and go to the end of the line; a backslash at the end of the line means it's continued on the next line, though the newline itself is discarded. Assignment operators also remove leading and trailing white space from strings before they carry out an assignment, so there's no difference between

     AUDIENCE=New Jersey

and

     AUDIENCE =      New Jersey

Either way we would find the string definition shown in the picture below if we could look through nmake's variable symbol table.

audience.gif

We could build the same string up in steps using the = and += operators. The two assignment statements

     AUDIENCE = New
     AUDIENCE += Jersey

do the job, but only because = overwrites the existing definition and += adds a single space before appending Jersey. We could even do it in three steps

     AUDIENCE =
     AUDIENCE += New
     AUDIENCE += Jersey

because nothing on the right side of = clears the definition, and that means we won't see the space separator from the first += assignment statement.

Be careful about bringing too much of your C or shell programming experience along when you talk to nmake. For example, quotes need to be balanced, but they're not string delimiters as they are in C, so

     QUOTES = "New Jersey"

defines another string, but this one has a double quote at each end. Once again, if we could look through nmake's variable symbol table we would find the two definitions shown in the following picture:

quotes.gif

2.7.4 Referencing Variables

Putting $( and ) around a variable name, as we did with $(AUDIENCE), is how we ask nmake for the value represented by the variable. The replacement process is officially called variable expansion, and when it really happens is an important and confusing topic. We will postpone our discussion for a few sections. Until then, just believe nmake expands the variables it finds in an action block right before it hands anything to the shell.

We'll often say "variable reference" when we're talking about expressions, like $(AUDIENCE), that will be expanded by nmake. If we want some variety we may call it a reference to a particular variable: in this case the words would be, "a reference to AUDIENCE." It's convenient terminology, but you won't find it defined in the manual's glossary or listed in the index, so it's not officially blessed.

References to undefined variables are quietly replaced by empty strings, so don't expect warning messages about typing mistakes. It means mistakes can linger until nmake tries to execute the offending code, and even then you may not notice.

2.7.5 Command Line Assignments

Variables can be defined on the command line, so

     nmake -f variable.mk AUDIENCE='New Jersey' hello

prints:

     hello, New Jersey

How we arrange the command line doesn't make much difference. Move the assignment right

     nmake -f variable.mk hello AUDIENCE='New Jersey'

or left

     nmake AUDIENCE='New Jersey'  -f variable.mk hello

and nothing changes. Command line assignments are handled after nmake reads your makefile, and that makes it easy to override hard-coded definitions. There's nothing special about the = operator; you can use += or any other assignment operator on the command line. In fact, you can even do complicated things like define assertions on the command line, but don't get carried away because there aren't many good reasons to do so.

2.7.6 An Automatic Variable

It's been a while, and what we're going to talk about next is very important, so here's variable.mk again:

     AUDIENCE = world
                                       
     goodbye :
        silent echo "goodbye, $(AUDIENCE)"
                                       
     hello :
        silent echo "hello, $(AUDIENCE)"

Do you notice any duplication? If we change the name of a target, say from goodbye to farewell, we probably would also want to edit the action block and update the arguments of the echo command. Target names are mentioned in action blocks, and that duplication means extra work and more opportunity for mistakes. A mechanism that would let us talk about the components of an assertion (e.g., the target or prerequisites) in an action block without actually mentioning names would help.

There are about a dozen nmake variables, called automatic variables, that are designed to be used in action blocks. They're automatically assigned values by nmake and many are closely connected to the target that's being built. Automatic variables have cryptic names - only a few are easy to remember. The one we need is $(<), which happens to be one of the easy ones. $(<) stands for the name of the target we're building, so the last example can be written as:

     AUDIENCE = world
                             
     goodbye :
        echo "$(<), $(AUDIENCE)"
                             
     hello :
        echo "$(<), $(AUDIENCE)"

That's a good start, but there's more. The two assertions now have identical action blocks, so we can combine them

     AUDIENCE = world
                                    
     goodbye hello :
        silent echo "$(<), $(AUDIENCE)"

and eliminate the last bit of duplication, because each target mentioned in an assertion inherits its own copy of the prerequisites and the action block. Automatic variables are a valuable resource, so make sure you look for similar opportunities in your own makefiles. As simple as this example is, it's not quite right. We'll save it in a file named message.mk and fix it up later when we introduce .FORCE and .VIRTUAL.

2.8 Variable Expansion

We've already relied on variable expansion in several examples - now it's time for the details.

2.8.1 The General Idea

nmake expands a variable reference, like $(AUDIENCE), by copying the variable's current definition into a buffer. The process is repeated if nmake finds a variable reference while it's copying the definition, so expanding one variable can trigger the expansion of another variable, and so on. It's not hard to imagine problems:

     AUDIENCE = you and $(AUDIENCE)
                                     
     hello :
        silent echo "hello, $(AUDIENCE)"

The definition of AUDIENCE includes a reference to AUDIENCE, so the process we just described looks like it might never end. Let's find out for sure - we can always hit interrupt if we get stuck. Save the example in a file named recursive.mk, type

     nmake -f recursive.mk hello

and we get:

     make: AUDIENCE: recursive variable definition

The error message is good news; nmake caught the problem and warned us. It's even easy to figure out when nmake noticed the mistake. Take the variable reference out of the action block

     AUDIENCE = you and $(AUDIENCE)
                                
     hello :
        silent echo "hello, world"

and the error message goes away, so nmake only complains about recursive variable definitions if it tries to use them.

2.8.2 In Assignment Statements

The = and := operators replace existing definitions by new ones, but they behave differently when they find variable references. For example, start with

     MAGIC = xyzzy
     EQUAL = the magic word is $(MAGIC)
     COLONEQUAL := the magic word is $(MAGIC)

and look through nmake's variable symbol table and we would find:

assign.gif

There's a variable reference left in EQUAL, but not in COLONEQUAL, because the := operator expands variable references, but = doesn't. Refer back to our description of the expansion process in the last section and you should appreciate the consequences: assign a new value to MAGIC and nmake gives back a different result when it expands $(EQUAL), but there's nothing left to expand in COLONEQUAL, so $(COLONEQUAL) doesn't change.

By the way, the += operator works just like := when it finds a variable reference, so we wouldn't see a difference if we typed

     PLUSEQUAL += the magic word is $(MAGIC)

and then compared COLONEQUAL and PLUSEQUAL (assuming PLUSEQUAL started out undefined).

2.8.3 Adjusting Expansion Time

There are simple techniques that let you delay or trigger a variable expansion. Each extra dollar sign in front of a variable reference postpones the expansion one step, so after nmake reads

     DELAYED := the magic word is $$(MAGIC)

we would find

delayed.gif

in the variable symbol table. One dollar sign was removed by the := operator, but the expansion of $(MAGIC) has been delayed and we ended up with a string named DELAYED that looks exactly like EQUAL.

There's also an easy way to force variable expansion. We'll mention it here for completeness and never talk about it again, because it's a feature few users ever need. Surrounding one or more nmake statements with eval and end forces an additional variable expansion. eval and end can be important if you're doing complicated things, like writing your own assertion operators, but not many of you ever will, so we won't even include an example.

2.8.4 In Action Blocks

Variable references in an action block are expanded when nmake executes the action block. The implementation is straightforward: nmake copies the action block into a temporary buffer, expands variable references when it finds them, and then usually hands whatever's in the temporary buffer to the shell. Using an action block doesn't change nmake's copy, so variables are expanded each time the action block is used.

2.8.5 In Target Lists

Variable references in a target list are expanded when nmake builds its internal representation of the assertion. Here's an easy example that we'll save in a file named targets.mk:

     AUDIENCE = world
     TARGETS = goodbye hello
                                    
     $(TARGETS) :
        silent echo "$(<), $(AUDIENCE)"

We started with message.mk, added a variable named TARGETS, and referenced TARGETS in the assertion. When nmake reads targets.mk and gets to the assertion, it expands $(TARGETS), ends up with goodbye and hello, and from that point on behaves exactly like message.mk. If we could look through nmake's assertion symbol table we would find the two definitions shown in the following picture:

targets.gif

It usually won't matter which makefile we use, even when we make a mistake, but there is an important difference: TARGETS is a variable and it can be defined on the command line, so when we type

     nmake -f targets.mk TARGETS=farewell farewell

we get:

   farewell, world

This time nmake got farewell when it expanded $(TARGETS), so now when we look through the assertion symbol table we find,

farewell.gif

but there's no trace of hello and goodbye. Change the command line assignment operator to += and we can build goodbye, hello, and farewell.

2.8.6 In Prerequisite Lists

Variable references in a prerequisite list are expanded when nmake builds its internal representation of the assertion, just like targets. Here's an example. It's not unusual to keep track of source files using a variable

     SOURCE = a.c b.c

and reference that variable in an assertion:

     first : $(SOURCE)

When nmake reads the assertion it expands $(SOURCE) and ends up building an internal representation of an assertion that has first as the target, and a.c and b.c as prerequisites. Adding a dollar sign

     second : $$(SOURCE)

delays the expansion, so we would find the following definitions

prereqs.gif

in the assertion symbol table after nmake finished reading the two assertions. They're clearly different, but they're also equivalent (as long as SOURCE isn't changed), because variable references left in the prerequisite list are expanded when nmake builds the target.

2.9 A Look Back

Real makefiles handle tough jobs and they can get complicated, so don't be misled by our examples. But we also want to make sure you don't underestimate what you've learned so far. We kept things simple, and that let us introduce fundamental concepts without getting lost in the details of the example.

FOOTNOTES:

[1]
Running nmake using a full pathname may work, but it's not something we recommend.
[2]
make users should appreciate the remarkable flexibility.
[3]
Use something like,
     silent ksh -c '...'
silent to complicated commands, like if or case statements, that need to be interpreted by the shell.
[4]
Can you figure out how to change mistake.mk and get three names in the error list?
[5]
See chapter 3 of the nmake User's Guide and chapter 4 of the nmake Reference Manual for more about variables.
[6]
Of course we're exaggerating, but the limits are so big we don't think you'll ever notice.
[7]
See chapter 7 of the nmake User's Guide for more details.

[Table of Contents] [Previous Section] [Next Section]

Last Update: Friday,26-Jul-2013 09:36:36 EDT