Specifiy a combined target in a makefile

Using GNU Make, how can I ensure other targets dependencies are made before a final task runs?

(I know this is a a bit of an abuse of make, but can it be done?)

Say I have 2 targets a and b. Building just a or just b is easy, but I want to trigger a special type of build if targetting both a and b.

For example, my Makefile (simplified from a real problem)

a_prereq:
    @echo "making a"

b_prereq:
    @echo "making b"

a: a_prereq default
b: b_prereq default

default:
    @echo "made!"

Calling make a prints making a; made!. This is correct.

Calling make b prints making b; made!. This is correct.

Calling make a b prints making a; made!; making b. Not what I wanted!

How do I get make a b to print making a; making b; made!; if I can only change the makefile and not the calling make a b.

(I know I could I could introduce c: a_prereq b_prereq default and call make c but can it be done without the new target?)


Solution 1:

This looks like an XY problem. What you apparently want (not 100% sure) is to force default to be built after a_prereq and b_prereq if and only if the a and b goals are passed on the command line. This is quite strange and not really what make is intended for but let's give it a try.

Forcing default to be built after a_prereq and b_prereq, even with parallel make, is straightforward: declare them as prerequisites of default (indeed, I do not know another way to do it):

default: a_prereq b_prereq
    @echo "made!"

Or, if you prefer to separate the declaration of prerequisites from that of the recipe:

default: a_prereq b_prereq

default:
    @echo "made!"

But as you want this only if the a and b goals are passed on the command line we need to detect this situation. Fortunately the MAKECMDGOALS GNU make variable lists the goals specified on the command line. So a bit of filter applied to it should make it.

We can set make variable MATCH to string ab if and only if the a and b goals are passed on the command line. And we can use make conditionals (ifeq) to declare a_prereq and b_prereq as prerequisites of default if and only if $(MATCH) equals ab. Just add the following 4 lines to your Makefile:

MATCH := $(filter a,$(MAKECMDGOALS))$(filter b,$(MAKECMDGOALS))
ifeq ($(MATCH),ab)
default: a_prereq b_prereq
endif

Note: if the real default recipe makes use of automatic variables that expand as prerequisites ($<, $^, ...) you will have to remember that their values are different for make a and for make a b...

Note: if targets a and b must be rebuild but are not passed as goals on the command line this will not work. For instance, if a and b are prerequisites of another, requested, goal. This is one of the main reasons why what you ask for looks so strange.

Note: if you also need to build default after a_prereq when running make a (same for b) an even simpler solution would be:

default: $(patsubst %,%_prereq,$(filter a b,$(MAKECMDGOALS)))

This way, if you run make a, a_prereq is declared as prerequisite of default, if you run make b, b_prereq is declared as prerequisite of default, is you run make a b both a_prereq and b_prereq are declared as prerequisites of default, and if you run make foo, default has no prerequisites at all.