|
|||
Tutorial: A Little Help With Alcatel-Lucent nmake[Table of Contents] [Previous Section] [Next Section] 2. An ExampleLet'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 BackgroundWe 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 MakefilesA 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 AssertionsThe two lines in hello.mk constitute an assertion. The components of the assertion in our example are shown below:
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 DeceivingPrograms 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 StartedSetting 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 nmakeWe 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 OptionWe 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 ArgumentThe 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 NoiseWe 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 silentWe 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 AssertionLet'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 MistakesMaking 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 MakefilePoint nmake at a makefile that doesn't exist
nmake -f missing.mk
and we get:
make: missing.mk: cannot read
2.6.2 No TargetsIf 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 TargetTry 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 PrerequisiteWe 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 FailureCommands 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 VariablesWe'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 NamesNames 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 ReservedA 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 Operatorsnmake 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.
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:
2.7.4 Referencing VariablesPutting $( 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 AssignmentsVariables 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 VariableIt'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 ExpansionWe've already relied on variable expansion in several examples - now it's time for the details. 2.8.1 The General Ideanmake 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 StatementsThe = 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:
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 TimeThere 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
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 BlocksVariable 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 ListsVariable 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:
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,
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 ListsVariable 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
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 BackReal 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:
[Table of Contents] [Previous Section] [Next Section] Last Update: Wednesday,20-Dec-06 13:22:14 CST
|
|||