Command-Line Compilation: Make

The disadvantage of compiling a program directly is that cl and link will go through the entire compilation process even if the source code files haven't changed. This is known as building, and could be rather wasteful if the process takes a long time.

There is, however, a method that takes into account the age of the various files such that if the executable file is newer than the source files that comprise it, then no compilation takes place (the executable is obviously up-to-date). This ‘conditional compilation’ process is known as making.

Because cl and link will compile and link the input file, irrespective of its timestamp, one has to take additional steps to check whether the source file is newer than the object file. This step can be performed by a program called make. Make is its traditional name, but Microsoft's make program is called nmake. Make/Nmake examines the rules in a ‘makefile’ and carries out the file's commands if the specified conditions are satisfied.

Typically, the rules indicate which output files are dependent on which input files, such that if the dependent files are older then the input files, then the instructions are performed. Although this make process can be used to run any program on any file, for the purposes of compilation, the obvious process is to compile a new executable if any of the source files have been modified.

The general structure looks as follows. Note that the space at the start of the line (the indentation) is necessary.

dependent: source
	command1
	command2
	...

Thus, a very simple example of a compilation process might look like this:

prog.exe: prog.c
     cl.exe /c prog.c
     link.exe prog.obj

The text before the colon indicates the name of the file that is dependent on the process; presumably the one that will be updated by the instructions performed by the rule. The text after the colon indicates the names of the files upon which the resultant file depends.

In this case, we're saying that the executable file depends on the source file, so if the source file is newer than the executable file, then the commands in the rule should be performed (in order to bring the executable up-to-date). Nmake will interpret this as meaning that if prog.c is newer than prog.exe, then it should run the cl.exe program to compile prog.c to produce prog.obj, and then link prog.obj to create prog.exe.

Makefiles have a fairly rich set of features, although the advanced ones can be somewhat obscure. Moreover, the syntax uses a lot of symbols which makes it rather difficult to read. Nevertheless, one can create a system to simplify the compilation process using only the basic features.

Step One: Locate the Compiler and Linker Programs

Let's assume the directory containing the cl and link programs does not appear in the system PATH. So, we have to give it explicitly. Makefiles, like batch files, support variables, and we can use them to store paths, thus:

PF_32="C:\Program Files (x86)\Microsoft Visual Studio
11.0\VC\bin"

Step Two: Locate the Header Files

As explained in the page on compiling programs directly, we need to tell cl and link where to find the header and lib files:

INC_VS="C:\Program Files (x86)\Microsoft Visual Studio 11.0\VC\Include"
INC_SDK_SH="C:\Program Files (x86)\Windows Kits\8.1\Include\shared"
INC_SDK_UM="C:\Program Files (x86)\Windows Kits\8.1\Include\um"

Step Three: Locate the Library Files

LIB_VS="C:\Program Files (x86)\Microsoft Visual Studio 11.0\VC\Lib"
LIB_SDK="C:\Program Files (x86)\Windows Kits\8.1\Lib\winv6.3\um\x86"

Step Four: Specify the Conditions and Commands

Now that we've stored the location of the various compilation files, we can now write the rules for compiling our program. We'll need only one rule here, but two instructions. The first instructions will perform the compilation, and the second will perform the linking:

prog.exe: prog.c
	"$(PF_32)\cl.exe" /c /W4 /nologo /EHsc /I $(INC_VS) /I $(INC_SDK_SH) /I $(INC_SDK_UM) prog.c
	"$(PF_32)\link.exe" /SUBSYSTEM:CONSOLE /LIBPATH:$(LIB_VS) /LIBPATH:$(LIB_SDK) prog.obj

The actual command in this rule looks much like the compilation rule in the page on building. However, variables are retrieved in a makefile using the syntax $(variable_name). Note that when /setting/ the value of a variable, the name is given without any surrounding characters.

Once you've understood this tutorial, you might want to have a look at my notes on compilation, which explains more advanced features of compilation and makefiles.



Home About Me
Copyright © Neil Carter

Last updated: 2016-03-04