4
votes

I'm using $(shell ...) gnu make function in a Makefile recipe, and it runs first before the preceding rows. Why?

A very simple example:

.PHONY: all
all:
    @echo 1
    @echo $(eval a=$(shell echo 2a 1>&2))2b
    @echo 3 $(a)

The output is:

2a
1
2b
3

First runs the $(shell ...) line (2a), then the other lines. How can I manage to run the $(shell ...) function when its row runs in the recipe, in this order?

1
2a
2b
3

Edit:

Without $(shell ...) it works as I expected:

.PHONY: all
all:
    @echo 1
    $(eval a=a)
    @echo 2 $(a)
    $(eval a=b)
    @echo 3 $(a)

Output:

1
2 a
3 b

Edit 2:

Here is a part of the original Makefile. The pieces at >>> show the essence of my problem: I want to put the output of udisksctl into a make variable instead of file [email protected] (and do the same with [email protected]).

$(HDIMG): $(BOOTBLOCK_MBR_BIN) $(BOOTBLOCK_EXT2_BIN) $(LOADER_BIN) | $(DESTDIR)
        dd if=/dev/zero [email protected] bs=1 seek=$(PSIZEB) count=0 2>/dev/null
        $(MKFSEXT2) -F [email protected] >/dev/null
        dd if=$(word 2,$^) [email protected] conv=notrunc 2>/dev/null
        cp $< $@
        dd if=/dev/zero of=$@ bs=1 seek=$(HDSIZEB) count=0 2>/dev/null
        echo $(PFDISK) | $(TR) | $(FDISK) $@ >/dev/null
        dd [email protected] of=$@ bs=512 seek=$(PSTART) conv=sparse,notrunc iflag=fullblock 2>/dev/null
>>>     udisksctl loop-setup --file $@ --offset $(PSTARTB) --size $(PSIZEB) >[email protected]
        sed -i -e 's/.* //;s/\.//' [email protected]
        cat [email protected]
>>>     udisksctl mount --block-device $$(cat [email protected]) >[email protected]
        sed -i -e 's/.* //;s/\.//' [email protected]
        cat [email protected]
        #
        mkdir -p $$(cat [email protected])/boot/
        cp $(word 2,$^) $$(cat [email protected])/boot/
        #/sbin/filefrag -b512 -e /
        #
        udisksctl unmount --block-device $$(cat [email protected])
        udisksctl loop-delete --block-device $$(cat [email protected])
        rm [email protected]
1
Why do you want them in a make variable? Why can't they be in shell variables? - MadScientist
@MadScientist: I don't want to merge this many rows into one logical row. But if there is no better solution, I will do it. - LGabci
It's quite possible that a script of this length should be put into a separate shell script rather than a make recipe. However if you want to just avoid adding semicolons or backslashes, you can (assuming a suitably new version of GNU make) enable the .ONESHELL feature so that the entire recipe is passed to the same shell without needing semicolon/backslash. See gnu.org/software/make/manual/html_node/One-Shell.html - MadScientist
@MadScientist Thanks for helping. I make a bash script, the makefile will be more clear. (I know about .ONESHELL and .SHELLFLAGS, but I don't want to use them, because I have more targets and recipes, more Makefiles that included into the main Makefile, and maybe this cause some other problem) I would accept your answer, but this is only a comment, not an answer. - LGabci

1 Answers

3
votes

When make runs a recipe, it first expands all variables/functions in it, and then runs the shell commands line by line.

You can work around that by executing every shell command with $(shell ...), or, in your case, by using $(info ...) instead of echo:

.PHONY: all
all:
    $(info 1)
    $(info $(eval a=$(shell echo 2a 1>&2))2b)
    $(info 3 $(a))

Output:

1
2a
2b
3
make: Nothing to be done for 'all'.

The Nothing to be done for 'all'. part is caused by the fact that after expanding the functions, the recipe is effectively empty (has 0 shell commands). Adding a no-op command (e.g. @true) to it removes the message.