makefilegnu-make

Why does my for-loop in dynamic goal not work?


I'm using GNU Make 3.82 and I want to dynamically create goals in a Makefile using define.

However there was a problem when trying to process the lists passed to the macro. Here is a simple example of code that for some reason does not work as expected:

MAKEFLAGS += -s
SHELL := /bin/bash

define create_target
$(1):
    echo $(1); \
    echo $(2); \
    for i in $(2); do \
        echo $$i; \
    done
endef

$(eval $(call create_target, test, 1 2 3 4 5))

When I run make test, the following output is produced:

test
1 2 3 4 5
<5 blank lines>

That is, the variable i in the loop does not expand as it should and I could not force it to do so with all combinations of $ and ().

How do I get the for loop to iterate over these values?


Solution

  • You have to count the expansions. You have an extra expansion due to the call, that you are not taking into account.

    After the call runs and expands the variable, it will have an expanded value like this:

    test:
            echo test; \
            echo 1 2 3 4 5; \
            for i in 1 2 3 4 5; do \
                echo $i; \
            done
    

    Note how the $$i was reduced to $i. You can already see the problem. Now eval will turn this into a recipe, then when make runs the recipe it will expand the $i as a make variable, which is the empty string, so the recipe passed to the shell will be:

        echo test; \
        echo 1 2 3 4 5; \
        for i in 1 2 3 4 5; do \
            echo ; \
        done
    

    You need to double-escape anything you want to be passed to the shell, if you're going to expand it with call (there is some verbiage in the manual about this I believe):

    define create_target
    $(1):
            echo $(1); \
            echo $(2); \
            for i in $(2); do \
                echo $$$$i; \
            done
    endef
    

    When working with eval the best debugging you can do is to replace the eval call with info. This will have make print out exactly what text it will be evaluating:

    $(info $(call create_target, test, 1 2 3 4 5))
    $(eval $(call create_target, test, 1 2 3 4 5))