Add Phase 0 build verification gate (T-004)
This commit is contained in:
@@ -0,0 +1,179 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Validate toolchain pins and Go 1.20 compatibility of core/win7 modules."""
|
||||
|
||||
import json
|
||||
import os
|
||||
import pathlib
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
|
||||
MAX_LEGACY_GO_VERSION = (1, 20)
|
||||
|
||||
|
||||
def version_tuple(value):
|
||||
match = re.fullmatch(r"(\d+)\.(\d+)(?:\.(\d+))?", value)
|
||||
if not match:
|
||||
raise ValueError("invalid Go version {!r}".format(value))
|
||||
return tuple(int(part or 0) for part in match.groups())
|
||||
|
||||
|
||||
def decode_json_stream(text):
|
||||
decoder = json.JSONDecoder()
|
||||
index = 0
|
||||
while index < len(text):
|
||||
while index < len(text) and text[index].isspace():
|
||||
index += 1
|
||||
if index >= len(text):
|
||||
return
|
||||
value, index = decoder.raw_decode(text, index)
|
||||
yield value
|
||||
|
||||
|
||||
def read_text(path):
|
||||
return path.read_text(encoding="utf-8")
|
||||
|
||||
|
||||
def go_directive(path):
|
||||
match = re.search(r"(?m)^go\s+(\d+\.\d+(?:\.\d+)?)\s*$", read_text(path))
|
||||
if not match:
|
||||
raise ValueError("{} has no go directive".format(path))
|
||||
return match.group(1)
|
||||
|
||||
|
||||
def required_version(path, module_path):
|
||||
pattern = r"(?m)^\s*{}\s+(v[^\s]+)".format(re.escape(module_path))
|
||||
match = re.search(pattern, read_text(path))
|
||||
if not match:
|
||||
raise ValueError(
|
||||
"{} does not require {}".format(path, module_path)
|
||||
)
|
||||
return match.group(1)
|
||||
|
||||
|
||||
def run_go(repo_root, cwd, arguments, go_work):
|
||||
environment = os.environ.copy()
|
||||
environment["GOWORK"] = str(go_work) if go_work else "off"
|
||||
result = subprocess.run(
|
||||
["go"] + arguments,
|
||||
cwd=str(cwd),
|
||||
env=environment,
|
||||
check=False,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
universal_newlines=True,
|
||||
)
|
||||
if result.returncode != 0:
|
||||
print(result.stderr, file=sys.stderr, end="")
|
||||
raise SystemExit(result.returncode)
|
||||
return result.stdout
|
||||
|
||||
|
||||
def validate_pins(repo_root):
|
||||
expected_go_directives = {
|
||||
repo_root / "go.work": "1.25.0",
|
||||
repo_root / "core" / "go.mod": "1.20",
|
||||
repo_root / "app-modern" / "go.mod": "1.25.0",
|
||||
repo_root / "app-win7" / "go.mod": "1.20",
|
||||
repo_root / "app-win7" / "go.work": "1.20",
|
||||
}
|
||||
violations = []
|
||||
|
||||
for path, expected in expected_go_directives.items():
|
||||
actual = go_directive(path)
|
||||
if actual != expected:
|
||||
violations.append(
|
||||
"{} go directive is {}, want {}".format(
|
||||
path.relative_to(repo_root), actual, expected
|
||||
)
|
||||
)
|
||||
|
||||
gio_pins = {
|
||||
repo_root / "app-modern" / "go.mod": "v0.10.1",
|
||||
repo_root / "app-win7" / "go.mod": "v0.6.0",
|
||||
}
|
||||
for path, expected in gio_pins.items():
|
||||
actual = required_version(path, "gioui.org")
|
||||
if actual != expected:
|
||||
violations.append(
|
||||
"{} pins gioui.org {}, want {}".format(
|
||||
path.relative_to(repo_root), actual, expected
|
||||
)
|
||||
)
|
||||
|
||||
return violations
|
||||
|
||||
|
||||
def validate_go20_toolchain(repo_root):
|
||||
output = run_go(repo_root, repo_root, ["version"], None).strip()
|
||||
if "go1.20.14" not in output:
|
||||
return ["compatibility scan uses {!r}, want go1.20.14".format(output)]
|
||||
return []
|
||||
|
||||
|
||||
def validate_module_versions(repo_root):
|
||||
module_sets = (
|
||||
("core", repo_root / "core", None),
|
||||
(
|
||||
"win7",
|
||||
repo_root / "app-win7",
|
||||
repo_root / "app-win7" / "go.work",
|
||||
),
|
||||
)
|
||||
violations = []
|
||||
checked = 0
|
||||
|
||||
for label, cwd, go_work in module_sets:
|
||||
output = run_go(
|
||||
repo_root,
|
||||
cwd,
|
||||
["list", "-m", "-json", "all"],
|
||||
go_work,
|
||||
)
|
||||
for module in decode_json_stream(output):
|
||||
checked += 1
|
||||
go_version = module.get("GoVersion")
|
||||
if not go_version:
|
||||
continue
|
||||
if version_tuple(go_version)[:2] > MAX_LEGACY_GO_VERSION:
|
||||
violations.append(
|
||||
"{} module {} declares go {}, exceeds 1.20".format(
|
||||
label, module["Path"], go_version
|
||||
)
|
||||
)
|
||||
|
||||
return violations, checked
|
||||
|
||||
|
||||
def self_check():
|
||||
if version_tuple("1.20")[:2] > MAX_LEGACY_GO_VERSION:
|
||||
raise AssertionError("Go 1.20 should be accepted")
|
||||
if version_tuple("1.20.14")[:2] > MAX_LEGACY_GO_VERSION:
|
||||
raise AssertionError("Go 1.20.14 should be accepted")
|
||||
if not version_tuple("1.21")[:2] > MAX_LEGACY_GO_VERSION:
|
||||
raise AssertionError("Go 1.21 should be rejected")
|
||||
|
||||
|
||||
def main():
|
||||
self_check()
|
||||
repo_root = pathlib.Path(__file__).resolve().parents[1]
|
||||
violations = validate_pins(repo_root)
|
||||
violations.extend(validate_go20_toolchain(repo_root))
|
||||
module_violations, checked = validate_module_versions(repo_root)
|
||||
violations.extend(module_violations)
|
||||
|
||||
if violations:
|
||||
for violation in violations:
|
||||
print("ERROR: " + violation, file=sys.stderr)
|
||||
raise SystemExit(1)
|
||||
|
||||
print(
|
||||
"Go version check passed: pins valid; {} module records are Go 1.20-compatible.".format(
|
||||
checked
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,95 @@
|
||||
#!/usr/bin/env pwsh
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
$Utf8 = [System.Text.UTF8Encoding]::new($false)
|
||||
[Console]::OutputEncoding = $Utf8
|
||||
$OutputEncoding = $Utf8
|
||||
$Root = Split-Path -Parent $PSScriptRoot
|
||||
Set-Location -Path $Root
|
||||
|
||||
function Assert-NativeSuccess {
|
||||
param([string]$Step)
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "$Step failed with exit code $LASTEXITCODE"
|
||||
}
|
||||
}
|
||||
|
||||
function Invoke-Step {
|
||||
param(
|
||||
[string]$Name,
|
||||
[scriptblock]$Command
|
||||
)
|
||||
Write-Host "==> $Name"
|
||||
& $Command
|
||||
Assert-NativeSuccess -Step $Name
|
||||
}
|
||||
|
||||
$GoPath = (& go env GOPATH).Trim()
|
||||
Assert-NativeSuccess -Step "Locate GOPATH"
|
||||
$GoBin = Join-Path $GoPath "bin"
|
||||
if (Test-Path (Join-Path $GoBin "go1.20.14.exe")) {
|
||||
$env:PATH = "$GoBin;$env:PATH"
|
||||
}
|
||||
|
||||
New-Item -ItemType Directory -Force -Path (Join-Path $Root "dist") | Out-Null
|
||||
|
||||
Invoke-Step "Sync root workspace" {
|
||||
$env:GOTOOLCHAIN = "go1.25.0"
|
||||
Remove-Item Env:GOWORK -ErrorAction SilentlyContinue
|
||||
go work sync
|
||||
}
|
||||
|
||||
Invoke-Step "Validate harness governance" {
|
||||
python scripts/validate_agent_context.py
|
||||
Assert-NativeSuccess -Step "Validate agent context"
|
||||
python -m unittest discover -s tests -p "test_*.py"
|
||||
Assert-NativeSuccess -Step "Run governance tests"
|
||||
python scripts/validate_harness_governance.py
|
||||
}
|
||||
|
||||
Invoke-Step "Validate core architecture boundary" {
|
||||
python scripts/check_core_boundaries.py --self-test
|
||||
Assert-NativeSuccess -Step "Self-test core boundary matcher"
|
||||
python scripts/check_core_boundaries.py
|
||||
}
|
||||
|
||||
Invoke-Step "Validate Go and dependency pins" {
|
||||
$env:GOTOOLCHAIN = "go1.20.14"
|
||||
python scripts/check_go_versions.py
|
||||
}
|
||||
|
||||
Invoke-Step "Vet and test core with Go 1.20.14" {
|
||||
$env:GOTOOLCHAIN = "go1.20.14"
|
||||
$env:GOWORK = "off"
|
||||
go -C core vet ./...
|
||||
Assert-NativeSuccess -Step "Vet core"
|
||||
go -C core test -count=1 ./...
|
||||
}
|
||||
|
||||
Invoke-Step "Test and build modern target with Go 1.25.0" {
|
||||
$env:GOTOOLCHAIN = "go1.25.0"
|
||||
$env:GOWORK = (Resolve-Path "go.work").Path
|
||||
Remove-Item Env:GOOS -ErrorAction SilentlyContinue
|
||||
Remove-Item Env:GOARCH -ErrorAction SilentlyContinue
|
||||
$env:CGO_ENABLED = "0"
|
||||
go -C app-modern test -count=1 ./ui/gio ./platform/windows
|
||||
Assert-NativeSuccess -Step "Test modern adapters"
|
||||
$env:GOOS = "windows"
|
||||
$env:GOARCH = "amd64"
|
||||
go -C app-modern build -trimpath "-ldflags=-H=windowsgui" -o ../dist/SoftBox.exe ./cmd/softbox
|
||||
}
|
||||
|
||||
Invoke-Step "Test and build Win7 target with Go 1.20.14" {
|
||||
$env:GOTOOLCHAIN = "go1.20.14"
|
||||
$env:GOWORK = (Resolve-Path "app-win7/go.work").Path
|
||||
Remove-Item Env:GOOS -ErrorAction SilentlyContinue
|
||||
Remove-Item Env:GOARCH -ErrorAction SilentlyContinue
|
||||
$env:CGO_ENABLED = "0"
|
||||
go -C app-win7 test -count=1 ./ui/gio ./platform/windows
|
||||
Assert-NativeSuccess -Step "Test Win7 adapters"
|
||||
$env:GOOS = "windows"
|
||||
$env:GOARCH = "amd64"
|
||||
go -C app-win7 build -trimpath "-ldflags=-H=windowsgui" -o ../dist/SoftBox-win7.exe ./cmd/softbox
|
||||
}
|
||||
|
||||
Write-Host "Phase 0 verification passed."
|
||||
@@ -0,0 +1,63 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
cd "$ROOT_DIR"
|
||||
|
||||
if python3 --version >/dev/null 2>&1; then
|
||||
PYTHON=python3
|
||||
elif python --version >/dev/null 2>&1; then
|
||||
PYTHON=python
|
||||
else
|
||||
echo "ERROR: Python 3 is required." >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
GO_PATH="$(go env GOPATH)"
|
||||
if command -v cygpath >/dev/null 2>&1; then
|
||||
GO_PATH="$(cygpath -u "$GO_PATH")"
|
||||
fi
|
||||
GO_BIN="$GO_PATH/bin"
|
||||
if [ -x "$GO_BIN/go1.20.14" ] || [ -x "$GO_BIN/go1.20.14.exe" ]; then
|
||||
export PATH="$GO_BIN:$PATH"
|
||||
fi
|
||||
|
||||
mkdir -p dist
|
||||
|
||||
echo "==> Sync root workspace"
|
||||
GOTOOLCHAIN=go1.25.0 go work sync
|
||||
|
||||
echo "==> Validate harness governance"
|
||||
"$PYTHON" scripts/validate_agent_context.py
|
||||
"$PYTHON" -m unittest discover -s tests -p "test_*.py"
|
||||
"$PYTHON" scripts/validate_harness_governance.py
|
||||
|
||||
echo "==> Validate core architecture boundary"
|
||||
"$PYTHON" scripts/check_core_boundaries.py --self-test
|
||||
"$PYTHON" scripts/check_core_boundaries.py
|
||||
|
||||
echo "==> Validate Go and dependency pins"
|
||||
GOTOOLCHAIN=go1.20.14 "$PYTHON" scripts/check_go_versions.py
|
||||
|
||||
echo "==> Vet and test core with Go 1.20.14"
|
||||
GOTOOLCHAIN=go1.20.14 GOWORK=off go -C core vet ./...
|
||||
GOTOOLCHAIN=go1.20.14 GOWORK=off go -C core test -count=1 ./...
|
||||
|
||||
echo "==> Test and build modern target with Go 1.25.0"
|
||||
GOTOOLCHAIN=go1.25.0 GOWORK="$ROOT_DIR/go.work" CGO_ENABLED=0 \
|
||||
go -C app-modern test -count=1 ./ui/gio ./platform/windows
|
||||
GOTOOLCHAIN=go1.25.0 GOWORK="$ROOT_DIR/go.work" CGO_ENABLED=0 \
|
||||
GOOS=windows GOARCH=amd64 \
|
||||
go -C app-modern build -trimpath -ldflags="-H=windowsgui" \
|
||||
-o ../dist/SoftBox.exe ./cmd/softbox
|
||||
|
||||
echo "==> Test and build Win7 target with Go 1.20.14"
|
||||
GOTOOLCHAIN=go1.20.14 GOWORK="$ROOT_DIR/app-win7/go.work" CGO_ENABLED=0 \
|
||||
go -C app-win7 test -count=1 ./ui/gio ./platform/windows
|
||||
GOTOOLCHAIN=go1.20.14 GOWORK="$ROOT_DIR/app-win7/go.work" CGO_ENABLED=0 \
|
||||
GOOS=windows GOARCH=amd64 \
|
||||
go -C app-win7 build -trimpath -ldflags="-H=windowsgui" \
|
||||
-o ../dist/SoftBox-win7.exe ./cmd/softbox
|
||||
|
||||
echo "Phase 0 verification passed."
|
||||
Reference in New Issue
Block a user