Reputation: 1883
I'm working with a tool that generates C++ source files that I need to include in my C++ project compilation.
To extraordinarily simplify things, my one non-generated source file, main.cpp
, is as follows:
extern void bar();
int main(int, char**)
{
bar();
return 0;
}
And my Makefile
is below. The overall structure is what's already existing in our project, and I'm simply adding in the parts noted to generate the needed source file bar.cpp
and makefile bar.mk
, which adds bar.cpp
to the SRCS
variable so it (I wish) gets included in the compilation:
SRCS := $(CURDIR)/main.cpp
#################### Adding everything between here... #####
BAR_MK := bar.mk
ifeq (,$(findstring clean,$(MAKECMDGOALS)))
include $(BAR_MK)
endif
#################### ...and here ###########################
OBJ_DIR := $(CURDIR)/obj
OBJS := $(patsubst %.cpp,%.o,$(subst $(CURDIR),$(OBJ_DIR),$(SRCS)))
a.out: $(OBJS)
@echo OBJS == $(OBJS)
@echo SRCS == $(SRCS)
$(CXX) $(OBJS)
#################### Adding everything between here... #####
GEN_DIR := $(CURDIR)/gen
# "Generate" my source file(s), add them to SRCS via an external makefile
$(BAR_MK): $(GEN_DIR)/bar.cpp
$(GEN_DIR)/bar.cpp:
mkdir -p $(dir $@)
echo "void bar () {}" > $@
echo SRCS += $@ > $(BAR_MK)
#################### ...and here ###########################
$(OBJ_DIR)/%.o: %.cpp
mkdir -p $(dir $@)
$(CXX) -c -o $@ $<
clean:
rm -rf $(OBJ_DIR)
rm -f a.out
rm -rf $(GEN_DIR)
rm -f $(BAR_MK)
The issue is, the compilation fails the first time I run make
due to "undefined reference to bar()
", but running make
immediately afterward succeeds. This is because upon the first run of make
, when it evaluates the a.out:
rule, it sees SRCS
as only containing main.cpp
, and so bar.cpp
doesn't get compiled. Then on the second (and subsequent) runs, SRCS
contains both main.cpp
and bar.cpp
, so bar.cpp
gets compiled that time.
This seems odd to me, because per the GNU Make manual on "How Makefiles Are Remade":
After all makefiles have been checked, if any have actually been changed, make starts with a clean slate and reads all the makefiles over again.
So after GNU Make processes include bar.mk
on my first make
invocation, sees that it's out of date and builds it, I'd expect that it would then start over from scratch with parsing the Makefile from the start, including bar.mk
and, in turn, having SRCS
contain both main.cpp
and bar.cpp
.
Is my expectation wrong? Or is there something wrong with my (additions to the) Makefile? I'm using GNU Make 4.1 on Linux.
Upvotes: 0
Views: 501
Reputation: 29345
The rule that has bar.mk
as a target has no recipe. So make considers that there is no way to generate the missing bar.mk
. But it does not fail immediately, it continues parsing the main Makefile, generates bar.cpp
... and bar.mk
but it had no way to know this in advance. The result is that bar.mk
is generated but not included... and make does not fail. Not sure whether this should be considered as a bug or not. I filled a maybe-a-bug report.
Anyway, if you tell make explicitly how to build bar.mk
it should work:
$(BAR_MK): $(GEN_DIR)/bar.cpp
echo 'SRCS += $<' > $@
$(GEN_DIR)/bar.cpp:
mkdir -p $(dir $@)
echo "void bar () {}" > $@
EDIT: add some more comments / alternate solutions
Note that the $(BAR_MK)
rule is still not perfect: it declares a prerequisite that is not a real one. But before removing it we must fix something else: your pattern rule will not match for the generated source file because of your use of $(CURDIR)
that prepends the full path. Let's get rid of it:
SRCS := main.cpp
BAR_MK := bar.mk
ifeq (,$(findstring clean,$(MAKECMDGOALS)))
include $(BAR_MK)
endif
OBJ_DIR := obj
OBJS := $(patsubst %.cpp,$(OBJ_DIR)/%.o,$(SRCS))
a.out: $(OBJS)
@echo OBJS == $(OBJS)
@echo SRCS == $(SRCS)
$(CXX) $(OBJS)
GEN_DIR := gen
$(BAR_MK):
echo 'SRCS += $(GEN_DIR)/bar.cpp' > $@
$(GEN_DIR)/bar.cpp:
mkdir -p $(dir $@)
echo "void bar () {}" > $@
$(OBJ_DIR)/%.o: %.cpp
mkdir -p $(dir $@)
$(CXX) -c -o $@ $<
clean:
rm -rf $(OBJ_DIR) a.out $(GEN_DIR) $(BAR_MK)
Finally, if you really want to generate both bar.mk
and gen/bar.cpp
with the same rule, there is one possibility, still telling make: a pattern rule with multiple targets:
Pattern rules may have more than one target. Unlike normal rules, this does not act as many different rules with the same prerequisites and recipe. If a pattern rule has multiple targets, make knows that the rule’s recipe is responsible for making all of the targets. The recipe is executed only once to make all the targets. When searching for a pattern rule to match a target, the target patterns of a rule other than the one that matches the target in need of a rule are incidental: make worries only about giving a recipe and prerequisites to the file presently in question. However, when this file’s recipe is run, the other targets are marked as having been updated themselves.
In the following it is the a
of bar
that will match the pattern rule's wildcard:
PATTERN_TARGET := $(subst bar,b%r,$(BAR_MK) $(GEN_DIR)/bar.cpp)
$(PATTERN_TARGET):
mkdir -p $(GEN_DIR)
echo "void bar () {}" > $(GEN_DIR)/bar.cpp
echo 'SRCS += $(GEN_DIR)/bar.cpp' > bar.mk
But this is probably too strange and would completely ruin maintainability. Prefer one of the two other options, they are easier to understand.
Upvotes: 1