Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 38 additions & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,9 @@ jobs:
- os: windows-latest
goos: windows
goarch: amd64
- os: windows-11-arm
goos: windows
goarch: arm64

runs-on: ${{ matrix.os }}
steps:
Expand All @@ -123,15 +126,48 @@ jobs:
rm -rf internal/web/dist
cp -r frontend/dist internal/web/dist

- name: Setup MinGW (Windows)
if: matrix.goos == 'windows'
- name: Setup MinGW (Windows amd64)
if: matrix.goos == 'windows' && matrix.goarch == 'amd64'
uses: msys2/setup-msys2@e9898307ac31d1a803454791be09ab9973336e1c # v2
with:
msystem: MINGW64
update: false
install: mingw-w64-x86_64-gcc
path-type: inherit

- name: Setup llvm-mingw (Windows arm64)
if: matrix.goos == 'windows' && matrix.goarch == 'arm64'
shell: pwsh
env:
# Self-contained aarch64 mingw clang toolchain (clang + runtime +
# lld). cgo (mattn/go-sqlite3) needs a C compiler; no Visual
# Studio / Windows SDK required. Pinned to a version + SHA256 for
# reproducibility and supply-chain integrity.
LLVM_MINGW_VERSION: "20260602"
LLVM_MINGW_NAME: llvm-mingw-20260602-ucrt-aarch64
LLVM_MINGW_SHA256: cb5c20fbe1808e31ada5cbe4efd9daa2fee19c91dac6ec5ca1ac46a9c7247e37
run: |
$ErrorActionPreference = 'Stop'
$zip = Join-Path $env:RUNNER_TEMP 'llvm-mingw.zip'
$url = "https://github.com/mstorsjo/llvm-mingw/releases/download/$env:LLVM_MINGW_VERSION/$env:LLVM_MINGW_NAME.zip"
Write-Host "Downloading $url"
Invoke-WebRequest -Uri $url -OutFile $zip
# Verify the archive against the pinned SHA256 before extracting or
# executing any of it, so a swapped or compromised upstream asset
# fails the build instead of producing a trojaned binary.
$actual = (Get-FileHash -Path $zip -Algorithm SHA256).Hash
if ($actual -ne $env:LLVM_MINGW_SHA256) {
throw "Checksum mismatch for $url`n expected $env:LLVM_MINGW_SHA256`n actual $actual"
}
# 7-Zip is preinstalled on the runner image and extracts faster
# than Expand-Archive for this many files.
7z x $zip -o"$env:RUNNER_TEMP" | Out-Null
$bin = Join-Path $env:RUNNER_TEMP "$env:LLVM_MINGW_NAME\bin"
$cc = Join-Path $bin 'aarch64-w64-mingw32-clang.exe'
if (-not (Test-Path $cc)) { throw "CC not found at $cc" }
Add-Content -Path $env:GITHUB_PATH -Value $bin
Add-Content -Path $env:GITHUB_ENV -Value "CC=$cc"

- name: Build
shell: bash
env:
Expand Down
4 changes: 4 additions & 0 deletions scripts/build_wheels.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,10 @@
"wheel_tag": "win_amd64",
"binary_name": "agentsview.exe",
},
"windows_arm64": {
"wheel_tag": "win_arm64",
"binary_name": "agentsview.exe",
},
}

_ARCHIVE_RE = re.compile(
Expand Down
19 changes: 14 additions & 5 deletions scripts/build_wheels_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ def test_all_required_platforms_present(self) -> None:
"darwin_amd64",
"darwin_arm64",
"windows_amd64",
"windows_arm64",
}
assert set(PLATFORM_MAP.keys()) == required

Expand All @@ -48,6 +49,7 @@ def test_each_entry_has_binary_name(self) -> None:

def test_windows_binary_has_exe_extension(self) -> None:
assert PLATFORM_MAP["windows_amd64"]["binary_name"] == "agentsview.exe"
assert PLATFORM_MAP["windows_arm64"]["binary_name"] == "agentsview.exe"

def test_unix_binaries_have_no_extension(self) -> None:
for key in ("linux_amd64", "linux_arm64", "darwin_amd64", "darwin_arm64"):
Expand All @@ -63,6 +65,7 @@ def test_macos_wheel_tags(self) -> None:

def test_windows_wheel_tag(self) -> None:
assert PLATFORM_MAP["windows_amd64"]["wheel_tag"] == "win_amd64"
assert PLATFORM_MAP["windows_arm64"]["wheel_tag"] == "win_arm64"


# ---------------------------------------------------------------------------
Expand All @@ -83,6 +86,10 @@ def test_parse_windows_amd64_zip(self) -> None:
result = parse_archive_filename("agentsview_0.15.0_windows_amd64.zip")
assert result == ("windows_amd64", "0.15.0")

def test_parse_windows_arm64_zip(self) -> None:
result = parse_archive_filename("agentsview_0.15.0_windows_arm64.zip")
assert result == ("windows_arm64", "0.15.0")

def test_parse_darwin_amd64_tar_gz(self) -> None:
result = parse_archive_filename("agentsview_2.0.0_darwin_amd64.tar.gz")
assert result == ("darwin_amd64", "2.0.0")
Expand Down Expand Up @@ -303,13 +310,14 @@ def test_wheel_filename_darwin_arm64(self, tmp_path: Path) -> None:

class TestBuildAllWheels:
def _make_fake_archives(self, input_dir: Path, version: str) -> None:
"""Create fake release archives for all 5 platforms."""
"""Create fake release archives for every supported platform."""
platforms = [
("linux_amd64", "agentsview", ".tar.gz"),
("linux_arm64", "agentsview", ".tar.gz"),
("darwin_amd64", "agentsview", ".tar.gz"),
("darwin_arm64", "agentsview", ".tar.gz"),
("windows_amd64", "agentsview.exe", ".zip"),
("windows_arm64", "agentsview.exe", ".zip"),
]
for platform_key, binary_name, ext in platforms:
content = f"binary-for-{platform_key}".encode()
Expand All @@ -322,13 +330,13 @@ def _make_fake_archives(self, input_dir: Path, version: str) -> None:
# Also add a SHA256SUMS file that should be skipped
(input_dir / f"agentsview_{version}_SHA256SUMS").write_text("checksums here")

def test_produces_five_wheels(self, tmp_path: Path) -> None:
def test_produces_a_wheel_per_platform(self, tmp_path: Path) -> None:
input_dir = tmp_path / "input"
output_dir = tmp_path / "output"
input_dir.mkdir()
self._make_fake_archives(input_dir, "0.15.0")
wheels = build_all_wheels(input_dir, output_dir, "0.15.0")
assert len(wheels) == 5
assert len(wheels) == len(PLATFORM_MAP)

def test_correct_wheel_names(self, tmp_path: Path) -> None:
input_dir = tmp_path / "input"
Expand All @@ -343,6 +351,7 @@ def test_correct_wheel_names(self, tmp_path: Path) -> None:
"agentsview-0.15.0-py3-none-macosx_11_0_x86_64.whl",
"agentsview-0.15.0-py3-none-macosx_11_0_arm64.whl",
"agentsview-0.15.0-py3-none-win_amd64.whl",
"agentsview-0.15.0-py3-none-win_arm64.whl",
}
assert names == expected

Expand All @@ -355,7 +364,7 @@ def test_unknown_platforms_skipped(self, tmp_path: Path) -> None:
unknown = input_dir / "agentsview_0.15.0_freebsd_amd64.tar.gz"
unknown.write_bytes(_make_targz("agentsview", b"fake"))
wheels = build_all_wheels(input_dir, output_dir, "0.15.0")
assert len(wheels) == 5 # still only 5
assert len(wheels) == len(PLATFORM_MAP) # unknown skipped

def test_output_dir_created_if_missing(self, tmp_path: Path) -> None:
input_dir = tmp_path / "input"
Expand Down Expand Up @@ -403,4 +412,4 @@ def test_require_all_passes_with_all_platforms(self, tmp_path: Path) -> None:
wheels = build_all_wheels(
input_dir, output_dir, "1.0.0", require_all=True
)
assert len(wheels) == 5
assert len(wheels) == len(PLATFORM_MAP)
Loading