Search code examples
functionmakefileconditional-statements

conditional statements are not getting evaluated inside Makefile function


I have a function in a Makefile where I am trying to evaluate input parameters in ifeq statements like below:

define set_target_dts
    ifeq ($(1),virgo)
        @echo "Building for Virgo"
        ifeq ($(2),soc_prototype)
            @echo "Building for soc prototype"
            DTS_FILE_NAME := virgo_soc_proto.dts
        else
            @echo "Building for soc"
            DTS_FILE_NAME := virgo.dts
        endif
    else ifeq ($(1),gemini)
        @echo "Building for Gemini"
        ifeq ($(2),soc_prototype)
            DTS_FILE_NAME := gemini_soc_proto.dts
        else
            DTS_FILE_NAME := gemini.dts
        endif
    else        
        $(info Selected Target is:$(1) $(2))
        $(error Please select the correct target...)
    endif
endef

Whenever I call this function using

$(call set_target_dts,virgo,soc_prototype)

It is throwing me this error:

Selected Target is:virgo soc
Makefile:48: *** Please select the correct target....  Stop.

Which means that neither the ifeq is evaluated correctly, nor the second parameter value is correctly retreived.

Please guide me about my mistakes. I have never made Makefile functions before.


Solution

  • At the outset I'll set aside your secondary problem:

    nor the second parameter value is correctly retreived.

    This is not reproduced for me when I run a make calling

    $(call set_target_dts,virgo,soc_prototype)
    

    with the definition of set_target_dts that you have posted. I get:

    Selected Target is:virgo soc_prototype
    Makefile:24: *** Please select the correct target.... Stop.
    

    as it should be, not:

    Selected Target is:virgo soc
    Makefile:48: *** Please select the correct target....  Stop. 
    

    as you did. I think something not very make-related is amiss there.

    And so onward

    You are misapprehending how define-ed variables are expanded and how function calls are expanded. A "user defined function" in GNU Make is conventionally a variable with a multi-line definition enclosed by define varname and endef, with deferred expansion of the enclosed body so that arguments can be substituted for $(n) parameters in that body when the the function-call construction $(func,args,...) is expanded1. make does not recognise any "logic" in the body of a variable definition while expanding it. (Although make might recognize such logic later if make itself uses the expansion).

    First you should carefully read the GNU Make manual: 3.7 How make Reads a Makefile. Having grasped the distinction between immediate and deferred expansion, note that the schema that fits your definition of set_target_dts is:

    define *immediate*
      *deferred*
    endef
    

    The expansion of the definition body is deferred until the variable is used - e.g. by function-call expansion $(call foo,args...). That is the right choice. You will see from the documentation that you could in theory adapt your definition to, say, the schema:

    define *immediate* :=
      *immediate*
    endef
    

    But if you did that then the definition would not be viable for functions calls, because the definition would be frozen with all $(n) parameters empty. Deferred expansion is necessary to give you a chance to supply args....

    With that in mind a worked example will best clarify what you've observed, and what's misconceived about the definition of set_target_dts. Consider this makefile:

    $ cat Makefile
    define foo
        ifeq (1,0)
            $(info In condition 1 = 0)
            $(error In condition 1 = 0)
        else
            $(info In condition 1 ~= 0)
            $(info $$(1) = [$(1)])
            echo "Parameter $$(1) = [$(1)]"
        endif
    endef
    
    $(info $$(foo,bar) = <<<$(call foo,bar)>>>)
    
    all:
        
    

    Let's just run it:

    $ make
    In condition 1 = 0
    Makefile:12: *** In condition 1 = 0. Stop.
    

    Observe: the fact that the $(error ...) call was within the scope of the unsatisfiable condition ifeq (1,0) didn't stop the the $(error ...) call from being fatally executed.

    Let's remove the error call so we can get further. I'll also label the lines within the definition of foo so I can talk through them:

    $ cat Makefile
    define foo
        ifeq (1,0) # A
            $(info In condition 1 = 0) # B
        else # C    
            $(info In condition 1 ~= 0) # D
            $(info $$(1) = [$(1)]) # E
            echo "Parameter $$(1) = [$(1)]" # F
        endif # G
    endef
    
    $(info $$(foo,bar) = <<<$(call foo,bar)>>>)
    
    all:
    

    We know that all the labelled lines fall into deferred expansion. In our Makefile, that will happen in:

    <<<$(call foo,bar)>>>
    

    (It will not happen in $$(foo,bar), because that expression is escaped.)

    In performing the expansion of the definition, make works like so:

    • Any un-escaped parameter $(n) is replaced with the nth argument, if any, otherwise the empty string.
    • Any un-escaped $(name[...]) construct is expanded.
    • Any escaped construct $$... is expanded to $...
    • Any text that is not in the scope of $ goes through unchanged.

    So here is what will happen, line by line, as make expands $(call foo,bar):

    • Line A: There are no $s here. Add it unchanged to the expansion.
    • Line B: Expand the $(info ...) call now. That action prints ... to the standard output now and returns the empty string. Add that empty string to the expansion.
    • Line C: Same as Line A.
    • Line D: Same as Line B
    • Line E: Same as Line B
    • Line F: The escaped $$(1) is replaced with literal $(1). The un-escaped $(1) in replaced with bar. The resulting line is added to the expansion.
    • Line F: Same as Line A.

    Thus the final expansion of $(call foo,bar) is:

        ifeq (1,0) # A
             # B
        else # C    
             # D
             # E
            echo "Parameter $(1) = [bar]" # F
        endif # G
        
    

    Let's confirm that by running the amended Makefile:

    $ make
    In condition 1 = 0
    In condition 1 ~= 0
    $(1) = [bar]
    $(foo,bar) = <<<    ifeq (1,0) # A
             # B
        else # C    
             # D
             # E
            echo "Parameter $(1) = [bar]" # F
        endif # G>>>
    make: Nothing to be done for 'all'.
    

    Note that the expansion of:

    $(info In condition 1 = 0) # B  => prints "In condition 1 = 0"
    

    was uninhibited by apparently being in the scope of the unsatisfiable condition ifeq (1,0) and that in the expanded definition it does not even appear to be that scope: the only text in that scope is <empty_string># B.

    By analogy you should now see why the $(error ...) call that we removed was executed despite lying "within the scope" of ifeq (1,0). In the context of expanding the definition of foo, that condition is merely some text that requires no expansion itself and is just added to the expansion of the function call. The $(error ...) call is some more text that does require expansion, and doing that immediately terminates make with failure.

    And in that light you can see that in your own case,

    $(call set_target_dts,virgo,soc_prototype)
    

    will execute:

    $(info Selected Target is:$(1) $(2))
    

    and also:

    $(error Please select the correct target...) 
    

    regardless of any surrounding conditional "make-logic", because in the context of expanding the function call make does not care what the surrounding text is: it isn't doing any make-logic here.

    But...

    If you intend to call your function in the context of a recipe, then it certainly does matter what the text of the expansion is, since recipes are executed by the shell. And since your definition of set_target_dts contains shell commands (echo) you evidently intend to use it that way. Our makefiles so far have also contained an echo command to hint at the same intention.

    Let's make a final revision of our Makefile to check that out:

    $ cat Makefile
    define foo
        ifeq (1,0) # A
            $(info In condition 1 = 0) # B
        else # C    
            $(info In condition 1 ~= 0) # D
            $(info $$(1) = [$(1)]) # E
            echo "Parameter $$(1) = [$(1)]" # F
        endif # G
    endef
    
    $(info $$(foo,bar) = <<<$(call foo,bar)>>>)
    
    all:
        @echo "Building all"
        $(call foo,bar)
        
    $ make
    In condition 1 = 0
    In condition 1 ~= 0
    $(1) = [bar]
    $(foo,bar) = <<<    ifeq (1,0) # A
             # B
        else # C    
             # D
             # E
            echo "Parameter $(1) = [bar]" # F
        endif # G>>>
    In condition 1 = 0
    In condition 1 ~= 0
    $(1) = [bar]
    Building all
    ifeq (1,0) # A
    /bin/sh: 1: Syntax error: word unexpected (expecting ")")
    make: *** [Makefile:15: all] Error 2
    

    See here that the $(call foo,bar) within the recipe for target all, just like the the previous previous one in <<<$(call foo,bar)>>>, is expanded before execution of the recipe (before "Building all"). It would have to be, wouldn't it?

    Then the expansion is given to the shell to execute and of course, the shell chokes on line A: ifeq (1,0) does not make sense to the shell.

    If the definition of set_target_dts is meant to be such that, after functional expansion with arguments, a shell command or expression will result then you must write that definition in such a way that, after expansion, you get nothing but a shell command or expression. Make-logic will not "do its job and vanish" in the context of a variable definition expansion destined for recipe execution. It will not do its job, and it will not vanish.


    1 @MadScientist correctly commented: "Just to note, a user-defined function in make doesn't have to be multiline or created with define. Any macro can be used with $(call ...). Generally any macro that refers to at least one variable (e.g., $1, $2) is a user-defined function. You could even have zero arguments but it only makes sense to use n-ary macros where n > 0: $(call foo) is legal but is the same thing as just $(foo)."