生成文件 - 根据条件分配变量



我正在尝试将GNUmake官方文档中的示例改编为我的用例: GNU make - 条件示例

libs_for_gcc = -lgnu
normal_libs =
ifeq ($(CC),gcc)
libs=$(libs_for_gcc)
else
libs=$(normal_libs)
endif
foo: $(objects)
$(CC) -o foo $(objects) $(libs)

因此,我创建了这个原型:

libs_for_gcc="gcc libs"
normal_libs="normal libs"
libs=

all: do_nothing
@echo "all: done."

do_nothing:
@echo "do_nothing: done."

example:
@echo "example: go ..."
@echo "example - cc: '$(CC)'"
@echo "libs_for_gcc: $(libs_for_gcc)"
@echo "normal_libs: $(normal_libs)"
@echo "libs: $(libs)"
ifeq ($(CC),gcc)
libs=$(libs_for_gcc) && echo "libs: '$$libs'"
@echo "example - libs: '$(libs)'"
else
libs=$(normal_libs) && echo "libs: '$$libs'"
@echo example - libs: $(libs)
endif
@echo "example - libs: '$(libs)'"
@echo "example: done."

test: libs += " -Ddebug"
test: example foo

bar:
@echo "bar: go ..."
ifeq (${libs}, "")
@echo "bar - libs: empty"
@echo "assigning libs"
libs=$(libs_for_gcc)
else
@echo "bar - libs: not empty"
@echo "bar - libs: '${libs}'"
endif
@echo "bar - libs: '${libs}'"
@echo "bar: done."

foo: 
@echo "foo: go ..."
ifneq ("$(libs)", "")
@echo "foo - libs: not empty"
@echo "foo - libs: '$(libs)'"
else
@echo "foo - libs: empty"
endif
@echo "foo: done."

现在,当我使用$ make运行默认目标时 它只会产生:

$ make
example: go ...
example - cc: 'cc'
libs_for_gcc: gcc libs
normal_libs: normal libs
libs: 
libs="normal libs"
example - libs:
example - libs: ''
example: done.

我看到libs的值没有按预期更改。

当我运行make bar目标时,它会产生:

$ make bar
bar: go ...
bar - libs: not empty
bar - libs: ''
bar - libs: ''
bar: done.

在这里libs不是空的,而是里面什么都没有

当我运行make foo目标时,它会产生:

$ make foo
foo: go ...
foo - libs: empty
foo: done.

这里libs被理解为

当我看到libs没有正确更改时,我尝试将语法更改为:

example:
# [...]
ifeq ($(CC),gcc)
libs := $(libs_for_gcc)
@echo "example - libs: '$(libs)'"
else
libs := $(normal_libs)
@echo example - libs: $(libs)
endif

但是后来我得到GNU错误

$ make
example: go ...
example - cc: 'cc'
libs_for_gcc: gcc libs
normal_libs: normal libs
libs: 
libs := "normal libs"
/bin/sh: 1: libs: not found
Makefile:7: recipe for target 'example' failed
make: *** [example] Error 127

我找不到有关此行为的任何文档,因此感谢您的任何建议。

>编辑:
  • 添加了alltest目标。
<小时 />

背景:

GNU make命令是打包和部署软件的工具链的重要组成部分,因此在系统管理员和DevOps工程师的日常工作中很重要。

  • DebianRPM打包使用GNU make来打包软件。 生成文件驱动的打包 它运行./configure && make && make install命令序列。
  • Travis CI工作流使用GNU make来运行测试套件。 C 语言自动化测试 它运行./configure && make && make test序列。

所有完全不同的用例都由同一个 Makefile管理。 现在,对于我的具体用例,我正在努力设置Travis CI工作流序列,以便为我的静态链接源代码库启用自动测试。 因此,与打包序列相反,自动化测试序列需要调试功能和高级输出评估来生成有意义的测试。 我希望测试检查错误报告和内存使用情况报告,以提醒我任何隐藏的错误。

使用关于在目标声明行设置变量的建议,我能够更改testexamplefoo目标的libs。 我还看到了关于Bash变量libs的重要提示,它只在同一行有效:

$ make
do_nothing: done.
all: done.
$ make test
example: go ...
example - cc: 'cc'
libs_for_gcc: gcc libs
normal_libs: normal libs
libs:  -Ddebug
libs="normal libs" && echo "libs: '$libs'" ;
libs: 'normal libs'
example - libs:  -Ddebug
example - libs: ' -Ddebug'
example: done.
foo: go ...
foo - libs: empty
foo - libs: ' -Ddebug'
foo: done.

配方libs=$(libs_for_gcc) && echo "libs: '$$libs'"显示创建了一个新的Bash变量libs,它不会影响GNU make变量。

条件ifneq ($(libs),)仍然无法检测到已为test目标设置libs

您的示例与 GNU make 手册中的示例之间的区别在于,在 GNU make 手册中,他们设置了一个名为libsmake变量(他们在任何配方之外分配变量)。

在您的用法中,您正在分配一个名为libsshell变量(您在配方中分配它,用 TAB 缩进)。

这就是为什么当您尝试使用:=时出现错误的原因,因为这是一个make赋值,不是有效的shell赋值。

然后在您的echo中打印根本没有设置的make变量$(libs)。 此外,配方的每个逻辑行都在它自己的外壳内运行。 因此,您运行的等效项为:

/bin/sh -c 'libs="gcc libs"'
/bin/sh -c 'echo '

因此,即使您确实更改了 echo 命令以打印 shell 变量(通过$$libs),它也将是空的,因为设置它的上一个 shell 已经退出。

您希望使用与示例中相同的语法:从配方中获取变量 OUT 的赋值并改为设置 make 变量:

libs_for_gcc = gcc libs
normal_libs = normal libs
ifeq ($(CC),gcc)
libs = $(libs_for_gcc)
else
libs = $(normal_libs)
endif
example:
@echo "example: go ..."
@echo "example - cc: '$(CC)'"
@echo "libs_for_gcc: $(libs_for_gcc)"
@echo "normal_libs: $(normal_libs)"
@echo "libs: $(libs)"
@echo "example - libs: '$(libs)'"
@echo "example: done."

最后我实现了让我的 Makefilemake testTarget 正常工作.
make test带有GNU make 的测试套件

关键是要理解有 3 种不同的上下文可以分配 GNU make 变量.
并且GNU make经常需要辅助函数,因为条件/em>>这些问题大多没有记录在案,只能通过尝试和错误并在StackOverflow.com上搜索来解决

1. 分配变量的第一个、最简单和最好的文档上下文是在全局上下文中,如官方文档和第一个答案所示.
GNU make- 条件示例

ifeq ($(CC),gcc)
libs=$(libs_for_gcc)
else
libs=$(normal_libs)
endif

全局变量${libs}具有全局可见性。

2.下一个不那么明显但仍记录在官方文档中的上下文是在目标定义行
GNU make- 特定于目标的变量值

test: libs += " -Ddebug"
test: example foo

此处${libs}test目标及其调用的所有依赖目标有效。
但唯一的条件是目标本身。

3.要在目标上下文中动态分配变量,请将$(eval )函数与$(shell )函数结合使用,如本StackOverflow.com所示 帖子:
分配变量作为命令的结果

test: examples_debug
# [...]
$(eval error := $(shell cat ./demo_error.log))
ifeq ($(strip $(error)),)
@$(ECHO) "NO Error: '${error}'"
else
@$(ECHO) "Error detected: '${error}'"
$(error "Demo failed with Error [Code: '${error_code}']")
@exit 1
endif

此处${error}是从文件中读取的。此外,$(strip )函数需要能够检查它是否为空,这是GNU制造的一些未记录的问题,并且对Bash开发人员来说很奇怪。

4.另一种有效但不使用Makefile变量且有些笨重的方法是在 Bash 中完全在1 行中评估变量,可以在以下位置找到: 使用Bash命令配方检查变量,之前的 Answer.> 也暗示

了它看起来像:
test: test_debug
# [...]
leak=`cat ./test_heap.log | grep -i "unfreed memory blocks" | awk '{print $$1}'` ; if [ $$leak -gt 1 ]; then echo "Memory Leaks: $$leak" && exit 1; else echo "Memory Leaks: NONE"; fi ;

这里$$leak是一个Bash变量,并且仅在同一行中有效。 一种类似于GitHub 工作流命令逻辑的方法。(实际上是直接从同一项目的GitHub工作流移植的)

至于条件评估GNU 中有许多未记录的问题需要Bash解决方法来实现目标.
如文档所述:
预处理 Makefile 评估的数值
比较数字存在问题,无法与0进行比较,这对于命令行应用程序来说非常重要的退出代码。 因此,解决方法看起来有点像:

test: test_debug
# Run the Testsuite Application
# [...]
$(eval error_code := $(shell export HEAPTRC="log=test_heap.log" ; echo -n "" >./test_heap.log ; ${wrkdir}/tests_hash-lists.dbg.run 2>./tests_error.log 1>./tests_exec.log ; echo "$$?"))
@$(ECHO) Application Tests: Execution finished with [${error_code}]
#Application Tests Execution Report
# [...]
$(eval is_error := $(shell if [ "${error_code}" = "0" ]; then echo false ; else echo true ; fi ;))
ifeq (${is_error}, true)
@$(ECHO) Exit Code non cero: '${error_code}'
@$(ECHO) "Tests failed with Error [Code: '${error_code}']"
@exit ${error_code}
endif

在这个用例中,${error_code}使用Bash条件进行测试,以用truefalse填充辅助变量${is_error},然后可以在GNU make中进行检查。

>讨论:

test目标不能在出错时退出。
为了对失败的自动测试进行故障排除,查看退出代码错误消息堆报告至关重要。

最新更新