Search code examples
makefilegnu-make

Makefile unconditional steps after endif results in error


I'm new to make. I want a condition inside my Makefile, followed by unconditional steps.

According to the Make manual, this should be possible:

Every conditional must end with an endif. Unconditional makefile text follows.

The following code works, but does not assign the new value

a = something
b =

build:
ifeq ($(a),)
    b=yes
else
    b=no
endif
    $(info what_is_b=$(b))
    @echo $(b)
    @echo "end of condition"

This code does work, but exits with an error and the two echo statements are skipped (same goes for info when a tab is added):

a = something
b =

build:
ifeq ($(a),)
b=yes
else
b=no
endif
$(info what_is_b=$(b))
    @echo $(b)
    @echo "end of condition"

Results in:

$ make build
what_is_b=no
Makefile:11: *** recipe commences before first target.  Stop.

What is wrong here?

Also while searching, I could only find examples where endif is followed by another if... but never by unconditional steps.

I'm using GNU Make 4.4


Solution

  • Note: My previous answer was a little lackluster. Rather than performing a rip & replace on it, I deleted it and started fresh.


    The following code works, but does not assign the new value

    a = something
    b =
    
    build:
    ifeq ($(a),)
        b=yes
    else
        b=no
    endif
        $(info what_is_b=$(b))
        @echo $(b)
        @echo "end of condition"
    

    The contents of a recipe is shell script, and, ordinarily, each line is executed in its own shell. Supposing that the assignments are indented with at least one leading tab, they are shell variable assignments, and

    • the shell in which one of those assignments is made terminates immediately after the assignment is performed
    • the subsequent appearances of $(b) expand a make variable of that name, which would not be the same thing even in the shell where the assignment was performed.

    So yes, supposing correct tab indentation, which does not carry through in the Stack Overflow medium, I would expect GNU make to accept that makefile and to be able to build target build based on it, and in doing so, to report that make variable b expands to an empty string.


    This code does work, but exits with an error and the two echo statements are skipped (same goes for info when a tab is added):

    a = something
    b =
    
    build:
    ifeq ($(a),)
    b=yes
    else
    b=no
    endif
    $(info what_is_b=$(b))
        @echo $(b)
        @echo "end of condition"
    

    I guess by "does work" you mean that the $(info) function prints a non-empty value for variable b, notwithstanding that make terminates with an error and the two echos are not executed.

    In the makefile text as modified according to any conditional processing and line-joining, make recognizes the recipe for a rule as all the zero or more immediately following tab-indented lines. The first line not indented with a tab terminates the recipe. In this version of the makefile, that's whichever of the b=yes or b=no emerges from the conditional. And because that's not a recipe line, it is a make variable assignment, not a shell variable assignment, which your $(info) can observe.

    That also strands the two echo commands. make recognizes from their leading tabs that you mean for them to be part of a recipe, but they are not, the recipe for build having been terminated, with zero lines, by the assignment to b. make aborts makefile parsing at that point, without attempting to build any target. However, because the $(info) appears outside a recipe, it has already been expanded at that point, causing its output to be emitted.


    What is wrong here?

    Different things in your two examples, as already discussed:

    • failure to recognize the distinction between make variables and shell variables
    • failure to account for each recipe line running in a separate shell
    • incorrect recipe syntax

    Overall, I suspect you have a flawed mental model of makefiles and make's execution. Makefiles are not scripts. make rules are not functions. You cannot use a rule to set or change make variables in a way that will be visible after the rule has run.

    GNU make does implement numerous extensions, some of which push the boundaries of the above. The conditionals you are using and the $(info) function are among them. I generally recommend avoiding GNU extensions, or at least minimizing their use, but it's possible that one or another GNU extension would provide a clean and simple solution to whatever the real problem is that you want to solve.

    As for the example code, however, if a make variable is what you want to use, then you probably want this:

    a = something
    ifeq ($(a),)
    b = yes
    else
    b = no
    endif
    
    build:
        $(info what_is_b=$(b))
        @echo $(b)
        @echo "end of condition"
    

    If you want a shell variable, on the other hand, then it would probably be better to use shell features for the conditional logic too. For example:

    a = something
    
    build:
        @a='$(a)'; b=$${a:+no}; echo $${b:-yes}
        @echo end
    

    (The appearances of $$ represent an escaped $$ to make. A single $ is passed through to the shell.)

    Also while searching, I could only find examples where endif is followed by another if... but never by unconditional steps.

    The GNU conditionals in your examples are just the context for your issue. They do not play a direct role in it. There is no particular reason unconditional recipe lines could not follow conditional-guarded ones.