下面是我的测试Makefile:
VERSION ?= $(shell cat VERSION | grep "VERSION" | cut -d'=' -f2)
VERSION_MAJOR ?= $(shell echo $(VERSION) | cut -d'.' -f1)
VERSION_MINOR ?= $(shell echo $(VERSION) | cut -d'.' -f2)
VERSION_PATCH ?= $(shell echo $(VERSION) | cut -d'.' -f3)
DATE ?= "$(shell date +"%Y-%m-%dT%H:%M")"
SOME_NESTED ?= "-X=aaa=$(VERSION_MAJOR) -X=bbb=$(VERSION_MINOR) -X=ccc=$(VERSION_PATCH) -X=ddd=$(DATE)"
#.EXPORT_ALL_VARIABLES:
t:
echo "Test $(SOME_NESTED)"
字符串
测试文件VERSION
:
echo "VERSION=1.2.3" > VERSION
型
这个makefile工作得很好,直到我用shell调用导出变量。
# use the original makefile
$ time make t
echo "Test "-X=aaa=1 -X=bbb=2 -X=ccc=3 -X=ddd="2023-10-26T10:00"""
Test -X=aaa=1 -X=bbb=2 -X=ccc=3 -X=ddd=2023-10-26T10:00
make t 0.02s user 0.00s system 116% cpu 0.021 total
# uncomment the .EXPORT_ALL_VARIABLES
$ time make t
echo "Test "-X=aaa=1 -X=bbb=2 -X=ccc=3 -X=ddd="2023-10-26T10:09"""
### (hanged for seconds here)
Test -X=aaa=1 -X=bbb=2 -X=ccc=3 -X=ddd=2023-10-26T10:09
make t 5.15s user 0.41s system 111% cpu 4.979 total
型
取消注解.EXPORT_ALL_VARIABLES
,命令需要5秒执行。
使用make -d t
,我可以看到这样的大量日志:
Makefile:1: not recursively expanding VERSION to export to shell function
Makefile:8: not recursively expanding SOME_NESTED to export to shell function
Makefile:2: not recursively expanding VERSION_MAJOR to export to shell function
Makefile:3: not recursively expanding VERSION_MINOR to export to shell function
Makefile:4: not recursively expanding VERSION_PATCH to export to shell function
Makefile:6: not recursively expanding DATE to export to shell function
Makefile:2: not recursively expanding VERSION_MAJOR to export to shell function
Makefile:3: not recursively expanding VERSION_MINOR to export to shell function
Makefile:1: not recursively expanding VERSION to export to shell function
Makefile:6: not recursively expanding DATE to export to shell function
Makefile:1: not recursively expanding VERSION to export to shell function
Makefile:2: not recursively expanding VERSION_MAJOR to export to shell function
Makefile:3: not recursively expanding VERSION_MINOR to export to shell function
Makefile:4: not recursively expanding VERSION_PATCH to export to shell function
Makefile:6: not recursively expanding DATE to export to shell function
Makefile:2: not recursively expanding VERSION_MAJOR to export to shell function
Makefile:3: not recursively expanding VERSION_MINOR to export to shell function
Makefile:4: not recursively expanding VERSION_PATCH to export to shell function
型
看起来make
在这里做了一些递归的事情。
我注意到要执行的命令echo "Test "-X=aaa=1 -X=bbb=2 -X=ccc=3 -X=ddd="2023-10-26T10:09"""
显示的延迟很小,但实际输出延迟了几秒钟。
我是不是用了一些不好的方法?为什么会这样?
我的制作版本:
$ make --version
GNU Make 4.4.1
Built for x86_64-pc-linux-gnu
Copyright (C) 1988-2023 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <https://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
型
2条答案
按热度按时间uurv41yg1#
如果一个
make
变量还没有值,那么用?=
设置它会使它成为一个 * 递归扩展 * 的变量。这意味着在变量本身被扩展之前,它的值中的变量和函数引用不会被扩展。相反,这是递归地完成的,* 每次 * 变量本身被扩展。这是make
变量的传统处理。例如,当您的
VERSION
变量具有递归扩展的值$(shell cat VERSION | grep "VERSION" | cut -d'=' -f2)
时,每次出现$(VERSION)
都会导致make
启动一个shell,并在其中运行cat
、grep
和cut
。这些活动在交互式用户的时间尺度上是快速的,但是它们仍然足够慢,因此通过执行足够的迭代来消耗不平凡的时间并不太困难。即使没有
.EXPORT_ALL_VARIABLES
,您也会执行许多迭代:为了扩展$(SOME_NESTED)
一次,我计算了8个shell,8个cuts
,4个cats
,4个greps
和1个date
(以及4个echo
s,但由于是内置shell,这些可能便宜得多)。但是如果你
.EXPORT_ALL_VARIABLES
,那么make
在每次启动子进程时都会扩展你的每个变量。这包括每个配方的每一行,以及shell
函数的每次运行。即使make
足够聪明,能够识别并打破其中固有的递归,你仍然大量增加了正在运行的进程的数量。我是不是用了一些不好的方法?为什么会这样?
在高层次上,你试图把makefile写得像脚本一样。这是一个导致各种麻烦的基本错误,尽管你经常可以让这样的东西工作。
从策略的Angular 来看,导出 * 所有 * 变量可能是不明智的,但如果你真的想导出所涉及的特定变量,那就没有意义了。
在细节层面上,GNU
make
“函数”,如$(shell)
,是POSIXmake
的扩展。如果你想要可移植性,那么使用任何一个都是错误的。即使可移植性对你来说不是一个问题,大多数GNU扩展最好少用和小心使用,$(shell)
尤其如此。假设你不想对变量进行延迟递归扩展,你可以通过使用另一个扩展来拯救你的性能,通过使用
:=
赋值给变量,使变量 * 立即扩展 *,如下所示:字符串
然而,这破坏了条件赋值的语义。如果你想保留从环境(或从同一个makefile中的早期赋值)接受所有这些变量的值的行为,同时也给它们立即扩展的值,那么你需要使用另一个GNU扩展:条件。例如,
型
但即使这样也不能完全达到你的目标,因为一个从环境中获取值的变量是递归扩展的,并且可以包含变量和函数引用(讨厌!)。如果你想绝对确保这些变量是递归扩展的,那么你需要对每个变量都遵循这个模型:
型
可选
做这类事情的传统方法是
make
之前使用某种准备脚本,该脚本可以创建数据文件,甚至是具有适当内容的makefile本身。例如,基于Autotools的项目的configure
脚本。和/或
make
将构建时生成的内容存储在文件中而不是变量中。示例:型
沿着,好好利用(在配方中)构建环境中可以依赖的标准工具。例如,
awk
。nr9pn0ug2#
假设你正在使用gnu make,另一个选择是使用make结构:
字符串