c++ - with - simple makefile
How to make a SIMPLE C++ Makefile? (5)
Copied from a wiki post I wrote for physics grad students.
Since this is for unix the executables have no extensions.
One thing to note is that
root-config is a utility which provides the right compilation and linking flags; and the right libraries for building applications against root. That's just a detail related to the original audience for this document.
Make Me Baby
or You Never Forget The First Time You Got Made
A introductory discussion of make, and how to write a simple makefile
What is Make? And Why Should I Care?
The tool called make is a build dependency manager. That is, it takes care of knowing what commands need to be executed in what order to take your software project from a collection of source files, object files, libraries, headers, etc. etc.---some of which may have changed recently---and turning them into a correct up-to-date version of the program.
Actually you can use make for other things too, but I'm not going to talk about that.
A Trivial Makefile
Suppose that you have a directory containing:
support.o which depend on
root and are supposed to be compiled into a program called
tool, and suppose that you've been hacking on the source files (which means the existing
tool is now out of date) and want to compile the program.
To do this yourself you could
1) check if either
support.hh is newer than
support.o, and if so run a command like
g++ -g -c -pthread -I/sw/include/root support.cc
2) check if either
tool.cc are newer than
tool.o, and if so run a command like
g++ -g -c -pthread -I/sw/include/root tool.cc
3) check if
tool.o is newer than
tool, and if so run a command like
g++ -g tool.o support.o -L/sw/lib/root -lCore -lCint -lRIO -lNet -lHist -lGraf -lGraf3d -lGpad -lTree -lRint \ -lPostscript -lMatrix -lPhysics -lMathCore -lThread -lz -L/sw/lib -lfreetype -lz -Wl,-framework,CoreServices \ -Wl,-framework,ApplicationServices -pthread -Wl,-rpath,/sw/lib/root -lm -ldl
Phew! What a hassle! There is a lot to remember and several chances to make mistakes. (BTW-- The particulars of the command lines exhibited here depend on our software environment. These ones work on my computer.)
Of course, you could just run all three commands every time. That would work, but doesn't scale well to a substantial piece of software (like DOGS which takes more than 15 minutes to compile from the ground up on my MacBook).
Instead you could write a file called
makefile like this:
tool: tool.o support.o g++ -g -o tool tool.o support.o -L/sw/lib/root -lCore -lCint -lRIO -lNet -lHist -lGraf -lGraf3d -lGpad -lTree -lRint \ -lPostscript -lMatrix -lPhysics -lMathCore -lThread -lz -L/sw/lib -lfreetype -lz -Wl,-framework,CoreServices \ -Wl,-framework,ApplicationServices -pthread -Wl,-rpath,/sw/lib/root -lm -ldl tool.o: tool.cc support.hh g++ -g -c -pthread -I/sw/include/root tool.cc support.o: support.hh support.cc g++ -g -c -pthread -I/sw/include/root support.cc
and just type
make at the command line. which will perform the three steps shown above automatically.
The un-indented lines here have the form "target: dependencies" and tell make that the associated commands (indented lines) should be run if any of the dependencies are newer than the target. That is the dependency lines describe the logic of what needs to be rebuilt to accommodate changes in various files. If
support.cc changes that means that
support.o must be rebuilt, but
tool.o can be left alone. When
tool must be rebuilt.
The commands associated with each dependency line are set off with a tab (see below) should modify the target (or at least touch it to update the modification time).
Variables, Built In Rules, and Other Goodies
At this point, our makefile is simply remembering the work that needs doing, but we still had to figure out and type each and every needed command in its entirety. It does not have to be that way: make is a powerful language with variables, text manipulation functions, and a whole slew of built-in rules which can make this much easier for us.
The syntax for accessing a make variable is
The syntax for assigning to a make variable is:
VAR = A text value of some kind
VAR := A different text value but ignore this for the moment).
You can use variables in rules like this improved version of our makefile:
CPPFLAGS=-g -pthread -I/sw/include/root LDFLAGS=-g LDLIBS=-L/sw/lib/root -lCore -lCint -lRIO -lNet -lHist -lGraf -lGraf3d -lGpad -lTree -lRint \ -lPostscript -lMatrix -lPhysics -lMathCore -lThread -lz -L/sw/lib -lfreetype -lz \ -Wl,-framework,CoreServices -Wl,-framework,ApplicationServices -pthread -Wl,-rpath,/sw/lib/root \ -lm -ldl tool: tool.o support.o g++ $(LDFLAGS) -o tool tool.o support.o $(LDLIBS) tool.o: tool.cc support.hh g++ $(CPPFLAGS) -c tool.cc support.o: support.hh support.cc g++ $(CPPFLAGS) -c support.cc
which is a little more readable, but still requires a lot of typing
GNU make supports a variety of functions for accessing information from the filesystem or other commands on the system. In this case we are interested in
$(shell ...) which expands to the output of the argument(s), and
$(subst opat,npat,text) which replaces all instances of
npat in text.
Taking advantage of this gives us:
CPPFLAGS=-g $(shell root-config --cflags) LDFLAGS=-g $(shell root-config --ldflags) LDLIBS=$(shell root-config --libs) SRCS=tool.cc support.cc OBJS=$(subst .cc,.o,$(SRCS)) tool: $(OBJS) g++ $(LDFLAGS) -o tool tool.o support.o $(LDLIBS) tool.o: tool.cc support.hh g++ $(CPPFLAGS) -c tool.cc support.o: support.hh support.cc g++ $(CPPFLAGS) -c support.cc
which is easier to type and much more readable.
- We are still stating explicitly the dependencies for each object file and the final executable
- We've had to explicitly type the compilation rule for both source files
Implicit and Pattern Rules
We would generally expect that all c++ source files should be treated the same way, and make provides three ways to state this
- suffix rules (considered obsolete in GNU make, but kept for backwards compatibility)
- implicit rules
- pattern rules
Implicit rules are built in, and a few will be discussed below. Pattern rules are specified in a form like
%.o: %.c $(CC) $(CFLAGS) $(CPPFLAGS) -c $<
which means that object files are generated from c source files by running the command shown, where the "automatic" variable
$< expands to the name of the first dependency.
Make has a whole host of built in rules that mean that very often, a project can be compile by a very simple makefile, indeed.
The GNU make built in rule for c source files is the one exhibited above. Similarly we create object files from c++ source files with a rule like
$(CXX) -c $(CPPFLAGS) $(CFLAGS)
Single object files are linked using
$(LD) $(LDFLAGS) n.o $(LOADLIBES) $(LDLIBS), but this won't work in our case, because we want to link multiple object files.
Variables Used By Built-in Rules
The built in rules use a set of standard variables that allow you to specify local environment information (like where to find the ROOT include files) without re-writing all the rules. The ones most likely to be interesting to us are:
CC-- the c compiler to use
CXX-- the c++ compiler to use
LD-- the linker to use
CFLAGS-- compilation flag for c source files
CXXFLAGS-- compilation flags for c++ source files
CPPFLAGS-- flags for the c-preprocessor (typically include file paths and symbols defined on the command line), used by c and c++
LDFLAGS-- linker flags
LDLIBS-- libraries to link
A Basic Makefile
By taking advantage of the built in rules we can simplify our makefile to:
CC=gcc CXX=g++ RM=rm -f CPPFLAGS=-g $(shell root-config --cflags) LDFLAGS=-g $(shell root-config --ldflags) LDLIBS=$(shell root-config --libs) SRCS=tool.cc support.cc OBJS=$(subst .cc,.o,$(SRCS)) all: tool tool: $(OBJS) $(CXX) $(LDFLAGS) -o tool $(OBJS) $(LDLIBS) tool.o: tool.cc support.hh support.o: support.hh support.cc clean: $(RM) $(OBJS) distclean: clean $(RM) tool
We have also added several standard targets that perform special actions (like cleaning up the source directory).
Note that when make is invoked without an argument, it uses the first target found in the file (in this case all), but you can also name the target to get which is what makes
make clean remove the object files in this case.
We still have all the dependencies hard-coded.
Some Mysterious Improvements
CC=gcc CXX=g++ RM=rm -f CPPFLAGS=-g $(shell root-config --cflags) LDFLAGS=-g $(shell root-config --ldflags) LDLIBS=$(shell root-config --libs) SRCS=tool.cc support.cc OBJS=$(subst .cc,.o,$(SRCS)) all: tool tool: $(OBJS) $(CXX) $(LDFLAGS) -o tool $(OBJS) $(LDLIBS) depend: .depend .depend: $(SRCS) $(RM) ./.depend $(CXX) $(CPPFLAGS) -MM $^>>./.depend; clean: $(RM) $(OBJS) distclean: clean $(RM) *~ .depend include .depend
- There are no longer any dependency lines for the source files!?!
- There is some strange magic related to .depend and depend
- If you do
ls -Ayou see a file named
.dependwhich contains things that look like make dependency lines
- GNU make manual
- Recursive Make Considered Harmful on a common way of writing makefiles that is less than optimal, and how to avoid it.
Know Bugs and Historical Notes
The input language for make is whitespace sensitive. In particular the action lines following dependencies must start with a tab. But a series of spaces can look the same (and indeed there are editors that will silently convert tabs to spaces or vice versa), which results in a make file that looks right and still doesn't work. This was identified as a bug early on but (the story goes) was not fixed because there were already 10 users.
We are required to use a Makefile to pull everything together for our project but our professor never showed us how to.
I only have ONE file,
The driver imports a class from a location
That's it, everything else is contained with the
How would I go about making a simple Makefile that creates an executable called
tool: tool.o file1.o file2.o $(CXX) $(LDFLAGS) $^ $(LDLIBS) -o [email protected]
LINK.o = $(CXX) $(LDFLAGS) $(TARGET_ARCH) tool: tool.o file1.o file2.o
The latter suggestion is slightly better since it reuse GNU make implicit rules. However, in order to work, a source file must have same name than final executable (ie:
Notice, it is not necessary to declare sources. Intermediate object files are generated using implicit rule. Consequently, this
Makefile work for C and C++ (and also for Fortran, etc...).
Also notice, by default, Makefile use
$(CC) as linker.
$(CC) does not work to link C++ objects. We modify
LINK.o only because of that. If you want to compile C code, you don't have to force
Sure, you can also add your compilation flags with variable
CFLAGS and add your libraries in
LDLIBS. For example:
CFLAGS = -Wall LDLIBS = -lm
One side note: if you have to use external libraries, I suggest to use pkg-config in order to correctly set
CFLAGS += $(shell pkg-config --cflags libssl) LDLIBS += $(shell pkg-config --libs libssl)
Attentive reader notice this
Makefile does not rebuild properly if one header is changed. Add these lines to fix problem:
override CPPFLAGS += -MMD include $(wildcard *.d)
-MMD allows to build .d files that contains Makefile fragments about headers dependencies. The second line just use them.
For sure, a well written Makefile should also include
clean: $(RM) *.o *.d distclean: clean $(RM) tool
$(RM) is equivalent of
rm -f but it is a good practice to not call
all rule is also appreciated. In order to work, it should be the first rule of your file:
You may also add
PREFIX = /usr/local install: install -m 755 tool $(DESTDIR)$(PREFIX)/bin
DESTDIR is empty by default. User can set it to install your program to an alternative system (mandatory for cross-compilation process). Package maintainers for multiple distribution may also change
PREFIX in order to install your package in
One final word, do not place source files in sub-directories. If you really want to do that, keep this
Makefile in root directory and use full paths to identify your files (ie.
So to summary, your full Makefile should looks like:
LINK.o = $(CXX) $(LDFLAGS) $(TARGET_ARCH) PREFIX = /usr/local override CPPFLAGS += -MMD include $(wildcard *.d) all: tool tool: tool.o file1.o file2.o clean: $(RM) *.o *.d distclean: clean $(RM) tool install: install -m 755 tool $(DESTDIR)$(PREFIX)/bin
I've always thought this was easier to learn with a detailed example, so here's how I think of makefiles. For each section you have one line that's not indented and it shows the name of the section followed by dependencies. The dependencies can be either other sections (which will be run before the current section) or files (which if updated will cause the current section to be run again next time you run
Here's a quick example (keep in mind that I'm using 4 spaces where I should be using a tab, won't let me use tabs):
a3driver: a3driver.o g++ -o a3driver a3driver.o a3driver.o: a3driver.cpp g++ -c a3driver.cpp
When you type
make, it will choose the first section (a3driver). a3driver depends on a3driver.o, so it will go to that section. a3driver.o depends on a3driver.cpp, so it will only run if a3driver.cpp has changed since it was last run. Assuming it has (or has never been run), it will compile a3driver.cpp to a .o file, then go back to a3driver and compile the final executable.
Since there's only one file, it could even be reduced to:
a3driver: a3driver.cpp g++ -o a3driver a3driver.cpp
The reason I showed the first example is that it shows the power of makefiles. If you need to compile another file, you can just add another section. Here's an example with a secondFile.cpp (which loads in a header named secondFile.h):
a3driver: a3driver.o secondFile.o g++ -o a3driver a3driver.o secondFile.o a3driver.o: a3driver.cpp g++ -c a3driver.cpp secondFile.o: secondFile.cpp secondFile.h g++ -c secondFile.cpp
This way if you change something in secondFile.cpp or secondFile.h and recompile, it will only recompile secondFile.cpp (not a3driver.cpp). Or alternately, if you change something in a3driver.cpp, it won't recompile secondFile.cpp.
Let me know if you have any questions about it.
It's also traditional to include a section named "all" and a section named "clean". "all" will usually build all of the executables, and "clean" will remove "build artifacts" like .o files and the executables:
all: a3driver ; clean: # -f so this will succeed even if the files don't exist rm -f a3driver a3driver.o
EDIT: I didn't notice you're on Windows. I think the only difference is changing the
-o a3driver to
Old question, I know, but for posterity. You had two options.
Option 1: simplest makefile = NO MAKEFILE.
Rename "a3driver.cpp" to "a3a.cpp", then on the command line write:
And that's it. If you're using gnu-make use "make" or "gmake" or whatever.
Option 2: a 2-line makefile.
a3a.exe: a3driver.obj link /out:a3a.exe a3driver.obj
Your make file will have one or two dependency rules depending on whether you compile and link with a single command, or with one command for the compile and one for the link.
Dependency are a tree of rules that look like this:
main_target : source1 source2 etc command to build main_target from sources source1 : dependents for source1 command to build source1
There must be a blank line after the commands for a target, and there must not be a blank line before the commands. The first target in the makefile is the overall goal, other targets are built only if the first target depends on them.
So your makefile will look something like this.
a3a.exe : a3driver.obj link /out:a3a.exe a3driver.obj a3driver.obj : a3driver.cpp cc a3driver.cpp