commit 3d0f8e6be1ebe7ad5a3aaa5d2cb6343852b5db4e
parent 057181985eec94a8d896c30197627082b85aed97
Author: Florian Dold <dold@taler.net>
Date: Sun, 23 Aug 2026 12:40:19 +0200
packaging/ng: remove stale local package builds
Diffstat:
2 files changed, 96 insertions(+), 8 deletions(-)
diff --git a/packaging/ng/buildscripts/generic b/packaging/ng/buildscripts/generic
@@ -13,8 +13,9 @@ import sys
import subprocess
import glob
import shutil
-import shlex
from email.utils import formatdate
+from pathlib import Path
+
def run_cmd(cmd, shell=False, cwd=None, env=None):
"""Helper to run commands and exit on failure (mimicking set -e)."""
@@ -28,10 +29,28 @@ def run_cmd(cmd, shell=False, cwd=None, env=None):
subprocess.check_call(cmd, shell=shell, cwd=cwd, env=command_env)
+
def get_output(cmd, shell=False, cwd=None):
"""Helper to get command output as string."""
return subprocess.check_output(cmd, shell=shell, cwd=cwd, text=True).strip()
+
+def remove_stale_packages(pkgdir):
+ """Remove package artifacts not referenced by a current-build manifest."""
+ pkgdir = Path(pkgdir)
+ current_packages = set()
+ for manifest in pkgdir.glob("*.built.current"):
+ current_packages.update(manifest.read_text().split())
+
+ for artifact in sorted(pkgdir.iterdir()):
+ if artifact.suffix not in (".deb", ".ddeb"):
+ continue
+ if artifact.name in current_packages:
+ continue
+ print(f"Removing stale local package {artifact.name}")
+ artifact.unlink()
+
+
def get_tag_debver(tag):
"""Get a debian version string from a git tag"""
if tag.startswith("v"):
@@ -179,13 +198,9 @@ def main():
for f in ddeb_files:
shutil.copy(f, "/pkgdir/")
- # Save built filenames
- built_current_path = f"/pkgdir/{PACKAGE}@{ARCH}.built.current"
- with open(built_current_path, 'w') as f:
- for deb in deb_files:
- f.write(os.path.basename(deb) + "\n")
- for ddeb in ddeb_files:
- f.write(os.path.basename(ddeb) + "\n")
+ built_artifacts = {
+ os.path.basename(artifact) for artifact in deb_files + ddeb_files
+ }
# 6. Testing
# Re-scan
@@ -240,6 +255,19 @@ def main():
print(f"Installed binary {fname} has linker issue")
sys.exit(2)
+ # Record the successful build before removing packages that are no longer
+ # current for any component or architecture.
+ built_current_path = f"/pkgdir/{PACKAGE}@{ARCH}.built.current"
+ with open(built_current_path, "w") as f:
+ for artifact in sorted(built_artifacts):
+ f.write(artifact + "\n")
+
+ remove_stale_packages("/pkgdir")
+
+ # Ensure the local repository metadata does not refer to removed packages.
+ os.chdir("/pkgdir")
+ run_cmd("dpkg-scanpackages . | xz - > /pkgdir/Packages.xz", shell=True)
+
# Finalize tag
with open(f"/pkgdir/{PACKAGE}@{ARCH}.built.tag", "w") as f:
f.write(TAG + "\n")
diff --git a/packaging/ng/testing/test_buildscript_generic.py b/packaging/ng/testing/test_buildscript_generic.py
@@ -0,0 +1,60 @@
+#!/usr/bin/env python3
+# This file is in the public domain.
+
+import importlib.machinery
+import importlib.util
+import tempfile
+import unittest
+from pathlib import Path
+
+SCRIPT_PATH = Path(__file__).parents[1] / "buildscripts" / "generic"
+LOADER = importlib.machinery.SourceFileLoader("buildscript_generic", str(SCRIPT_PATH))
+SPEC = importlib.util.spec_from_loader(LOADER.name, LOADER)
+GENERIC = importlib.util.module_from_spec(SPEC)
+LOADER.exec_module(GENERIC)
+
+
+class RemoveStalePackagesTests(unittest.TestCase):
+ def test_removes_only_unreferenced_package_artifacts(self):
+ with tempfile.TemporaryDirectory() as tmp:
+ pkgdir = Path(tmp)
+ current = {
+ "component_2.0_amd64.deb",
+ "component-dbgsym_2.0_amd64.ddeb",
+ "other_1.0_arm64.deb",
+ }
+ (pkgdir / "component@amd64.built.current").write_text(
+ "component_2.0_amd64.deb\n" "component-dbgsym_2.0_amd64.ddeb\n"
+ )
+ (pkgdir / "other@arm64.built.current").write_text("other_1.0_arm64.deb\n")
+
+ stale = {
+ "component_1.0_amd64.deb",
+ "component-dbgsym_1.0_amd64.ddeb",
+ "orphan_0.1_all.deb",
+ }
+ for name in current | stale:
+ (pkgdir / name).touch()
+ (pkgdir / "Packages.xz").touch()
+ (pkgdir / "build.changes").touch()
+
+ GENERIC.remove_stale_packages(pkgdir)
+
+ self.assertTrue(all((pkgdir / name).exists() for name in current))
+ self.assertTrue(all(not (pkgdir / name).exists() for name in stale))
+ self.assertTrue((pkgdir / "Packages.xz").exists())
+ self.assertTrue((pkgdir / "build.changes").exists())
+
+ def test_removes_all_packages_when_no_manifest_references_them(self):
+ with tempfile.TemporaryDirectory() as tmp:
+ pkgdir = Path(tmp)
+ (pkgdir / "orphan_1.0_all.deb").touch()
+ (pkgdir / "orphan-dbgsym_1.0_all.ddeb").touch()
+
+ GENERIC.remove_stale_packages(pkgdir)
+
+ self.assertEqual([], list(pkgdir.iterdir()))
+
+
+if __name__ == "__main__":
+ unittest.main()