Reputation: 1873
I'm trying to use (a slightly-modified version of) Paul Smith's Advanced VPATH Method for Multi-Architecture Builds to build our app in different subdirectories, with different compiler flags for each one, based on environment variables passed to make
on the command line.
The following Makefile
is the simplified example of what we're doing:
make debug=1
, the output
target will be built into the _debug
subdirectory with specific compiler flagsmake release=1
, the output
target will be built into the _release
subdirectory with specific compiler flagsRELEASEDIR := _release
DEBUGDIR := _debug
OBJDIRS :=
ifdef debug
OBJDIRS += $(DEBUGDIR)
endif
ifdef release
OBJDIRS += $(RELEASEDIR)
endif
ifneq ($(MAKECMDGOALS),clean)
ifndef OBJDIRS
$(error Must define "debug" and/or "release" in the environment, e.g. "make debug=1")
endif
endif
#----- Begin section to determine what subdir(s) to change into
ifeq (,$(filter _%,$(notdir $(CURDIR))))
.SUFFIXES:
MAKETARGET = $(MAKE) --no-print-directory -C $@ -f $(CURDIR)/Makefile \
SRCDIR=$(CURDIR) $(MAKECMDGOALS)
.PHONY: $(OBJDIRS)
$(OBJDIRS):
+@[ -d $@ ] || mkdir -p $@
+@$(MAKETARGET)
Makefile : ;
%.mk :: ;
% :: $(OBJDIRS) ;
.PHONY: clean
clean:
rm -rf _*
else
#----- End subdir(s) section
VPATH = $(SRCDIR)
# do special things (e.g. set compiler flags) based on the subdirectory
CURRENT_SUBDIR := $(notdir $(CURDIR))
ifneq (,$(findstring $(RELEASEDIR),$(CURRENT_SUBDIR)))
$(info ...set compiler flags specific to release build...)
endif
ifneq (,$(findstring $(DEBUGDIR),$(CURRENT_SUBDIR)))
$(info ...set compiler flags specific to debug build...)
endif
output : prereq
cp -f $< $@
# user can specify v=1 to turn verbosity up
$(v).SILENT:
#----- End the "subdirs" if-statement
endif
This works as I'm expecting if I only specify one of the two variables:
$ make debug=1 v=1
...set compiler flags specific to debug build...
cp -f /home/me/work/prereq output
and:
$ make release=1 v=1
...set compiler flags specific to release build...
cp -f /home/me/work/prereq output
And so the file output
is created in both the _debug
and the _release
subdirectories.
The problem is that when I try to specify both variables, it only builds the target in the _debug
subdirectory:
$ make clean
rm -rf _*
$ make debug=1 release=1 v=1
...set compiler flags specific to debug build...
cp -f /home/me/work/prereq output
I don't understand this behavior. OBJDIRS
is equal to "_debug _release"
in this case, and so my terminal match-anything rule (% :: $(OBJDIRS) ;
) has both _debug
and _release
as prerequisites. Therefore, I'd expect GNU Make to check (and build) both of those prerequisites, but it's only building the first one (_debug
).
Interestingly, if I explicitly specify the target to build on the command line, it builds both:
$ make clean
rm -rf _*
$ make debug=1 release=1 v=1 output
...set compiler flags specific to debug build...
cp -f /home/me/work/prereq output
...set compiler flags specific to release build...
cp -f /home/me/work/prereq output
I'm assuming that there's something I'm misunderstanding about how terminal match-anything rules work. I don't get why the behavior is different for when a target is explicitly specified versus when not.
I'm using GNU Make 3.82 in CentOS 7.
EDIT: I'm also seeing this behavior in GNU Make 4.2.1 on Arch Linux.
Upvotes: 1
Views: 147
Reputation: 17383
I have to admit that I have not completely analyzed your makefile
, but the following stood out to me. Reading 9.2 Arguments to Specify the Goals:
By default, the goal is the first target in the makefile (not counting targets that start with a period). Therefore, makefiles are usually written so that the first target is for compiling the entire program or programs they describe. If the first rule in the makefile has several targets, only the first target in the rule becomes the default goal, not the whole list.
So executing make debug=1 release=1 v=1
, without an explicit target mentioned, will result in only the first entry in your $(OBJDIRS)
value to become the target, not the whole list. This will be $(DEBUGDIR)
, and not $(DEBUGDIR) $(RELEASEDIR)
. This seems to explain what you are observing.
An experiment with this snippet that mimics your situation demonstrates the behavior:
RELEASEDIR := _release
DEBUGDIR := _debug
OBJDIRS :=
ifdef debug
OBJDIRS += $(DEBUGDIR)
endif
ifdef release
OBJDIRS += $(RELEASEDIR)
endif
.PHONY: $(OBJDIRS)
$(OBJDIRS):
@echo OBJDIRS = $(OBJDIRS), Target = $@
output: $(OBJDIRS)
@echo Creating the output target
with the following results:
$ make debug=1
OBJDIRS = _debug, Target = _debug
$ make release=1
OBJDIRS = _release, Target = _release
$ make release=1 debug=1
OBJDIRS = _debug _release, Target = _debug
$ make release=1 debug=1 _release
OBJDIRS = _debug _release, Target = _release
$ make release=1 debug=1 output
OBJDIRS = _debug _release, Target = _debug
OBJDIRS = _debug _release, Target = _release
Creating the output target
Update: The above identifies the problem, and here is a solution (found by the OP)
Upvotes: 1
Reputation: 1873
Reinier's answer is correct for what my problem was. My solution was check if $(OBJDIRS)
contains more than one word, and if so, make words 2 through N (I just chose an arbitrarily high N of 100) prerequisites of word 1:
ifneq (1,$(words $(OBJDIRS)))
$(firstword $(OBJDIRS)): $(wordlist 2,100,$(OBJDIRS))
endif
So that section of the Makefile
now looks like this:
.SUFFIXES:
MAKETARGET = $(MAKE) --no-print-directory -C $@ -f $(CURDIR)/Makefile \
SRCDIR=$(CURDIR) $(MAKECMDGOALS)
.PHONY: $(OBJDIRS)
$(OBJDIRS):
+@[ -d $@ ] || mkdir -p $@
+@$(MAKETARGET)
ifneq (1,$(words $(OBJDIRS)))
$(firstword $(OBJDIRS)): $(wordlist 2,100,$(OBJDIRS))
endif
Makefile : ;
%.mk :: ;
% :: $(OBJDIRS) ;
.PHONY: clean
clean:
rm -rf _*
Upvotes: 1