• entries
59
114
• views
22513

# Zero to Normal: NMAKE

12 views

Zero to Normal: NMAKE

No one probably cares about this, but I figure this would be a good place to start this series. NMAKE is Microsoft's version of make, which is to say, a tool to take a build instruction set and then compile and link the required components into a final binary result. You probably won't need to touch this, basically ever. Using say Visual Studio, these .mak files are generated for you and run when you build. You control this with setting up multiple build configurations and then adjusting the settings for each configuration. So now that you know you will probably never need to directly work with .mak files, you can either go back to something more interesting or continue on.

First off, we want nmake.exe to be able to be called anywhere from the command line. You can either make of a copy of nmake into one of your path directories or append to your path directory listing the location of nmake that gets installed with Visual Studio. In VS .Net 2k3 nmake can be found at C:\Program Files\Microsoft Visual Studio .NET 2003\Vc7\bin.
• copy nmake.exe to %SystemRoot%\system32. Most likely c:\windows\system32, but is c:\windows.0\system32 on the machine I'm currently working on.

• Update you path directory by, My Computer -> Properties -> Advanced -> Environment Variables. Click the path variable and click Edit. Add on the end a semicolon to indicate a new path location, then type or paste in the directory above. Once that is all set, reboot the computer.

Now you probably understand why I prefer to copy nmake.exe to system32 directory. We're going to first look at the inside of a nmake file.

Macros act as definitions. The are placed anywhere not inside target descriptions, one per line. To define a new macro, simply add to the file on it's own line
MACRO_NAME = set macro to this stringMACRO2 = This macro contains the first macro $(MACRO_NAME) The next thing you should know about Macros is that they any Enviroment Variables set for that command line or globally are accessed like a macro. What you would access from the command prompt as %evnvar% is accessed in nmake as$(varname). On top of this, you can also define Macros as one of the parameters when calling nmake.exe. I'll cover that more below when I cover calling nmake and using batch files.

After Macros, you now have Commands to NMAKE that indicate dependency and then commands to complete it's own dependency. To do this a command/target/inference rule follows this format.
name: dependency list or actions to perform action to perform with a space 	or tab in front

What the name is and how it is formated is the main difference to nmake and in the Microsoft documentation on what a specific command group is. Say you wish to compile a build, but you want to delete all intermediate files on every compile, you would make blocks that look like this.
all : clean; final.execlean :	del final.exe	del *.obj

So what happens is, when you make the right call to the command line to build all, you will first call the dependency clean. Clean itself has no dependencies, so it's code block gets executed. The code block deletes final.exe in it's current directory and all files that end in .obj. Next final.exe is built according to inference rules that are not in the above code block, which would point first to a linker dependency which would then point to multiple object dependcies which point to multiple source files. Ok moving on, we're going to look at things from source going up.

First thing to do, since we're working from the bottom we're going to define how to convert/compile a .cpp file into a .obj file. To do this we use a variation in the name of the command to indicated that this is a inference rule. The format is ".cpp.obj". You can see that we first say what starting extension this rule should deal with, and what it is being converted too. In this case we don't really have any dependencies. Below is an example.
.cpp.obj:	$(MSVC2K3BIN)\cl$(@F)

Now that nmake can convert a cpp file into a obj, we now need to link all objects, so we define a new rule to convert a list of .obj file into a .exe file. Since to need objects to link, we drop them in as a dependency.
TARGET = finalEXETYPE = exeOBJS = main.obj class1.obj class2.obj$(TARGET).$(EXETYPE): $(OBJS)$(MSVC2K3BIN)\link /OUT:$(TARGET).$(EXETYPE) $(OBJS) Ok, so we defined via macros, what the applications final name will be, which in this case is final. What the exe type is to attach to that name. The fun one is the macro OBJS. This is the list of objects that will be the final executable. So what happens is, what ever above final.exe that is dependent on final.exe to be built trigers this code. This code triggers all the objects to ensure that they exist. Now the hang up right now is that nmake doesn't know what file format say main.o originally was. It is very possible have many inference rules so nmake can't assume that every .obj started as a corresponding cpp file. To make that happen we can either strictly define what objs are dependents on what source files and let the inference rules kick in, or blanket compile all source files and hope things work out. To indicate that main.obj is dependent on main.cpp we define a line as follows main.obj : main.cppclass1.obj : class1.cppclass2.obj : class2.cpp Not so hard, but what's happening here is that the rule .cpp.obj is getting invoked, but the way we have out .cpp.obj rule defined it will only convert main.cpp -> main.obj. So the following code will screw up. main_mia.obj : main.cpp main.cpp will get compiled, but will get compiled to main.obj, not main_mia.obj. There is probably a way around this, and any nmake guru's out there that feel like shedding a light on a better way to handle this leave me a comment. Ok, so we now have a rule to convert .cpp files into .objs; A rule the depends on each obj first compiling it's associative source file; and finally a rule that requires the objs to be called/created and then links them to form the executable. Now the last and final step is defining a target. As we saw earlier, a target like all is pretty simple. It just depends on final.exe. This cascades down, as we see from the reverse direction above. To make that a little nice and follow form better we can update. Ok wrapping and fixing up the code a bit. TARGET = finalEXETYPE = exeOBJS = main.obj class1.obj class2.objall : clean;$(TARGET).$(EXETYPE)clean : del$(OBJS) del $(TARGET).$(EXETYPE) .cpp.obj: $(MSVC2K3BIN)\cl$(@F) $(TARGET).$(EXETYPE):$(OBJS)$(MSVC2K3BIN)\link /OUT:$(TARGET).$(EXETYPE) \$(OBJS) main.obj : main.cppclass1.obj : class1.cppclass2.obj : class2.cpp

Ok, if that gets saved as a file mymak.mak you then have to call nmake to make this.
From the command prompt you can call
nmake /f mymak.mak all

This will clean and then make final.exe.
Now one more thing before I sign off for the night. You can specify macro's from the command line which is great when your using batch files easy batch jobs of multiple make files and doing other things like automatically pushing code into source control. To do that after you define /f makefilelocation and prior to indication what targets to build you enter in macros.
nmake /f mymak.mak "HELLOWORLD= Hi" all

This will define the macro HELLOWORLD and set it's string to Hi. Anyways, this is only part of what NMAKE is capable of, hence Zero -> Normal. Maybe I'll get around to writing the Normal -> Hero article, but probably not if there is zero interest. Hopefully there is zero interest because more sane people have moved onto things like the Apache Ant project or a quality IDE. If you want more reading check out the MSDN section on NMAKE. Now that you got the basics (hopefully) you can use that site to gain more knowledge. It lacks a ground up explanation or a quality example file to learn from so hopefully this has helped.

Edit: Spelling Corrections, forgot to run it through a word proc.

That looks really good; thanks for writing it. You mentioned its the first in a series. Is the topic of the series build systems or something else? I'm interested; I guess I'm insane. [embarrass]

I tried the SCons build system but dropped it after I ran into a number of roadblocks caused by incompleteness. I'm now using GNU make and its working almost perfectly with automatic header dependencies, except for one minor issue where if it can't find the included file, it won't continue after you fix the include until you delete the generated *.d file.

I've been pondering doing series of tutorials entiled Zero to Hero. More or less going from knowing nothing to being an "expert". That's a little far fetched probably, but I know when I was messing around w/ nmake I couldn't find any good resources on it. So I figured I'd pass on some information.

Some of the tutorials I'd like to do is C++, C++ -> Java, Simple 3D Worlds, BREW and possibly J2ME. I'm personally still learning Java and J2ME, so those won't be done anytime soon.

I'm probably going to fix this up a bit at some point. Entire things was written from 1:30am -> 3am. Also I notice I didn't cover comments, do a better example make file, or explain that a make file is just a text file and recommend some good text editors. I've personally been using jEdit for anything that notepad won't cut beans for.

Hmmm, I also notice that my grammer is damn hard to follow in some areas. It could also use more explinations. Oh well I'll work on that later.

Dude I've been using jEdit for a couple of years. Great minds must think alike... I like your idea of writing tutorials whenever you conquer a new subject. Not only would it solidify your understanding, but it makes it easier for others to follow the in same path.