Subdirectories in makefile

Started by
8 comments, last by Maeriden 11 years, 2 months ago

In an effort to try to learn makefiles I wrote this


C = $(CXX)
CFLAGS = -g -Wall -Wextra -I$(SRC_DIR) -std=c++0x

SRC_DIR = src
BIN_DIR = bin
OBJ_DIR = obj

vpath %.cpp $(SRC_DIR)
vpath %.h $(SRC_DIR)
vpath %.o $(OBJ_DIR)

GPATH = src obj

SRC = main.cpp resources.cpp ui.cpp
OBJ = $(SRC:.cpp=.o)

.PHONY: clean all

all: $(OBJ)
    $C -o $(BIN_DIR)/exe $^


main.o: main.cpp defaults.h
    $C -c $< -o $(OBJ_DIR)/$@ $(CFLAGS)

resources.o: resources.cpp resources.h
    $C -c $< -o $(OBJ_DIR)/$@ $(CFLAGS)

ui.o: ui.cpp ui.h defaults.h
    $C -c $< -o $(OBJ_DIR)/$@ $(CFLAGS)


clean:
    rm -f $(OBJ_DIR)/*.o


When I run make and the obj directory is empty it compiles all the .cpp files and places them in $(OBJ_DIR)/$@ -> obj/file.o

However when it is time to execute the all target, g++ doesn't find .o files, because $^ contains only the files without the directory prepended

According to http://www.gnu.org/software/make/manual/make.html#Search-Algorithm this is the intended behavior (though not what I want at all) and it says I can modify it using the GPATH variable, which I set but seems to be ignored by make

Notes: if I run make again it finds the files in the obj directory, and since they don't need to be recompiled the path is preserved, so I can't even modify all to be


$C -o $(BIN_DIR)/exe $(addprefix $(OBJ_DIR)/$^)

A first invocation of make would build and link the files correctly, but the second time it would try to link obj/obj/file.o and fail



What am I doing wrong?

Advertisement

Using make when not all files are in the same dir is notoriously complicated (maybe switch to a less time consuming tool?).

It could help if in your link command under all: you change $^ to $(OBJ_DIR)/$^ so those files can be found.

That's what I thought at the beginning (see the note). In that case if any .o is not found it gets built in $(OBJ_DIR), then make links files in $(OBJ_DIR) and makes me happy. But every time that a file does NOT need to be rebuilt, the path where make finds it is remembered (so main.o becomes obj/main.o) when used both as target and dependency. Proceeding to the link command it becomes g++ -o bin/exe obj/obj/main.o

What really makes my head hurt is why make ignores GPATH. I wouldn't have any problem if only that worked

If you set the SRC .cpp files to include the directory for each one, it might work however I don't really recommend that.

Why do you need you .o files in a separate folder? If you do "rm -f $(OBJ)" it will clean them all up anyway.

Also, I dont know if you care, but I personally try to keep away from GNU extensions so that it works with different make tools (i.e BSD make).
main.o: main.cpp defaults.h
    $C -c $< -o $(OBJ_DIR)/$@ $(CFLAGS)

resources.o: resources.cpp resources.h
    $C -c $< -o $(OBJ_DIR)/$@ $(CFLAGS)

ui.o: ui.cpp ui.h defaults.h
    $C -c $< -o $(OBJ_DIR)/$@ $(CFLAGS)
You also dont need this bit, you can just make a rule to generate .o files from .cpp (this is the main reason for make) An example of such a rule would be ".cpp.o:".

If you need an example, I am very happy to post my template.
http://tinyurl.com/shewonyay - Thanks so much for those who voted on my GF's Competition Cosplay Entry for Cosplayzine. She won! I owe you all beers :)

Mutiny - Open-source C++ Unity re-implementation.
Defile of Eden 2 - FreeBSD and OpenBSD binaries of our latest game.

Why do you need you .o files in a separate folder? If you do "rm -f $(OBJ)" it will clean them all up anyway.

Well, I don't actually plan on cleaning the .o files very often, and I prefer not to keep them in the project root directory

Also, I dont know if you care, but I personally try to keep away from GNU extensions so that it works with different make tools (i.e BSD make).

I don't specifically need to write a file that works with other tools, but I suppose it would be better to learn how to do it. Which extensions did I use?

You also dont need this bit, you can just make a rule to generate .o files from .cpp (this is the main reason for make) An example of such a rule would be ".cpp.o:".

I've been wondering about this kind of rules. I've seen "$(OBJ): %.o: %.cpp" (here) but I can't understand how that would handle recompilation in the case a header file included by the source is modified

Thanks for the offer, but I've already seen quite a bit of examples. What I need is someone who would explain them

but I can't understand how that would handle recompilation in the case a header file included by the source is modified

Yeah, this is a little bit tricky (which is where a lot of devs use a tool to generate the makefile for them). You ideally need to work out the dependencies of each cpp to work out which header they rely on. GCC does support this with the use of .d files. I have never really looked into this.

Personally... when working with a smallish project I just get a list of all my .h files and add them as a rule dependency. I.e if any header in the project changes, the whole thing gets re-compiled.

When I work on a large project... I break the project up into seperate modules (each with their own Makefile) and am back to working with a smallish project ;D
http://tinyurl.com/shewonyay - Thanks so much for those who voted on my GF's Competition Cosplay Entry for Cosplayzine. She won! I owe you all beers :)

Mutiny - Open-source C++ Unity re-implementation.
Defile of Eden 2 - FreeBSD and OpenBSD binaries of our latest game.

Using make when not all files are in the same dir is notoriously complicated (maybe switch to a less time consuming tool?).

It could help if in your link command under all: you change $^ to $(OBJ_DIR)/$^ so those files can be found.

Well, I had the same problem a while ago, and wanted to find a general-purpose solution, and sort of found one, at least for Linux. What I did was mirror the src directory structure onto obj, and use some arcane commands to make it work. I had to ask on stackoverflow... Anyway here it is, at least for one of my projects:


EXECUTABLE = epsilon
INCLUDE = -Iinclude/
CXX = g++

OBJECTS = $(subst cpp,o,$(subst src/,obj/,$(shell find src/ -name '*.cpp')))

HEADERS = $(shell find include/ -name '*.hpp')

CPPFLAGS = -O2 -std=c++11 -march=native
LDLIBS = -lOpenCL

$(EXECUTABLE): $(OBJECTS) 
	@mkdir -p bin/
	@echo Linking $(EXECUTABLE)...
	@$(CXX) $(OBJECTS) -o $(addprefix bin/, $(EXECUTABLE)) $(LDLIBS)

$(OBJECTS): obj/%.o : src/%.cpp $(HEADERS)
	@mkdir -p $(@D)
	@echo Building $@ from $<...
	@$(CXX) $(CPPFLAGS) $(INCLUDE) -c $< -o $@

clean:
	@echo Cleaning $(EXECUTABLE)...
	@rm -f $(addprefix bin/, $(EXECUTABLE))
	@rm -f obj/ --recursive

I don't remember if you need to create the root obj/ directory first, try it out, I guess. I think it is not very efficient, however, but that may just be my own incompetence at writing makefiles. Yes, using a shell find is probably a horrible idea and any experienced member will likely laugh at the code above, but it has worked for me so far and designing pretty makefiles is not one of my priorities.

“If I understand the standard right it is legal and safe to do this but the resulting value could be anything.”

Mmm it's not very different from mine. To get around the issue of directories the target objects are written directly with obj/ prepended.
I wanted to avoid this solution because I didn't understand how to make make (no pun) work out the headers dependencies with a rule like $(OBJECTS): obj/%.o : src/%.cpp and thus decided to write a rule for each .o to avoid recompiling the whole project. Doing so meant that I had to manually write the directory variable in each rule, which seemed kind of dumb.

From the informations I was able to gather (I couldn't find an example that explained problems with GPATH) it looks like the solutions are: writing the path manually for each rule, using the general rule and have all the sources recompiled every time an header changes, or learning to use .d files (which will wait since I got fed up with make for a while).
Or use a different make system.

Thank you all for your advice, I really apreciate it :)

Make does have variables, so you can have the directory be a variable, so you only have to change it in one place.

I know. It just didn't seem a smart solution so I looked for something else

This topic is closed to new replies.

Advertisement