Search code examples
makefilegnu

Makefile executes the same recipe twice when using multiple jobs


I'm looking at the following toy Makefile to track a bug:

all: hey bye

hey bye: hi
    cat hi
    touch hey bye

In this scenario, I've created the hi file beforehand.

I then run make -j 2 all and get the following output:

cat hi
cat hi
hey
touch hey bye
hey
touch hey bye

Whereas if I run it with make -j 1 all, the commands get executed only once. The debug output is pretty verbose, but from what I understand, it's executing it twice because it tried to satisfy hey and bye with two different jobs.

How can I prevent this from happening? Is this correct behaviour? Why does it think running a command twice at once will make it finish faster?


Solution

  • The behavior is correct, because you're misunderstanding what you are telling make.

    You can read the details in the GNU make manual.

    This rule:

    hey bye: hi
            ...
    

    Does not tell make that one invocation of the recipe will build both targets. Instead, make interprets it exactly the same way as if you'd written:

    hey: hi
            ...
    bye: hi
            ...
    

    That is, you've declared two completely different targets where each one would be created by a separate invocation of ....

    When you run make without -j it sees that hey is out of date and runs the recipe. Then it looks at bye, and now (since the previous recipe just updated it) it's up to date so it seems to work.

    But if you run with -j2, make sees hey is out of date and starts that job, then looks for more work. Now when it checks bye, the recipe for hey didn't update it yet so make thinks it's still out of date and starts another instance of the recipe.

    The best way to fix this depends on the details of your actual usage, which you haven't given.

    If your inputs and outputs are related by the names of the targets and prerequisite, you can write a pattern rule. Unlike explicit rules, multiple target patterns tell make that one invocation of the recipe builds all the targets:

    %.x %.y : %.z
            ...
    

    If that won't work and you're willing to restrict your builds to using GNU make 4.3 or better you can take advantage of the "grouped targets" feature:

    hey bye &: hi
            ...
    

    If that isn't feasible, you'll have to use a sentinel target, something like:

    all: hey bye
    
    hey bye: .sentinel ;
    
    .sentinel: hi
            ...
            @touch $@