1

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?

obskyr
  • 1,380
  • 1
  • 9
  • 25
  • Apparently, adding `touch file1.txt` after `file1.txt: file2.txt` makes this work. But... *why*? Why does it matter that you touch `file1.txt` when `file2.txt` has been modified? – obskyr Mar 14 '18 at 14:51

2 Answers2

2

Your makefile does neither generate nor update file1.txt at all (i.e.: file1.txt must exist at the moment of running make). It contains no recipe for generating file1.txt from file2.txt. It has just an empty rule (i.e.: a rule without recipe):

file1.txt: file2.txt

Since file1.txt a prerequisite of output.txt, this empty rule just implies that file2.txt must exist for output.txt to be built, it does not even update file1.txt when file2.txt is generated.

Since file1.txt is the only prerequisite of output.txt and file1.txt is never updated by make, once output.txt is generated, it remains always up-to-date (provided file1.txt is not externally updated).

file2.txt being changed never causes output.txt to be rebuilt because:

  • it is not a prerequisite of output.txt.
  • it does not update file1.txt (which is the only prerequisite of output.txt).

Solution

Given your current output.txt rule:

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

If you want output.txt to be built every time file2.txt changes, then you need file1.txt to be built every time file2.txt changes. This can be achieved by means of a rule whose recipe actually updates file1.txt and has file2.txt as prerequisite, e.g.:

file1.txt: file2.txt
    touch $@
JFMR
  • 23,265
  • 4
  • 52
  • 76
1

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.

G.M.
  • 12,232
  • 2
  • 15
  • 18
  • Oh, so Make doesn't keep track of which targets it's built? It *only* goes by modification time? – obskyr Mar 14 '18 at 15:37
  • It's not so much a case of "Make doesn't keep track of which targets it's built". It's simply that, having run a command/recipe to rebuild a target, it doesn't then simply *assume* that the target was updated -- modification times will always be used when checking whether or not a target needs to be updated based on one or more of its dependencies. – G.M. Mar 14 '18 at 15:52