matnieuw
matnieuw

Reputation: 11

GNU Make does not execute recipe line

Given the following part of a Makefile:

$(call pkg-source,$(1)):
$(call unpack,$(call tarball,$(1)))
$(foreach pch, $(sort $(wildcard src/$(notdir $(ver_$(1))).patch*)), patch -d $(1)* -p1 < $(pch))
$(foreach var, $(shell find $(1)* -type f -iname abc.xyz), cp my_abc.xyz $(var))
touch $$@
noopstop

The noopstop is there to force make to terminate with "command not found".

First run after make clean, external source is unpacked, then (the first foreach) patched (if patches are present).

Then (the second foreach) a file of a known name that came with the source has to be overwritten by my own version. This always fails on the first run, yet the file is there.

Removing the file created by touch $$@, and running make again, the file is now replaced.

Starting make with the -j1 option does not make a difference; the -d option does not help to find the problem either.

The GNU Make pages state: "Normally, make will execute only one recipe at a time, waiting for it to finish before executing the next." So after the first foreach (for patching), why doesn't the second see the file?

This is on Fedora_35, x86_64, GNU make 4.3

Thanks, Mat

Upvotes: 1

Views: 544

Answers (1)

MadScientist
MadScientist

Reputation: 100936

It's very hard to understand your example because you haven't provided it in the context of an actual rule. But I'll assume that the entirety of the content you provide above is in some define variable that you are using with call. But, the details of how this is used are critical and you don't provide them.

The problem is that you're doing all this work with make variables and functions, not shell commands. The way make works is that all lines of the entire recipe are expanded first (so all make variables and functions are expanded), and only after that is complete will make invoke a shell with each line of the recipe one at a time.

A good rule of thumb is that if you ever find yourself running the make shell function in a recipe, you're probably doing something wrong. A recipe is already run in a shell, so there's little to gain except confusion by using make's shell function.

In addition, your operations will not work correctly if they ever find more than one file.

For example:

$(foreach pch, $(sort $(wildcard src/$(notdir $(ver_$(1))).patch*)), patch -d $(1)* -p1 < $(pch))

Suppose the wildcard here expands to two files, src/xyz.patch-01 and src/xyz.patch-02. Now the result of expanding this will be something like:

patch -d foo* -p1 < src/xyz.patch-01 patch -d foo* -p1 < src/xyz.patch-02

which is clearly wrong. If you want to have a make foreach loop expand to multiple shell commands, you have to remember to add a semicolon or && to separate the commands:

$(foreach pch, ..., patch -d $(1)* -p1 < $(pch) &&) true

You'll have the same problem with the other command, except you don't want to do this with a make foreach and shell anyway as I discussed above. You should be using shell constructs here, not make constructs:

for pch in $$(ls -1 src/$(notdir $(ver_$(1))).patch*); do \
    patch -d $(1)* -p1 < $$pch; \
done
for var in $$(find $(1)* -type f -iname abc.xyz); do \
    cp my_abc.xyz $$var; \
done

Upvotes: 1

Related Questions