Search code examples
makefileopenwrt

What does semicolon-termination inside a Makefile's define directive do?


I'm wondering what the semicolons in the following makefile snippet do:

define Package/xxsim/CopyLocalFiles
    $(call cp, files/Adapter20Sim.h, $(PKG_BUILD_DIR)/xxsim);
    $(call cp, files/Adapter20Sim.cpp, $(PKG_BUILD_DIR));
endef
Hooks/Prepare/Post+=Package/xxsim/CopyLocalFiles

In case it matters, the makefile is for a custom component (xxsim) in the OpenWRT buildsystem.

I would expect that the semicolons are unnecessary, per, e.g., this example (source):

define two-lines =
    echo foo
    echo $(bar)
endef

However, if we build without those semicolons, the second call to cp fails (the first call to cp is implicitly successful):

 [CP]        files/Adapter20Sim.h
 [CP]        files/Adapter20Sim.cpp
 cp: target ' [MKDIR]    xxsim docinfo in staging dir' is not a directory

Clearly, cp receives incorrect input parameters. So, what does adding the semicolons actually do, why does the component build correctly with semicolons present?

Update: added buildsystem context below, resulting from Etan's help in the comments:

The Hooks/Prepare/Post is used in a foreach statement, where each of its values is used in a call function:

$(foreach hook,$(Hooks/Prepare/Post),$(call $(hook))$(sep))

The context of the foreach:

 $(STAMP_PREPARED) : export PATH=$$(TARGET_PATH_PKG)
 $(STAMP_PREPARED):| $(PKG_BUILD_DIR) $(STAGING_DIR)/include $(STAGING_DIR)/lib$(LIB_SUFFIX)
    $(foreach hook,$(Hooks/Prepare/Pre),$(call $(hook))$(sep))
    $(Build/Prepare)
    $(foreach hook,$(Hooks/Prepare/Post),$(call $(hook))$(sep))
    $(call touch,$$@,Prepared $(PKG_NAME))

Solution

  • The issue here is how a multi-line define is expanded in a recipe context (and specifically what the contents of the define are).

    In a recipe context a multi-line define is expanded as multiple lines so the example from the make manual works correctly.

    You would assume, as such, that the scenario under discussion here would work as well which it clearly doesn't. Two things combine to cause this problem.

    The first of which is what the value of the define is exactly.

    You might assume that given this snippet:

    PKG_BUILD_DIR := build
    cp = @echo '[CP] $1'; cp '$1' '$2'
    
    define Package/xxsim/CopyLocalFiles
        $(call cp, files/Adapter20Sim.h, $(PKG_BUILD_DIR)/xxsim)
        $(call cp, files/Adapter20Sim.cpp, $(PKG_BUILD_DIR))
    endef
    

    that the value of Package/xxsim/CopyLocalFiles would be:

    @echo '[CP] files/Adapter20Sim.h'; cp 'files/Adapter20Sim.h' 'build/xxsim'
    @echo '[CP] files/Adapter20Sim.cpp'; cp 'files/Adapter20Sim.cpp' 'build'
    

    and it is... except.

    You might assume that that was two lines with two newlines so really this:

    @echo '[CP] files/Adapter20Sim.h'; cp 'files/Adapter20Sim.h' 'build/xxsim'\n
    @echo '[CP] files/Adapter20Sim.cpp'; cp 'files/Adapter20Sim.cpp' 'build'\n
    

    but it isn't. What it actually is is this:

    @echo '[CP] files/Adapter20Sim.h'; cp 'files/Adapter20Sim.h' 'build/xxsim'\n
    @echo '[CP] files/Adapter20Sim.cpp'; cp 'files/Adapter20Sim.cpp' 'build'
    

    with no final newline.

    Now normally (and in the example in the makefile) that doesn't matter because the define is used something like this:

    tgt:
            $(Package/xxsim/CopyLocalFiles)
    

    and the final character of the define is followed immediately by a newline (on the recipe line itself).

    But that isn't what is happening in this case. In this case the define is being expanded in a $(foreach) loop and the manual says this about foreach:

    The multiple expansions of text are concatenated, with spaces between them, to make the result of foreach.

    So when the loop expands two such defines back-to-back:

    mkdir = @echo '[MKDIR] $1'; mkdir '$1'
    
    define Package/xxsim/CopyLocalFiles
        $(call cp, files/Adapter20Sim.h, $(PKG_BUILD_DIR)/xxsim)
        $(call cp, files/Adapter20Sim.cpp, $(PKG_BUILD_DIR))
    endef
    HOOKS += Package/xxsim/CopyLocalFiles
    define Package/xxsim/MkdirStaging
        $(call mkdir, $(PKG_BUILD_DIR)/xxsim/staging)
    endef
    HOOKS += Package/xxsim/MkdirStaging
    
    tgt:
            $(foreach hook,$(HOOKS),$(call $(hook)))
    

    what you get as the expanded output is this:

    tgt:
        @echo '[CP] files/Adapter20Sim.h'; cp 'files/Adapter20Sim.h' 'build/xxsim'
        @echo '[CP] files/Adapter20Sim.cpp'; cp 'files/Adapter20Sim.cpp' 'build' @echo '[MKDIR] build/xxsim/staging'; mkdir build/xxsim/staging
    

    which causes the error you are seeing and which including the trailing semi-colons fixes.

    Including an additional blank line in the define might also solve the problem (if make only chomps a single newline off the end of the literal define value).

    Like this:

    define Package/xxsim/CopyLocalFiles
        $(call cp, files/Adapter20Sim.h, $(PKG_BUILD_DIR)/xxsim)
        $(call cp, files/Adapter20Sim.cpp, $(PKG_BUILD_DIR))
    
    endef