5

I have a simple project that looks something like this

.
├── build
│   ├── file1.o
│   └── one
│       ├── file1.o
│       └── file2.o
├── .depend
├── Makefile
└── src
    ├── file1.cpp
    └── one
        ├── file1.cpp
        └── file2.cpp

The Makefile is something like this:

# Get all of the source files
SRC = $(shell find src/ -name "*.cpp")
# Get all of the object files
OBJ = $(subst src,build,$(SRC:.cpp=.o))

$(OBJ):
    @mkdir -p $(shell dirname $@)
    g++ -g -c $(subst build,src,$(subst .o,.cpp,$@)) -o $@

all: depend build

build: $(OBJ)
    gcc -o project $^

depend:
    g++ -MM $(SRC) > .depend
    sed -i 's/.\/src/.\/build\//g' .depend

sinclude .depend

I am attempting to generate makefile dependencies by running g++ -MM src/file1.cpp src/one/file1.cpp src/one/file2.cpp > .depend, and it generates the following directives:

file1.o: src/file1.cpp <other headers>
file1.o: src/one/file1.cpp <other headers>
file2.o: src/one/file2.cpp <other headers>

The problem with this, is that build/file1.o does not match file1.o, and as a result, changing src/file1.cpp or any of the headers it depends on does not cause the object file to be rebuilt. At first I thought it might have been an issue where sinclude .depend was run before the .depend file was generated, but the problem persists even if I run make depend followed by make build. From everything I've read, there are no g++ arguments or options that would preserve the path of the name.

Is it possible to generate a dependency file this way, or is this a fundamentally incorrect approach to building a project?

I took a look at the answers for the question this question was marked as a possible duplicate of, but it seems that question is asking how to create a complete makefile for a project, whereas my issue is not with the creation of a Makefile, but rather an issue with gcc -MM dependency generation. The answers to that question do not address my problems.

  • 1
    I have a couple of blog articles on this at https://latedev.wordpress.com/2014/11/08/generic-makefiles-with-gcc-and-gnu-make which might help –  Dec 12 '17 at 23:19
  • What would you like the target to look like? Couldn't you do another `sed` substitution to fix it up the way you want? – jxh Dec 12 '17 at 23:22
  • 1
    Thanks for the link, but I couldn't find the answer to my issue there. I still can't figure out how to get `g++ -MM` to include full paths, or `%.o: %.cpp` to match `build/file1.o`. – Nicholas Hollander Dec 12 '17 at 23:35
  • I couldn't use a `sed` substitution in this case as far as I know, because this scenario involves information that is missing entirely. `sed` would allow me to remove path information that matched a pattern, but the issue I'm having is path information that was never there to begin with. – Nicholas Hollander Dec 12 '17 at 23:37
  • 1
    There is a way to do it, but are you sure you want to have one big dependency file (`.depend`), rather than one for each object file? There are some advantages to the latter approach, but it's your decision. – Beta Dec 13 '17 at 02:19
  • I'm not sure what the best practice for make files is. This is the first time I'm attempting to work on a project in an IDE that does not have its own build system baked in. – Nicholas Hollander Dec 13 '17 at 02:24
  • I'm writing an answer, but it's getting long and I have to run. I'll post in a few hours. – Beta Dec 13 '17 at 03:27
  • Did you read https://gcc.gnu.org/news/dependencies.html ? – Vroomfondel Dec 13 '17 at 12:15
  • Possible duplicate of [Building C-program "out of source tree" with GNU make](https://stackoverflow.com/questions/39015453/building-c-program-out-of-source-tree-with-gnu-make) – Tim Dec 13 '17 at 13:35

3 Answers3

2

What about:

# Get all of the source files
SRC = $(shell find src/ -name "*.cpp")
# Get all of the object files
OBJ = $(patsubst src/%.cpp,build/%.o,$(SRC))

.PHONY: all

all: project

project: $(OBJ)
    gcc -o $@ $^

$(OBJ): build/%.o: src/%.cpp
    @mkdir -p $(dir $@)
    g++ -g -c $< -o $@

.depend: $(SRC)
    g++ -MM $^ > $@ && \
    sed -Ei 's#^(.*\.o: *)src/(.*/)?(.*\.cpp)#build/\2\1src/\2\3#' $@

include .depend

Dependencies computation

The sed command substitutes any:

file.o: src/file.cpp ...

by:

build/file.o: src/file.cpp ...

and any:

file.o: src/X/Y/Z/file.cpp ...

by:

build/X/Y/Z/file.o: src/X/Y/Z/file.cpp ...

The target is directly .depend and it has all source files as dependencies such that it is automatically rebuilt if missing or older than any source file. No need to use the depend phony target or to add it as a pre-requisite of all (make automatically tries to rebuild files included with include, if needed).

Note

I added some GNU make features (patsubst, static pattern rule, systematic use of automatic variables...) Rework the non-supported ones if you use another make.

Renaud Pacalet
  • 25,260
  • 3
  • 34
  • 51
  • Thanks, this does exactly what I'm looking for. The only thing I noticed was that `@mkdir $(dir $@)` will fail in the event the build folder does not exist, and the first directory being created is more than one level deep (e.g. `build/folder1/file.o`). I was able to fix this by adding the `-p` option to the command, turning it into `@mkdir -p $(dir $@)`. – Nicholas Hollander Dec 14 '17 at 17:19
  • 1
    @NicholasHollander Ooops, sorry for that. Edited my answer to add the `-p` option. – Renaud Pacalet Dec 14 '17 at 17:20
  • Is there a good reason to write the `.depend` file and then `sed -i` instead of a simple pipe through `sed`? – Toby Speight Dec 15 '17 at 11:30
  • @TobySpeight No other reason than aesthetic: I do not like long lines and I do not like breaking lines on pipes. Nothing important. – Renaud Pacalet Dec 15 '17 at 11:48
1

Here are three approaches.

One, modify the output with sed (similar to Renaud Pacalet's answer):

depend:
    g++ -MM $(SRC) | sed 's/.*: src\([^ ]*\)cpp/build\1o: src\1cpp/' > .depend                  

Two, use a shell loop:

STEMS := file1 one/file1 one/file2
depend:
    rm -f .depend
    for x in $(STEMS); do g++ -MM -MT build/$$x.o src/$$x.cpp >> .depend; done

Three, a Make approach:

DEPENDENCIES := $(addsuffix -depend,$(STEMS))

clear-depend:
    rm -f .depend

depend: $(DEPENDENCIES)

%-depend: clear-depend
    g++ -MM -MT build/$*.o src/$*.cpp >> .depend

(My favorite approach is to have a separate dependency file for each object file, instead of one big .depend file. It has several advantages, but it takes some time to explain, and it's also tricky if there are name collisions in your source tree, such as file1.cpp and file1.cpp.)

Beta
  • 96,650
  • 16
  • 149
  • 150
0

I use the following sequence for my build:

define req
$(subst ..,__,$(dir build-$(TARGET)$(build_dir_ext)/$(1)))%.o: $(dir $1)%.cpp
    mkdir -p $$(dir $$(subst ..,__,$$@))
    $$(CXX) -MM $$(CXXFLAGS) $$< -MT   $$(subst ..,__,$$@) > $$(patsubst %.o,%.d,$$(subst ..,__,$$@))
    $$(CXX)     $$(CXXFLAGS) $$< -c -o $$(subst ..,__,$$@)
endef

$(eval $(foreach x,$(OBJ),$(call req,$(x))))

As a result make is now able to handle a path which can be "outside" the source tree, simply by using '__' instead of '..' and the build dir is set accordingly to the found patterns, so there is no problem with src and build anymore. I need the "outside" files to use a source pool where the local build dir is under the root of source pool and project directory.

In hope that helps...

EDIT 1: Why replacing '..'

Think of the following source tree:

./sourcepool/lib1/src/one.cpp
./sourcepool/project/build

If your Makefile is in the ./sourcepool/project path and one of the OBJ is "../lib1/src/one.o" the Makefile should create a equivalent path in the build directory. That is, if '..' is used, not possible, because the path is then not longer in build but one depth higher. If replacing .. with __ the result is as following:

./sourcepool/project/build/__/lib1/src/one.o

This makes it possible to not copy or link all used dirs to the local project and build file tree.

Klaus
  • 24,205
  • 7
  • 58
  • 113
  • I completely don't grok it: why did you replace `..`? – Vroomfondel Dec 13 '17 at 12:12
  • To avoid such trickery, the Gods of GNUmake introduced the `abspath` function. It resolves all `.` and `..` to the canonical path name which afterwards can be used with the other substitutions and path functions in a normal manner. – Vroomfondel Dec 13 '17 at 13:53
  • @Vroomfondel: If using abspath I will copy the complete filesystem structure under my build path. My solution only add the needed depth. – Klaus Dec 13 '17 at 14:06
  • `abspath` turns your `../lib1/src/one.cpp` into `/home/klaus/sourcepool/lib1/src/one.cpp` (or whichever path it is in). I don't know what you mean by the whole structure under the build path. Its just a string function. – Vroomfondel Dec 13 '17 at 14:11
  • Try this: `PRJROOT = $(abspath ..)` `OUTOFTREE := $(wildcard $(PRJROOT)/lib1/src/*.cpp)` `$(info $(foreach f,$(OUTOFTREE),$(subst $(PRJROOT),$(PRJROOT)/build,$(f))))` – Vroomfondel Dec 13 '17 at 14:13