4

I'm struggling to find any information on makefiles which is truly beginner level. Even the question here, as asked, is presented using a load of symbols which I don't understand, like $(CC) $(INC) $< $(CFLAGS) -o $(BIN)$@ $(LIBS). That throws me off.

My makefile looks like this:

cpptest: main.o
    g++ -o cpptest main.o

main.o: main.cpp
    g++ -c main.cpp

clean:
    rm cpptest main.o

This is the full limit of what I know how to do with makefiles. It works, but places the main.o and cpptest.exe files into the project root.

I want to place the main.o file and the cpptest.exe file into a /build/ directory. However if I put ./build/ before every main.o and every cpptest, the object file appears in the project root and the exe file doesn't appear at all.

I'm building this thing using Sublime Text 3 on Windows, for what it's worth.

matt_rule
  • 1,220
  • 1
  • 12
  • 23
  • https://www.google.com/search?q=makefile+tutorial – Ken White Jun 22 '17 at 18:15
  • I looked at the first four results but the one which mentioned directories did so after introducing everything else first, so the example was hard to follow - and also the author seemed to get the macro / variable names for directories confused. – matt_rule Jun 22 '17 at 18:37

1 Answers1

6

A slightly better solution would be as follows: Notice, I'm doing a couple of funky things, but I'll explain along the way

all: build/cpptest

.PHONY: all

build:
    @echo "building directory $@"
    mkdir -p $@

build/cpptest: build/main.o | build
    @echo building $@
    g++ -o $@ $^

build/main.o: main.cpp | build
    @echo building $@
    g++ -o $@ -c $^

clean:
    @echo cleaning...
    rm -f build/*

Ok, lets look at the first and second targets:

all: build/cpptest

.PHONY: all

I created a default target called all. This is a dummy target (it doesn't actually build a file called all), so I marked all as phony by making it a dependency on .PHONY. (this basically tells make not to ignore the all rule, if a new file called all already exists). all has no recipes, therefore it doesn't do anything except cause its dependencies to be created. This might seem a bit convoluted, but it is standard practice in makefiles, as it gives you the freedom to organize the rules below in any order you find useful.

Third target:

build:
    @echo "building directory $@"
    mkdir -p $@

For the first target, build, this is the name of the directory you are creating. build is the target, it has no dependencies, and has two recipe lines. The first recipe line starts with a @. This just means don't print the recipe before you run it. If you didn't have this your output would look like:

 echo "building directory build"
 building directory build

Which is ugly. The @ only does that if it's at the start of the line. You'll notice that later on the line you have $@. This is different. It is a variable that expands to the name of the target (build in this case). It's a good habit to get into using this variable when appropriate, as it will simplify more complicated makefiles later on. The second recipe of the build target creates the directory.

Fourth target:

build/cpptest: build/main.o | build
    @echo building $@
    g++ -o $@ $^

This says build/cpptest is dependent on build/main.o and build (i.e., don't start building these until the other two are done). One big caviate: notice the | symbol before build. This makes it an order-only dependency. This means that if build directory already exists, and its timestamp is newer than build/cpptest, then don't consider build/cpptest to be out-of-date (don't rebuild it). On the other hand, the other dependency, build/main.o is to the left of the | symbol. This tells make that if build/main.o doesn't exist, OR if it is NEWER than build/cpptest, then make should rebuild build/cpptest.

The reason that we need the | (order only dependency) for build is that the directory's date is updated each time you add a new file. Thus, build will always have a newer timestamp than build/cpptest. This sort of falls under advanced makefile stuff, but it is the proper way to do it, so I thought I'd show it here.

As @Felix pointed out, order-only is not available in all versions of make (GNU make does support it though).

For the recipe, I used the $@ variable and the $^ variable. $^ represents all the non-order-only dependencies (i.e. main.cpp in this case). These are expanded before the rule is run.

You can go a few steps further to make this truly proper (define an $(objs) variable for example, etc), which makes the makefile easier to maintain in the future, but this hopefully will give you a good start.

blackghost
  • 1,730
  • 11
  • 24
  • This is a good answer. Handling directories in a sensible way in Makefiles is indeed a complicated thing (you don't care for the timestamp of the directory, just have to ensure it's there), and *order only dependencies* are a good way to handle this. Still, AFAIR, they are not commonly supported in all flavours of `make`, so I suggest to add that it's the recommended thing for *GNU* make. –  Jun 22 '17 at 22:46
  • This isn't actually correct because main.o and cpptest are never created. I fixed it by making 'build' the third rule, not the first, and also when using a .cpp to create the .o you need a -c argument before the -o. Sorry but as it stands this is not a solution, can you accept my edits? – matt_rule Jun 25 '17 at 09:19
  • I have to say it was very informative otherwise and it was the introduction I needed. – matt_rule Jun 25 '17 at 09:23
  • Ahh, yes -- if you don't specify a target, make will run the first target it sees, and `build` would have been the first target. The other part was me not checking my answer. I'll update. – blackghost Jun 26 '17 at 13:30