GNU Make Utility

Makefiles and Visual Studio

Learn Makefiles by comparison with Visual Studio

Preface
Introduction
Under the hood of Visual Studio
GNU/Linux Equivalent
Visual Studio to Make Utility mapping
Example
Source Structure
Build
Run
Makefile Details
Targets
Dependencies
Contents
NMake
Conclusion
External Links

Preface

If you develop software only on Windows using Visual studio, it’s a luxury. Enjoy it while it lasts. Sooner than later, you will come across Makefiles, maybe exploring some software on Linux or the misfortune of having a build system that uses make with Cygwin on Windows.

Now you figure out that Makefiles are text files and open it in an editor hoping to get some insight into its workings. But, what do you see? Lots of cryptic hard to understand syntax and expressions. So, where do you start? Internet searches for make and Makefiles provide a lot of information but under the assumption that you come from a non-IDE Unix/Linux development environment. Pampered Visual Studio developers are never the target audience.

Here I will try to relate to the Visual Studio build system which will hopefully give an easier understanding of Makefiles. The goal is not to provide yet another tutorial on makefiles (because there are plenty available on the internet) but to instill the concept by comparison.

Introduction

Visual Studio provides features that are taken for granted until you have to read/create a classic Makefile. For example, Visual Studio auto-magically does the following.

  • Compiles all the sources in the project file
  • Create an output directory and puts all the intermediate object files in it
  • Manages dependencies between the source and object files
  • Manages dependencies between the object files and binaries
  • Links the object files and external dependent libraries to create binaries

All of the above have to be explicitly specified in a Makefile. The make utility in some ways is the equivalent of Visual Studio devenv.exe (without the fancy GUI).

Under the hood of Visual Studio

Visual Studio is essentially a GUI over the compilation and link process. It utilizes an underlying command line compiler cl.exe and linker link.exe. Additionally, it provides a source code editor, debugger and other development tools.

c:\tmp> cl.exe
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.30319.01 for 80x86
Copyright (C) Microsoft Corporation.  All rights reserved.

usage: cl [ option... ] filename... [ /link linkoption... ]
c:\tmp> link.exe
Microsoft (R) Incremental Linker Version 10.00.30319.01
Copyright (C) Microsoft Corporation.  All rights reserved.

 usage: LINK [options] [files] [@commandfile]

A simple win32 console application project in Visual Studio is shown below. You have a solution file which contains a project file.

Test solution

Invoking a build on the solution in Visual Studio calls something like the following under the hood. Yes, it looks ugly! But that is the the project properties translated to compiler/linker flags and options.

C:\Program Files (x86)\Microsoft Visual Studio 10.0\VC\bin\CL.exe /c /ZI /nologo /W3 /WX- /Od /Oy- /D WIN32 /D _DEBUG /D _CONSOLE /D _UNICODE /D UNICODE /Gm /EHsc /RTC1 /MDd /GS /fp:precise /Zc:wchar_t /Zc:forScope /Fo"Debug\\" /Fd"Debug\vc100.pdb" /Gd /TP /analyze- /errorReport:prompt Test.cpp

C:\Program Files (x86)\Microsoft Visual Studio 10.0\VC\bin\link.exe /ERRORREPORT:PROMPT /OUT:"c:\tmp\Test\Debug\Test.exe" /INCREMENTAL /NOLOGO kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /MANIFEST /ManifestFile:"Debug\Test.exe.intermediate.manifest" /MANIFESTUAC:"level='asInvoker' uiAccess='false'" /DEBUG /PDB:"c:\tmp\Test\Debug\Test.pdb" /SUBSYSTEM:CONSOLE /TLBID:1 /DYNAMICBASE /NXCOMPAT /IMPLIB:"c:\tmp\Test\Debug\Test.lib" /MACHINE:X86 Debug\Test.exe.embed.manifest.res Debug\Test.obj

GNU/Linux Equivalent

It is very similar in GNU/Linux. The equivalent of a compiler and linker is gcc, the GNU project C and C++ compiler. It does the preprocessing, compilation, assembly and linking.

$ gcc --help
Usage: gcc [options] file...

Shown below is a very simple Makefile which can be accessed from GitHub https://github.com/cognitivewaves/Simple-Makefile.

# Specify compiler
CC=gcc

# Specify linker
LINK=gcc

.PHONY : all
all : app

# Link the object files into a binary
app : main.o
	$(LINK) -o app main.o -lstdc++

# Compile the source files into object files
main.o : main.cpp
	$(CC) -c main.cpp -o main.o

# Clean target
.PHONY : clean
clean :
	rm main.o app 

Invoking the make command to build will output the following.

~/src/Simple-Makefile $ make
gcc -c main.cpp -o main.o
gcc -o app main.o -lstdc++

Visual Studio to Make Utility mapping

Below is a table relating Visual Studio aspects to Make utility. At a high level, the Project file is equivalent to a Makefile.

Visual Studio make Utility
Command devenv.exe make
Source structure Solution (.sln) has project files (typically in sub-directories) Starting at the root, each Makefile can indicate where other Makefiles (typically in sub-directories) exist
Library build dependency Solution (.sln) has projects and build order Makefile
Source files list Project (.vcproj) Makefile
Source to Object dependency Project (.vcproj) Makefile
Compile and Link options Project (.vcproj) Makefile
Compiler cl.exe gcc, g++, c++ (or any other compiler, even cl.exe)
Linker link.exe gcc, ld (or any other linker, even link.exe)

Example

Download the example sources from GitHub at https://github.com/cognitivewaves/Makefile-Example.
Note that very basic Makefile constructs are used because the focus is on the concept and not the capabilities of make itself.

Source Structure

Makefile-Example
   |-- app
   |   |-- Makefile
   |   |-- main.cxx
   |-- math
   |   |-- advanced
   |   |   |-- AdvancedFunctions.cxx
   |   |   |-- AdvancedFunctions.h
   |   |-- simple
   |   |   |-- SimpleFunctions.cxx
   |   |   |-- SimpleFunctions.h
   |   |-- Makefile
   |-- Makefile

Build

Visual Studio make Utility
Building in Visual Studio is via a menu item in the IDE or invoking devenv.exe on the .sln file at the command prompt. This will automatically create the necessary directories and build only the files modified after the last build. Initiating a build with makefiles is to invoke the make command at the shell prompt. Creating output directories has to be explicitly done either in the Makefile or externally.

In this example, to keep the makefiles simple, the directories are created at the shell prompt.

~/src/Makefile-Example $ mkdir -p app/bin app/obj math/bin math/obj

The make utility syntax is shown below. See make manual pages for details.

make [ -f makefile ] [ options ] ... [ targets ]

Execute the command make and specify the “root” Makefile. However, it is more common to change to directory where the “root” Makefile exists and call make. This will read the file named Makefile in the current directory and call the target all by default.
Notice how make enters sub-directories to build. This is because of nested Makefiles which is explained later in the Makefiles Details section.

~/src/Makefile-Example $ make
make -C math make[1]: Entering directory `/home/user/src/Makefile-Example/math' g++ -fPIC -I. -c -o obj/AdvancedFunctions.o advanced/AdvancedFunctions.cxx g++ -fPIC -I. -c -o obj/SimpleFunctions.o simple/SimpleFunctions.cxx g++ -fPIC -shared -o bin/libmath.so obj/AdvancedFunctions.o obj/SimpleFunctions.o make[1]: Leaving directory `/home/user/src/Makefile-Example/math' make -C app make[1]: Entering directory `/home/user/src/Makefile-Example/app' g++ -I../math -c -o obj/main.o main.cxx g++ obj/main.o -L../math/bin -o bin/app.exe -lmath make[1]: Leaving directory `/home/user/src/Makefile-Example/app'

Run

Once the code is built, run the executable. This is nothing specific to makefiles but has been elaborated in case you are not familiar with Linux as you will notice that by default is will fail to run with an error message.

~/src/Makefile-Example $ app/bin/app.exe 4
app/bin/app.exe: error while loading shared libraries: libmath.so: cannot open shared object file: No such file or directory

This is because the executable app.exe requires the shared object libmath.so which is in a different directory and is not in the system path. Set LD_LIBRARY_PATH to specify the path to it.

~/src/Makefile-Example $ export LD_LIBRARY_PATH=~/src/Makefile-Example/math/bin/

~/src/Makefile-Example $ app/bin/app.exe 4
The sum of 4 and 4 is 8 The square root of 4 is 2

Makefile Details

The basis of a Makefile has a very simple structure.

target: dependencies
[tab] system commands


The [tab] separator is very important! Spaces instead of a [tab] is not the same. You will see rather obscure error messages as shown below.
Makefile:12: *** missing separator. Stop.

Targets

Here target is a physical file on disk. When the target is more of a label, then it has to be tagged as .PHONY to indicate that the target is not an actual file.

Visual Studio make Utility
Visual Studio by default provides options to clean and rebuild a project or solution. Clean and rebuild have to be explicitly written in a makefile as targets which can then be invoked.

A typical case would be to clean before rebuilding.

~/src/Makefile-Example $ make clean
make -C math clean make[1]: Entering directory `/home/api/src/Makefile-Example/math' rm -fv bin/* obj/* removed `bin/libmath.so' removed `obj/AdvancedFunctions.o' removed `obj/SimpleFunctions.o' make[1]: Leaving directory `/home/api/src/Makefile-Example/math' make -C app clean make[1]: Entering directory `/home/api/src/Makefile-Example/app' rm -fv bin/* obj/* removed `bin/app.exe' removed `obj/main.o' make[1]: Leaving directory `/home/api/src/Makefile-Example/app'

Dependencies

Dependencies can be files on disk or other targets (including phony targets).

Visual Studio make Utility
Visual Studio by default supports implicit dependencies (source to object files) within a project. Library(project) dependencies have to specified in the solution file Every dependency has to be explicitly defined in makefiles

For example, the target all, depends on app.exe which in turn depends on libmath.so. If you remove app.exe, make is capable of recognizing that libmath.so need not be built again.

~/src/Makefile-Example $ rm -v app/bin/*
removed `app/bin/app.exe'
~/src/Makefile-Example $ make
make -C app make[1]: Entering directory `/home/api/src/Makefile-Example/app' g++ obj/main.o -L../math/bin -o bin/app.exe -lmath make[1]: Leaving directory `/home/api/src/Makefile-Example/app'

Contents

File: Makefile

# all is the default target
.PHONY : all 
all : app/bin/app.exe

# Invoke the Makefile in the sub-directory "app"
app/bin/app.exe : math/bin/libmath.so
	$(MAKE) -C app

# Invoke the Makefile in the sub-directory "math"
math/bin/libmath.so :
	$(MAKE) -C math

# Invoke the clean target in the "math" and "app" sub-directory Makefile
.PHONY : clean
clean :
	$(MAKE) -C math clean
	$(MAKE) -C app  clean

File: math/Makefile

# The shared object libmath.so depends on the object files
# The command is to link and create the .so if the dependencies are up to date
# Note the .so is put in a sub-directory 'bin' by specifying 'g++ -o' option
bin/libmath.so : obj/AdvancedFunctions.o obj/SimpleFunctions.o
	g++ -fPIC -shared -o bin/libmath.so obj/AdvancedFunctions.o obj/SimpleFunctions.o

# The object file depends on the source
# The command is to compile the .cxx to .o
obj/AdvancedFunctions.o : advanced/AdvancedFunctions.cxx
	g++ -fPIC -I. -c -o obj/AdvancedFunctions.o advanced/AdvancedFunctions.cxx

# The object file depends on the source
# The command is to compile the .cxx to .o
obj/SimpleFunctions.o : simple/SimpleFunctions.cxx
	g++ -fPIC -I. -c -o obj/SimpleFunctions.o simple/SimpleFunctions.cxx

# clean target for this sub-directory Makefile
.PHONY : clean
clean : 
	rm -fv bin/* obj/

File: app/Makefile

# The executable depends on the main.o object file
# Generate the exe if the object file dependencies are up to date
# The exe is put in a sub-directory 'bin' by specifying 'g++ -o' option
bin/app.exe : obj/main.o
	g++ obj/main.o -L../math/bin -o bin/app.exe -lmath

# The object file depends on the source
# The command is to compile the .cxx to .o
# The object file is put in sub-directory 'obj' by specifying 'g++ -o' option
obj/main.o : main.cxx
	g++ -I../math -c -o obj/main.o main.cxx

# clean target for this sub-directory Makefile
.PHONY : clean
clean : 
	rm -fv bin/* obj/

NMake

NMake is the native Windows alternative to the *nix make utility. The syntax is very similar to *nix makefiles. However, this does not mean that *nix makefiles can be executed seamlessly on Windows. See Makefiles in Windows for a discussion.

Conclusion

Makefiles are very powerful and gives a lot of control and flexibility compared to Visual Studio, but the content is not easily understandable. As an alternative, CMake has adopted similar concepts but the script is much easier and more readable. See CMake and Visual Studio.

External Links

Makefile in detail for beginners

4 thoughts on “Makefiles and Visual Studio

  1. Pingback: Makefiles for Visual Studio Developers | Cognitive Waves

  2. Steve Kauffman

    I have 22 years as a C++ on Visual Studio / msbuild, and am one of our in-house experts on .vcxproj / .props / .targets files. Only now I’m having to go back and re-learn makefiles (did some as an undergrad). I think I’m your exact target audience! Thanks for this.

    Reply

Leave a comment