1

I have a project for school and I want to write a Makefile, I have seen some examples of using Makefile with multiple source directories and multiple executables but still could not implement it properly to my Makefile.

PS: I'm using doctest for the unit testing (and I can't change it).

Here is the project structure (and I can't change it):

.
├── bin
├── build
├── extern
│   └── doctest.h
├── include
│   ├── file1.hpp
│   └── file2.hpp
├── src
│   ├── file1.cpp
│   └── file2.cpp
├── tests
│   ├── file1-test.cpp
│   └── file2-test.cpp
└──  Makefile

I have the following directories:

  • bin: for all the executables.

  • build: for all the objects (.o).

  • extern: for the doctest header (this is where I would have stored any other library)

  • include: for all the headers (.hpp).

  • src: for all the classes (.cpp).

  • tests: for all the unit tests (also .cpp)

You can see file1.cpp as a class, file1.hpp as the class header and file1-test.cpp as the unit tests for the class.

In the exemple above I have 2 tests files but at the very end of the project I'll have a lot more, and for each test file I'll have an executable.


My goals:

  • I want to run make and compile all the units tests (all the .cpp in the tests/ directory).

  • And I want all the executables to be stored in the bin/ directory and all the binary files in the build/ directory.


Here is my Makefile:

BIN_DIR = ./bin/
BUILD_DIR = ./build/
EXTERN_DIR = ./extern/
INCLUDE_DIR = ./include/
SOURCE_DIR = ./src/
TESTS_DIR = ./tests/

vpath %.cpp $(SOURCE_DIR) $(TESTS_DIR)

CXX = clang++
CXXFLAGS = -Wall -std=c++11 -g -O3 -I$(INCLUDE_DIR) -I$(EXTERN_DIR)

EXEC_FILES = file1-test file2-test
BIN = $(addprefix $(BIN_DIR), $(EXEC_FILES))

all: $(BIN) | $(BIN_DIR)

$(BUILD_DIR)%.o: %.cpp | $(BUILD_DIR)
    $(CXX) $(CXXFLAGS) -c -o $@ $^

$(BIN_DIR) $(BUILD_DIR):
    mkdir -p $@

$(BIN_DIR)file1-test: $(BUILD_DIR)file1.o $(BUILD_DIR)file1-test.o
    $(CXX) -o $@ $^

$(BIN_DIR)file2-test: $(BUILD_DIR)file1.o $(BUILD_DIR)file2.o $(BUILD_DIR)file2-test.o
    $(CXX) -o $@ $^

clean:
    -rm -f $(BIN_DIR)* $(BUILD_DIR)*

It's working well but I feel like it's doing redondant stuff that i could avoid with more knownledge in the Makefile art, especially here:

$(BIN_DIR)file1-test: $(BUILD_DIR)file1.o $(BUILD_DIR)file1-test.o
    $(CXX) -o $@ $^

$(BIN_DIR)file2-test: $(BUILD_DIR)file1.o $(BUILD_DIR)file2.o $(BUILD_DIR)file2-test.o
    $(CXX) -o $@ $^

For the moment this Makefile is correct because I only have 2 executables, but I'll end up with 15+ and I dont want to have 15 times this for each executable:

$(BIN_DIR)xxx-test: $(BUILD_DIR)xxx.o etc.
    $(CXX) -o $@ $^

What I exactly need ...:

Basically, I need to write a generic rule that will fetch all the appropriated dependencies for a given target.

After reading multiple posts I think it's all about auto-dependencies.

I'm pretty sure the final result would look like this, but sadly I can't make it works in my case:

$(BIN_DIR)%: ???
    @$(CXX) -o $@ $^

I already looked at this (and many other posts about the subject): http://make.mad-scientist.net/papers/advanced-auto-dependency-generation/, but I still can't figure it out.

So how can I write an expression that will do the job, can someone give me a working exemple or something similar ?

EDIT 1:

Based on this post: Makefile (Auto-Dependency Generation).

I added these lines to my Makefile:

SRC = $(wildcard $(SOURCE_DIR)*.cpp)
SRC += $(wildcard $(TESTS_DIR)*.cpp)

The idea is to fetch all the .cpp from the source directories (src and tests). Then I added -MDD option to my CXXFLAGS variable to create a .d file for each target (atleast it's what I thought it's doing):

CXXFLAGS = -Wall -std=c++11 -g -O3 -I$(INCLUDE_DIR) -I$(EXTERN_DIR) -MMD

And finally, I added this:

$(BIN_DIR)%: $(SRC)
    $(CXX) -o $@ $^

-include $(SRC:.cpp=.d)

What I expect it to do:

  • Create a .d file with all the dependencies for each target.

  • Fetch the dependencies in the .d file and transform them to .o to get all the objects needed for the given target.

But it seems that it's not doing what I'm expecting.

EDIT 3:

After some changes I end up with this Makefile:

BIN_DIR := bin/
BUILD_DIR := build/
EXTERN_DIR := extern/
INCLUDE_DIR := include/
SOURCE_DIR := src/
TESTS_DIR := tests/
DEP_DIR := .dep/

DEPENDS := $(patsubst %.o, $(BUILD_DIR)$(DEP_DIR)%.d, $(notdir $(wildcard $(BUILD_DIR)*.o)))

EXE := $(addprefix $(BIN_DIR), Coord-test Fourmi-test)

OBJS_1 := $(addprefix $(BUILD_DIR), Coord.o)
OBJS_2 := $(addprefix $(BUILD_DIR), Coord.o Fourmi.o)

CXX := clang++
CXXFLAGS := -Wall -std=c++11 -g -O3 -I$(INCLUDE_DIR) -I$(EXTERN_DIR)

vpath %.cpp $(SOURCE_DIR) $(TESTS_DIR)

all: $(EXE)

$(BUILD_DIR):
    mkdir -p $@ $@/$(DEP_DIR)

$(BIN_DIR):
    mkdir -p $@

$(BUILD_DIR)%.o: %.cpp | $(BUILD_DIR)
    $(CXX) $(CXXFLAGS) -MMD -MP -MF $(BUILD_DIR)$(DEP_DIR)$(notdir $(basename $@).d) -c $< -o $@

$(BIN_DIR)%: $(BUILD_DIR)%.o | $(BIN_DIR)
    $(CXX) -o $@ $^

$(BIN_DIR)Coord-test: $(OBJS_1)
$(BIN_DIR)Fourmi-test: $(OBJS_2)

.PRECIOUS: $(BUILD_DIR)%.o

-include $(DEPENDS)

clean:
    -rm -f $(BIN_DIR)* $(BUILD_DIR)* $(BUILD_DIR)$(DEP_DIR)*

It's working but I'll have to add OBS_X for each new executable.

I also wanted factorize this, but I don't know if it's possible ? If someone could tell me.

$(BIN_DIR)%: $(BUILD_DIR)%.o | $(BIN_DIR)
    $(CXX) -o $@ $^

$(BIN_DIR)Coord-test: $(OBJS_1)
$(BIN_DIR)Fourmi-test: $(OBJS_2)
Nelson
  • 41
  • 5

1 Answers1

1

Since you know that you will always have a foo-test.o to build a foo-test program, you can write your pattern rule like this:

$(BIN_DIR)%: $(BUILD_DIR)%.o
        $(CXX) -o $@ $^

However, there's no way make can infer what OTHER objects might be needed to build these executables. You'll just have to tell it. So for the above examples you can add this:

$(BIN_DIR)file1-test: $(BUILD_DIR)file1.o 
$(BIN_DIR)file2-test: $(BUILD_DIR)file1.o $(BUILD_DIR)file2.o

You don't need to put the recipe here, this is just adding more prerequisites to these targets. You also don't have to put in the $(BUILD_DIR)file1-test.o etc. because this is inferred from the pattern rule.

But, if you do have other object files you need to use you'll have to list them explicitly, there's no way around it.

MadScientist
  • 92,819
  • 9
  • 109
  • 136
  • Ok thank's, that's probably what I'm gonna do if I can't manage to make it works, but looking at this post: https://stackoverflow.com/questions/8025766/makefile-auto-dependency-generation, I feel like we can use the `-MMD` flag to achieve what I want. I edited my original post. – Nelson May 03 '21 at 07:53
  • No. That method allows you to automatically figure out the dependencies of object files (which header files are used to compile an object file). That's _totally_ different than figuring out which object files are used to create an executable. Each object file is built from a source file and each source file explicitly contains references to all the header files that it uses, so the compiler can track them all and print them out. There's no similar listing of needed object files anywhere, so you have to write them into your makefile yourself, by hand. – MadScientist May 03 '21 at 12:41
  • Ok I did some changes following your advices and I have some other questions now, I edited it. – Nelson May 03 '21 at 18:03
  • Each question in SO should ask one specific question and get the answer to that question. It's not appropriate to create a long chain of incremental problems within the same question. Please create another question if you have a different problem, and be sure to show your makefile (or enough of it to understand the situation), and show the make command you invoked and the error output you got or a clear description of what happened and why it wasn't what you wanted to happen. _It's not doing what I expected_ isn't a problem we can help with. – MadScientist May 03 '21 at 18:14
  • I did, sorry and thanks and for the advices, here is the new post: https://stackoverflow.com/questions/67375725/c-makefile-is-it-possible-to-factorize-it-even-more. – Nelson May 03 '21 at 20:57