Reputation: 1569
I'm trying to transition away from my ad-hoc build script, which iterates over all children of the project's home, and creates a corresponding zip file for each:
for i in */; do
if [[ -d $i ]]; then
echo "Processing folder $i";
zip "${i%/}.zip" "$i"*.txt
echo "Created ${i%/}.zip"
fi
done;
The first line is clunky, but it's just iterating through all subdirs excluding hidden ones. As you can see the logic is straightforward, but doing this in a Makefile without explicitly specifying each subdirectory seems difficult.
Ideally we should start with:
project
subdir1
file1.txt
file2.txt
subdir2
file3.txt
...and end up with:
project
subdir1
file1.txt
file2.txt
subdir1.zip
subdir2
file3.txt
subdir2.zip
Is there a way to safely get this sort of recursion out of make?
edit: Cleaned up base script, thanks Etan
Upvotes: 1
Views: 9862
Reputation: 41
Defining a make rule for generating a zip from a directory is surprisingly complex.
If you only need it to update if you change any of the files you can use a shell expansion to get the file list as dependencies
all: foo.zip
# Enable second expansion with $$ so that the file list is parsed at the right stage
.SECONDEXPANSION:
foo.zip: $$(shell find foo -type f | sort -u)
# Create a new zipfile atomically rather than update
zip -MM [email protected].$$$$ $^ && mv [email protected].$$$$ $@
If you need to support re-creating the zip file when files are only added or removed then you need to generate an intermediate manifest file as a target and only updating that if it changes.
all: foo.zip
# Enable second expansion with $$ so that the file list is parsed at the right stage
.SECONDEXPANSION:
FORCE: # dummy target required to have a rule rebuild without prerequisites
.PHONY: FORCE # treat the FORCE file as never existing
# Depends on FORCE instead of using .PHONY since that would make downstream dependencies unconditionally rebuild
# when we want them to only rebuild if our manifest is newer
foo.zip.MANIFEST: FORCE
find foo -type f | sort -u >[email protected].$$$$ && \
if test -e $@ && cmp [email protected].$$$$ $@ >/dev/null 2>&1; then \
rm [email protected].$$$$; \
else \
mv [email protected].$$$$ $@; \
fi
foo.zip: foo.zip.MANIFEST $$(shell find foo -type f | sort -u)
# Create a new zipfile atomically rather than update
# Using the contents of the manifest as the file list
zip -MM [email protected].$$$$ -@ <$< && mv [email protected].$$$$ $@
Generalising this to multiple directories is an exercise left to the reader.
Upvotes: 0
Reputation: 80931
Something like this might do what you want.
SUBDIRS := $(wildcard */)
ZIPS := $(addsuffix .zip,$(subst /,,$(SUBDIRS)))
$(ZIPS) : %.zip : | %
zip $@ $*/*.txt
dist: $(ZIPS)
You might need to adjust the prerequisites on that static pattern rule, etc. to get the correct rebuilding behavior (should make detect when it needs to rebuild, should it always rebuild, should it never rebuild when the .zip
file exists, etc).
Upvotes: 5