98 lines
3.5 KiB
Python
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()
|