Upgrade the pinned driver, validate menu and taskbar focus ownership, patch untitled Shell resolution, and remove duplicate scroll scaling. Publish package provenance, verification boundaries and matching marketplace checksum. Go tests, vet, build and byte-level package verification passed.
177 lines
6.6 KiB
Python
177 lines
6.6 KiB
Python
"""Build a self-contained Windows plugin from a hash-pinned Cua release."""
|
|
|
|
import argparse
|
|
import hashlib
|
|
import json
|
|
import os
|
|
import re
|
|
import subprocess
|
|
import urllib.request
|
|
import zipfile
|
|
from pathlib import Path
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
SOURCE = ROOT / "plugins/cua-driver"
|
|
UPSTREAM_VERSION = "0.38.0"
|
|
COMMIT = "85e2b1318bd77ccf3cfa88ccdf875a4bb7eaed19"
|
|
DIGEST = "5edaa74d0afa1b55a9668dbc4f32736507bd2bd4d75b18ca1dc8881bb12402b5"
|
|
URL = "https://github.com/trycua/cua/releases/download/cua-driver-rs-v0.38.0/cua-driver-rs-0.38.0-windows-x86_64-binary.zip"
|
|
PATCH = SOURCE / "patches/cua-driver-0.38.0-untitled-shell.patch"
|
|
PATCHED_PATH = "libs/cua-driver/rust/crates/platform-windows/src/win32/windows.rs"
|
|
|
|
|
|
def build_patched_runtime(source: Path, cargo: str) -> dict:
|
|
source = source.resolve()
|
|
head = subprocess.check_output(["git", "rev-parse", "HEAD"], cwd=source, text=True).strip()
|
|
if head != COMMIT:
|
|
raise ValueError("Patched Cua source must use the pinned upstream commit")
|
|
changes = subprocess.check_output(["git", "diff", "--name-only", "HEAD"], cwd=source, text=True)
|
|
if changes.splitlines() != [PATCHED_PATH]:
|
|
raise ValueError("Cua source may contain only the reviewed untitled-shell patch")
|
|
diff = subprocess.check_output(
|
|
["git", "diff", "--binary", "HEAD", "--", PATCHED_PATH], cwd=source
|
|
)
|
|
if diff.replace(b"\r\n", b"\n") != PATCH.read_bytes().replace(b"\r\n", b"\n"):
|
|
raise ValueError("Cua source does not match the packaged compatibility patch")
|
|
project = source / "libs/cua-driver/rust"
|
|
command = [
|
|
cargo,
|
|
"build",
|
|
"--release",
|
|
"--locked",
|
|
"-p",
|
|
"cua-driver",
|
|
"-p",
|
|
"cua-driver-uia",
|
|
"-p",
|
|
"cua-driver-sdk",
|
|
]
|
|
subprocess.run(command, cwd=project, check=True, timeout=1800)
|
|
binaries = {
|
|
name: project / "target/release" / name
|
|
for name in ("cua-driver.exe", "cua-driver-uia.exe", "cua_driver_sdk.dll")
|
|
}
|
|
if not all(path.is_file() for path in binaries.values()):
|
|
raise ValueError("Patched Cua runtime build is incomplete")
|
|
return binaries
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(__doc__)
|
|
parser.add_argument("upstream_zip", type=Path)
|
|
parser.add_argument("--go", default="go")
|
|
parser.add_argument("--cargo", default="cargo")
|
|
parser.add_argument("--upstream-source", type=Path, required=True)
|
|
parser.add_argument("--output", type=Path, default=ROOT / "artifacts")
|
|
args = parser.parse_args()
|
|
version = json.loads((SOURCE / "plugin.json").read_text("utf-8"))["version"]
|
|
if version.split("+", 1)[0] != UPSTREAM_VERSION:
|
|
raise ValueError("Plugin version must match the pinned Cua Driver version")
|
|
match = re.search(r'const adapterVersion = "([^"]+)"', (SOURCE / "main.go").read_text("utf-8"))
|
|
if not match or match[1] != version:
|
|
raise ValueError("Adapter MCP version must match plugin.json")
|
|
if hashlib.sha256(args.upstream_zip.read_bytes()).hexdigest() != DIGEST:
|
|
raise ValueError("Cua release archive hash mismatch")
|
|
patched_runtime = build_patched_runtime(args.upstream_source, args.cargo)
|
|
output = args.output.resolve()
|
|
output.mkdir(parents=True, exist_ok=True)
|
|
adapter = output / "artemis-cua-adapter.exe"
|
|
env = {
|
|
**os.environ,
|
|
"CGO_ENABLED": "0",
|
|
"GOOS": "windows",
|
|
"GOARCH": "amd64",
|
|
"GOTOOLCHAIN": "local",
|
|
}
|
|
for command in (
|
|
[args.go, "test", "./..."],
|
|
[
|
|
args.go,
|
|
"build",
|
|
"-trimpath",
|
|
"-buildvcs=false",
|
|
"-ldflags=-s -w",
|
|
"-o",
|
|
str(adapter),
|
|
".",
|
|
],
|
|
):
|
|
subprocess.run(command, cwd=SOURCE, env=env, check=True, timeout=180)
|
|
license_file = SOURCE / "LICENSE.upstream"
|
|
if not license_file.exists():
|
|
with urllib.request.urlopen(
|
|
f"https://raw.githubusercontent.com/trycua/cua/{COMMIT}/LICENSE.md",
|
|
timeout=30,
|
|
) as response:
|
|
license_file.write_bytes(response.read())
|
|
target = output / f"cua-driver-{version}-windows-amd64.zip"
|
|
with (
|
|
zipfile.ZipFile(target, "w", zipfile.ZIP_DEFLATED) as dest,
|
|
zipfile.ZipFile(args.upstream_zip) as upstream,
|
|
):
|
|
for name in (
|
|
"plugin.json",
|
|
"SKILL.md",
|
|
"LICENSE.upstream",
|
|
"RELEASE.md",
|
|
"VERIFICATION.md",
|
|
"BUILD.md",
|
|
):
|
|
dest.write(SOURCE / name, name)
|
|
for source in sorted(SOURCE.glob("*.go")):
|
|
dest.write(source, f"source/{source.name}")
|
|
for name in ("go.mod", "tools.json"):
|
|
dest.write(SOURCE / name, f"source/{name}")
|
|
dest.write(PATCH, f"source/patches/{PATCH.name}")
|
|
dest.write(adapter, "bin/artemis-cua-adapter.exe")
|
|
for name in upstream.namelist():
|
|
# Only the release's executable runtime; no global installer/hooks.
|
|
if Path(name).name in {
|
|
"cua-driver.exe",
|
|
"cua-driver-uia.exe",
|
|
"cua-cursor-theme.exe",
|
|
"cua_driver_sdk.dll",
|
|
}:
|
|
basename = Path(name).name
|
|
if basename not in patched_runtime:
|
|
dest.writestr(f"bin/{basename}", upstream.read(name))
|
|
for name, binary in patched_runtime.items():
|
|
dest.write(binary, f"bin/{name}")
|
|
dest.writestr(
|
|
"provenance.json",
|
|
json.dumps(
|
|
{
|
|
"source_commit": COMMIT,
|
|
"upstream_version": UPSTREAM_VERSION,
|
|
"plugin_version": version,
|
|
"adapter_revision": int(version.rsplit(".", 1)[1]),
|
|
"prerelease": True,
|
|
"upstream_release_channel": "stable",
|
|
"url": URL,
|
|
"sha256": DIGEST,
|
|
"compatibility_patch": {
|
|
"file": f"source/patches/{PATCH.name}",
|
|
"sha256": hashlib.sha256(PATCH.read_bytes()).hexdigest(),
|
|
"rebuilt_binaries": {
|
|
name: hashlib.sha256(binary.read_bytes()).hexdigest()
|
|
for name, binary in patched_runtime.items()
|
|
},
|
|
"cargo_locked": True,
|
|
},
|
|
},
|
|
indent=2,
|
|
),
|
|
)
|
|
print(
|
|
json.dumps(
|
|
{
|
|
"archive": str(target),
|
|
"sha256": hashlib.sha256(target.read_bytes()).hexdigest(),
|
|
}
|
|
)
|
|
)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|