输入包装foreach的自定义定义后如何扩展变量?

How to expand variables after entering custom define that wraps foreach?

我在 makefile 中有许多非常相似的 $(foreach..) 循环,它们的工作方式类似于下面的 target_old 目标:

define NL


endef
extras=some/path/
vars=a b c

all: target_old target_new

target_old:
        # foreach and some multiple evals inside and some multiple commands inside
        $(foreach var, ${vars}, \
                $(eval extra := ${extras}${var}) \
                @echo var is ${var} and extras is ${extra}$(NL) \
        )


# my try to wrap it in a function
define foreach_vars
        $(foreach var, ${vars},
                $(eval extra := ${extras}${var}) \
                $(NL) \
        )
endef

target_new:
        @echo this is wrong:
        $(call foreach_vars, \
                @echo var is ${var} and extras is ${extra} \
        )

我有很多多个这样的 foreach 循环,在 foreach 中有所有相同的 eval。所以我想在我自己的 foreach_vars 函数中用 eval 包装 foreach 循环。所以我不必 $(eval extra := ${extras}${var}) 在每个 foreach 调用中。我创建了 target_new 目标来测试它。我希望两个目标的输出相同,make target_old 打印:

$ make target_old
var is a and extras is some/path/a
var is b and extras is some/path/b
var is c and extras is some/path/c

但是 target_new 没有从循环内部选择 ${var}${var} 只是展开为空:

$ make target_new
this is wrong:
var is and extras is
var is and extras is
var is and extras is

我猜这是因为扩展发生在进入 $(call...) 之前。有什么方法可以用来 "defer" 在 $(call...) 调用中扩展参数,直到在我的函数中 foreach 内?是否可以在 make 中编写自定义的类似 foreach 的宏?是否只有其他方法可用于实现此类功能?

是的,您的问题来自扩展,这些扩展没有按您希望的时间和顺序发生。

您对 make 的使用非常不寻常,因为您在通常很简单的食谱中使用 make 构造(foreachevalcall...)shell.我猜你有一个很好的理由,但如果你将 make 世界和 shell 世界分开,会不会容易得多?像下面这样,例如:

extras := some/path/
vars   := a b c

target_old:
    @for var in $(vars); do \
       extra=$(extras)$${var}; \
       echo var is $${var} and extra is $${extra}; \
     done

它使用make变量(varsextras)和shell变量(extravar)。食谱很简单shell。注意 $$ 用于逃避 make 的第一次扩展,这样 shell 扩展 ${xxx} 由 shell 完成。另请注意构成单行配方的行延续 (\),尽管看起来如此。由于 make 配方的每一行都由单独的 shell 执行,因此需要在 shell 脚本的命令之间传递 shell 变量。

如果你愿意,你也可以将 shell for 循环包装在一个 make 递归扩展的变量中:

for = for var in $(vars); do $(1); done

target_new:
    @$(call for,extra=$(extras)$${var}; echo var is $${var} and extra is $${extra})

${var} 立即展开,因此需要将其转义为 $${var}。这本身并不能解决问题,因为现在 </code> 包含文字 <code>${var},它不会在 foreach 中展开。我会做一个简单的 subst 来修复它,例如:

$ cat Makefile
define NL


endef
extras=some/path/
vars=a b c

define foreach_vars
        $(foreach var, ${vars},
                $(eval extra := ${extras}${var}) \
                $(subst $$(var),$(var), \
                $(subst $$(extra),$(extra), \
                $(1))) \
                $(NL) \
        )
endef

target_new:
        $(call foreach_vars, \
                @echo var is $$(var) and extras is $$(extra) \
        )

输出:

$ make target_new
var is a and extras is some/path/a
var is b and extras is some/path/b
var is c and extras is some/path/c

make 开始构建 target_new 时(例如当您键入 make target_new 时):

  • 它扩展了整个配方
    重要:配方在启动之前扩展shell

  • 对于生成的扩展的每一行,它一次传递一个到 shell

  • 的新调用

值得展示扩展 make 所做的令人痛苦的细节。我们有食谱:

@echo this is wrong:
$(call foreach_vars, \
    @echo var is ${var} and extras is ${extra} \
)
  • 首先,${var} 变为空,${extra}
  • make剩下$(call foreach_vars, @echo var is and extras is )。现在开始通话
    • 1 设置为 @echo var is and extras is
    • make 展开 $(foreach var, ${vars}, $(eval extra := ${extras}${var}) $(NL) )
    • ${vars}a b c
      • 第一次迭代:
        • var 设置为 a
        • 生成 求值extra := some/path/a
        • eval 的扩展是空的,我们只剩下 $(NL)(模一些空格),留下 @echo var is and extras is
      • 第二次迭代:${extra}变成some/path/b,我们又剩下@echo var is and extras is
      • 最后一次迭代:${extra} 变为 some/path/c,我们再次剩下 @echo var is and extras is

最后的配方:

@echo this is wrong:
@echo var is  and extras is
@echo var is  and extras is
@echo var is  and extras is

产生您描述的输出。

那么如何避免参数过早膨胀呢? 一旦好的解决方案是将您想要的命令行粘贴到变量中, 并改为传递该变量的名称。

define foreach_vars # 1: variable containing command-line
    $(foreach var,${vars},
            $(eval extra := ${extras}${var}) \
            ${}$(NL) \
    )
endef

cmds<target_new> = @echo var is ${var} and extras is ${extra}

target_new:
    @echo this is right:
    $(call foreach_vars,cmds<$@>)

为什么要将变量名称与目标名称混淆?查找表很好,您可能会发现许多目标都以相同的配方结束。

cmds<target_new> = @echo var is ${var} and extras is ${extra}
cmds<target_beta> = ${MAKE} ${var}-${extra}
cmds<target_release> = script ${var} | eat ${extra}

target_new target_beta target_release:
    $(call foreach_vars,cmds<$@>)

等等