#!/usr/bin/env python3 """Publish the SDK 1.5.378.182 comparator (lmxopcua#799) self-contained against an immutable SDK1 publication. Like ../baseline-canonical/build.py: never rebuilds product code. References come from the given RID-specific framework-dependent publications of the SDK1 host and client (host wins duplicate names; native DLLs are skipped as references and copied beside the output), plus Shouldly and its assertion dependencies. The comparator is published self-contained for the requested RID on the same pinned runtime as the SDK2 run (10.0.14). """ import argparse import hashlib import json from pathlib import Path import shutil import subprocess import xml.etree.ElementTree as ET RUNTIME = "10.0.14" SOURCES = ["Program.cs", "BaselineRig.cs", "BaselineMetrics.cs", "CanonicalOperations.cs", "WindowsDescriptors.cs"] def managed(path): data = path.read_bytes() if data[:2] != b"MZ": return False pe = int.from_bytes(data[0x3C:0x40], "little") magic = int.from_bytes(data[pe + 24:pe + 26], "little") directories = pe + 24 + (112 if magic == 0x20B else 96) clr = directories + 14 * 8 return int.from_bytes(data[clr:clr + 4], "little") != 0 parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("--host", type=Path, required=True) parser.add_argument("--client", type=Path, required=True) parser.add_argument("--assertions", type=Path, required=True) parser.add_argument("--rid", default="win-x64") parser.add_argument("--output", type=Path, required=True) args = parser.parse_args() root = args.output.resolve() root.mkdir(parents=True, exist_ok=False) source = Path(__file__).resolve().parent references, natives = {}, {} for directory in [args.host, args.client]: for dll in sorted(directory.resolve(strict=True).glob("*.dll")): (references if managed(dll) else natives).setdefault(dll.name, dll) for name in ["Shouldly.dll", "DiffEngine.dll", "EmptyFiles.dll", "EmptyFiles.Xml.dll"]: path = args.assertions.resolve(strict=True) / name if path.exists(): references.setdefault(name, path) project = ET.Element("Project", Sdk="Microsoft.NET.Sdk") properties = ET.SubElement(project, "PropertyGroup") for key, value in {"OutputType": "Exe", "TargetFramework": "net10.0", "Nullable": "enable", "ImplicitUsings": "enable", "TreatWarningsAsErrors": "true", "EnableDefaultCompileItems": "false", "AssemblyName": "sdk1-comparator", "RuntimeIdentifier": args.rid, "SelfContained": "true", "RuntimeFrameworkVersion": RUNTIME, "NoWarn": "CA1416"}.items(): ET.SubElement(properties, key).text = value items = ET.SubElement(project, "ItemGroup") ET.SubElement(items, "FrameworkReference", Include="Microsoft.AspNetCore.App") for name, path in references.items(): reference = ET.SubElement(items, "Reference", Include=path.stem) ET.SubElement(reference, "HintPath").text = str(path) for name in SOURCES: ET.SubElement(items, "Compile", Include=str(source / name)) path = root / "comparator.csproj" ET.indent(project) ET.ElementTree(project).write(path, encoding="unicode") publish = root / "publish" with (root / "build.log").open("w") as log: result = subprocess.run(["dotnet", "publish", str(path), "-c", "Release", "-o", str(publish), "-m:2", "--nologo"], stdout=log, stderr=subprocess.STDOUT) if result.returncode == 0: for name, native in natives.items(): if not (publish / name).exists(): shutil.copy2(native, publish / name) manifest = {"diagnosticOnly": True, "productRebuilt": False, "rid": args.rid, "runtime": RUNTIME, "references": {n: hashlib.sha256(p.read_bytes()).hexdigest() for n, p in references.items()}, "natives": {n: hashlib.sha256(p.read_bytes()).hexdigest() for n, p in natives.items()}, "sources": {n: hashlib.sha256((source / n).read_bytes()).hexdigest() for n in SOURCES}} (root / "provenance.json").write_text(json.dumps(manifest, indent=2) + "\n") raise SystemExit(result.returncode)