Wednesday, 5 October 2011

How to use nmake and makefile

Let's build simple "Hello, world!" application from command line by using Microsoft's nmake. Necessary steps are:

1) create project directory ("..\HelloWorld")
2) create source file ("..\HelloWorld\main.cpp")
3) create makefile ("..\HelloWorld\makefile")
4) set up environment (environment variables) for nmake. I will use my setenv.bat script from my post "NMAKE and its environment" (run "..\HelloWorld\setenv.bat" with    "x86" argument)
5) run nmake

main.cpp:
#include <iostream>
int main()
{
  std::cout << "Hello, world!" << std::endl;
  return 0;
}

The shortest version of makefile needs to contain at least one description block:
target : dependencies
 commands

Target name MUST start at the beginning of the line. Spaces are allowed (but not requred) around the colon that separates target and dependents. Commands in description block MUST be preceded with a SPACE character; nmake will otherwise complain with message makefile(.) : fatal error U1034: syntax error : separator missing.

makefile:
foo: main.cpp
 cl main.cpp


Here's the order of commands in the command prompt window:

C:\DEVELOPMENT\RESEARCH\C++\HelloWorld>setenv x86
Setting environment for using Microsoft Visual Studio 2010 x86 tools.

C:\DEVELOPMENT\RESEARCH\C++\HelloWorld>where nmake
c:\Program Files\Microsoft Visual Studio 10.0\VC\bin\nmake.exe

C:\DEVELOPMENT\RESEARCH\C++\HelloWorld>where cl.exe
c:\Program Files\Microsoft Visual Studio 10.0\VC\bin\cl.exe

C:\DEVELOPMENT\RESEARCH\C++\HelloWorld>where link.exe
c:\Program Files\Microsoft Visual Studio 10.0\VC\bin\link.exe

C:\DEVELOPMENT\RESEARCH\C++\HelloWorld>nmake

Microsoft (R) Program Maintenance Utility Version 10.00.30319.01
Copyright (C) Microsoft Corporation.  All rights reserved.

        cl main.cpp
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.30319.01 for 80x86
Copyright (C) Microsoft Corporation.  All rights reserved.

main.cpp
c:\Program Files\Microsoft Visual Studio 10.0\VC\include\xlocale(323) : warning
C4530: C++ exception handler used, but unwind semantics are not enabled. Specify
 /EHsc
Microsoft (R) Incremental Linker Version 10.00.30319.01
Copyright (C) Microsoft Corporation.  All rights reserved.

/out:main.exe
main.obj

C:\DEVELOPMENT\RESEARCH\C++\HelloWorld>main
Hello, world!

C:\DEVELOPMENT\RESEARCH\C++\HelloWorld>

Compiler creates obj file and automatically invokes the linker after compiling unless the /c option is used. Linker creates executable. Upon nmake call, we have following files in our project directory:
..\HelloWorld\main.cpp
              main.exe
              main.obj
              makefile
              setenv.bat

Ok, we got binary but let us tidy things a bit and let us see how to involve header files. By default, compiler gives binary a default name using the base name of the first source or object file specified on the command line. I want binary to be named as "helloworld.exe" and want it to be placed in "..\HelloWorld\bin\x86" directory. OBJ files should be put in the intermediate directory "..\HelloWorld\intermediate\x86". I want my main function to be in "..\HelloWorld\src\main.cpp" and to use some function declared in header "..\HelloWorld\include\printx.h" and defined in "..\HelloWorld\src\printx.cpp". Furthermore, makefile should be in "..\HelloWorld\build". Project structure before nmake call should be:

..\HelloWorld\src\main.cpp
..\HelloWorld\src\printx.cpp
..\HelloWorld\include\printx.h
..\HelloWorld\build\makefile
..\HelloWorld\build\setenv.bat

And after nmake call:
..\HelloWorld\src\main.cpp
..\HelloWorld\src\printx.cpp
..\HelloWorld\include\printx.h
..\HelloWorld\build\makefile
..\HelloWorld\build\setenv.bat
..\HelloWorld\intermediate\x86\main.obj
..\HelloWorld\intermediate\x86\printx.obj
..\HelloWorld\bin\x86\helloworld.exe

Let us assume that new unit in our project, printx, contains implementation of our custom manipulator which inserts given number of asterisks into output stream.

printx.h:

#ifndef _PRINTX_H_
#define _PRINTX_H_
#include <iomanip>

// helper function which appends an array of asterisks to ostream
void append_asterisks(std::ios_base& os, int nCount);

// custom manipulator, based on std::_Smanip macro
std::_Smanip __cdecl insert_asterisks(int nCount);
#endif //_PRINTX_H_

printx.cpp:

#include "printx.h"

void append_asterisks(std::ios_base& os, int nCount)
{
   std::ostream* pos = dynamic_cast <std::ostream*>(&os);

   if(pos)
   {
      for(int i = 0; i < nCount; i++)
      (*pos) << '*';
   };
}

std::_Smanip<int> __cdecl insert_asterisks(int no)
{
return (std::_Smanip<int>(&append_asterisks, no));
}

We are using this function in our main:
main.cpp:

#include <iostream>
#include "printx.h"

int main()
{
std::cout << insert_asterisks(10)  << "Hello, world!" << insert_asterisks(10) << std::endl;
return 0;
}

New makefile uses following compiler options:
   /EHsc - to suppress previous compiler warning related to exceptions handling
   /Fe - to define binary output destination directory
   /I - to provide path to header file(s)

It contains pseudo-targets which operate on directory structure ("create_dirs", "clean") and one that groups all actions into a single command ("all").

makefile:

#define macros
EXECUTABLE_NAME = helloworld.exe
DIR_SRC = ..\src
DIR_INCLUDE = ..\include
DIR_BIN = ..\bin
DIR_BIN_X86 = $(DIR_BIN)\x86
DIR_INTERMEDIATE = ..\intermediate
DIR_INTERMEDIATE_X86 = $(DIR_INTERMEDIATE)\x86

SRC_FILES= \
  $(DIR_SRC)\main.cpp \
  $(DIR_SRC)\printx.cpp

# description block
$(EXECUTABLE_NAME) : $(SRC_FILES)
  cl /EHsc /Fe$(DIR_BIN_X86)\$(EXECUTABLE_NAME) /I$(DIR_INCLUDE) $(SRC_FILES)
  copy *.obj $(DIR_INTERMEDIATE_X86)
  del *.obj

# build application
helloworld: $(EXECUTABLE_NAME)

# create output directories
create_dirs:
@if not exist $(DIR_BIN_X86) mkdir $(DIR_BIN_X86)
@if not exist $(DIR_INTERMEDIATE_X86) mkdir $(DIR_INTERMEDIATE_X86)

# delete output directories
clean:
@if exist $(DIR_BIN) rmdir /S /Q $(DIR_BIN)
@if exist $(DIR_INTERMEDIATE) rmdir /S /Q $(DIR_INTERMEDIATE)

# create directories and build application
all: clean create_dirs helloworld

Here's the order of commands in the command prompt window:

C:\DEVELOPMENT\RESEARCH\C++\HelloWorld\build>nmake all

Microsoft (R) Program Maintenance Utility Version 10.00.30319.01
Copyright (C) Microsoft Corporation.  All rights reserved.

        cl /EHsc /Fe..\bin\x86\helloworld.exe /I..\include ..\src\main.cpp  ..\s
rc\printx.cpp
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.30319.01 for 80x86
Copyright (C) Microsoft Corporation.  All rights reserved.

main.cpp
printx.cpp
Generating Code...
Microsoft (R) Incremental Linker Version 10.00.30319.01
Copyright (C) Microsoft Corporation.  All rights reserved.

/out:..\bin\x86\helloworld.exe
main.obj
printx.obj
        copy *.obj ..\intermediate\x86
main.obj
printx.obj
        2 file(s) copied.
        del *.obj

C:\DEVELOPMENT\RESEARCH\C++\HelloWorld\build>cd "..\bin\x86"

C:\DEVELOPMENT\RESEARCH\C++\HelloWorld\bin\x86>helloworld.exe
**********Hello, world!**********

C:\DEVELOPMENT\RESEARCH\C++\HelloWorld\bin\x86>

We can separate compiling from linking in two steps - compiling source files into object files and linking object files into executable.

makefile:

#define macros
EXECUTABLE_NAME = helloworld.exe
DIR_SRC = ..\src
DIR_INCLUDE = ..\include
DIR_BIN = ..\bin
DIR_BIN_X86 = $(DIR_BIN)\x86
DIR_INTERMEDIATE = ..\intermediate
DIR_INTERMEDIATE_X86 = $(DIR_INTERMEDIATE)\x86

OBJ_FILES = \
  $(DIR_INTERMEDIATE_X86)\main.obj \
  $(DIR_INTERMEDIATE_X86)\printx.obj

$(DIR_INTERMEDIATE_X86)\main.obj: $(DIR_SRC)\main.cpp
  cl /c /EHsc /Fe$(DIR_INTERMEDIATE_X86)\main.obj /I$(DIR_INCLUDE) $(DIR_SRC)\main.cpp
  copy main.obj $(DIR_INTERMEDIATE_X86)
  del main.obj

$(DIR_INTERMEDIATE_X86)\printx.obj: $(DIR_SRC)\printx.cpp
  cl /c /EHsc /Fe$(DIR_INTERMEDIATE_X86)\printx.obj /I$(DIR_INCLUDE) $(DIR_SRC)\printx.cpp
  copy printx.obj $(DIR_INTERMEDIATE_X86)
  del printx.obj

$(EXECUTABLE_NAME) : $(OBJ_FILES)
   link /out:$(DIR_BIN_X86)\$(EXECUTABLE_NAME) $(OBJ_FILES)

# build application
helloworld: $(EXECUTABLE_NAME)

# create output directories
create_dirs:
@if not exist $(DIR_BIN_X86) mkdir $(DIR_BIN_X86)
@if not exist $(DIR_INTERMEDIATE_X86) mkdir $(DIR_INTERMEDIATE_X86)

# delete output directories
clean:
@if exist $(DIR_BIN) rmdir /S /Q $(DIR_BIN)
@if exist $(DIR_INTERMEDIATE) rmdir /S /Q $(DIR_INTERMEDIATE)

# create directories and build application
all: clean create_dirs helloworld


We can avoid repeating description blocks for each OBJ file by using inference rules. They are generic description blocks that define rules of how files with one extension are converted into files with the same name but different extensions. These rules are applied when resolving dependants. In the following makefile, EXE depends on OBJ files and inference rule defines how are OBJ files generated from CPP files:

makefile:

#define macros
EXECUTABLE_NAME = helloworld.exe
DIR_SRC = ..\src
DIR_INCLUDE = ..\include
DIR_BIN = ..\bin
DIR_BIN_X86 = $(DIR_BIN)\x86
DIR_INTERMEDIATE = ..\intermediate
DIR_INTERMEDIATE_X86 = $(DIR_INTERMEDIATE)\x86

SRC_FILES= \
  $(DIR_SRC)\main.cpp \
  $(DIR_SRC)\printx.cpp

{$(DIR_SRC)}.cpp{$(DIR_INTERMEDIATE_X86)}.obj ::
        @echo Compiling...
cl /c /EHsc /Fo$(DIR_INTERMEDIATE_X86)\ /I$(DIR_INCLUDE) $<

$(EXECUTABLE_NAME) : $(DIR_INTERMEDIATE_X86)\*.obj
   @echo Linking $(EXECUTABLE_NAME)...
   link /out:$(DIR_BIN_X86)\$(EXECUTABLE_NAME) $(DIR_INTERMEDIATE_X86)\*.obj

# build application
helloworld: $(EXECUTABLE_NAME)

# create output directories
create_dirs:
@if not exist $(DIR_BIN_X86) mkdir $(DIR_BIN_X86)
@if not exist $(DIR_INTERMEDIATE_X86) mkdir $(DIR_INTERMEDIATE_X86)

# delete output directories
clean:
@if exist $(DIR_BIN) rmdir /S /Q $(DIR_BIN)
@if exist $(DIR_INTERMEDIATE) rmdir /S /Q $(DIR_INTERMEDIATE)

# create directories and build application
all: clean create_dirs helloworld

Here is the order of commands in the command prompt window:


C:\DEVELOPMENT\RESEARCH\C++\HelloWorld\build>nmake all

Microsoft (R) Program Maintenance Utility Version 10.00.30319.01
Copyright (C) Microsoft Corporation.  All rights reserved.

Compiling...
        cl /c /EHsc /Fo..\intermediate\x86\ /I..\include ..\src\*.cpp
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.30319.01 for 80x86
Copyright (C) Microsoft Corporation.  All rights reserved.

main.cpp
printx.cpp
Generating Code...
Linking helloworld.exe...
        link /out:..\bin\x86\helloworld.exe ..\intermediate\x86\*.obj
Microsoft (R) Incremental Linker Version 10.00.30319.01
Copyright (C) Microsoft Corporation.  All rights reserved.


C:\DEVELOPMENT\RESEARCH\C++\HelloWorld\build>cd "..\bin\x86"

C:\DEVELOPMENT\RESEARCH\C++\HelloWorld\bin\x86>helloworld.exe
**********Hello, world!**********

C:\DEVELOPMENT\RESEARCH\C++\HelloWorld\bin\x86>



Here is yet another example of a simple makefile which compiles single main.cpp into main.obj and then links obj file to myapp.exe. Note that full names (with extensions) of source, object and binary files are used as target and dependent names:

#define macros
EXECUTABLE_NAME = myapp.exe

main.obj : main.cpp
 @echo.
 @echo Compiling...
 cl /c /EHsc main.cpp
 @echo Compiling done!

$(EXECUTABLE_NAME) : main.obj
 @echo.
 @echo Linking...
 link /out:$(EXECUTABLE_NAME) main.obj
 @echo Linking done!

cleanup:
 @echo.
 @echo Cleanup...
 del main.obj
 del $(EXECUTABLE_NAME)
 @echo Cleanup done!

all: cleanup $(EXECUTABLE_NAME)

Links and references:
Make (software) (Wikipedia)
NMAKE Reference (MSDN)
C/C++ Building Reference (MSDN)
Example Demonstrates Using Paths in NMAKE (MSDN)
MS C/C++: The nmake Command
The Makefile
Using Nmake
Nmake Makefile Tutorial and Example
Makefile.msvc (makefile example)
Building a simple OpenGL application
NMAKE (course; pdf)
Managing Projects with NMAKE
The make utility
Post a Comment