为什么 Make 不允许缩进 "if" 语句?

Why doesn't Make allow indented "if" statements?

当我尝试读取带有嵌套逻辑的 makefile 时,这一直是我的痛处,Make 不允许缩进 if 语句.为什么会这样,是否有解决此限制的好方法,并且仍然具有可读的 makefile?

更新:我现在意识到这个问题是基于一个错误的前提,但我相信把它留在这里可能对任何犯过我同样错误的人有价值.

我不知道为什么您认为不支持缩进条件。当我在以下示例中使用它们时,它们似乎工作正常:

.PHONY: all
all:

CONFIGS :=

ifeq ($(CONFIG1),1)
  $(info CONFIG1 selected)

  CONFIGS += config1
  all: config1

  config1:
    @echo $@


  ifeq ($(CONFIG2),1)
    $(info CONFIG2 selected)

    CONFIGS += config2
    all: config2

    config2:
    @echo $@

  else
    $(info CONFIG2 not selected)
  endif
else
  $(info CONFIG1 NOT selected)
endif

all:
    @echo "all: $(CONFIGS)"

注意:我示例中的 TABS 可能无法在复制和粘贴后保留下来。所以你必须重新输入它们的食谱。

测试运行:

$ make
CONFIG1 NOT selected
all:

$ make CONFIG1=1
CONFIG1 selected
CONFIG2 not selected
config1
all:  config1

$ make CONFIG1=1 CONFIG2=1
CONFIG1 selected
CONFIG2 selected
config1
config2
all:  config1 config2

但是...

有一种情况是缩进会导致问题。引用 GNU make manual:

A recipe is an action that make carries out. A recipe may have more than one command, either on the same line or each on its own line. Please note: you need to put a tab character at the beginning of every recipe line! This is an obscurity that catches the unwary.

由于 GNU make 在规则之后采用 所有 TAB 缩进行 作为规则配方的一部分,因此以下内容对于 make CONFIG1=1 将失败:

.PHONY: all
all:

CONFIGS :=

config1:
# TAB in the following line
    @echo $@

# the following lines are indented with TABs
    ifeq ($(CONFIG1),1)
    CONFIGS += config1
    test1:
        @echo $@
    endif

ifeq ($(CONFIG1),1)
all: config1
endif

all:
# TAB in the following line
    @echo "all: $(CONFIGS)"
$ make CONFIG1=1
config1
ifeq (1,1)
/bin/sh: -c: line 0: syntax error near unexpected token `1,1'
/bin/sh: -c: line 0: `ifeq (1,1)'
make: *** [Makefile:9: config1] Error 1

解决方案

  1. 组织 makefile 先有条件,然后有规则,即规则后不再有 TAB 缩进,除了配方,或
  2. 始终确保将 SPACE 用于条件、变量赋值和规则行。
  3. .RECIPEPREFIX 设置为 非空白 字符,例如> 并用它来表示配方行。1

除非您有一个显示 TAB 和 SPACEs 之间区别的编辑器,否则备选方案 2 可能会让您发疯。我会建议替代方案 1...

以下适用于 make CONFIG2=1

.PHONY: all
all:

CONFIGS :=

config2:
# TAB in the following line
    @echo $@

# the following lines are indented with SPACES
    ifeq ($(CONFIG2),1)
    CONFIGS += config2
    test2:
# 2 TABs in the following line
        @echo $@
    endif

ifeq ($(CONFIG2),1)
all: config2
endif

all:
# TAB in the following line
    @echo "all: $(CONFIGS)"
$ make CONFIG2=1
config2
all:  config2

1 您可能会想像这样将 .RECIPEPREFIX 设置为 SPACE:

_empty        :=
_space        := $(_empty) $(_empty)
.RECIPEPREFIX := $(_space)

然后将您的编辑器切换为仅使用 SPACEs。但这让事情变得更糟,即现在 make 无法区分正常缩进和配方缩进。如果您使用上面的示例尝试此操作,您会注意到它现在对于启用缩进规则之一的任何调用都会失败。

感谢其他人的帮助,我现在意识到我的问题是在错误的前提下提出的。 Makefiles 绝对 do 允许缩进 if 语句,或者更准确地说是缩进条件语句。他们不允许的 - 至少开箱即用 - 是 tabbed 条件。这是因为,默认情况下,Maketabs 解释为特别有意义的字符。几乎所有以制表符开头的行都被解释为 recipe 的一部分。因此,任何 not 旨在成为食谱一部分的行 - 例如条件 - 应该 not 以制表符开头。

至于回答我的问题为什么他们选择以这种方式使用制表符的部分,我还没有找到答案。也许设计者打算少用条件语句。

至于解决方法,在这里我将尝试描述几个。

如果您没有显示 whitespace 字符的编辑器,第一个解决方案会非常痛苦,但如果有,最简单的方法可能就是添加一些 空格 来缩进你的非配方代码。这是一个相当骇人听闻的解决方法,而且可能是不明智的。

另一个解决方案(由@Stefan Becker 提供)是将 special variable.RECIPEPREFIX 设置为制表符以外的字符。这是我尝试过的示例:

.RECIPEPREFIX := >
# Now, all recipes will begin with the > character rather than a tab.

things = something another_thing something_else nothing
nothing = true

something: another_thing something_else
# See how each line of a recipe now begins with >.
# You can see I also added a tab after the >. 
# These tabs doesn't mean anything to Make; it's just for readability.
>   $(info Making $@.)
>   @touch $@

another_thing:
>   $(info Making $@.)
    # See also how lines like comments can be tabbed, 
    # but no longer add anything meaningful to recipes.
>   @touch $@

something_else:
>   $(info Making $@.)
>   @touch $@
    # And just to prove the situation with conditionals is resolved...
    # See how the @touch command begins with the new RECIPEPREFIX 
    # but the conditionals don't.
    ifeq ($(nothing),true)
>       $(info Also making nothing, because nothing is true.)
>       @touch nothing
    endif

.PHONY: everything_clean
everything_clean:
>   $(info Cleaning up everything.)
>   rm -f $(things)

值得记住的一件事是食谱行必须以新的RECIPEPREFIX开始。也就是说,像这样 不会 工作:

something: another_thing something_else
# Remember that the RECIPEPREFIX must come first. 
# Indenting your recipe lines first and then using the RECIPEPRIFX will not work.
    >$(info Making $@.)
    >@touch $@