【问题标题】:How to implement makefile goal which relies on other makefile goal being invoked or not如何实现依赖于是否调用其他makefile目标的makefile目标
【发布时间】:2017-11-20 01:02:33
【问题描述】:

我的原始用例相当复杂,但可以提炼为以下内容:

a:
    export AAA=true

b:
    echo ${AAA}
ifdef AAA 
    echo "AAA mode is on."
else
    echo "AAA mode is off."
endif

我想要实现的是有条件的b 目标取决于a 是否被调用。

但是行为并不直观,而 AAA=true make b 确实可以工作 make a b 不能按预期工作(我更喜欢后者)。

任何有用的建议将不胜感激!

【问题讨论】:

  • 你不能;配方在不同的进程中执行,您只能将环境变量从父级传递给子级,而不能从兄弟级传递给兄弟级。
  • "是否调用了a..." 你的意思是a 是否已经被执行,或者a 是否在Make 的要重建的目标列表中,或者其他什么? 为什么您希望b 规则的行为以这种方式变化?这可能是 XY 问题。
  • > "a 是否在 Make 的要重建的目标列表中" ^ 这是我的场景 > "这可能是 XY 问题" ^ 有什么好的链接可以说明什么是 XY 问题?
  • 这确实是我第一次被谷歌击中:meta.stackexchange.com/questions/66377/what-is-the-xy-problem
  • 哦,总有一天我会在提问之前学会使用谷歌。谢谢@tripleee

标签: bash unix makefile


【解决方案1】:

如果您愿意限制自己使用 GNU make,您可以使用 eval 函数。我通常认为这不是一个好主意,我同意 Beta 的观点,即这可能是一个 XY 问题,但是:

AAA = false

a:
        $(eval AAA = true)

b:
        echo ${AAA}

请注意,您绝对不能使用像 ifeqifdef 这样的 make 预处理器语句:这些语句在解析 makefile 时进行评估,即使是包含在配方,这显然发生在任何可能设置 AAA 等的配方被调用之前。

预计到达时间:

以下是上述 makefile 的示例,以显示它的工作原理:

$ make b
false

$ make b a
false

$ make a b
true

如 cmets 其他地方所述,AAA 的值在 b 的配方中被重置,IFFa 的配方已被调用。这就是我解释您的请求的方式我想要实现的是有条件的b 目标取决于是否调用了a,但也许您有不同的行为。

【讨论】:

  • 好的,拿回来。当涉及到我的场景等时,这个答案确实是“全面的”。另一种解决方案是依赖${MAKECMDGOALS},但它要求makefile目标来自用户输入(这是我的用例,但我在提问时没有明确提及)。
【解决方案2】:

好的,我正在尝试使用 evals(没用)和文件标记(不理想,因为我有 makefile 的层次结构,其中一些被调用了两次,因此$(shell rm -f .target-marker) 被调用了多次),我认为适合我的使用最好的情况是探索当前调用的目标:

a:
    #market target

b:
    @if [[ "${MAKECMDGOALS}" = a* ]]; then \
        echo "AAA mode is on"; \
    else \
        echo "AAA mode is off"; \
    fi; 

MAKECMDGOALS 包含所有目标,我可以简单地检查某个目标是否在列表中。

【讨论】:

  • 这个解决方案有很多问题。但在此之前,这正是我们所说的“XY 问题”。您说“条件目标b 取决于是否调用了a”,但没有说必须在make 命令行上指定a。知道 makefile 中的某个随机目标是否运行比知道用户是否在命令行上指定了目标要困难得多。
  • 除此之外,这是非常不可移植的:您在食谱中使用了 bash 特定的功能,但 make 总是调用 /bin/sh。如果您在/bin/sh 不是 bash 的任何系统上运行此 makefile,它将无法工作。如果你想使用这个解决方案,你必须在你的 makefile 中专门设置SHELL = /bin/bash。除此之外,这仅在用户指定a 作为命令行上的第一个目标时才有效:如果他们运行make x a b 会怎样?或者如果使用另一个以a 开头的目标怎么办?你应该使用这样的 make 函数:@if [ "$(filter a,$(MAKECMDGOALS))" = a ]; then ...
  • 仅供参考,eval 解决方案绝对有效。也许您的 GNU make 版本太旧了? eval 是在 2002 年发布的 GNU make 3.80 中引入的。这与此解决方案之间的另一个区别:eval 版本仅在实际调用了 a 目标的配方时才有效(因为 a 没有启动迄今为止),这正是您所要求的。但是,如果用户在命令行上指定了目标,MAKECMDGOALS 版本将匹配,即使它是最新的,因此未调用配方。也许这就是你想要的,但你的问题是“a 被调用”。干杯!
  • @MadScientist #1 - 好的,“用户指定”的目标是隐含在我脑海中的。很抱歉,我应该更明确地说它是用户驱动器的选择。
  • @MadScientist #2 - 请将此作为参考示例。实际目标名称更具描述性,可以调整正则表达式以允许make b withA 调用方式的模式匹配。虽然 #1 是合法评论,但我认为第二部分 #2 是挑剔的。至于 bash 特定的 - 我可以接受这个限制。
【解决方案3】:

您不能将一个外壳的环境暴露给另一个外壳,但您可以使用标志文件。

# Kludge: Force removal when make starts
$(shell rm -f .done-a)

a:
    : things
    touch .done-a

b:
    if [ -e .done-a ]; then : stuff ...;  \
    else : other stuff ...; \
    fi

【讨论】:

    【解决方案4】:

    您的主题有两个变体,只有一个定义。正如@tripleee 所说,您不能传递外部变量,但您可以在makefile 中有一个通用定义。我也尽量避免空壳变量。在这种情况下,可以使用直接的真/假值。

    #last 定义“获胜”
    AAA=假
    AAA=真
    
    A:
        出口 AAA=$(AAA)
    
    乙:
        出口 AAA=$(AAA)
        回声 $${AAA}; #note shell 命令中的双倍美元
        $$AAA && echo "AAA 模式开启。" || echo "AAA 模式已关闭。"
    
    嵌套_c:
        $(制作) AAA=$(AAA) c
    

    【讨论】: