makefiledependenciesdependency-graph

Make doesn't recognize changes in indirect dependencies


Consider this simple makefile:

all: output.txt

# The actual build command won't be this simple.
# It'll more be like "some-compiler file1.txt",
# which includes file2.txt automatically.
output.txt: file1.txt
    cat file1.txt file2.txt > output.txt

file2.txt:
    echo "heyo" > file2.txt

file1.txt: file2.txt

On first run, Make recognizes that file2.txt is a dependency of file1.txt, and so it needs to be built for output.txt to be built. Thus, it runs echo "heyo" > file2.txt and then cat file1.txt file2.txt > output.txt.

However, on subsequent runs, if file2.txt is changed, Make doesn't rebuild! If file1.txt is changed it does, but not for file2.txt. It just gives the dreaded make: Nothing to be done for 'all'. message.

One hacky solution I've seen people suggest is to do the following:

all: output.txt

output.txt: file1.txt file2.txt
    cat file1.txt file2.txt > output.txt

However, that's not possible in my case, as my secondary dependencies (the lines like file1.txt: file2.txt) are dynamically generated using include.

How do I make sure Make checks for modifications all the way up the tree when I have multiple levels of dependencies?


Solution

  • I think the problem here is that your makefile is slightly too simple.

    Let a -> b denote a depends on b. From your makefile you have...

    output.txt -> file1.txt -> file2.txt
    

    When make tries to update output.txt it sees that output.txt depends on file1.txt. It then notices that file1.txt depends on file2.txt. At that point the dependency chain stops. If make sees that file2.txt is newer than file1.txt it will run the command(s) that is associated with the file1.txt: file2.txt delendency. In this case, however, there aren't any commands -- just the dependency itself. That's fine as things go, but it does mean that even if file2.txt is updated file1.txt won't be. Hence, when make moves up the dependency chain to...

    output.txt: file1.txt
    

    it sees that output.txt is still newer than file1.txt so there is no need to run any command associated with that dependency.

    If you add the touch command...

    file1.txt: file2.txt
            touch $@
    

    then file1.txt will be updated and so the dependency chain works as you expect.