Search code examples
makefilewarningsgnu-make

Makefile warning: pattern recipe did not update peer target


Background:

We have a Makefile that sits in the middle of a dev-ops pipeline that does things in unusual ways, for legacy reasons not worth describing. This has worked fine for years, but upon upgrading to GNU Make 4.4, it started generating a new warning of the form:

Makefile:X: warning: pattern recipe did not update peer target 'Y'.

I'm 99% sure this warning is harmless for our use case, but the new warning output is tripping CI failures in our pipeline. This Makefile is invoked by an automated script and the output is also parsed by an automated script, neither of which can easily be changed.

Minimal reproducer:

Makefile:

%-seq %-par :: %.cpp force
        $(MAKE) do_complicated_stuff SRC=$< TGT=$@

do_complicated_stuff:
        @echo doing complicated stuff with SRC=$(SRC) TGT=$(TGT)
        touch $(TGT)

%-seq :: %.c force
        echo Error: this rule should not be run in this MRE
        exit 1

.PHONY: force

Command:

$ rm -f *-{seq,par} ; touch foo.cpp ; make --no-print-directory foo-seq

make do_complicated_stuff SRC=foo.cpp TGT=foo-seq
doing complicated stuff with SRC=foo.cpp TGT=foo-seq
touch foo-seq
Makefile:2: warning: pattern recipe did not update peer target 'foo-par'.

Here make was invoked to build the foo-seq target, but it's complaining about the foo-par target which does not exist and was not even mentioned. A given make command will be invoked to build exactly one target at a time, and the .PHONY dependency ensures the rule will be run (regardless of whether or not Make considers dependencies to be up-to-date). Make will be invoked (by the script) many times in the same directory to build each test.

Question:

Why is GNU Make 4.4 suddenly generating this new warning for an idiom that has always silently worked correctly in the past, and what is the minimal change to silence this harmless warning?

Constraints/Requirements:

  1. The solution probably needs to involve a pattern rule of some kind, because the set of possible source file names cannot be encoded in the Makefile. The name is provided only on the command line (which cannot be changed), but the rule needs to match it against the existence of the source file to ensure the correct rule is selected and executed.
  2. In the real Makefile both of the rules modeled above are much more complicated than shown here, so we'd like to avoid duplicating the first rule for each of the target patterns (which does silence the warning, but causes maintainability problems).
  3. Finally, for portability reasons the solution needs to continue functioning correctly without new warnings for all versions of GNU Make back to v3.80 (possibly negotiable if there's really no better solution).

Solution

  • The current plan is that in the next release of GNU make, this will become an error not a warning. So you should address it now. The reason for the change is that there are other errors in how GNU make handles patterns that cannot be fixed without changing this.

    The problem is this rule:

    %-seq %-par :: %.cpp force
            $(MAKE) do_complicated_stuff SRC=$< TGT=$@
    

    I'm not sure what you intend for this rule to actually do, but what it tells make it will do is a single invocation of this recipe will create both targets %-seq and %-par. That's what multiple patterns in a single rule means, and has always meant.

    If your recipe does not actually build both of those targets then you will see this issue.

    The simplest thing to do is write the rule twice, once for each target, which will work on all versions of GNU make and was always the correct way to write it:

    %-seq :: %.cpp force
            $(MAKE) do_complicated_stuff SRC=$< TGT=$@
    %-par :: %.cpp force
            $(MAKE) do_complicated_stuff SRC=$< TGT=$@
    

    ETA

    Wanting to avoid "duplicating the recipe" is trivial enough. Just put it into a variable:

    define COMPLEX_RECIPE
        $(MAKE) do_complicated_stuff SRC=$< TGT=$@
    endef
    
    %-seq :: %.cpp force ; $(COMPLEX_RECIPE)
    %-par :: %.cpp force ; $(COMPLEX_RECIPE)