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

It's a constant thorn in my side when trying to read a makefile with nested logic, that Make does not allow indented if statements. Why is this, and is there a good way to work around this limitation, and still have readable makefiles?

Update: I now realise that this question is based on a false premise, but I believe that leaving it here may be valuable to anyone who makes the same mistake that I did.



Solution 1:[1]

I don't know why you are under the impression that indented conditionals aren't supported. They do seem to work fine when I use them in the following example:

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

NOTE: the TABS in my example will probably not survive copy & paste. So you'll have to re-enter them for the recipes.

Test run:

$ 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

But...

There is one case where indentation can lead to problems. To quote the 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.

As GNU make takes all TAB indented lines after a rule to be part of the recipe for the rule the following will fail for 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

Solution

  1. organize the makefile to have conditionals first, then rules, i.e. no TAB indentation after rules anymore except for recipes, or
  2. always make sure to use SPACEs for conditional, variable assignment and rule lines.
  3. set .RECIPEPREFIX to a non-whitespace character, e.g. > and use that to indicate recipe lines.1

Unless you have an editor which shows the difference between TABs and SPACEs, alternative 2 will probably drive you insane. I would suggest alternative 1 instead...

The following works for 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 you might be tempted to set .RECIPEPREFIX to SPACE like this:

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

and then switch your editor to use only SPACEs. But that makes things worse, i.e. now make can't distinguish between normal and recipe indentation. If you try this with the above example you will note that it now fails for any invocation that enables one of the indented rules.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1