0

I'm learning how to use makefiles for complex projects. I got it working but I don't know how to add pre-requisites under certain conditions without specifying them one by one. In this particular case I'm trying to add header files as requisites of objects goals so if any header file is modified, that particular cpp has to be rebuild again and linked with the rest.

After looking for clues on the manual I tried to use some shell scripting but I guess the substitution I'm doing is expanded after checking for dependencies so it does nothing. I have tried to use the $(if ) expression in the requisites too but I ended up moving it just in case it was some kind of conflict with using it inside a static pattern rule.

Some additional notes:

  • I'm learning the tool so I would prefer to avoid CMake or any kind of utility that builds the makefile for me (I would gladly use the facilities after being sure I can replicate at some degree what they do by myself)
  • I also prefer to avoid solutions tied to the language I'm using in the example as it should work with any compiler and other kind of files and goals/requisites.

I have the following files in my working directory:

.
├── Makefile
└── src
    ├── main.cpp
    └── somemodule
        ├── module.cpp
        └── module.h

My makefile has to generate the following structure:

.
├── app
├── Makefile
├── obj
│   ├── main.o
│   └── somemodule
│       └── module.o
└── src
    ├── main.cpp
    └── somemodule
        ├── module.cpp
        └── module.h

My current makefile:

APP := app
SRCDIR := src
OBJDIR := obj
OBJS := obj/main.o obj/somemodule/module.o
SRCS := $(OBJS:.o=.cpp)

$(APP): $(OBJDIR) $(OBJS)
    g++ $(OBJS) -o $(APP)

$(OBJDIR):
    mkdir -p $(patsubst $(SRCDIR)%,$(OBJDIR)%,$(shell tree src -fid --noreport))

$(OBJS): HEADERS += $(if $(shell if [ -e $(@:obj%.o=src%.h) ]; then echo "y"; fi), $(@:obj%.o=src%.h))
$(OBJS): $(OBJDIR)%.o: $(SRCDIR)%.cpp $(HEADERS)
    g++ -c $(patsubst $(OBJDIR)%.o,$(SRCDIR)%.cpp,$@) -o $@

.PHONY: clean
clean:
    rm -rf $(OBJDIR)
    rm $(APP)
Raulillo
  • 166
  • 1
  • 19
  • Note: It doesn't seem like you could run make on one of the `.o` files directly after doing `make clean`. – fabian Sep 23 '22 at 18:23
  • @fabian I have tested it running make clean; make And it compiles everything without problems. What do you mean? – Raulillo Sep 23 '22 at 18:30
  • In that case therre should be no need for the `OBJDIR` rule. If the compiler is regenerating the directory anyways. Honestly I don't really have experience with writing this low level logic anymore. Since I started working on bigger projects I simply let `CMake` do the job of creating the makefiles or similar project files. Imho cmake syntax is much easier to write, since you simply don't need to care about all the intermediate steps between sources and binary and just tell it to create an exe from the given sources. It also makes managing dependencies between targets pretty easy... – fabian Sep 23 '22 at 18:41
  • @fabian without it the compiler can't create the object file in the separated folder because it doesn't exist after deleting it with clean. Also the [difference between CMake and Makefile](https://stackoverflow.com/questions/25789644/what-is-the-difference-between-using-a-makefile-and-cmake-to-compile-the-code) is not related to this question as I'm learning how to do it myself before letting a program do it for me. I could skip half of the problems using an IDE too but that just cripples yourself in the long run when that tool fails and you don't understand the underlying tool. – Raulillo Sep 23 '22 at 18:50
  • It's not right to depend on a directory, because the way directories handle modification times is very different than what make expects. You'll get things rebuilt when you don't want/need it. Also a rule of makefiles you should always try to follow is that each recipe builds _exactly_, and _only_, the targets that are mentioned in the rule (e.g., builds exactly `$@`). Here your `$(OBJDIR)` recipe builds a lot more things than just `$(OBJDIR)`; if those things are deleted without deleting`$(OBJDIR)` make won't notice. – MadScientist Sep 23 '22 at 20:54
  • @MadScientist I don't see the problem. For the folder structure to be altered you have to manually delete one of the nested folders, in that case cleaning and rebuilding will be the way to go. It could also be solved by doing the substitution in the variable definition so the goal triggers in every folder. I didn't realize that the directory would start every time something inside changed, thank you for pointing that out. I think it shouldn't be problematic though as `mkdir -p` should ignore existing directories so it wouldn't trigger the rebuilding sources which is what I'm concerned about. – Raulillo Sep 24 '22 at 09:11

1 Answers1

1

trying to add header files as requisites of objects goals so if any header file is modified, that particular cpp has to be rebuild again and linked with the rest doesn't sound complex: that's table-stakes for a good build system, if I understood it correctly.

I don't understand what your syntax of setting HEADERS is trying to do here but there's no way it can work. Please read carefully the introduction to automatic variables in the GNU make manual; in particular this text:

It’s very important that you recognize the limited scope in which automatic variable values are available: they only have values within the recipe. In particular, [...] they cannot be accessed directly within the prerequisite list of a rule.

Luckily I see no need for any of that. Have you considered using dependency generation methods such as the one discussed here?

In response to the requirement you added when you updated the question:

I also prefer to avoid solutions tied to the language I'm using in the example as it should work with any compiler and other kind of files and goals/requisites.

I answered:

There is no way to do this only using only make because make doesn't know how to parse your code, and so it can't figure out what the dependencies of a source file are. Similarly, you can't do it in a generic way for all different languages because different languages use different syntax for declaring dependencies. If you want to have automatic dependency tracking, and not do it by hand, you must have SOME tool that can parse your source to find the dependencies. You can use GCC or makedepend or even some fancy shell script if you don't need it to be too accurate.

MadScientist
  • 92,819
  • 9
  • 109
  • 136
  • Yes I have found that usually people delegate this task to the compiler but as it is also mentioned there, it's only possible with some compilers. I would prefer to learn the tool without relying on other tools exclusive to g++ because it will be useful in other contexts. – Raulillo Sep 23 '22 at 18:35
  • After re-reading the automatic variables documentation again, seems that secondary expansion is what I'm looking for. I will try using it. *edit: didn't work – Raulillo Sep 23 '22 at 18:40
  • There is no way to do this only using make because make doesn't know how to parse your code, and so it can't figure out what the dependencies of a source file are. Similarly, you can't do it in a generic way for all different languages because different languages use different syntax for declaring dependencies. If you want to have _automatic_ dependency tracking, and not do it by hand, you must have _SOME_ tool that can parse your source. You can either use GCC or `makedepend` or even some fancy shell script if you don't need it to be too accurate. – MadScientist Sep 23 '22 at 20:12
  • Note I didn't say that using automatic variables was the _only_ problem with the example you showed, I just said it was the _first_ reason why it couldn't work :). – MadScientist Sep 23 '22 at 20:13
  • It looks to me like your example _only_ cares about the object files depending on its _own_ header file. So you only care about `foo.o` depending on `foo.h`, but not other header files that `foo.cpp` might also include... which is really what you need to be caring about. – MadScientist Sep 23 '22 at 20:56
  • can you add these two comments to the answer? The confirmation of it being impossible to create a makefile that tracks dependencies without a third-party tool is the answer I was looking for. Starting the answer with "you need an external tool or script to track dependencies as you need to parse each file to know what you need for each source file" would work. The detailed explanation you give me was great, thank you. I will check it later to accept it :D – Raulillo Sep 24 '22 at 09:02