Write an update archive with only the files changed since the last publish
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
Executable
+77
@@ -0,0 +1,77 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Archives the files of a publish folder that differ from the previous archive.
|
||||||
|
|
||||||
|
usage: publish-update.py <previous archive> <folder> <output archive>
|
||||||
|
|
||||||
|
The previous archive is the full one the last publish left behind (.zip or
|
||||||
|
.tar.gz). A file goes into the output when it is new or its content changed;
|
||||||
|
files the new publish no longer has are listed but cannot be removed by
|
||||||
|
unpacking over the old install. The archive keeps the folder name as its top
|
||||||
|
directory, like the full one. Prints nothing and writes nothing when no file
|
||||||
|
changed.
|
||||||
|
"""
|
||||||
|
import hashlib
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import tarfile
|
||||||
|
import zipfile
|
||||||
|
|
||||||
|
|
||||||
|
def digest(data):
|
||||||
|
return hashlib.sha256(data).hexdigest()
|
||||||
|
|
||||||
|
|
||||||
|
def read_previous(path):
|
||||||
|
"""Maps each file's path inside the archive to a hash of its content."""
|
||||||
|
hashes = {}
|
||||||
|
if path.endswith(".zip"):
|
||||||
|
with zipfile.ZipFile(path) as archive:
|
||||||
|
for info in archive.infolist():
|
||||||
|
if not info.is_dir():
|
||||||
|
hashes[info.filename] = digest(archive.read(info))
|
||||||
|
else:
|
||||||
|
with tarfile.open(path) as archive:
|
||||||
|
for member in archive.getmembers():
|
||||||
|
if member.isfile():
|
||||||
|
hashes[member.name] = digest(archive.extractfile(member).read())
|
||||||
|
return hashes
|
||||||
|
|
||||||
|
|
||||||
|
def main(previous, folder, output):
|
||||||
|
root = os.path.basename(os.path.normpath(folder))
|
||||||
|
old = read_previous(previous)
|
||||||
|
changed = []
|
||||||
|
current = set()
|
||||||
|
for directory, _, names in os.walk(folder):
|
||||||
|
for name in sorted(names):
|
||||||
|
full = os.path.join(directory, name)
|
||||||
|
inside = root + "/" + os.path.relpath(full, folder).replace(os.sep, "/")
|
||||||
|
current.add(inside)
|
||||||
|
with open(full, "rb") as file:
|
||||||
|
if old.get(inside) != digest(file.read()):
|
||||||
|
changed.append((full, inside))
|
||||||
|
removed = sorted(set(old) - current)
|
||||||
|
|
||||||
|
if os.path.exists(output):
|
||||||
|
os.remove(output)
|
||||||
|
if not changed:
|
||||||
|
print("no file changed since the previous archive")
|
||||||
|
return
|
||||||
|
changed.sort(key=lambda pair: pair[1])
|
||||||
|
if output.endswith(".zip"):
|
||||||
|
with zipfile.ZipFile(output, "w", zipfile.ZIP_DEFLATED) as archive:
|
||||||
|
for full, inside in changed:
|
||||||
|
archive.write(full, inside)
|
||||||
|
else:
|
||||||
|
with tarfile.open(output, "w:gz") as archive:
|
||||||
|
for full, inside in changed:
|
||||||
|
archive.add(full, inside, recursive=False)
|
||||||
|
print(f"{output}: {len(changed)} changed of {len(current)} files")
|
||||||
|
for gone in removed:
|
||||||
|
print(f" no longer in the publish: {gone}")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
if len(sys.argv) != 4:
|
||||||
|
sys.exit(__doc__)
|
||||||
|
main(*sys.argv[1:])
|
||||||
+27
-7
@@ -5,7 +5,9 @@
|
|||||||
# files, so the second publish only adds what the first lacks. The target
|
# files, so the second publish only adds what the first lacks. The target
|
||||||
# machine needs no .NET install. Use publish-windows.sh or publish-linux.sh, or
|
# machine needs no .NET install. Use publish-windows.sh or publish-linux.sh, or
|
||||||
# pass the runtime id, for example win-arm64 (the bridge and XMMT.ocx are 32
|
# pass the runtime id, for example win-arm64 (the bridge and XMMT.ocx are 32
|
||||||
# bit x86 either way).
|
# bit x86 either way). When an archive from an earlier run exists, a second
|
||||||
|
# archive, dist/nonemm-<rid>-update, holds only the files that are new or
|
||||||
|
# changed since then, for unpacking over the old install.
|
||||||
rid="${1:?usage: publish.sh <runtime id>}"
|
rid="${1:?usage: publish.sh <runtime id>}"
|
||||||
cd "$(dirname "$0")" || exit 1
|
cd "$(dirname "$0")" || exit 1
|
||||||
name="nonemm-$rid"
|
name="nonemm-$rid"
|
||||||
@@ -13,20 +15,38 @@ out="dist/$name"
|
|||||||
mkdir -p dist
|
mkdir -p dist
|
||||||
rm -rf "$out"
|
rm -rf "$out"
|
||||||
|
|
||||||
|
case "$rid" in
|
||||||
|
win-*) extension=zip ;;
|
||||||
|
*) extension=tar.gz ;;
|
||||||
|
esac
|
||||||
|
archive="dist/$name.$extension"
|
||||||
|
update="dist/$name-update.$extension"
|
||||||
|
|
||||||
|
# the full archive is replaced below, so keep the old one to compare against
|
||||||
|
previous=""
|
||||||
|
if [ -f "$archive" ]; then
|
||||||
|
previous="dist/$name.previous.$extension"
|
||||||
|
cp "$archive" "$previous" || exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
for project in Nonemm.App Nonemm.Recorder.App; do
|
for project in Nonemm.App Nonemm.Recorder.App; do
|
||||||
./build.sh publish "src/$project" -c Release -r "$rid" --self-contained -o "$out" || exit 1
|
./build.sh publish "src/$project" -c Release -r "$rid" --self-contained -o "$out" || exit 1
|
||||||
done
|
done
|
||||||
rm -f "$out"/*.pdb
|
rm -f "$out"/*.pdb
|
||||||
|
|
||||||
case "$rid" in
|
case "$extension" in
|
||||||
win-*)
|
zip)
|
||||||
rm -f "dist/$name.zip"
|
|
||||||
python3 -c 'import shutil, sys; shutil.make_archive(sys.argv[1], "zip", "dist", sys.argv[2])' \
|
python3 -c 'import shutil, sys; shutil.make_archive(sys.argv[1], "zip", "dist", sys.argv[2])' \
|
||||||
"$out" "$name" || exit 1
|
"$out" "$name" || exit 1
|
||||||
echo "dist/$name.zip"
|
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
tar -C dist -czf "dist/$name.tar.gz" "$name" || exit 1
|
tar -C dist -czf "$archive" "$name" || exit 1
|
||||||
echo "dist/$name.tar.gz"
|
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
echo "$archive"
|
||||||
|
|
||||||
|
rm -f "$update"
|
||||||
|
if [ -n "$previous" ]; then
|
||||||
|
python3 "$(dirname "$0")/publish-update.py" "$previous" "$out" "$update" || exit 1
|
||||||
|
rm -f "$previous"
|
||||||
|
fi
|
||||||
|
|||||||
Reference in New Issue
Block a user