tags:

views:

618

answers:

4

Hi everyone,

For a project, we are required to use a makefile to pull everything together, but our abhorrent professor never showed us how to.

I only have ONE file, a3driver.cpp. The driver imports a class from a location "/user/cse232/Examples/example32.sequence.cpp".

That's it, everything else is contained with the .cpp.

How would I go about making a simple Makefile that creates an executable called "a3a.exe"?

+1  A: 

A quick google search ("simple makefile") results in the following URL: http://www.network-theory.co.uk/docs/gccintro/gccintro_16.html

It seems to be pretty good and answers your question pretty well.

EightyEight
+2  A: 

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 updates will cause the current section to be run again).

Here's a quick example (keep in mind that I'm using 4 spaces where I should be using a tab, SO won't let me use tabs):

all: 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 (all). all 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 all and compile that into the file 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):

all: 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.

EDIT: I didn't notice you're on Windows. I think the only difference is changing the -o a3driver to -o a3driver.exe.

Brendan Long
The absolute code I'm trying to use is:p4a.exe: p4driver.cpp g++ -o p4a p4driver.cppBUT, it tells me "missing separator". I'm using TAB, but it still tells me that. Any idea?
Befall
As far as I can tell, that error message only comes up if you have spaces. Make sure that you don't have any lines starting with spaces (space + tab will give that error). That's the only thing I can think of..
Brendan Long
+1  A: 

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
John Knoeller
+10  A: 

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 you 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: tool tool.cc tool.o support.cc support.hh, and 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 and want to compile the program.

To do this yourself you could

1) check if either support.cc or support.hh is newer than support.o, and if so run a command like

g++ -g -c -D_REENTRANT -pthread -I/sw/include/root support.cc

2) check if either support.hh or tool.cc are newer than tool.o, and if so run a command like

g++ -g  -c -D_REENTRANT -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 something 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 -D_REENTRANT -pthread -I/sw/include/root tool.cc

support.o: support.hh support.cc
    g++ -g -c -D_REENTRANT -pthread -I/sw/include/root support.cc

and just type make. 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 the same. When support.o changes 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.

Make Variables

The syntax for accessing a make variable is $(VAR).

The syntax for assigning to a make variable is:

VAR = A text value of some kind

(or 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 -D_REENTRANT -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

Make Functions

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 opat with 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.

Notice that

  1. We are still stating explicitly the dependencies for each object file and the final executable
  2. 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 was to state this

  1. suffix rules (considered obsolete in GNU make, but kept for backwards compatibility)
  2. implicit rules
  3. 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.

Built-in Rules

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 $(CC) $(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
  • CPPFLGS -- 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)
    g++ $(LDFLAGS) -o tool $(OBJS) $(LDLIBS) 

tool.o: tool.cc support.hh

support.o: support.hh support.cc

clean:
    $(RM) $(OBJS)

dist-clean: 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)
    g++ $(LDFLAGS) -o tool $(OBJS) $(LDLIBS) 

.depend: depend

depend: $(SRCS)
    rm -f ./.depend
    $(CXX) $(CPPFLAGS) -MM $^>>./.depend;

clean:
    $(RM) $(OBJS)

dist-clean: clean
    $(RM) *~ .dependtool

include .depend

Notice that

  1. There are no longer any dependency lines for the source files!?!
  2. There is some strange magic related to .depend and depend
  3. If you do make then ls -A you see a file named .depend which contains things that look like make dependency lines

Other Reading

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.

dmckee
Doesn't really qualify as "simple"..
Brendan Long
@Brendan: Surely the first one is simple enough for you? It's just just dependency lines with one action each: no variables, no implicit commands or patterns, not cleverness.
dmckee
I think you've just been doing this long enough that you don't know what "simple" means to a beginner ;) Simple != wall of text
Brendan Long
@dmckee: -1 This is just too long. You should link to your wiki and add brief notes.
quamrana
It *is* long, but is also about the minimum that sets the reader up to deal with non-trivial makefiles. Why? Because there is a *lot* going on in the average makefile. Even at this I've left out the important distinction between `=` and `:=`. The original is not available for public consumption, nor would I post a bare link in any case.
dmckee