0

I want to define a variable differently depending on another variables value in a makefile. I thought using conditionals would solve the problem, like this in the makefile:

ifeq ($(BOOT_FLAG),installed)
        BOOT_TEST=$(BOOT_FLAG)
else
        BOOT_TEST=no
endif

BOOT_DEFINE=$(BOOT_FLAG)

BOOT_FLAG=installed

.PHONY: all
all:
        @echo $(BOOT_TEST)
        @echo $(BOOT_DEFINE)

I expected the output to be:

installed
installed

but I got this instead:

no
installed

apparently the ifeq does not expand the BOOT_FLAG to installed
but setting of the BOOT_DEFINE variable manages to expand it correctly.

I read in the manual that:

"make evaluates conditionals when it reads a makefile. Consequently, you cannot use automatic variables in the tests of conditionals because they are not defined until commands are run"

but the BOOT_FLAG is not an automatic variable. Also if I move the definition of BOOT_FLAG to before the ifeq, then it works as I want it. However, I want to keep the current order of the definitions (and I don't understand why make does an exception to the order independence of the definitions when using conditions)

3 Answers3

2

The answer is right there in the statement you quoted:

make evaluates conditionals when it reads a makefile.

Since make has evaluated the conditional when it read that line in the makefile, and the variable has not been defined when it read that line, there's no way that variables set after the conditional can take effect.

Just because the documentation lists one consequence of this behavior (the one that most people get confused by) doesn't mean that this is the only consequence of this behavior.

However, I want to keep the current order of the definitions

You can't.

(and I don't understand why make does an exception to the order independence of the definitions when using conditions)

It would be virtually impossible, and even if it could be done the resulting behavior would be almost indecipherable except in the most trivial situations. If you don't believe me, try to write down an algorithm describing how that could work. Remember to consider things like simple variable assignments, nested conditionals, variables used in target and prerequisite lists, variables that are intentionally reset in different parts of makefiles, etc.

ETA You could do it, by putting the ifeq into a define variable then using eval later, after BOOT_FLAG is set, to expand it. Seems gross to me but...

MadScientist
  • 92,819
  • 9
  • 109
  • 136
  • I realized that afterwards. The "preprocessor" evaluates conditionals before the make file is actually run. I just assumed (hoped?) that make didn't work this way. I'll have to find some alternative way around this. – Per Viberg Aug 07 '19 at 14:10
  • To be clear, it works like this: make parses makefiles and internalizes structure into a directed graph representing targets and their prerequisites. As part of this parsing it will set variables and handle conditionals, includes, etc. After the parsing is complete, make walks the graph it constructed looking for "work to do" (by comparing modification times). I understand that this simple behavior isn't what you want, but if you tried to write down an algorithm that would behave the way you hoped I'm confident you'd see it's really not feasible. – MadScientist Aug 07 '19 at 16:50
1

This is because makefile is evaulating the ifeq as it parses the file.

So when it gets to the ifeq..., then BOOT_FLAG is yet not set, so BOOT_TEST = no

Then you set BOOT_FLAG.

Then once all the variables are parsed, makefile will go through and run your rule - so in this case BOOT_DEFINE is evaluated to $(BOOT_FLAG) final value of installed

Try this:

$(info start - BOOT_FLAG=$(BOOT_FLAG))
ifeq ($(BOOT_FLAG),installed)
        BOOT_TEST=$(BOOT_FLAG)
else
        BOOT_TEST=no
endif

$(info after if - BOOT_FLAG=$(BOOT_FLAG))

BOOT_DEFINE=$(BOOT_FLAG)

BOOT_FLAG=installed

$(info after assignment - BOOT_FLAG=$(BOOT_FLAG))

.PHONY: all
all:
        @echo $(BOOT_TEST)
        @echo $(BOOT_DEFINE)

You will see various values printed at different times during the makefile parsing. On the first pass it evaluates the variables (and if's) and then on the second pass it can do the target rules.

code_fodder
  • 15,263
  • 17
  • 90
  • 167
0

As others noted the problem is that ifeq is expanded and evaluated in-place.

If you want to postpone the evaluation until some late moment, you must keep the whole expression inside of a recursive variable. Then the conditional could be implemented by $(if ...) function, instead of ifeq (okay, $(eval ifeq...) should also be doable, but... well, gross).

Of course, this is quite an overhead for such simple case, but nonetheless it could be done like this:

BOOT_TEST=$(if $(subst _installed,,_$(BOOT_FLAG)),no,installed)

BOOT_DEFINE=$(BOOT_FLAG)

BOOT_FLAG=installed

.PHONY: all
all:
    @echo $(BOOT_TEST)
    @echo $(BOOT_DEFINE)
Matt
  • 13,674
  • 1
  • 18
  • 27
  • this solves the problem I stated. Unfortunately the real problem (that I reduced to that simplified version) is more tricky since the BOOT_FLAG is a target of a recipe. I'll try to re-reduced the problem again, in another stackoverflow question. – Per Viberg Aug 07 '19 at 14:24
  • @PerViberg I guess you're talking about something [like this](https://stackoverflow.com/questions/57069527/makefile-dynamic-variables-as-prerequisites). But that's a misuse, for sure. – Matt Aug 07 '19 at 14:33
  • 1
    I solved my original problem! (ignore that what I wrote about BOOT_FLAG being a target.. that's me being newbie at makefiles). The $(if ..) function was the key!. I managed to implement the wanted behaviour inside the $(if ,,) instead of inside an ifeq .. else .. thanks a million for pointing me in the right direction!. – Per Viberg Aug 08 '19 at 08:11