bashshellmakefilegnu-make

Debugging makefiles with overriden arguments


I'm trying to understand the following target and why it's done this way:

Top-level makefile and target:

collect:
    file_list="file_list.txt"
    for makefile in $(ALL_PROJECTS) ; do \
        $(MAKE) -s -f $$(basename $$makefile) --directory=$$(dirname $$makefile) GetDependenciesFile RELATIVE_DIR="$$(dirname $$makefile)" DEPENDENCIES_FILE="$$file_list"; \
    done;

Makefile that contains the target I'm trying to understand:

.PHONY: GetDependenciesFile
$(if $(filter GetDependenciesFile, $(.OVERRIDEN)),OVERRIDEN,) GetDependenciesFile: DependenciesFile.tmp
ifeq ("$(DEPENDENCIES_FILE)","")
    echo "DEPENDENCIES_FILE is not specified"
else
    cat DependenciesFile.tmp >> $(DEPENDENCIES_FILE)
endif


DependenciesFile.tmp:
    echo "$(ALL_DEPENDENCIES)" >> DependenciesFile.tmp

This is a simplified version of what I'm dealing with (I removed sed and other steps that are obvious, like sorting and removing duplicates).

I understand that the goal is to traverse all makefiles contained in $(ALL_PROJECTS), and within each makefile append the contents of the variable $(ALL_DEPENDENCIES) to DependenciesFile.tmp, the contents of which then appended to $(DEPENDENCIES_FILE) which is file_list.txt from the top-level target.

What I don't understand is this specific line:

$(if $(filter GetDependenciesFile, $(.OVERRIDEN)),OVERRIDEN,) GetDependenciesFile: DependenciesFile.tmp

I tried to grep OVERRIDEN in makefiles but couldn't find anything. I don't understand what's happening when the result of if is evaluated and why it's placed before the target name.

Any suggestions?


Solution

  • What I don't understand is this specific line:

    $(if $(filter GetDependenciesFile, $(.OVERRIDEN)),OVERRIDEN,) GetDependenciesFile: DependenciesFile.tmp
    

    The $(if ...) is a GNU make "function" that expands conditionally based on the arguments within (functions and function expansion are a GNU extension to standard make). The syntax is:

    $(if condition,then-part[,else-part])
    

    The condition part is considered true if stripping preceding and trailing whitespace and then expanding whatever is left results in a non-empty string. In that case, the then-part is evaluated to produce the expansion of the if. Otherwise, the else-part is evaluated to produce the expansion, where not providing that part of the conditional is equivalent to providing an empty string.

    In your case, the condition is $(filter GetDependenciesFile, $(.OVERRIDEN)), the then-part is OVERRIDEN, and the else-part is (explicitly) empty.

    The $(filter ...) is another GNU make function. The syntax is:

    $(filter pattern...,text)
    

    , where patterns are expressed in a syntax idiosyncratic to GNU make. The effect is to split the text on whitespace, and expand to all of the resulting words that match any of the specified patterns, joined by whitespace.

    In your case, only one pattern is given, GetDependenciesFile. The text to be filtered is the expansion of make variable .OVERRIDEN. Thus, if GetDependenciesFile appears as a whole word in the value of $(.OVERRIDEN) then

    Otherwise,

    I don't understand what's happening when the result of if is evaluated and why it's placed before the target name.

    Regular make rules can have multiple targets, expressed as a whitespace-delimited list. The $(if) is placed so that when it expands to anything other than whitespace, that will be interpreted as one or more additional targets for the rule.

    A rule with multiple targets expresses that each specified target depends on all the specified prerequisites, and that it can be built, individually, via the given recipe. Thus, when your $(if) expands to OVERRIDEN, the result is that the rule for GetDependenciesFile can also be used to build a target named OVERRIDEN.

    The recipe does not actually create a file with either name, so unless such a file is created by other means, each of those targets will always be considered out of date. My best guess is that the intent is to allow the recipe to be executed twice with some of the makefiles, once for target GetDependenciesFile, and once for target OVERRIDEN, whereas it is executed at most once with other makefiles. There are other possibilities related to building target OVERRIDEN, too, depending on the circumstances under which that is done.