我正在尝试将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
我找不到有关此行为的任何文档,因此感谢您的任何建议。
>编辑:- 添加了
all
和test
目标。
背景:
GNU make命令是打包和部署软件的工具链的重要组成部分,因此在系统管理员和DevOps工程师的日常工作中很重要。
- Debian和RPM打包使用GNU make来打包软件。 生成文件驱动的打包 它运行
./configure && make && make install
命令序列。 - Travis CI工作流使用GNU make来运行测试套件。 C 语言自动化测试 它运行
./configure && make && make test
序列。
所有完全不同的用例都由同一个 Makefile管理。 现在,对于我的具体用例,我正在努力设置Travis CI工作流序列,以便为我的静态链接源代码库启用自动测试。 因此,与打包序列相反,自动化测试序列需要调试功能和高级输出评估来生成有意义的测试。 我希望测试检查错误报告和内存使用情况报告,以提醒我任何隐藏的错误。
使用关于在目标声明行设置变量的建议,我能够更改test
、example
和foo
目标的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 手册中,他们设置了一个名为libs
的make变量(他们在任何配方之外分配变量)。
在您的用法中,您正在分配一个名为libs
的shell变量(您在配方中分配它,用 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 test
Target 正常工作.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条件进行测试,以用true
或false
填充辅助变量${is_error}
,然后可以在GNU make中进行检查。
test
目标不能在出错时退出。
为了对失败的自动测试进行故障排除,查看退出代码、错误消息和堆报告至关重要。