【问题标题】:Makefile - Assigning Variable according to a ConditionalMakefile - 根据条件分配变量
【发布时间】:2020-12-05 16:25:21
【问题描述】:

我正在尝试将 GNU make 官方文档中的示例改编为我的用例: GNU make - Example of a Conditional

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 错误:

$ 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 来打包软件。 Makefile Driven Packaging 它运行./configure && make && make install 命令序列。
  • Travis CI 工作流程使用 GNU make 来运行测试套件。 C Language Automated Testing 它运行./configure && make && make test 序列。

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

使用关于在目标声明行设置变量的建议,我能够将libs 更改为testexamplefoo 目标。 我还看到了关于 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),) 仍然无法检测到 libs 已为 test 目标设置。

【问题讨论】:

    标签: makefile gnu-make


    【解决方案1】:

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

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

    这就是为什么您在尝试使用 := 时会出错,因为这是一个 make 分配,不是有效的 shell 分配。

    然后在您的echo 中打印根本没有设置的make 变量$(libs)。此外,配方的每个逻辑行都在它自己的 shell 中运行。所以你正在运行相当于:

    /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."
    

    【讨论】:

    • 我总是建议 ALL 让变量赋值在赋值运算符周围使用空格:这有助于提醒自己区别,因为 shell 变量不能在 = 周围有空格。而且,您不应该像在 shell 变量中那样在 make 变量中的整个值周围添加引号:make 变量不需要引号。
    • 感谢您的详细解释。我还注意到文档示例和我的用例之间的巨大差异。但现在在我的用例中,我需要 libs 根据每个 Target 具有不同的值。假设debugreleasetest 目标。如何更改每个 Targetlibs 的值?
    • 好吧,你必须描述你想怎么做:你没有在你的例子中展示你如何指定你想要的不同值的任何例子。处理此类事情的一种常见方法是通过构造宏名称。有关示例,请参阅此博客文章:make.mad-scientist.net/constructed-macro-names 另一种选择是使用特定于目标的变量:gnu.org/software/make/manual/html_node/Target_002dspecific.html
    • 谢谢,您对 Bash 变量 $$libs 和有限的有效性范围的解释非常有帮助。
    【解决方案2】:

    我终于实现了让我的 Makefile make test Target 工作。
    make test Test Suite with GNU make

    关键是要了解可以分配 GNU make 变量的 3 种不同上下文。
    GNU make 经常需要辅助函数,因为 Conditionals 存在一些奇怪的问题
    这些问题大多没有记录,只能通过 Try and Error 并在 StackOverflow.com

    上搜索来解决

    1. 如官方文档和第一个答案所示,分配变量的第一个、最简单且记录最好的上下文位于 全局上下文中。
    @ 987654322@

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

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

    2. 下一个不太明显但仍记录在官方文档中的上下文位于目标定义行
    GNU make - Target-specific Variable Values

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

    这里${libs}test 目标及其调用的所有相关目标有效。
    但唯一的条件是目标本身。

    3. 要在 Target 的上下文中动态分配变量,就是将 $(eval ) 函数与 $(shell ) 函数结合使用,如 StackOverflow.com 发帖:
    Assign a Variable as Result of a Command

    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 ) 函数需要能够检查它是否为 empty,这是 GNU make 存在并且对 感到奇怪的一些未记录问题Bash 开发人员。

    4. 另一种有效但不使用 Makefile 变量且体积较大的方法是完全在 Bash 中的 strong>1 单行,可在以下位置找到:Check Variable with Bash Command Recipe,并且在上一个答案中也有提示。
    看起来像:

    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 Workflow移植到同一个项目)

    关于条件评估GNU make 中有许多未记录的问题,需要 Bash 变通方法来实现目标。
    如记录在:
    Preprocessing Numerical Values for the Makefile Evaluation
    比较数字存在问题,无法与 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 进行测试.


    讨论:

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多