[c++] How to make a SIMPLE C++ Makefile

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, 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?

This question is related to c++ makefile

The answer is


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 (note that the indent must be a TAB):

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, and 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

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 make).

Here's a quick example (keep in mind that I'm using 4 spaces where I should be using a tab, Stack Overflow 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 -o a3driver.exe.


Why does everyone like to list out source files? A simple find command can take care of that easily.

Here's an example of a dirt simple C++ Makefile. Just drop it in a directory containing .C files and then type make...

appname := myapp

CXX := clang++
CXXFLAGS := -std=c++11

srcfiles := $(shell find . -name "*.C")
objects  := $(patsubst %.C, %.o, $(srcfiles))

all: $(appname)

$(appname): $(objects)
    $(CXX) $(CXXFLAGS) $(LDFLAGS) -o $(appname) $(objects) $(LDLIBS)

depend: .depend

.depend: $(srcfiles)
    rm -f ./.depend
    $(CXX) $(CXXFLAGS) -MM $^>>./.depend;

clean:
    rm -f $(objects)

dist-clean: clean
    rm -f *~ .depend

include .depend

I suggest (note that the indent is a TAB):

tool: tool.o file1.o file2.o
    $(CXX) $(LDFLAGS) $^ $(LDLIBS) -o $@

or

LINK.o = $(CXX) $(LDFLAGS) $(TARGET_ARCH)
tool: tool.o file1.o file2.o

The latter suggestion is slightly better since it reuses GNU Make implicit rules. However, in order to work, a source file must have the same name as the final executable (i.e.: tool.c and tool).

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 the linker. $(CC) does not work for linking C++ object files. We modify LINK.o only because of that. If you want to compile C code, you don't have to force the LINK.o value.

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 and LDLIBS:

CFLAGS += $(shell pkg-config --cflags libssl)
LDLIBS += $(shell pkg-config --libs libssl)

The attentive reader will notice that this Makefile does not rebuild properly if one header is changed. Add these lines to fix the problem:

override CPPFLAGS += -MMD
include $(wildcard *.d)

-MMD allows to build .d files that contains Makefile fragments about headers dependencies. The second line just uses them.

For sure, a well written Makefile should also include clean and distclean rules:

clean:
    $(RM) *.o *.d

distclean: clean
    $(RM) tool

Notice, $(RM) is the equivalent of rm -f, but it is a good practice to not call rm directly.

The all rule is also appreciated. In order to work, it should be the first rule of your file:

all: tool

You may also add an install rule:

PREFIX = /usr/local
install:
    install -m 755 tool $(DESTDIR)$(PREFIX)/bin

DESTDIR is empty by default. The user can set it to install your program at an alternative system (mandatory for cross-compilation process). Package maintainers for multiple distribution may also change PREFIX in order to install your package in /usr.

One final word: Do not place source files in sub-directories. If you really want to do that, keep this Makefile in the root directory and use full paths to identify your files (i.e. subdir/file.o).

So to summarise, your full Makefile should look 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

You had two options.

Option 1: simplest makefile = NO MAKEFILE.

Rename "a3driver.cpp" to "a3a.cpp", and then on the command line write:

nmake a3a.exe

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

I used friedmud's answer. I looked into this for a while, and it seems to be a good way to get started. This solution also has a well defined method of adding compiler flags. I answered again, because I made changes to make it work in my environment, Ubuntu and g++. More working examples are the best teacher, sometimes.

appname := myapp

CXX := g++
CXXFLAGS := -Wall -g

srcfiles := $(shell find . -maxdepth 1 -name "*.cpp")
objects  := $(patsubst %.cpp, %.o, $(srcfiles))

all: $(appname)

$(appname): $(objects)
    $(CXX) $(CXXFLAGS) $(LDFLAGS) -o $(appname) $(objects) $(LDLIBS)

depend: .depend

.depend: $(srcfiles)
    rm -f ./.depend
    $(CXX) $(CXXFLAGS) -MM $^>>./.depend;

clean:
    rm -f $(objects)

dist-clean: clean
    rm -f *~ .depend

include .depend

Makefiles seem to be very complex. I was using one, but it was generating an error related to not linking in g++ libraries. This configuration solved that problem.