Question

I currently have a tool which generates multiple files given a single input. So given a file "a.parent", it will generate a bunch of unknown named files such as "b.child", "c.child", "z.child", etc. This is all dependent on what lives within "a.parent".

My question is, how can I properly construct a makefile target for this such that 1) it won't run the tool more than once since a single run will generate all of them, 2) if any of the generated files are deleted, it will rerun the tool again and re-generate all of them, 3) if "a.parent" is newer than one of its dependents, it will rerun the tool, and 4) I do not have to list each and every child file within the Makefile itself as I really don't care what is generated, only that it is generated.

The problem, at least to me, is that Makefiles are built around knowing exactly what your final state should be. However, in this case, I want it to only care roughly what it is.

Was it helpful?

Solution

A makefile like the one below may suffice.

This is the idea: As a proxy target for the multiple unspecified *.child files that your tool may generate, use a manifest of those files that is created by the same recipe that creates the files. The recipe deletes the stale *.child files and manifest, then runs your tool on a.parent to make the new *.child files, then makes a new manifest of the new *.child files.

The rule for making this recipe is:

  • The manifest does not exist, or
  • the existing manifest is older than a.parent, or
  • the existing manifest is older than some *.child file, or
  • the existing manifest is untrue, i.e. the set of *.child files named in the manifest differs from the set that exists.

Details are explained in the comments:

.PHONY: all clean

# $(manifest) will be the space-punctuated list of files, if any, that are
# listed in 'manifest.txt', if it exists, else the empty string.
manifest = $(shell if [ -f manifest.txt ]; then cat manifest.txt; fi)

# $(outputs) will be the space-punctuated list of '*.child' files, if any, in
# the current directory, else the empty string
outputs = $(shell ls -1 *.child 2>/dev/null)

# Default target:
# If there is any mismatch between $(manifest) and $(outputs)
# then clean and remake. Else just make 'manifest.txt'
ifneq '$(outputs)' '$(manifest)'
all:
    $(MAKE) clean && $(MAKE)
else
all: manifest.txt
endif

manifest.txt : a.parent $(outputs)
    $(MAKE) clean && ./tool.sh a.parent && ls -1 *.child > $@

clean:
    rm -f *.child manifest.txt

In the recipe for manifest.txt, tool.sh stands for whatever tool you are running on a.parent. To test the makefile, I simply used:

#!/bin/bash
# tool.sh

while read line
do
    echo "This is file $line" > $line
done < $1

together with a.parent containing the names of the *.child files to be generated, one per line, e.g.

b.child
c.child
d.child

(So for my tool.sh, manifest.txt will just contain the same lines as a.parent.)

This solution isn't bulletproof, of course. For instance if someone deletes some *.child files both from the filesystem and from the manifest, the make will not notice. Also, an explicit make manifest.txt will do nothing even if someone falsifies the manifest. That loophole can be closed with some complication of the makefile, but precautions against such sabotage-like eventualities as these are probably not vital.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top