Zac
Zac

Reputation: 1569

Makefile: Generating zip files of all sub folders

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

Answers (2)

Richard Maw
Richard Maw

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

Etan Reisner
Etan Reisner

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

Related Questions