Search code examples
makefilegnu-make

Makefile: prerequisite-only rule not firing


Code:

DEST = <some-folder>
EXES = compiled1
SCRIPTS = script1
COPIED = $(addprefix $(DEST)/,$(EXES) $(SCRIPTS))

$(shell mkdir -p $(DEST))

all: $(COPIED)

$(EXES):
    $(CXX) -o $@ $^ $(CXXFLAGS) $(LDFLAGS)

$(COPIED):
    cp $< $@
    chmod +x $@

$(DEST)/%: %
compiled1: compiled1.cpp

The idea is as follow: I have a folder with some scripts and source files. Source files are compiled to form 1 or more binary programs.

Then I need to copy both the scripts and the binary programs to a same destination folder. The scripts require, besides being copied, to be chmoded. Compiled programs don't but I did it anyway to merge rules.

  • The all: rule depends on the copied-files.
    • The $(DEST)/%: rule is meant to make sure each copied-file depends on my local version.
      • The $(EXES): rule generate the local binary programs.
      • The local scripts don't need to be generated, so there's no rule for them.

The problem is that, for some reason, the $(DEST)/%: % prerequisite-only rule is not firing, so within the $(COPIED) rule, $< is empty, and cp fails, and it should fire because $(COPIED) follows the $(DEST)/% pattern.


Solution

  • The problem is that, for some reason, the $(DEST)/%: % prerequisite-only rule is not firing, so within the $(COPIED) rule, $< is empty, and cp fails, and it should fire because $(COPIED) follows the $(DEST)/% pattern.

    Prerequisite-only implicit rules do not make much sense for adding prerequisites to targets (and they specifically do not have that effect). make will consider implicit rules for a given target only if there is no explicit rule for the target, and it will select one implicit rule from among those applicable for building it. If no implicit rule matching a given target provides a recipe, and the target is not phony, then there must be an explicit rule that suffices to build it, and that moots all implicit rules for the same target.

    One alternative would be to do the copying directly in the implicit rule, instead of having a separate rule for that:

    # $(COPIED):
    #     cp $< $@
    #     chmod +x $@
    
    $(DEST)/%: %
        cp $< $@
        chmod +x $@
    

    All the files designated by $(COPIED) are named as prerequisites for all, so building all will trigger the implicit rule (with its recipe) for each of them. You can also build any one of them explicitly, just as you could before. You can also get make to try to build and copy other files, which is a potential weakness, but perhaps one you can live with.

    Another alternative would be write a more conventional installation rule,* maybe something like this:

    DEST = <some-folder>
    EXES = compiled1
    SCRIPTS = script1
    
    all: $(EXES)
    
    install: all
        mkdir -p -m 0755 $(DEST)
        cp $(EXES) $(SCRIPTS) $(DEST)
        chmod 0755 $(SCRIPTS)
    
    .PHONY: all install
    

    * And if the copying is not well characterized as installation, then maybe rethink more deeply. If the copying does not require more privilege than the building does, then you probably shouldn't be doing it at all. Instead, build the EXEs directly to the location where you want them, and distribute the scripts (or copies of them) pre-installed in that location (which must be within the distribution if you can be sure that no extra privilege is required to copy files there).