fix: redeploy the site on any script it imports

This commit is contained in:
Abdessamad Derraz committed 2026-10-06 05:23:27 +02:00
1 parent d02672c17b
commit cd90f46937
2 files changed
+129 -5

No files matched your search

+40 -5
View File
@@ -8,11 +8,47 @@ on:
- "emulators/**"
- "provenance/**"
- "wiki/**"
- "scripts/generate_site.py"
- "scripts/generate_readme.py"
- "scripts/validate_site.py"
- "scripts/verify.py"
# Every script the build runs or imports (tests/test_workflow_paths.py).
- "scripts/artifacts.py"
- "scripts/auto_fetch.py"
- "scripts/common.py"
- "scripts/cross_reference.py"
- "scripts/crypto_verify.py"
- "scripts/deterministic_zip.py"
- "scripts/dumpcatalog.py"
- "scripts/generate_pack.py"
- "scripts/generate_readme.py"
- "scripts/generate_site.py"
- "scripts/hashing.py"
- "scripts/largefiles.py"
- "scripts/list_platforms.py"
- "scripts/nativemode.py"
- "scripts/packextras.py"
- "scripts/packpaths.py"
- "scripts/packreadme.py"
- "scripts/packresolve.py"
- "scripts/packverify.py"
- "scripts/profile_sync.py"
- "scripts/provenance_report.py"
- "scripts/refresh_data_dirs.py"
- "scripts/region.py"
- "scripts/release_record.py"
- "scripts/restore_large_files.py"
- "scripts/safeparse.py"
- "scripts/sect233r1.py"
- "scripts/siterender.py"
- "scripts/sitesources.py"
- "scripts/sitewrite.py"
- "scripts/slot.py"
- "scripts/slots.py"
- "scripts/split_pack.py"
- "scripts/truth.py"
- "scripts/upstream.py"
- "scripts/validate_schemas.py"
- "scripts/validate_site.py"
- "scripts/validation.py"
- "scripts/verify.py"
- "scripts/ziptools.py"
- "schemas/**"
- "tests/**"
- "docs_assets/**"
@@ -20,7 +56,6 @@ on:
- "install.py"
- "database.json"
- "release.json"
- "scripts/release_record.py"
- "mkdocs.yml"
- ".github/workflows/deploy-site.yml"
workflow_dispatch:
+89
View File
@@ -0,0 +1,89 @@
"""The site redeploys whenever a script it runs changes.
Deploy Site listed five scripts by hand while the build imports forty:
a change to siterender.py, which writes every page, published nothing
until some unrelated file moved.
"""
from __future__ import annotations
import ast
import fnmatch
import unittest
from pathlib import Path
import yaml
REPO_ROOT = Path(__file__).resolve().parent.parent
SCRIPTS = REPO_ROOT / "scripts"
def _module_file(name: str) -> Path | None:
name = name.removeprefix("scripts.")
for candidate in (
SCRIPTS / (name.replace(".", "/") + ".py"),
SCRIPTS / name.replace(".", "/") / "__init__.py",
):
if candidate.exists():
return candidate
return None
def _imports(path: Path) -> set[Path]:
found: set[Path] = set()
for node in ast.walk(ast.parse(path.read_text(encoding="utf-8"))):
if isinstance(node, ast.Import):
names = [alias.name for alias in node.names]
elif isinstance(node, ast.ImportFrom) and node.level == 0 and node.module:
names = [node.module, *(f"{node.module}.{a.name}" for a in node.names)]
elif isinstance(node, ast.ImportFrom):
base = path.parent / (node.module or "").replace(".", "/")
candidates = [base / f"{a.name}.py" for a in node.names]
candidates += [base.with_suffix(".py"), base / "__init__.py"]
found.update(c for c in candidates if c.is_file())
continue
else:
continue
found.update(f for f in map(_module_file, names) if f)
return found
def closure(entry_points: list[str]) -> set[str]:
seen: set[Path] = set()
stack = [SCRIPTS / name for name in entry_points]
while stack:
path = stack.pop()
if path not in seen:
seen.add(path)
stack.extend(_imports(path))
return {str(p.relative_to(REPO_ROOT)) for p in seen}
class DeploySiteTriggers(unittest.TestCase):
def test_every_script_the_build_imports_triggers_it(self):
workflow = yaml.safe_load(
(REPO_ROOT / ".github/workflows/deploy-site.yml").read_text(encoding="utf-8")
)
on = workflow.get("on", workflow.get(True))
patterns = on["push"]["paths"]
run = "\n".join(
step.get("run", "") for job in workflow["jobs"].values() for step in job["steps"]
)
entry_points = sorted(
{
word.removeprefix("scripts/")
for word in run.split()
if word.startswith("scripts/") and word.endswith(".py")
}
)
self.assertIn("generate_site.py", entry_points)
uncovered = sorted(
path
for path in closure(entry_points)
if not any(fnmatch.fnmatch(path, p.replace("**", "*")) for p in patterns)
)
self.assertEqual(uncovered, [])
if __name__ == "__main__":
unittest.main()