Nokia Networks Home

Nokia nmake Product Builder

Quick Links

Related Products

Tutorial: A Little Help With Nokia nmake

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

9. A Program - Finally

Our next few examples will finally build a real program. We need somewhere to work, so let's start with:

     mkdir /tmp/n2
     cd /tmp/n2

We picked /tmp/n2, but you can use any directory you want, as long it's empty and you remember to substitute your directory when we use /tmp/n2.

9.1 Three Source Files

We'll need a few source files that build a program, but we won't care what the program does, as long as it compiles. A main program that calls routines in two other files will be good enough, so we'll put

     main() {
        a();
        b();
     }

in main.c,

     #include <stdio.h>

     a() {
        puts("In routine a");
     }

in a.c, and

     #include <stdio.h>

     b() {
        puts("In routine b");
     }

in b.c.

9.2 And A Makefile

Building our program by hand is simple. Just type

     cc -o prog main.c a.c b.c

and the compiler handles everything, but it's not efficient, even for our trivial program. The compiler does exactly what we ask, so all three files are compiled and linked every time we run the command. nmake could help, because it has a good memory; it's even easy to go directly from the command line to a makefile. Build an assertion that has prog as the target, the three source files as prerequisites, and our command line as the action block

     prog : main.c a.c b.c
        cc -o prog main.c a.c b.c

and we can let nmake keep everything up to date. It's a start and it even works, but there's a bit more to do before we use the makefile.

9.2.1 Automatic Variables

The first line of the assertion in our makefile is almost repeated in the action block. We already know about $(<) and $(~), so

     prog : main.c a.c b.c
        cc -o $(<) $(~)

looks like a good way to clean things up. But don't jump the gun because $(~) in our action block stands for all the explicit prerequisites of prog, so we could get more than we want. We'll show you the problem and an easy fix after we make one more change.

9.2.2 CC

There's a variable named CC that's defined to be cc in Makerules.mk, and it's customary to use $(CC) to talk about a C or C++ compiler. We get CC for free, so we can replace cc in our action block

     prog : main.c a.c b.c
        $(CC) -o $(<) $(~)

and not notice a difference. CC is a variable, so changing compilers is easy. We can redefine CC in our makefile if we want a different default, or change it on the command line when we want to try another compiler. For example, if you're using C++ your makefile probably should look something like:

     CC = CC
                       
     prog : main.c a.c b.c
        $(CC) -o $(<) $(~)

9.2.3 A State Variable

Our action block uses $(CC) as the compiler, so it's natural to want to have nmake rebuild prog when CC changes. We already know about state variables, but there's a problem when we add (CC) to prog's prerequisite list. Save

     prog : main.c a.c b.c (CC)
        $(CC) -o $(<) $(~)

in broken.mk and type

     nmake -f broken.mk

and we get some strange complaints from the shell:

     ksh[52]: syntax error at line 2 : `(' unexpected
     ksh[52]: Cset:  not found

You may see something different on your system, but that shouldn't matter - figuring out what's wrong from the shell's error messages isn't easy. Fortunately, there's a way to find out what nmake handed to the shell. The -n option means print shell actions rather than execute them, so when we type

     nmake -f broken.mk -n

we get,

     + cc -o prog main.c a.c b.c (CC)

and now it's easy to see what happened. nmake picked up the three source files and (CC) when it expanded $(~), and the shell didn't like the parentheses around CC. But there's more wrong than just parentheses: (CC) isn't a file, so the compiler would complain even if the shell didn't.

9.2.4 A New Automatic Variable

We get too much with $(~), but there's an automatic variable named $(*) that stands for all the explicit file prerequisites of the current target. (CC) is a prerequisite, but it's not a file so it shouldn't show up when $(*) is expanded. Let's replace the $(~) in broken.mk by $(*)

     prog : main.c a.c b.c (CC)
        $(CC) -o $(<) $(*)

and save the result in fixed.mk.

9.2.5 Let's Build The Program

We can finally build our program by typing:

     nmake -f fixed.mk prog

What you see will depend on your compiler. We get

     + cc -o prog main.c a.c b.c
     main.c:
     a.c:
     b.c:
     Linking:

and we end up with an executable program named prog. It's easy to prove nmake has already helped. Try to build the program again and nothing happens, and that's exactly as it should be, because prog is up to date.

9.3 A Better Makefile

Our makefile works, but it could be better. We'll show you two problems and then spend the rest of this section trying to improve things. It's going to take some time and we'll even go backwards at first, but we'll eventually end up with a simple solution that you should understand and appreciate.

9.3.1 What's Wrong

The assertion in fixed.mk says prog depends on main.c, a.c, and b.c, so nmake should rebuild prog when a source file changes. Let's see what happens - touch one of the source files and run nmake again

     touch a.c
     nmake -f fixed.mk prog

and we get:

     + cc -o prog main.c a.c b.c
     main.c:
     a.c:
     b.c:
     Linking:

nmake built a new prog, but we did more work than was necessary. Any prerequisite that are newer than the target triggers the build [19] but $(*) hands the three source files to the compiler, so they're all recompiled when nmake executes our action block. There's another reason why we're not happy with fixed.mk. Try to clean up using clobber

     nmake -f fixed.mk clobber

and we get:

     + ignore rm -f fixed.mo fixed.ms prog

nmake removed everything it knew about, but the compiler created main.o, a.o, and b.o when nmake ran our action block, and they weren't removed.

9.3.2 Object Files

We can address both complaints using object files, but our first few tries won't be simple. We'll start with an assertion that says prog depends on main.o, a.o, and b.o,

     prog : main.o a.o b.o
        $(CC) -o $(<) $(*)

add three assertions that tell nmake how to build the object files,

     main.o : main.c (CC)
        $(CC) -c $(*)

     a.o : a.c (CC)
        $(CC) -c $(*)

     b.o : b.c (CC)
        $(CC) -c $(*)

and save everything in a file named object.mk. If we build prog using object.mk we get,

     + cc -c main.c
     + cc -c a.c
     + cc -c b.c
     + cc -o prog main.o a.o b.o

but now when we touch a.c and rebuild we get:

     + cc -c a.c
     + cc -o prog main.o a.o b.o

The only source file that was recompiled is the one we touched, and that's exactly the behavior we want. There's also good news when it comes to clobber, because nmake now knows about main.o, a.o, and b.o, but we'll leave the proof to you.

9.3.3 CCFLAGS

object.mk passed our first few tests, but it's still not right. We mentioned nmake's cpp earlier in the paper, but so far we're not using it. Compilers often let you plug in your own preprocessor using options or environment variables, and nmake does a good job figuring out how. The details are interesting, but complicated. If you ever run nmake and see something like

     probing C language processor /bin/cc for make information
     probing C language processor /bin/cc for pp information

then you're watching nmake figure important things out about your compiler, including how to select an alternate preprocessor.

The results we're interested in (and much more) are hidden in a special variable named CCFLAGS. We haven't talked about the auxiliary value that can be associated with any variable using the &= assignment operator, but it's a good guess it was implemented to accommodate sophisticated variables like CCFLAGS. nmake can hide important stuff, like the instructions needed to use nmake's preprocessor, in the auxiliary value associated with CCFLAGS and be sure it's not trashed when we do things like:

     CCFLAGS = -g

There's much more to CCFLAGS, but most of it is well beyond the scope of this paper. Fortunately, we can use CCFLAGS even if we don't understand all the details. If we start with object.mk, reference CCFLAGS in the assertions that build our object files, add (CCFLAGS) to the three prerequisite lists,

     prog : main.o a.o b.o
        $(CC) -o $(<) $(*)

     main.o : main.c (CC) (CCFLAGS)
        $(CC) $(CCFLAGS) -c $(*)

     a.o : a.c (CC) (CCFLAGS)
        $(CC) $(CCFLAGS) -c $(*)

     b.o : b.c (CC) (CCFLAGS)
        $(CC) $(CCFLAGS) -c $(*)

save our work in ccflags.mk, and type

     nmake -f ccflags.mk prog

we get something like:

     + cc -O -Qpath /nmake/lib -I-D/nmake/lib/probe/C/pp/835E4F4F5bincc -I- -c main.c
     + cc -O -Qpath /nmake/lib -I-D/nmake/lib/probe/C/pp/835E4F4F5bincc -I- -c a.c
     + cc -O -Qpath /nmake/lib -I-D/nmake/lib/probe/C/pp/835E4F4F5bincc -I- -c b.c
     + cc -o prog main.o a.o b.o

Most of the options come from expanding $(CCFLAGS), and all but -O are stored in the auxiliary value. The -Qpath option points our compiler at nmake's cpp. The file following -I-D is called the probe file: it's an initialization file that helps tune nmake's cpp to the compiler we're using. CCFLAGS is also responsible for the -I- option; additional -I options are strategically placed around -I- when the source file that's being compiled includes header files from non-standard directories.

9.3.4 Metarules

We've done more than necessary in ccflags.mk. Remove the three assertions that build the object files, save what's left

     prog : main.o a.o b.o
        $(CC) -o $(<) $(*)

in meta.mk, and type

     nmake -f meta.mk prog

and everything works exactly as it did when we used ccflags.mk! We're down to a two-line makefile, but how? The assertion says prog depends on main.o, a.o, and b.o, but there's nothing left in meta.mk that explains how the object files are built.

It turns out nmake can guess how to build certain files using special instructions called metarules. The metarule we're relying on is defined in Makerules.mk; it looks like

     %.o : %.c (CC) (CCFLAGS)
        $(CC) $(CCFLAGS) -c $(>)

and tells nmake how to build a .o file from a .c file. It's not hard to understand what happened. nmake found main.o in the prerequisite list, but we deleted the instructions it needed, so nmake looked around and found a file named main.c and a metarule that it could use to build main.o from main.c and was perfectly happy. Mentally replace the two % characters in the metarule by main, a, and b and you'll see how nmake resurrected the three assertions that we deleted from ccflags.mk [20].

9.3.5 Not Quite

meta.mk is simple, but it's still not right. The action block builds prog by linking the three object files, but we could miss some flags by completely ignoring CCFLAGS, and there are other variables (e.g., LDFLAGS) that contain important information for the link editor and should also be used in the action block.

The clean common action also misbehaves. It should remove intermediate files like main.o, a.o, and b.o, but it's not supposed to touch programs. However, when we type

     nmake -f meta.mk clean

we get,

     + ignore rm -f b.o a.o main.o prog

so prog is also removed. There are easy solutions - we'll show you the one that uses the :: assertion operator.

9.3.6 Assertion Operators

Assertion operators have the same look - a colon, an optional letter or underscore followed by zero or more letters, digits, or underscores, and a closing colon. There are about twenty assertion operators defined in Makerules.mk and most are also documented in the manual. Find Makerules.mk and you can list the ones you get for free using a command something like:

     grep '. OPERATOR' Makerules.mk

The assertion operator we're interested in is ::, which is sometimes called the main source dependency operator. It's easy to use - put prog on the left side of :: and our three source files on the right side,

     prog :: main.c a.c b.c

and we're done. It's a perfectly general one-line solution, and is the way we recommend you build programs. Save the makefile in prog.mk, because we'll need it again when we talk about viewpathing in the next section.

One way to understand prog.mk is to pretend nmake calls a function with three arguments whenever it finds an assertion operator. Everything to the left of the assertion operator is one argument, everything to the right is another, and the action block, if there is one, is the last argument. The assertion operator itself (i.e., the body of our function) is written in nmake's programming language [21]. The fact that we used an assertion operator in our makefile isn't recorded in the objectfile, so assertion operators work by creating variables and assertions that end up in the objectfile.

That's exactly what happened when we used :: in prog.mk. If you can imagine using a for loop to march through the three source files (i.e., the second argument) and an edit operator to replace the .c suffix by .o, then you can understand how the :: operator was able to build an improved version of the assertion we used in meta.mk. The changes that :: threw in to fix the problems we mentioned in the last section are simple and could easily be included in meta.mk, but they would have to be duplicated in other makefiles. Having everything isolated in the :: operator in Makerules.mk is an enormous advantage.

FOOTNOTES:

[19]
Touching prog also triggers a build, because the statefile and the file system disagree about prog's time stamp.
[20]
$(>) instead of $(*) is the only difference, and in this example it's insignificant. You can read about $(>) in chapter 4 of the nmake Reference Manual.
[21]
The three arguments are referenced using $(<), $(>), and $(@) in the assertion operator.

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

Last Update: Friday,12-Aug-2016 12:19:40 EDT