GNU Make - 在规则/目标中从shell命令输出中设置MAKEFILE变量

4

我正在努力编写一些复杂的makefile规则,以自动化使用多个编译器构建项目。我有一个规则来创建一些动态生成的变量并将变量赋值给它们,然后将这些变量传递给调用另一个规则来构建的命令。

.PHONY: all
all:
    @echo "Detected CPULIST:${CPULIST_DETECTED}"
    @echo "CPULIST:${CPULIST}"
    @for cpu in $(CPULIST_DETECTED); do                   \
        echo "CPU:$${cpu}:";                                \
        eval VARIANTLIST_DETECTED=$(shell 2>&1 find ./.build/linux/$$cpu -mindepth 1 -maxdepth 1 | grep -v '\.svn' |  grep -v warning ); \
        eval echo "Detected Variant List:$${VARIANTLIST_DETECTED}"; \
        eval variant_$${cpu}=$${cpu};                      \
        eval echo "variant_\$${cpu}:\$${variant_$${cpu}}"; \
        $(MAKE) build CPULIST=$${cpu};                     \
    done

.PHONY: build
build: sanity_check $(TARGET)
    @true

我遇到了两个问题。第一个问题是,即使通过$$cpu双重转义cpu,它在以下行中仍然被翻译成null:

eval VARIANTLIST_DETECTED=$(shell 2>&1 find ./.build/linux/$$cpu -mindepth 1 -maxdepth 1 | grep -v '\.svn' |  grep -v warning ); \

因此,find命令在每次循环中针对./.build/linux/进行搜索,而不是像我预期的那样循环遍历./.build/linux/arm./.build/linux/x86。在下面的参考文献中,我已经包含了一篇描述这种情况的文章。我怀疑这可能会导致问题,因为我正在规则本身内部尝试执行此操作,而不是在makefile的全局赋值部分(规则之前)中执行。
另一个问题发生在完全相同的行上。似乎shell命令被计算出来,并赋值给VARIANTLIST_DETECTED,但然后VARIANTLIST_DETECTED被执行,就好像它是一个命令一样,因为我在构建过程中收到以下错误:
Detected CPULIST:arm x86
CPULIST:
CPU:arm:
/bin/sh: 3: ./.build/linux/x86: Permission denied
Detected Variant List:
variant_arm:arm

它试图将我的查询结果中的第一个结果作为命令运行。该行也应该类似于./.build/linux/x86/so
如何解决这两个问题?它们是我 makefiles 完成的最后两个难点。
谢谢!
参考:
1.《将 makefile 变量值分配给 bash 命令结果?》, 2014-06-19 访问, <https://dev59.com/HXE95IYBdhLWcg3wSsCD>
2个回答

7
你需要稍微退后一步,思考一下这里发生了什么:你似乎只是把东西扔进你的 makefile 中,并希望结果是你想要的,而不是真正理解正在发生的事情并采取目的明确的方法来解决问题。首先要理解的是 make 如何调用配方:首先,make 展开配方 。展开意味着 make 查看配方文本并查找以 $ 开头的任何内容,并将其视为 make 函数或变量,并评估它。因此,$$ 被转换为单个 $$(CPU_LIST_DETECTED) 扩展为该变量的值,$(shell ....) 是一个 make 函数,运行并将结果包含在配方文本中。也许你已经看到你的配方出了什么问题...

然后第二,在 所有 配方都被展开之后,make 将配方的展开文本传递给 shell。然后 make 等待 shell 完成,并查看 shell 的退出代码。如果是 0,则配方成功,make 继续进行。如果不是 0,则配方失败,make 会带着错误退出。在 make 和 shell 之间没有交互,除了 make 用脚本启动它并获取退出代码。如果之前你看不出来,也许现在你可以看到你的配方出了什么问题。

在配方内部使用 $(shell ...) 函数几乎总是错误(或者至少是不必要的)......配方已经在 shell 中运行了!你不需要让 make 先运行另一个 shell,然后将那些结果提供给新 shell。

你的 shell 调用尝试使用 shell 变量 $cpu。但是当 make 运行 $(shell ...) 函数时,该变量在此处没有值,因为 make 在启动 shell 脚本之前运行该函数。你不能在 make 函数中引用那些在运行配方时才定义的东西。这将运行 shell 脚本 2>&1 find ./.build/linux/$cpu -mindepth 1 -maxdepth 1 | grep -v '\.svn' | grep -v warning,但是 shell 变量 cpu 没有设置,因此查找将递归处理 ./.build/linux/ 下的所有内容。

其次,你在这里错误地使用 eval。 shell 函数扩展为一系列单词,比如 foo bar baz。然后,eval 将如下所示:

eval VARIANTLIST_DETECTED=foo bar baz

这与仅写入以下内容相同:

VARIANTLIST_DETECTED=foo bar baz

这意味着在将变量 VARIANTLIST_DETECTED 设置为值 foo 后,使用参数 baz 运行命令 bar。这与“运行您的目录”相同,因此它看起来像是这样。您可能想要这个:
VARIANTLIST_DETECTED="foo bar baz"

(注意引号)。然而,通过 eval 获取报价很棘手,你需要对它们进行转义。幸运的是,在这里根本不需要使用 eval ,因为您并不想要分配一个动态命名的变量。您只需直接编写该赋值即可。

因此,丢弃shell函数和eval,该行应该写成:

VARIANTLIST_DETECTED=`2>&1 find ./.build/linux/$$cpu -mindepth 1 -maxdepth 1 | grep -v '\.svn' |  grep -v warning`; \

同样地,对于echo命令您不需要使用eval。
然而,我真的看不出设定这个变量或者variant_$cpu变量的意义。因为您从未使用过它们。

我想说的是,你正在设置_shell_变量VARIANTLIST_DETECTEDvariant_$cpu。这些都是本地shell变量。然后你运行一个子make,并且没有以任何方式将这些变量传递给子make。因此,设置它们是完全无用的:即使你从未设置这些变量,你的makefile也会以完全相同的方式工作。除非你所做的事情比你展示的更多;如果是这样,如果你展示你真正想做什么,我们将能够给你更好的建议。 - MadScientist

2

一般来说,尽量避免在命令中嵌入Make语法。可以尝试以下方式:

VARIANTLIST_DETECTED=$$(find ./.build/linux/$${cpu} -mindepth 1 -maxdepth 1 | grep -v '.svn' |  grep -v warning );

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接