Files

98 lines
3.5 KiB
Python

"""Build the device-test plugin from a fixed Git commit without local artifacts."""
import argparse
import hashlib
import io
import json
from pathlib import Path, PurePosixPath
import subprocess
import tarfile
import zipfile
def git(root, *args):
return subprocess.run(["git", *args], cwd=root, check=True, capture_output=True).stdout
def write_immutable(path, data):
if path.exists():
if path.read_bytes() != data:
raise ValueError(f"Refusing to replace different existing bytes: {path.name}")
return
with path.open("xb") as handle:
handle.write(data)
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--ref", default="HEAD", help="Commit containing the plugin source")
parser.add_argument("--output", type=Path, default=Path("artifacts/device-test"))
args = parser.parse_args()
root = Path(__file__).resolve().parents[1]
commit = git(root, "rev-parse", "--verify", "--end-of-options", args.ref + "^{commit}")
commit = commit.decode("ascii").strip()
# A subtree archive does not retain its parent's attributes; disable checkout
# conversion explicitly so source-manifest hashes still bind the Git blobs.
source = git(
root,
"-c",
"core.autocrlf=false",
"archive",
"--format=tar",
commit + ":plugins/artemis-device-test",
)
files = {}
with tarfile.open(fileobj=io.BytesIO(source)) as tree:
for entry in tree:
name = entry.name
path = PurePosixPath(name)
if entry.isdir():
continue
if (
not entry.isfile()
or path.is_absolute()
or ".." in path.parts
or str(path) != name
or any(
part in {"__pycache__", ".git", "node_modules", ".venv"} for part in path.parts
)
or name.endswith((".pyc", ".pyo"))
or path.name == ".env"
):
raise ValueError(f"Non-distributable source entry: {name}")
handle = tree.extractfile(entry)
if handle is None:
raise ValueError(f"Missing source bytes: {name}")
files[name] = handle.read()
manifest = json.loads(files["plugin.json"])
if manifest["id"] != "artemis-device-test":
raise ValueError("Unexpected plugin ID")
target = f"artemis-device-test-{manifest['version'].replace('+', '-')}.zip"
buffer = io.BytesIO()
with zipfile.ZipFile(buffer, "w", compression=zipfile.ZIP_DEFLATED) as archive:
for name, data in sorted(files.items()):
info = zipfile.ZipInfo(name, date_time=(1980, 1, 1, 0, 0, 0))
info.compress_type = zipfile.ZIP_DEFLATED
info.external_attr = 0o100644 << 16
archive.writestr(info, data, compresslevel=9)
data = buffer.getvalue()
digest = hashlib.sha256(data).hexdigest()
record = {
"plugin_id": manifest["id"],
"version": manifest["version"],
"source_commit": commit,
"filename": target,
"sha256": digest,
"size_bytes": len(data),
"files": len(files),
}
args.output.mkdir(parents=True, exist_ok=True)
write_immutable(args.output / target, data)
write_immutable(args.output / (target + ".sha256"), f"{digest} {target}\n".encode("ascii"))
write_immutable(args.output / "build.json", (json.dumps(record, indent=2) + "\n").encode())
print(json.dumps(record))
if __name__ == "__main__":
main()