84 lines
5.3 KiB
Python
84 lines
5.3 KiB
Python
#!/usr/bin/env python3
|
|
"""Prepare a fresh immutable candidate bundle. This command never launches the experiment."""
|
|
import argparse
|
|
from datetime import datetime, timezone
|
|
import hashlib
|
|
import json
|
|
from pathlib import Path
|
|
import shutil
|
|
import subprocess
|
|
from build_saturation_observer import build
|
|
|
|
|
|
def sha(path):
|
|
return hashlib.sha256(path.read_bytes()).hexdigest()
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
parser.add_argument("--output", type=Path, required=True)
|
|
parser.add_argument("--libraries", type=Path, required=True)
|
|
parser.add_argument("--image", required=True)
|
|
options = parser.parse_args()
|
|
source = Path(__file__).resolve().parent
|
|
repo = source.parents[2]
|
|
revision = subprocess.check_output(["git", "rev-parse", "HEAD"], cwd=repo, text=True).strip()
|
|
if subprocess.check_output(["git", "status", "--porcelain"], cwd=repo, text=True).strip():
|
|
raise RuntimeError("Commit and review the exact source before preparing immutable artifacts")
|
|
if not options.image.startswith("sha256:"):
|
|
raise ValueError("Use an immutable Linux .NET SDK image digest")
|
|
root = options.output.resolve()
|
|
root.mkdir(parents=True, exist_ok=False)
|
|
project = repo / "tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.IntegrationTests/ZB.MOM.WW.OtOpcUa.OpcUaServer.IntegrationTests.csproj"
|
|
command = ["dotnet", "publish", str(project), "-c", "Release", "-r", "linux-arm64", "--self-contained", "true",
|
|
"-p:RuntimeFrameworkVersion=10.0.14", "-p:PublishSingleFile=false", "-p:PublishTrimmed=false", "-p:UseAppHost=true",
|
|
"--artifacts-path", str(root / "build"), "-o", str(root / "app"), "-m:2", "--nologo"]
|
|
with (root / "publish.log").open("w") as log:
|
|
subprocess.run(command, cwd=repo, stdout=log, stderr=subprocess.STDOUT, check=True)
|
|
observer = build(root / "observer-build", options.libraries, True)
|
|
shutil.copytree(observer, root / "observer")
|
|
tools = root / "tools"
|
|
tools.mkdir()
|
|
for name in ["launch_saturation.py", "container_saturation.sh", "SATURATION.md"]:
|
|
shutil.copy2(source / name, tools / name)
|
|
manifest = {str(path.relative_to(root)): sha(path) for directory in ["app", "observer", "tools"]
|
|
for path in sorted((root / directory).rglob("*")) if path.is_file()}
|
|
for config in ["app/ZB.MOM.WW.OtOpcUa.OpcUaServer.IntegrationTests.runtimeconfig.json", "observer/observer.runtimeconfig.json"]:
|
|
runtime = json.loads((root / config).read_text())
|
|
included = runtime["runtimeOptions"]["includedFrameworks"]
|
|
if not included or any(value["version"] != "10.0.14" for value in included):
|
|
raise RuntimeError("Published participant is not the prospectively fixed self-contained runtime")
|
|
dependencies = json.loads((root / "app/ZB.MOM.WW.OtOpcUa.OpcUaServer.IntegrationTests.deps.json").read_text())["libraries"]
|
|
gateway_libraries = [name for name in dependencies if name.startswith("ZB.MOM.WW.HistorianGateway.")]
|
|
if not gateway_libraries or any(name.rsplit("/", 1)[-1] != "1.0.0" for name in gateway_libraries):
|
|
raise RuntimeError("The reviewed candidate requires the landed HistorianGateway 1.0.0 graph")
|
|
metadata = root / "module-info-build"
|
|
metadata.mkdir()
|
|
(metadata / "module-info.csproj").write_text(
|
|
'<Project Sdk="Microsoft.NET.Sdk"><PropertyGroup><OutputType>Exe</OutputType>'
|
|
'<TargetFramework>net10.0</TargetFramework><ImplicitUsings>enable</ImplicitUsings>'
|
|
'<EnableDefaultCompileItems>false</EnableDefaultCompileItems></PropertyGroup>'
|
|
'<ItemGroup><Compile Include="' + str(source / "ArtifactModuleInfo.cs") + '" /></ItemGroup></Project>')
|
|
with (metadata / "build.log").open("w") as log:
|
|
subprocess.run(["dotnet", "build", str(metadata / "module-info.csproj"), "-c", "Release", "-m:2"],
|
|
stdout=log, stderr=subprocess.STDOUT, check=True)
|
|
modules = json.loads(subprocess.check_output(["dotnet", str(metadata / "bin/Release/net10.0/module-info.dll"),
|
|
str(root), str(root / "app"), str(root / "observer")], text=True))
|
|
receipt = {"protocol": "sdk2-saturation-v1", "sourceRevision": revision, "image": options.image,
|
|
"rid": "linux-arm64", "runtimeVersion": "10.0.14", "selfContained": True,
|
|
"canonicalCycles": 384, "completedRestarts": 96, "settlingCycles": 1, "maximumPhaseSeconds": 1800,
|
|
"windows": [[321, 352], [353, 384]], "observerBufferMiB": 16, "runtimeKeywords": "0x18", "rundown": False,
|
|
"acceptance": False, "preparedUtc": datetime.now(timezone.utc).isoformat(), "publishCommand": command,
|
|
"sdkLibraries": [name for name in dependencies if name.startswith("OPCFoundation.")],
|
|
"gatewayLibraries": gateway_libraries, "managedModules": modules,
|
|
"testSourceSha256": sha(project.parent / "Sdk2LifecycleSoakTests.cs"),
|
|
"observerSourceSha256": sha(source / "SaturationObserver.cs"), "files": manifest}
|
|
receipt_path = root / "receipt.json"
|
|
receipt_path.write_text(json.dumps(receipt, indent=2) + "\n")
|
|
print(json.dumps({"bundle": str(root), "receiptSha256": sha(receipt_path), "sourceRevision": revision,
|
|
"launched": False}, indent=2))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|