0
votes

I'm trying to generically add some behaviour to every target in a Makefile, without modifying the targets.

My current attempt is thus:

%: $*
     @echo 'Logging $* target'
.PHONY: test
test:
     @echo 'Inside explicit test target'

When I run make test, I'd like to match the % pattern rule, which would execute test as a prerequisite ($* expanding to the pattern stem), and then log the target that was run.

$ make test
Inside explicit test target
Logging test target

Instead, what happens is that make test matches the explicit test target (presumably since it's a closer match):

$ make test
Inside explicit test target

How can I get this to work, without changing the explicit test target?

EDIT: Another attempt...

.SECONDEXPANSION:
%: $$*
     @echo 'Logging $* target'

results in

$ make test
make: Circular Makefile <- Makefile dependency dropped.
inside actual test target
2
Would you be willing to use an additional makefile?Beta
If that helps, sure?Karl Barker

2 Answers

1
votes

I appears from your own answer, which has beaten me to the punch, that you're concerned only to trigger a preliminary action for targets that are mentioned on the commandline - $(MAKECMDGOALS). From the posting I took it that you wanted such an action for "every target in a Makefile", which would include all targets that are prerequisite to the commandline targets or, if there are no commandline targets, to the default target.

Anyhow, you may still be interested in a solution to the more general problem.

You want a preliminary action to be executed before the recipe for every target. Your question is: how to match a patten rule before explicit rule?

This is an XY way of posing the problem, because make will consult pattern rules to find a way of making a target only if you don't give it an explicit recipe. You know, for example, that make has a pre-defined pattern rule for making an .o file from a .c file. Even so, if my makefile is:

test.o:
    @echo $@

then make prints test.o, without any attempt to find test.c and compile it. And if my make file is:

test.o: test.c
    @echo $@

test.c:
    @echo $@

then make prints:

test.c
test.o

needing no resort to the pattern rule. But if my makefile is:

test.o: test.c

Then make says:

make: *** No rule to make target 'test.c', needed by 'test.o'. Stop

So you can't do what you're after in the way your question supposes, because the preliminary action you want to provoke from the pattern rule could be provoked only if there were no other action for the target.

In that case the reasons for the failures of your two posted attempts are fairly academic, and you may wish to scroll to The Chase.

In your first attempt, with:

%: $*
    @echo 'Logging $* target'

The pattern rule - which is unemployed by make test - is equivalent to:

%:
    @echo 'Logging $* target'

because $* only assumes a value in the recipe, not in the pattern rule. You can make this pattern rule be employed by making any target for which the makefile does not provide a recipe, e.g. make nonsuch will print Logging nonsuch target; but that is of no use.

The second attempt, with:

.SECONDEXPANSION:
%: $$*
     @echo 'Logging $* target'

does the right thing to create the rule you intend to create. But the meaning of that rule is:

<target>: <target>
     @echo 'Logging <target> target'

making every target to which this rule is applied a prerequisite of itself. Inevitably this will result in a circular dependency error for all such targets. As you saw, this circularity does not affect the your test target because it has an explicit recipe and does not employ the rule. But it does provoke the surprising error:

make: Circular Makefile <- Makefile dependency dropped.

That happens because the first target that make automatically considers is the makefile itself. Unlike the test target, you have no recipe for the makefile; so the pattern rule applies to it, making the makefile dependent on itself.

The Chase

You can achieve what you want by a different approach. In a actual project it is more than likely that in any makefile you can compute a list of all possible targets. From this you can generate a corresponding list of auxiliary targets, say, target => target.prelim, where the sole purpose of target.prelim is to provoke, when it should and not otherwise, the required preliminary action for target; and you can get make to generate a list of order-only rules, target: | target.prelim, for each target, such that target.prelim will not be considered in determining whether target must be made, but will be made before target whenever target needs to be made.

Here is an illustration:

SRCS := main.c foo.c
OBJS := $(SRCS:.c=.o)
TARGETS := all prog $(OBJS)
PRELIMS := $(patsubst %,%.prelim,$(TARGETS))

define prelim_rule =
$(1): | $(1).prelim
endef

$(foreach target,$(TARGETS),$(eval $(call prelim_rule,$(target))))

.PHONY: all

all: prog

prog: $(OBJS)
    $(CC) $(CPPFLAGS) $(CFLAGS) $(LDFLAGS) -o $@ $(OBJS) $(LIBS)

clean:
    rm -f $(OBJS) $(PRELIMS) prog

%.prelim:
    @echo "Logging target $(@:%.prelim=%)"
    @touch $@       

And a sample session:

$ make
Logging target all
Logging target main.o
cc    -c -o main.o main.c
Logging target foo.o
cc    -c -o foo.o foo.c
Logging target prog
cc    -o prog main.o foo.o 
$ make
make: Nothing to be done for 'all'.
$ make clean
rm -f main.o foo.o all.prelim prog.prelim main.o.prelim foo.o.prelim prog
$ make main.o
Logging target main.o
cc    -c -o main.o main.c
$ make main.o
make: 'main.o' is up to date.
$ # A prelim can't out-date its target...
$ touch main.o.prelim
$ make main.o
make: 'main.o' is up to date.
0
votes

I realise that this isn't answering my question as asked, but it has the effect I want - executing a shell command as late in the Makefile processing as possible.

MYVAR?=foo
.PHONY: test
test:
    @echo 'Inside test target'

LOG=$(shell echo 'Logging $(MAKECMDGOALS), myvar=$(MYVAR)' > log)
.SECONDEXPANSION:
force: $$(LOG)

LOG is a deferred variable, so is not expanded until Make evaluates the prerequisite list of the force target.

In a single Makefile, the .SECONDEXPANSION: part is not needed, since the force target is evaluated after MYVAR is set.

However, if I move the LOG variable and force variable into a sub-makefile, it would be easy to include subMakefile before the MYVAR?= line - which would not work.
By specifying .SECONDEXPANSION for force, the reliance on ordering is removed.