villapx
villapx

Reputation: 1873

Terminal match-anything pattern rule, multiple prerequisites

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:


RELEASEDIR := _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

Answers (2)

Reinier Torenbeek
Reinier Torenbeek

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

villapx
villapx

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

Related Questions