Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does patsubst stop working when using secondary expansion of $$*?

Here is an example from the GNU Make manual section on Secondary Expansion (slightly simplified):

foo_SRCS := bar.c baz.c

.SECONDEXPANSION:
# $$@ expands to the target ("foo" in this case)
foo: $$(patsubst %.c,%.o,$$($$@_SRCS))

This works great; it builds bar.o and baz.o:

cc    -c -o bar.o bar.c
cc    -c -o baz.o baz.c

But if I tweak this example only slightly, the patsubst stops working:

all: foo.a

foo_SRCS := bar.c baz.c

.SECONDEXPANSION:
# $$* expands to the stem of the match ("foo" in this case).
%.a: $$(patsubst %.c,%.o,$$($$*_SRCS))
        ar rcs $@ $^

It is no longer building bar.o and baz.o, and instead is using the *.c files directly as prerequisites!

ar rcs foo.a bar.c baz.c

Please note that the $$($$*_SRCS) part is clearly working, as evidenced by the fact that it found foo_SRCS and used that as the prerequisites. But for some reason the patsubst part has become a no-op! Instead of replacing %.c with %.o, it is just using foo_SRCS directly.

What is going on here? How can I get my example to work?

EDIT: I had a theory that the % characters inside the patsubst were getting evaluated early, using the stem match (foo), so that the patsubst itself was looking something like this:

$(patsubst foo.c,foo.o,bar.c baz.c)

To test this theory, I added a file called foo.c to foo_SRCS:

all: foo.a

foo_SRCS := foo.c bar.c baz.c

.SECONDEXPANSION:
# $$* expands to the stem of the match ("foo" in this case).
%.a: $$(patsubst %.c,%.o,$$($$*_SRCS))
        ar rcs $@ $^

That resulted in something even weirder:

make: *** No rule to make target `foo.a', needed by `all'.  Stop.
like image 978
Josh Haberman Avatar asked Aug 31 '14 06:08

Josh Haberman


2 Answers

The percent characters are being read by make as matches to the wildcard in the stem and are being replaced with the stem match. If you check the make -p output for your example you'll see that the parsed target line looks like this:

%.a: $(patsubst %.c,%.o,$($*_SRCS))

Which, as far as make is concerned, is just a really odd set of patterned targets (or something like that).

If you escape the percent characters from make parsing in a similar way to how you escape the $ from make evaluation you can get what you want to work:

pc := %
$$(patsubst $$(pc).c,$$(pc).o,$$($$*_SRCS))

For added information substitution references (i.e. $(foo_SRCS:.c=.o)) can be used for transformations like this in place of the longer call to patsubst. In this case however, while it works in this scenario with a similar escaping of : (via c := :) it doesn't seem to function as the sole prerequisite of the target (with make giving a Makefile:23: *** commands commence before first target. Stop. error that I don't quite understand) at least with GNU Make 3.81.

like image 75
Etan Reisner Avatar answered Mar 02 '23 21:03

Etan Reisner


You're mixing three features that don't go well together: secondary expansion, pattern rules, and patsubst. I'll try to explain in detail what make is doing when evaluating your code (AFAIUI).

Let's start with your first Makefile:

foo_SRCS := bar.c baz.c

.SECONDEXPANSION:
%.a: $$(patsubst %.c,%.o,$$($$*_SRCS))
        ar rcs $@ $^

Read phase. All dollar signs are escaped, so no evaluation happens here. Make enters the following rule in its database:

%.a: $(patsubst %.c,%.o,$($*_SRCS))

Pattern substitution. As far as make is concerned, this is just another pattern rule, with target %.a and two prerequisites separated by whitespace: $(patsubst and %.c,%.o,$($*_SRCS)).

foo.a matches the target pattern, and so the first % in each prerequisite will be replaced by foo. The rule becomes:

foo.a: $(patsubst foo.c,%.o,$($*_SRCS))

Target update phase. As you requested secondary expansion, the pattern is evaluated again in the target update phase:

    foo.a: $(patsubst foo.c,%.o,$($*_SRCS))
==> foo.a: $(patsubst foo.c,%.o,bar.c baz.c)
==> foo.a: bar.c baz.c

And so make ends up executing the command

ar rcs foo.a bar.c baz.c

And what about foo.c? If you add foo.c to foo_SRCS, the secondary expansion looks like this:

    foo.a: $(patsubst foo.c,%.o,$($*_SRCS))
==> foo.a: $(patsubst foo.c,%.o,foo.c bar.c baz.c)
==> foo.a: %.o bar.c baz.c

And the rule fails because make doesn't know how to build %.o.

Work-around. You can escape the % characters with a backslash:

.SECONDEXPANSION:
%.a: $(patsubst \%.c,\%.o,$$($$*_SRCS))
        ar rcs $@ $^
like image 30
pdw Avatar answered Mar 02 '23 21:03

pdw