From cfa72fd77698877dde2b43b2612db3712fd5c598 Mon Sep 17 00:00:00 2001 From: Benjamin Carlsson Date: Thu, 10 Sep 2026 20:17:17 -0700 Subject: [PATCH] Bundle Windows voice runtime for release rehearsal --- .../scripts/build-codex-package-archive.sh | 7 +- .github/scripts/setup-voice-windows.ps1 | 96 +++++++ .github/scripts/test_voice_cygwin_inputs.py | 83 ++++++ .github/scripts/voice-cygwin-inputs.py | 98 +++++++ .github/scripts/voice-cygwin-snapshot.json | 145 ++++++++++ .github/scripts/voice_windows_tools.py | 80 ++++++ .github/scripts/watch_voice_bazel.py | 76 ++++++ .github/workflows/rust-release-windows.yml | 256 ++++++++++++++++-- MODULE.bazel | 2 + patches/BUILD.bazel | 2 + patches/ring_windows_msvc_include_dirs.patch | 20 +- .../rules_foreign_cc_make_msvc_stdint.patch | 13 + patches/rules_rs_windows_msvc_linker.patch | 7 +- ...les_rust_windows_execroot_separators.patch | 52 ++++ ...s_rust_windows_msvc_direct_link_args.patch | 2 +- third_party/voice/NOTICE.md | 10 + third_party/voice/README.md | 18 +- third_party/voice/assemble_package.py | 1 + third_party/voice/release_runtime.py | 2 + third_party/voice/test_assemble_package.py | 41 +++ third_party/voice/windows-crt.json | 18 ++ third_party/voice/windows_crt.py | 83 ++++++ 22 files changed, 1084 insertions(+), 28 deletions(-) create mode 100644 .github/scripts/setup-voice-windows.ps1 create mode 100644 .github/scripts/test_voice_cygwin_inputs.py create mode 100644 .github/scripts/voice-cygwin-inputs.py create mode 100644 .github/scripts/voice-cygwin-snapshot.json create mode 100644 .github/scripts/voice_windows_tools.py create mode 100644 .github/scripts/watch_voice_bazel.py create mode 100644 patches/rules_foreign_cc_make_msvc_stdint.patch create mode 100644 patches/rules_rust_windows_execroot_separators.patch create mode 100644 third_party/voice/windows-crt.json create mode 100644 third_party/voice/windows_crt.py diff --git a/.github/scripts/build-codex-package-archive.sh b/.github/scripts/build-codex-package-archive.sh index 1ac54c0cce..ecac99f533 100644 --- a/.github/scripts/build-codex-package-archive.sh +++ b/.github/scripts/build-codex-package-archive.sh @@ -117,8 +117,8 @@ if [[ -z "$target" || -z "$bundle" || -z "$entrypoint_dir" || -z "$archive_dir" usage >&2 exit 1 fi -if [[ ( -n "$voice_release_dir" || -n "$release_version" ) && ( -z "$voice_release_dir" || -z "$release_version" || "$bundle" != "primary" || ( "$target" != *-apple-darwin && "$target" != *-unknown-linux-musl ) ) ]]; then - echo "Voice resources require a primary macOS or Linux release package version" >&2 +if [[ ( -n "$voice_release_dir" || -n "$release_version" ) && ( -z "$voice_release_dir" || -z "$release_version" || "$bundle" != "primary" || ( "$target" != *-apple-darwin && "$target" != *-unknown-linux-musl && "$target" != *-pc-windows-msvc ) ) ]]; then + echo "Voice resources require a primary supported release package version" >&2 exit 1 fi @@ -221,9 +221,10 @@ if [[ -n "$voice_release_dir" ]]; then fi voice_package="${RUNNER_TEMP:-/tmp}/${archive_stem}-voice-${target}" rm -rf "$voice_package" + voice_helper="${voice_release_dir%/}/codex-voice-host${exe_suffix}" "$python_bin" "${repo_root}/third_party/voice/assemble_package.py" \ --package "$package_dir" \ - --helper "${voice_release_dir%/}/codex-voice-host" \ + --helper "$voice_helper" \ --runtime "${voice_release_dir%/}/runtime" \ --voice-target "$voice_target" \ --build-commit "$(git -C "$repo_root" rev-parse HEAD)" \ diff --git a/.github/scripts/setup-voice-windows.ps1 b/.github/scripts/setup-voice-windows.ps1 new file mode 100644 index 0000000000..a24d29b1f0 --- /dev/null +++ b/.github/scripts/setup-voice-windows.ps1 @@ -0,0 +1,96 @@ +# CI-only prerequisites. Never install these tools into a Codex package. +param( + [Parameter(Mandatory = $true)][string]$Target, + [Parameter(Mandatory = $true)][string]$SnapshotArchive +) + +$ErrorActionPreference = "Stop" +$PkgHashes = @{ + "x86_64-pc-windows-msvc" = @("x64", "5604cf25ef38bb6a09520cff25ae9f0ecd8c2443053b15e40df4ad1eae0e4405") + "aarch64-pc-windows-msvc" = @("arm64", "d5752ce2ac2296c8abb91fb12c12e75ee99ba8a18b52ce5febf325847b6f062b") +} +if (-not $PkgHashes.ContainsKey($Target)) { throw "Unsupported target: $Target" } +$Root = Join-Path $env:RUNNER_TEMP "voice-windows-build-tools" +$Evidence = Join-Path $Root "evidence" +$Cygwin = Join-Path $Root "cygwin" +$Cache = Join-Path $Root "cache" +New-Item -ItemType Directory -Path $Root, $Evidence | Out-Null +$ManifestPath = Join-Path $PSScriptRoot "voice-cygwin-snapshot.json" +$Manifest = Get-Content -Raw $ManifestPath | ConvertFrom-Json +$SnapshotTool = Join-Path $PSScriptRoot "voice-cygwin-inputs.py" +& python $SnapshotTool extract --archive $SnapshotArchive --directory (Join-Path $Cache $Manifest.cacheDirectory) +if ($LASTEXITCODE -ne 0) { throw "Cygwin snapshot could not be verified and extracted" } +Copy-Item $ManifestPath (Join-Path $Evidence "cygwin-snapshot.json") + +# Official https://cygwin.com/setup/sha512.sum and pkgconf release asset digests. +# The complete package closure is pinned separately in cygwin-snapshot.json. +$Inputs = @( + @{ + url = "https://cygwin.com/setup/setup-2.937.x86_64.exe" + file = "setup.exe" + algorithm = "SHA512" + digest = "6acea47c59781c9e7f544a18d53935d59df6e44d5d52ac95ee165671b8e388820455eebf30ccc7d254b00c3d0eb694269a0f8dc84b17944c1f355dac9c5aafcc" + }, + @{ + url = "https://github.com/pkgconf/pkgconf/releases/download/pkgconf-3.0.6/pkgconf-$($PkgHashes[$Target][0])-3.0.6.msi" + file = "pkgconf.msi" + algorithm = "SHA256" + digest = $PkgHashes[$Target][1] + } +) +foreach ($InputFile in $Inputs) { + $Destination = Join-Path $Root $InputFile.file + Invoke-WebRequest -Uri $InputFile.url -OutFile $Destination + $Actual = (Get-FileHash -Path $Destination -Algorithm $InputFile.algorithm).Hash.ToLowerInvariant() + if ($Actual -ne $InputFile.digest) { throw "Build input digest mismatch: $($InputFile.file)" } +} +$Inputs | ConvertTo-Json | Set-Content (Join-Path $Evidence "bootstrap-inputs.json") +$Packages = $Manifest.requestedPackages -join "," +# Only setup.xz + setup.xz.sig are present: local mode still verifies that +# signature. Never substitute an unsigned setup.ini or enable a network fallback. +$Setup = Start-Process -FilePath (Join-Path $Root "setup.exe") -Wait -PassThru -ArgumentList @( + "--quiet-mode", "--no-admin", "--no-shortcuts", "--only-site", + "--local-install", "--no-version-check", + "--root", "`"$Cygwin`"", "--local-package-dir", "`"$Cache`"", + "--packages", $Packages +) +if ($Setup.ExitCode -ne 0) { throw "Cygwin setup failed: $($Setup.ExitCode)" } +Copy-Item (Join-Path $Cygwin "var/log/setup.log*") $Evidence +Copy-Item (Join-Path $Cygwin "etc/setup/installed.db") $Evidence +& (Join-Path $Cygwin "bin/cygcheck.exe") -cd | Set-Content (Join-Path $Evidence "packages.txt") +if ($LASTEXITCODE -ne 0) { throw "Cannot inventory Cygwin packages" } +& python $SnapshotTool check-installed --inventory (Join-Path $Evidence "packages.txt") +if ($LASTEXITCODE -ne 0) { throw "Installed Cygwin package set differs from the snapshot" } +Get-ChildItem $Cache -File -Recurse | ForEach-Object { + @{ file = [IO.Path]::GetRelativePath($Cache, $_.FullName); bytes = $_.Length; + sha512 = (Get-FileHash $_.FullName -Algorithm SHA512).Hash.ToLowerInvariant() } +} | ConvertTo-Json | Set-Content (Join-Path $Evidence "package-cache.json") + +# Administrative extraction uses TARGETDIR, not a normal MSI installation: +# no product registration or system PATH modification. Fail rather than fall +# back to an ambient executable or ordinary installer if the payload is absent. +$Image = Join-Path $Root "pkgconf-image" +$Extract = Start-Process msiexec.exe -Wait -PassThru -ArgumentList @( + "/a", "`"$(Join-Path $Root 'pkgconf.msi')`"", "/qn", "/norestart", + "TARGETDIR=`"$Image`"", "/L*v", "`"$(Join-Path $Evidence 'pkgconf-extract.log')`"" +) +if ($Extract.ExitCode -ne 0) { throw "pkgconf extraction failed: $($Extract.ExitCode)" } +$Candidates = @(Get-ChildItem $Image -Filter pkgconf.exe -File -Recurse) +if ($Candidates.Count -ne 1) { throw "Expected exactly one native pkgconf executable" } +# Bazel shell-quotes execpaths containing spaces; Cargo executes PKG_CONFIG +# directly. Keep the executable and its support files at a space-free execpath. +$Native = Join-Path $Image "native" +if (Test-Path $Native) { throw "Native pkgconf destination must be fresh" } +Move-Item -LiteralPath $Candidates[0].Directory.FullName -Destination $Native +$PkgConfig = Join-Path $Native "pkgconf.exe" +$Version = & $PkgConfig --version +if ($LASTEXITCODE -ne 0 -or $Version -ne "3.0.6") { throw "Unexpected native pkgconf version" } +@{ target = $Target; cygwin_root = $Cygwin; pkg_config = $PkgConfig; + pkgconf_version = $Version; requested_packages = $Packages } | + ConvertTo-Json | Set-Content (Join-Path $Evidence "tools.json") +# Declare complete installed support trees only after snapshot and tool checks. +& python (Join-Path $PSScriptRoot "voice_windows_tools.py") --root $Root --target $Target --pkg-config $PkgConfig +if ($LASTEXITCODE -ne 0) { throw "Cannot declare Windows Bazel tool inputs" } +"VOICE_WINDOWS_BAZEL_REPOSITORY=$Root" | Out-File $env:GITHUB_ENV -Encoding utf8 -Append +"VOICE_CYGWIN_ROOT=$Cygwin" | Out-File $env:GITHUB_ENV -Encoding utf8 -Append +"VOICE_PKG_CONFIG=$PkgConfig" | Out-File $env:GITHUB_ENV -Encoding utf8 -Append diff --git a/.github/scripts/test_voice_cygwin_inputs.py b/.github/scripts/test_voice_cygwin_inputs.py new file mode 100644 index 0000000000..3b5a4d31ae --- /dev/null +++ b/.github/scripts/test_voice_cygwin_inputs.py @@ -0,0 +1,83 @@ +"""Offline checks for the public Windows build-input bootstrap.""" + +import hashlib +import importlib.util +import io +from pathlib import Path +import tarfile +import tempfile +import unittest + + +SCRIPT = Path(__file__).with_name("voice-cygwin-inputs.py") +SPEC = importlib.util.spec_from_file_location("voice_cygwin_inputs", SCRIPT) +inputs = importlib.util.module_from_spec(SPEC) +SPEC.loader.exec_module(inputs) + + +class VoiceCygwinInputsTests(unittest.TestCase): + def test_input_paths_cannot_escape_the_local_cache(self): + manifest = { + "metadata": [ + {"file": "x86_64/setup.xz"}, + {"file": "x86_64/setup.xz.sig"}, + ], + "packages": [{"file": "../outside.tar.xz"}], + } + with self.assertRaisesRegex(ValueError, "unsafe Cygwin input"): + inputs.records(manifest) + + def test_extract_verifies_archive_and_every_input_before_offline_installation(self): + with tempfile.TemporaryDirectory() as directory: + root = Path(directory) + payloads = { + "x86_64/setup.xz": b"metadata", + "x86_64/setup.xz.sig": b"signature", + "x86_64/release/bash/bash.tar.xz": b"package", + } + records = [ + { + "file": name, + "bytes": len(data), + "sha512": hashlib.sha512(data).hexdigest(), + } + for name, data in payloads.items() + ] + archive = root / "inputs.tar.gz" + + def write_archive(contents): + with tarfile.open(archive, "w:gz") as bundle: + for name, data in contents.items(): + info = tarfile.TarInfo(name) + info.size = len(data) + bundle.addfile(info, io.BytesIO(data)) + return { + "bytes": archive.stat().st_size, + "sha256": hashlib.sha256(archive.read_bytes()).hexdigest(), + } + + manifest = { + "metadata": records[:2], + "packages": records[2:], + "archive": write_archive(payloads), + } + destination = root / "cache" + inputs.extract(manifest, archive, destination) + self.assertEqual( + { + record["file"]: (destination / record["file"]).read_bytes() + for record in records + }, + payloads, + ) + + (destination / records[2]["file"]).unlink() + payloads[records[2]["file"]] = b"wrong" + manifest["archive"] = write_archive(payloads) + with self.assertRaisesRegex(ValueError, "Cygwin input mismatch"): + inputs.extract(manifest, archive, root / "rejected") + self.assertFalse((root / "rejected").exists()) + + +if __name__ == "__main__": + unittest.main() diff --git a/.github/scripts/voice-cygwin-inputs.py b/.github/scripts/voice-cygwin-inputs.py new file mode 100644 index 0000000000..11b2ebda7d --- /dev/null +++ b/.github/scripts/voice-cygwin-inputs.py @@ -0,0 +1,98 @@ +"""Verify the pinned Cygwin build-input archive before offline installation.""" + +import argparse +import hashlib +import json +from pathlib import Path, PurePosixPath +import shutil +import tarfile + + +def records(manifest): + metadata = manifest["metadata"] + if {item["file"] for item in metadata} != { + "x86_64/setup.xz", + "x86_64/setup.xz.sig", + }: + raise ValueError("signed setup metadata is required") + entries = metadata + manifest["packages"] + expected = {entry["file"]: entry for entry in entries} + if len(expected) != len(entries): + raise ValueError("duplicate Cygwin input") + for name in expected: + path = PurePosixPath(name) + if ( + not path.parts + or path.is_absolute() + or ".." in path.parts + or path.as_posix() != name + or "\\" in name + or ":" in name + ): + raise ValueError(f"unsafe Cygwin input: {name}") + return expected + + +def extract(manifest, archive, destination): + expected = records(manifest) + pin = manifest["archive"] + with archive.open("rb") as stream: + if ( + archive.stat().st_size != pin["bytes"] + or hashlib.file_digest(stream, "sha256").hexdigest() != pin["sha256"] + ): + raise ValueError("Cygwin archive digest or size mismatch") + stream.seek(0) + with tarfile.open(fileobj=stream, mode="r:gz") as bundle: + members = bundle.getmembers() + if len(members) != len(expected) or {m.name for m in members} != set( + expected + ): + raise ValueError("missing, extra, or duplicate Cygwin inputs") + for member in members: + if not member.isfile() or member.issparse(): + raise ValueError(f"non-regular Cygwin input: {member.name}") + with bundle.extractfile(member) as source: + if ( + member.size != expected[member.name]["bytes"] + or hashlib.file_digest(source, "sha512").hexdigest() + != expected[member.name]["sha512"] + ): + raise ValueError(f"Cygwin input mismatch: {member.name}") + destination.mkdir(parents=True) + try: + bundle.extractall(destination, members=members, filter="data") + except BaseException: + shutil.rmtree(destination) + raise + + +def check_installed(manifest, inventory): + installed = sorted( + tuple(line.split()) + for line in inventory.read_text().splitlines() + if len(line.split()) == 2 and not line.startswith("Package ") + ) + expected = sorted((item["name"], item["version"]) for item in manifest["packages"]) + if installed != expected: + raise ValueError("installed Cygwin packages differ from reviewed inputs") + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("command", choices=("extract", "check-installed")) + parser.add_argument("--archive", type=Path) + parser.add_argument("--directory", type=Path) + parser.add_argument("--inventory", type=Path) + args = parser.parse_args() + pinned = json.loads( + Path(__file__).with_name("voice-cygwin-snapshot.json").read_text() + ) + if args.command == "extract": + if args.archive is None or args.directory is None: + parser.error("extract requires --archive and --directory") + extract(pinned, args.archive, args.directory) + else: + if args.inventory is None: + parser.error("check-installed requires --inventory") + check_installed(pinned, args.inventory) diff --git a/.github/scripts/voice-cygwin-snapshot.json b/.github/scripts/voice-cygwin-snapshot.json new file mode 100644 index 0000000000..8679adca1e --- /dev/null +++ b/.github/scripts/voice-cygwin-snapshot.json @@ -0,0 +1,145 @@ +{ + "schemaVersion": 1, + "sourceMirror": "https://mirrors.kernel.org/sourceware/cygwin/", + "cacheDirectory": "https%3a%2f%2fmirrors.kernel.org%2fsourceware%2fcygwin%2f", + "requestedPackages": [ + "bash", + "make", + "automake1.18", + "coreutils", + "diffutils", + "findutils", + "grep", + "sed", + "gawk" + ], + "signingFingerprint": "56405CF6FCC81574682A5D561A698DE9E2E56300", + "archive": { + "tag": "voice-cygwin-108b38cf67cbb731", + "name": "cygwin-build-inputs.tar.gz", + "bytes": 62276721, + "sha256": "108b38cf67cbb731801e5ca8d8412b8f349961e4e71d8e3d5a1768f1a8d15c50" + }, + "metadata": [ + { + "file": "x86_64/setup.xz", + "bytes": 3598392, + "sha512": "306bffefa1e86178d9c6ff78a8add2e1af3c96f7cd698d62b58d0237a568e1a9f1b30016ed5891f9fe1d1c8a1e58089c3d9cb8989eb7bed758a928f212078e25" + }, + { + "file": "x86_64/setup.xz.sig", + "bytes": 566, + "sha512": "7c466da6446bbef67762ff4038dd4986018ed09432bd6ee6ca9e33150874dbb9d61c7e4b06bd925f932a55fda345401224689bbaddc135d7300651f67ec8160b" + } + ], + "sourceArchive": { + "name": "cygwin-build-sources.tar", + "bytes": 368824320, + "sha256": "13a42ea4e6465f77c32f91e4162f41bcfeab7c0eca47701bb6a8b9aca1eb99f4" + }, + "packages": [ + {"name": "_autorebase", "version": "001091-1", "file": "noarch/release/_autorebase/_autorebase-001091-1.tar.zst", "bytes": 4696, "sha512": "a9416ce992ca57f3a6208f46517520d21dfac61988ac84cbb5b17b76e053a0845c15368ccb8ca9c483d8935f9e3db81568433c0d08d0265ec5e6916ea30f2831"}, + {"name": "alternatives", "version": "1.31-1", "file": "x86_64/release/alternatives/alternatives-1.31-1.tar.xz", "bytes": 123748, "sha512": "94adf3376e7fcaf937594be718f54a99f2a2b66367eb2bb294b6fd4a933a9515954a9e9f309ed74cc62fbb353adb053d8500bbaf42994e4355f7180664afa62c"}, + {"name": "autoconf", "version": "15-3", "file": "noarch/release/autoconf/autoconf-15-3-noarch.tar.zst", "bytes": 2396, "sha512": "edaa6c97c5b50e138acef4b80406bfdd0988da3360cc622be2541593f6e926a4fbe1088ff608ddadfacf275b3924605d54f59e140cc7c79b31f4885e5878ad6f"}, + {"name": "autoconf2.1", "version": "2.13-12", "file": "noarch/release/autoconf2.1/autoconf2.1-2.13-12.tar.bz2", "bytes": 204911, "sha512": "96f8f592fff4665512c0023d6a7844fdc281ba99dc98dee9e55c37523b3a1279ae8fd31559fa6426edb1da7559a483b3eae99ae7911d50646ec60338ef209c18"}, + {"name": "autoconf2.5", "version": "2.69-5", "file": "noarch/release/autoconf2.5/autoconf2.5-2.69-5.tar.zst", "bytes": 341597, "sha512": "3739719f29ad3ab1c1045b72ccbfe6c58e3f7696ac063c2ff4e09e11e1a9015f289fa8fc267a62b43cb2f6b682b83d3b4b6450040e733f9f41d086dc4a2f36b9"}, + {"name": "autoconf2.7", "version": "2.73-1", "file": "noarch/release/autoconf2.7/autoconf2.7-2.73-1-noarch.tar.zst", "bytes": 906344, "sha512": "0493d68a7ea9cd61e403b6b7086b5302bad5bc1cc35715349f59605a795ac7a9f901c444cb387e5f44d1860de3e5d0fcfceee4ee2726d68904cfc5e2db6e90e9"}, + {"name": "automake1.18", "version": "1.18.1-1", "file": "noarch/release/automake1.18/automake1.18-1.18.1-1-noarch.tar.zst", "bytes": 921189, "sha512": "416ef8041505ecddc23e9e72394ef3a81311c6c378b8b6d348e989584c77924d40aa427b47ab4d3a9a2b83438d13995bb7c6302afb73af3d3af7f2e9061579c0"}, + {"name": "base-cygwin", "version": "3.8-2", "file": "noarch/release/base-cygwin/base-cygwin-3.8-2.tar.xz", "bytes": 1260, "sha512": "e03949b4ae6b692858e1d29fb764afbda990c07a6b70b0eb7219fc12f28dfba3cf7e757a7570c31e315be79f091b012d6a6ddafdb4b0b8961afe9af9f4977fab"}, + {"name": "base-files", "version": "4.3-3", "file": "noarch/release/base-files/base-files-4.3-3.tar.zst", "bytes": 46481, "sha512": "dfafbe28fbe89d9b72a2eeb10901712e04de71d9089a85cfd2b8d45ffd739bdd49dadb7b57d098f01b8902456f5b839e9d4a10dbd6a8001573e035fde6797b63"}, + {"name": "bash", "version": "5.2.21-1", "file": "x86_64/release/bash/bash-5.2.21-1.tar.xz", "bytes": 1650932, "sha512": "c3a5594f1c248dd27a3b5aa10c31fb1651072eae878479d3d8cab09c28354ce4509a2fe230f176ad200a6484a561bc10c2dc9c74c4e9f03164190dac2e683d65"}, + {"name": "bzip2", "version": "1.0.8-2", "file": "x86_64/release/bzip2/bzip2-1.0.8-2-x86_64.tar.xz", "bytes": 36396, "sha512": "86e4b0b21b64d17b9fb60260a787626125102b0cb786de67893cb1d39a109457637bdb93956c992429f1135f64b07def64dd0116682a6190b635ba12f81395d7"}, + {"name": "ca-certificates", "version": "2025.2.80_v9.0.304-1", "file": "noarch/release/ca-certificates/ca-certificates-2025.2.80_v9.0.304-1-noarch.tar.zst", "bytes": 902180, "sha512": "49d08f490db12855f8794cd5cfc721940504d36deffdd3b9aea0fc75909e27471f795d48c739630498fd4baeecb76ee0adb5e5c4cecab4db2ea39b03391a02d0"}, + {"name": "coreutils", "version": "9.0-1", "file": "x86_64/release/coreutils/coreutils-9.0-1.tar.xz", "bytes": 2767296, "sha512": "530f47640d878f5953580f3556c492a93786191abe573322026b70206a46c7f17d05927784db3e7f2c044d1555f35941880316f7288b887eaec770a9eda4c687"}, + {"name": "crypto-policies", "version": "20190218-1", "file": "noarch/release/crypto-policies/crypto-policies-20190218-1.tar.xz", "bytes": 14032, "sha512": "44d50ff08528df786eeb26ec1d312445ec92d77ecc9f448a3b741f7647e187f11eabdc0b38f8ab8de803e21d2e610f67eb44de7554f3fa83f6e8172e609b0054"}, + {"name": "cygutils", "version": "1.4.17-4", "file": "x86_64/release/cygutils/cygutils-1.4.17-4-x86_64.tar.xz", "bytes": 90580, "sha512": "a9c63f8ac86e529fa8809c9637bb80651721003160726e2058c361687e5df6377c4cdbea2d023677b23e1d166bbce37d5a412f7d1135baf075ae2631fdeb77b2"}, + {"name": "cygwin", "version": "3.6.10-1", "file": "x86_64/release/cygwin/cygwin-3.6.10-1-x86_64.tar.xz", "bytes": 1626060, "sha512": "50e5a100f5229eecba279799363a30d599f8c6ad06967eabc4fc8ed9105736576a522539abc250110282cf18da8536d4e408823cc6c0d2abd8692a91d9fd3341"}, + {"name": "dash", "version": "0.5.12-5", "file": "x86_64/release/dash/dash-0.5.12-5.tar.xz", "bytes": 79612, "sha512": "8891165224ca8531ad7b601309bc1a74f8b68dad24721060fd3db5d12ae504e3c96bbd0217006bbcbe0650d13283f04c1f58412bde54c40f19acb5291dfaced9"}, + {"name": "diffutils", "version": "3.12-1", "file": "x86_64/release/diffutils/diffutils-3.12-1-x86_64.tar.xz", "bytes": 431400, "sha512": "2a9d8fa0acd5a6108f74afe515f55a2a7cade6fa7f96368e46a6f33cf3b49d270b50dc3cee0320740c8f1729edcbf8dd3824f4b268261fba7aeed0d75a4e943c"}, + {"name": "editrights", "version": "1.04-1", "file": "x86_64/release/editrights/editrights-1.04-1.tar.xz", "bytes": 7112, "sha512": "aadc188201441ee1ff5710df0e5550c8ca5cb65421d025c5845921510560a2875c317d1f35b6ea8abbb5077172c0de7fc8f2f55ae95dc8d1e59d26c963c93aa7"}, + {"name": "file", "version": "5.46-1", "file": "x86_64/release/file/file-5.46-1-x86_64.tar.xz", "bytes": 840940, "sha512": "e4b52477d353557d36af625a6a55c97a11ae41053ce17602624210df2a6d2683f46250c2b9f6db5a9f9e5fcccb7b0be4b24705a36d196cee677bf47d90dfee99"}, + {"name": "findutils", "version": "4.11.0-1", "file": "x86_64/release/findutils/findutils-4.11.0-1-x86_64.tar.xz", "bytes": 768172, "sha512": "cbb1359fd81c55f98da1485f84f11f905a7a3fe9ce1676424e86da4d7f0effa5fc298546bd5c62c9707e8ddf93c31597d6da01e60be0610c50b9b22ef9475814"}, + {"name": "gawk", "version": "5.4.0-1", "file": "x86_64/release/gawk/gawk-5.4.0-1-x86_64.tar.xz", "bytes": 1496256, "sha512": "3604c99921d0cff387279aec1d3b332e8afcd2746b052304c0511221efdb6ae6d4a3117b36593e44adc43c67b6f6c4c48bccf6b686e2e1a5cac644c89613d829"}, + {"name": "getent", "version": "2.18.90-5", "file": "x86_64/release/getent/getent-2.18.90-5.tar.xz", "bytes": 17864, "sha512": "b860f3d01f32da7312a0ce118bdaa53e2418c53f0ac5cfd52b3e4267f353fa21ae27fd2649eb9c6bff48b3d10a0a9f7bcaa2d644f770f0f3916f5d969beedd65"}, + {"name": "gettext-locale-alias", "version": "0.26-1", "file": "x86_64/release/gettext/gettext-locale-alias/gettext-locale-alias-0.26-1-x86_64.tar.xz", "bytes": 1596, "sha512": "289feafbe00598a2451527b64b0187ab62115577ceaa960774b020270e5a8c5bff4038f389a44c7d9eb870f7e2741c7bb2b27be507843cc8c4ae5c33bc74c8f3"}, + {"name": "grep", "version": "3.12-1", "file": "x86_64/release/grep/grep-3.12-1-x86_64.tar.xz", "bytes": 418788, "sha512": "2ba28b5d93c352bc3f3f0f232e2052e6651b71cc535841f4ff8cedb23e61e9e4c030d7d5516ed9c797074e1a59cbf3a56732ca678cb0e986e86dcbbf25955976"}, + {"name": "groff", "version": "1.24.1-1", "file": "x86_64/release/groff/groff-1.24.1-1-x86_64.tar.zst", "bytes": 5878965, "sha512": "9e8aaab0e6f464df3fec6b96fed6679673091aaeafaa463ed518081cb156ef7441fb5f9341a251f602334862640ea0b0fc1b158746c08a1e2529cceeb37e9802"}, + {"name": "gzip", "version": "1.14-1", "file": "x86_64/release/gzip/gzip-1.14-1-x86_64.tar.xz", "bytes": 164124, "sha512": "57995d2e90bf435ffff8f61cd79c4970ad7260e0c407ab52d4dbacb0540a74c7f8fd0d8bd905316eff31525d9b0367284f9f250950f78f179e5260ce59be3a98"}, + {"name": "hostname", "version": "3.13-1", "file": "x86_64/release/hostname/hostname-3.13-1.tar.bz2", "bytes": 14024, "sha512": "75114326fa4575b8341434658a881feb81a6565e7e0adf8fd100dde87022d3818b5fc3df430bd9b3298d818a1d23f6b6c4bc7ff33efb3ded5f0ea0bf118c1aae"}, + {"name": "info", "version": "7.3-1", "file": "x86_64/release/texinfo/info/info-7.3-1-x86_64.tar.zst", "bytes": 607040, "sha512": "3af0e7c2edc34a068d88974c6c1b0f529663f4bbce145b5a33f3b7e90671fd908fb4a544e033b98a6906d3244c64a18732104c5f1d323c0ccac8a5134e13f411"}, + {"name": "ipc-utils", "version": "1.1-1", "file": "x86_64/release/ipc-utils/ipc-utils-1.1-1.tar.xz", "bytes": 11120, "sha512": "374ef40c871d00eb3b4774c383bb85872443a2e8b9abf74b8b9b9b27984598abfb6284716c2d2cd264181e6d012cf7dc8cd04f6bab91f9b083f72e9b51e6226b"}, + {"name": "less", "version": "704-1", "file": "x86_64/release/less/less-704-1-x86_64.tar.xz", "bytes": 171024, "sha512": "2d1ecf29272e7c1781bb0df5b0f0531b009972edc8d246d63e17edf7d8314109f0a8f38668b371c6f5e1dc1f948d63384eecdca5409eacb927c4f267f89f4bfe"}, + {"name": "libargp", "version": "20250917-2", "file": "x86_64/release/libargp/libargp-20250917-2-x86_64.tar.xz", "bytes": 19508, "sha512": "a83ef7c1809cae1b10a50e228643a640ea14e1be324429b9f53be9a4c8cee03ddd0efb46b24e159493dfa36569e73b636bc897943d61cdb90511e5e4cbb54257"}, + {"name": "libattr1", "version": "2.5.2-2", "file": "x86_64/release/attr/libattr1/libattr1-2.5.2-2-x86_64.tar.xz", "bytes": 6160, "sha512": "345b8f84b9c4df2eeea5e211dbaff5e0382aaee788435e1f4cc69f546e1603bbb45e9baec02a37d935674fcd55fb782d924c3a1540d2f829bde4969c96945ce4"}, + {"name": "libblkid1", "version": "2.40.2-2", "file": "x86_64/release/util-linux/libblkid1/libblkid1-2.40.2-2.tar.xz", "bytes": 120936, "sha512": "d931c47cce5a7caf18799a16ed526e0f0d0f82c0f6d0af952420d50d88f1e0f0ea9d6c07be72f9d0baa06a3299b2db8c3bd3c43d13d94400edc816e7c00da148"}, + {"name": "libbz2_1", "version": "1.0.8-2", "file": "x86_64/release/bzip2/libbz2_1/libbz2_1-1.0.8-2-x86_64.tar.xz", "bytes": 26884, "sha512": "d15d88e8e0fae69f7c4c3025515e748c273d758a0f470312162cec46c4cf0d340d2ef036e3a4f4d03b5edf4508b4927834ffa0f9aa45585a5f3bc3b3163ff675"}, + {"name": "libcrypt2", "version": "4.5.2-1", "file": "x86_64/release/libxcrypt/libcrypt2/libcrypt2-4.5.2-1-x86_64.tar.xz", "bytes": 106104, "sha512": "7668d29aaebba8ca94e48e810d92108db72b067b2786cea8dcf4a049c29f20f0aeb8653a6e9524869c69e0b1df9d4d0ac499a052a86f0d44178e8a8167002c97"}, + {"name": "libdb18.1", "version": "18.1.40-1", "file": "x86_64/release/db/libdb18.1/libdb18.1-18.1.40-1.tar.xz", "bytes": 733872, "sha512": "51955ae95823c54051b5345a7dbe6927521411754dff416ad6af2061bd3c9b2230ca2ea88f9b921b4b09b7654c7dac924b0ae08af708a93604cc193d6a18f4ff"}, + {"name": "libfdisk1", "version": "2.40.2-2", "file": "x86_64/release/util-linux/libfdisk1/libfdisk1-2.40.2-2.tar.xz", "bytes": 160264, "sha512": "0c8e6854eb72bb8cc51fc33565ee0d36a747e520e98e074a1d2400672a45651807b53be0f5a1c97efa0ce34975063f86953c9465312dcfdaba8104edb1298d62"}, + {"name": "libffi8", "version": "3.8.0-1", "file": "x86_64/release/libffi/libffi8/libffi8-3.8.0-1-x86_64.tar.xz", "bytes": 196924, "sha512": "935a0567f740a314d70ecd4adc8c4cf5ad37c6fb43f2d26dee882d4fda71f08efb2646bee10cee6e2000f52e25cce87fa29ce07665ba6d22532e0daa76bc887c"}, + {"name": "libgc1", "version": "8.2.12-1", "file": "x86_64/release/libgc/libgc1/libgc1-8.2.12-1-x86_64.tar.xz", "bytes": 62392, "sha512": "faf226ea2b58949da714e2c50f1aa95738082c3499e2ae4828af5173bdd617248f000bf4868636b31a2b76b3a905057ab307c4f6a0d7112f23422531a97be92d"}, + {"name": "libgcc1", "version": "14.4.0-1", "file": "x86_64/release/gcc/libgcc1/libgcc1-14.4.0-1-x86_64.tar.zst", "bytes": 59166, "sha512": "5ac8a56e5eee285efcd6dbd05a473e795e181021d7194b8a98dfe2fa201a71f0abe2908e0b4c81490eb5ef472efaeac8e56a61a5f289eee4ad6e90e52a844ff8"}, + {"name": "libgdbm6", "version": "1.26-1", "file": "x86_64/release/gdbm/libgdbm6/libgdbm6-1.26-1-x86_64.tar.xz", "bytes": 25436, "sha512": "8ed666a5b978b40f8cfc69d270d47cd674a31eb41bac499f783137eedfde6aa02ba4346de3d5dc3184d9af10ec80f57730c7517a88732616f4c6e0ef637d4bce"}, + {"name": "libgdbm_compat4", "version": "1.26-1", "file": "x86_64/release/gdbm/libgdbm_compat4/libgdbm_compat4-1.26-1-x86_64.tar.xz", "bytes": 3892, "sha512": "fd026dd74c495c1ee67b790269e77e8525ad78d52d4895923443feab55f7bb32dc07b115d490143c7e0277167d9cdbef5dad4707ecc7ca6df13a9f1a5184f8ca"}, + {"name": "libgmp10", "version": "6.3.0-1", "file": "x86_64/release/gmp/libgmp10/libgmp10-6.3.0-1.tar.zst", "bytes": 254680, "sha512": "947cd56e1945ddf180622b453f17e6655b4ee3e79fcc91238370a8826b88cd07c31d939f1ed7b87d91344d46fee90af2560105267a9207dcca800cba72459191"}, + {"name": "libguile3.0_1", "version": "3.0.11-1", "file": "x86_64/release/guile3.0/libguile3.0_1/libguile3.0_1-3.0.11-1-x86_64.tar.xz", "bytes": 6969492, "sha512": "a559914ed5d62d0a701e4ce3810276436cf1ce54d579c72fc72c3b4688f2af71ef93f3cdae7089ad7f85107c7ca5c87f2e497cac6f2550b38df1bc6ba7f16230"}, + {"name": "libiconv2", "version": "1.19-2", "file": "x86_64/release/libiconv/libiconv2/libiconv2-1.19-2-x86_64.tar.xz", "bytes": 555664, "sha512": "a1b23e24e0a495e7163923bcf40a4b933403e0826cfc0d9bde785bc92c909b8c4fb94b1e6b2c0242f4328a723855e72ff3baab9f557ef855b7fe7a7d901b0943"}, + {"name": "libintl8", "version": "0.22.5-1", "file": "x86_64/release/gettext/libintl8/libintl8-0.22.5-1.tar.xz", "bytes": 43256, "sha512": "370a19af888bc7c9ed95cf8ade0d36d5e7cdb634abdebcba260cad61fa9a4cc05b5e748e315c9709f66861e8756b227d6757d529846591189e19ee2b2d00002d"}, + {"name": "liblastlog2", "version": "2.40.2-2", "file": "x86_64/release/util-linux/liblastlog2/liblastlog2-2.40.2-2.tar.xz", "bytes": 4908, "sha512": "89350921fe76c9065662dd9d72dd464d2f404f1690e806210cbb828ae8325ea5f3f9909d8eb04c8342b80417a4e1e22c3569241515762ad081ecd58d56927c02"}, + {"name": "liblz4_1", "version": "1.9.4-1", "file": "x86_64/release/lz4/liblz4_1/liblz4_1-1.9.4-1.tar.xz", "bytes": 53296, "sha512": "70e6b0f3af2c5e04089c955b46c13293ab935ee4683e4690da0cebfffba4ba91614004aede99dd804f54e4def93461159f55d151e7fbb647d1ca97ca29e619cc"}, + {"name": "liblzma5", "version": "5.8.3-1", "file": "x86_64/release/xz/liblzma5/liblzma5-5.8.3-1-x86_64.tar.zst", "bytes": 87321, "sha512": "7399c6d117f23788a7edb11cf7f5eec77724aad2bf7cf8a437001fc1515941f6ca415f42363981be35361290f43cd60cd04e15bb1b01cffc0fd275f3167037e0"}, + {"name": "libmpfr6", "version": "4.2.2-1", "file": "x86_64/release/mpfr/libmpfr6/libmpfr6-4.2.2-1.tar.zst", "bytes": 284416, "sha512": "8203dc61bf646041c71f7fece9fe59422ded96ac28dd24826ffb00a9607f274f3226673f9bee441f32b424a7e26c2f178281bd7836db26b0253d37da208d06a8"}, + {"name": "libncursesw10", "version": "6.5+20240427-1", "file": "x86_64/release/ncurses/libncursesw10/libncursesw10-6.5+20240427-1.tar.xz", "bytes": 393928, "sha512": "08102f7a7083cb5663fecc46d819f2ece4123444a8f94c42300f03f7eebd40395f556c44b22ab58bf57321b44d9b8fb8b19358e535c7500d36e52d48b05dcca8"}, + {"name": "libp11-kit0", "version": "0.26.5-1", "file": "x86_64/release/p11-kit/libp11-kit0/libp11-kit0-0.26.5-1-x86_64.tar.xz", "bytes": 212700, "sha512": "dd64f129fe71233e26e0845b6711db5cf2eb2e6d2d050f07b6aec0d9dd54d980e87390614744fbf022cf57aa2b3f7edbdc30c548c2f9d480188c22bcde0c2eba"}, + {"name": "libpcre1", "version": "8.45-1", "file": "x86_64/release/pcre/libpcre1/libpcre1-8.45-1.tar.zst", "bytes": 166711, "sha512": "85c793ed3622a00b811773b94c45376882513e267b9f1d2a3efa0bc42ff86a4e4bebd8362a193d302e66ae9e5c9b93befc1bb1096c0fd397e2cd0f5632c808dd"}, + {"name": "libpcre2_8_0", "version": "10.47-1", "file": "x86_64/release/pcre2/libpcre2_8_0/libpcre2_8_0-10.47-1-x86_64.tar.zst", "bytes": 237134, "sha512": "a98b38e1591c19404a5794cef488810747d86cc27ce5ee326e706cfa7069cf088d8a64571933f4c1f7a611fd8c232008837271bebe2e81c04a3f6ec571f0009e"}, + {"name": "libpipeline1", "version": "1.5.8-1", "file": "x86_64/release/libpipeline/libpipeline1/libpipeline1-1.5.8-1-x86_64.tar.xz", "bytes": 24880, "sha512": "1d062ac4909d37fbac3ce66b07ac691df0ae5acbd01f08c5019a5ed897cb2bbef95f9e347c5bc71185ae3317d89383306e46f2c59acae5d28a18fa65f66d4e80"}, + {"name": "libpopt-common", "version": "1.19-1", "file": "x86_64/release/popt/libpopt-common/libpopt-common-1.19-1.tar.xz", "bytes": 18536, "sha512": "4d69e6d7e196cdb4caad558c5d7543f706a4c6d7053476be861be07672506e378084126f22903deab8113557f757d2a07578f4a790ae316a2ea50ba612148a43"}, + {"name": "libpopt0", "version": "1.19-1", "file": "x86_64/release/popt/libpopt0/libpopt0-1.19-1.tar.xz", "bytes": 18748, "sha512": "e820a7bac13cdd3bff07673ac54dcb80f342c6a32917035f218ef255bdb707d654ee6a1684242d7ce8439eebf050ade6566f27f5118896fbf8e0171173b5bfee"}, + {"name": "libreadline7", "version": "8.2-2", "file": "x86_64/release/readline/libreadline7/libreadline7-8.2-2.tar.xz", "bytes": 113340, "sha512": "1d27576cf49b056f35f90ad933e39c5d946bc271ddcd109daa98f95b97123b428256827889ff45ca8a76409cdb1afd0146e99c9b70150b302538356512acfe38"}, + {"name": "libreadline8", "version": "8.3-1", "file": "x86_64/release/readline/libreadline8/libreadline8-8.3-1-x86_64.tar.xz", "bytes": 120084, "sha512": "64142c5f305b9836c5be9a4271e4163d8b3e739a0451c0d152373eeb3338342f7cf5691e4437b8e6d0a8404c30ea7a21832e342e7a1ca3b5f3e8e3403051b96e"}, + {"name": "libsmartcols1", "version": "2.40.2-2", "file": "x86_64/release/util-linux/libsmartcols1/libsmartcols1-2.40.2-2.tar.xz", "bytes": 96124, "sha512": "71ded6f02d264343d068b90450d96f4fdeb4087f278b990709aaa814c16123f4ae947714e5f10020260c30a6d78865c2d35d9ffe57a51b038815db52c3274317"}, + {"name": "libsqlite3_0", "version": "3.49.1-1", "file": "x86_64/release/sqlite3/libsqlite3_0/libsqlite3_0-3.49.1-1.tar.xz", "bytes": 686212, "sha512": "cd81686fe5778a94ab91e4aebdd2a229cb963318ce7b393e0a0299e29ed7648c24135733c1978cbebbb926813d7f994b9e38318ede12c72fe79ad404dbfdeaf2"}, + {"name": "libssl3", "version": "3.5.7-1", "file": "x86_64/release/openssl/libssl3/libssl3-3.5.7-1-x86_64.tar.zst", "bytes": 2081790, "sha512": "18161f1292404554742ced4beaa10548c7e1bbfb9398ec0b733e632401dfaa49c33336ad4b1e80e2b7bd77c20f00c10196c5804c327e37f641d49da2288837b8"}, + {"name": "libstdc++6", "version": "14.4.0-1", "file": "x86_64/release/gcc/libstdc++6/libstdc++6-14.4.0-1-x86_64.tar.zst", "bytes": 651767, "sha512": "3d86409193c952618eee7e2dea32bdeb97f22ee800e13cc46f98b5095d4d0b1b31ac4336dc5819abb2d553ad1c2151619e9f6f337dfe921ba80fda1323f6f4e3"}, + {"name": "libtasn1_6", "version": "4.21.0-1", "file": "x86_64/release/libtasn1/libtasn1_6/libtasn1_6-4.21.0-1-x86_64.tar.xz", "bytes": 32072, "sha512": "d28713b27d5d41408b29caed785e29380da4d8b4ccdc1c479ae13906e6765bedc6d6b9e6c1c22fd2111d678fcf1663187d3de27d7ad2e104cd48c38086189e36"}, + {"name": "libuchardet0", "version": "0.0.8-1", "file": "x86_64/release/uchardet/libuchardet0/libuchardet0-0.0.8-1.tar.zst", "bytes": 72637, "sha512": "1fad5711590a82a8386c2492286dd9c0ad7cbe086aab249d07ad27277757327a6a67264e9718aef7d30307515462fe67d64f23582f08f0ced85a19cc8e0afe23"}, + {"name": "libunistring-devel", "version": "1.4.1-1", "file": "x86_64/release/libunistring/libunistring-devel/libunistring-devel-1.4.1-1-x86_64.tar.xz", "bytes": 43332, "sha512": "14e067719e5c0a973950955a05685c18d6fa97df5defa8d0915125ea3af1d01ac921c2a84565a49cc7fb785b15a8be4640ee209f3f0d2e13cb542eb6c75b51f9"}, + {"name": "libunistring5", "version": "1.4.1-1", "file": "x86_64/release/libunistring/libunistring5/libunistring5-1.4.1-1-x86_64.tar.xz", "bytes": 498220, "sha512": "cbd454f5068bfb9291d440a3b03b0c58437aa0342a199007d5cce6fa006b6b5ea4d3ba10c136fb0257ffb0d0199db73484275c632506e4ec31c81e1746667b80"}, + {"name": "libuuid1", "version": "2.40.2-2", "file": "x86_64/release/util-linux/libuuid1/libuuid1-2.40.2-2.tar.xz", "bytes": 12532, "sha512": "3ba0c5d5a401c88796077e9f45164532408882f2f5d9f0ab99b2fdf6eb915917250124cde3653a7c53b648866e091aa3baf3304d0b243337f0d4e254a7631f55"}, + {"name": "libzstd1", "version": "1.5.7-1", "file": "x86_64/release/zstd/libzstd1/libzstd1-1.5.7-1.tar.zst", "bytes": 253786, "sha512": "f89cdaa004487ec8c4796b8f56887c81f6f497873c1495b9cb585ffe6c8d6e6b8ad4b52617d5238075f965f1fe001da287d9a00d121590274b2d87672b415a0f"}, + {"name": "login", "version": "1.13-1", "file": "x86_64/release/login/login-1.13-1.tar.xz", "bytes": 17264, "sha512": "66acc3255962a27af8d701a8c02846f3b9180b7b1d65d946f6fb27cd24aeedc8e6a9a2601b419e981c94b356ef376a5c565ea31a4a220e5add9d70ae501a11d1"}, + {"name": "m4", "version": "1.4.21-1", "file": "x86_64/release/m4/m4-1.4.21-1-x86_64.tar.xz", "bytes": 363204, "sha512": "0d86c88d4bbbfc59e136bba3101ca3714139fba2ab7fea2883bc773ca884c3876f8a8f3a2ea8b05c29fd5280c1fb2d686401346f2761160917edf0818a2b232b"}, + {"name": "make", "version": "4.4.1-2", "file": "x86_64/release/make/make-4.4.1-2.tar.xz", "bytes": 598004, "sha512": "9a89a0f4ceadf5c2d0390029d16274fbed0190d2410ad6beb76e39b7153f38d0d1169ad0abf57c96929e6ab0523a0a76c345868e9ae46f9cd3d708d23f6fa3e6"}, + {"name": "man-db", "version": "2.13.1-1", "file": "x86_64/release/man-db/man-db-2.13.1-1-x86_64.tar.zst", "bytes": 1301518, "sha512": "a1e8a0d6d5a75465e32be83c13e685a73f084fa41abc5500d9437837a2c9de2f897827d3516c1076fa6cdd07a3d1aaaa4751f3b393fcfe64c370ff8f6b8b7f84"}, + {"name": "mintty", "version": "3.8.3-1", "file": "x86_64/release/mintty/mintty-3.8.3-1-x86_64.tar.xz", "bytes": 1190356, "sha512": "79068708c81a7bf6abba3ad1cd0550a2c3eb6f6eea21adade8b0d2b77339cd2519716f528159da9f0972f93bb624867e394d782c3e3b0b7e841a8db985395b92"}, + {"name": "ncurses", "version": "6.5+20240427-1", "file": "x86_64/release/ncurses/ncurses-6.5+20240427-1.tar.xz", "bytes": 111396, "sha512": "e15664bdab3f4d82bac6481deaa66476adbe34404937079983b5165930fa18cec3aa0345b93fbb2c390f8304ef20f041cd47d268552b92d01a51adb6abde0fc5"}, + {"name": "openssl", "version": "3.5.7-1", "file": "x86_64/release/openssl/openssl-3.5.7-1-x86_64.tar.zst", "bytes": 1392875, "sha512": "c50821b61eec47d58d1624a4a3d5a4013c80a8b48cdc803d81a656e6859079d321b34fa0ff97db4c093735a76b71a68c8a7f12184b2c4f0677014e910d1ba9d3"}, + {"name": "p11-kit", "version": "0.26.5-1", "file": "x86_64/release/p11-kit/p11-kit-0.26.5-1-x86_64.tar.xz", "bytes": 332724, "sha512": "1f057de789a5553f04649c80e6f78385135b57de7ba078e9dd51f2db60afee37717bb6de9dcf0a330cf72fce1359e08c0186988632b5f858992ebf95739bb840"}, + {"name": "p11-kit-trust", "version": "0.26.5-1", "file": "x86_64/release/p11-kit/p11-kit-trust/p11-kit-trust-0.26.5-1-x86_64.tar.xz", "bytes": 110096, "sha512": "2f98ae11e42f69fa4626638f7cefe2ba2fed808ae618093c1e6c568f8a8be79e6690b40e39e20df02a8d363c371cca8b8320fea7b288e8561cd299f15cc5b796"}, + {"name": "perl", "version": "5.44.0-1", "file": "x86_64/release/perl/perl-5.44.0-1-x86_64.tar.zst", "bytes": 5129424, "sha512": "42f80064ff94ea778319a24e011d4a7236944c79d9db22e0ed64a7ca7d9b65bc2df5998130eb64c50aa95c173937776cc42665174c5717e84fe6dca523368f27"}, + {"name": "perl-Algorithm-Diff", "version": "1.2010-5", "file": "noarch/release/perl-Algorithm-Diff/perl-Algorithm-Diff-1.2010-5-noarch.tar.zst", "bytes": 31580, "sha512": "d840b605043448ceab8ded77313e12653c81fd7db08062a99010fb19b334429c15628b1a0a805f88110e7aae4dcd3f667b515c18870ba873169533663f4374de"}, + {"name": "perl-Archive-Zip", "version": "1.680.0-1", "file": "noarch/release/perl-Archive-Zip/perl-Archive-Zip-1.680.0-1-noarch.tar.zst", "bytes": 85917, "sha512": "de23da6dac8aa8c8218c141c845723d092f6eaf2f1e67ced96d7b0385206c2e335b150bb7066bd0c7d257125ad1b3fc7691686dc535c56429b4a0f4535b6c73a"}, + {"name": "perl-Class-Inspector", "version": "1.360.0-1", "file": "noarch/release/perl-Class-Inspector/perl-Class-Inspector-1.360.0-1-noarch.tar.zst", "bytes": 21261, "sha512": "299489c9d0bc86fe8ba625c0ec0530f0e6ecacab3f67b2826760413f6bc58252c6c11f45583063cfcf0944f9127ed2bece9168fd76220e735841f374a57943f5"}, + {"name": "perl-Devel-Cycle", "version": "1.120.0-1", "file": "noarch/release/perl-Devel-Cycle/perl-Devel-Cycle-1.120.0-1-noarch.tar.zst", "bytes": 8884, "sha512": "308211af5b57106629b168be340fd682e23670fd97cc7c7f80c3bac48223984c1a226148c99cf5d904b09e352f1008f13bd4645897223a162a22af819aa7af11"}, + {"name": "perl-File-ShareDir", "version": "1.118.0-1", "file": "noarch/release/perl-File-ShareDir/perl-File-ShareDir-1.118.0-1-noarch.tar.zst", "bytes": 19857, "sha512": "7cb0f5fe4c327e9f870fc9c303f5e7e1483d2d2d012dbb5487f4b08f350170b7eb0a345766bd0a809bb4b8fa138788112eed2528b92718e817a5f21a9b82774d"}, + {"name": "perl-Text-Diff", "version": "1.450.0-1", "file": "noarch/release/perl-Text-Diff/perl-Text-Diff-1.450.0-1-noarch.tar.zst", "bytes": 30982, "sha512": "c19c80564d50d1d8efd44b156013aa5620b79205fd291897b938735c49214c84867a1337dfe9e7857f97e6f924ec919f5a0197df2d3bfeae78e0ec9dc8b9a6ac"}, + {"name": "perl-Win32-API", "version": "0.840.0-1", "file": "x86_64/release/perl-Win32-API/perl-Win32-API-0.840.0-1-x86_64.tar.zst", "bytes": 84514, "sha512": "758b1881d55942e91365780bede1dca63838995ad6574f9a242c15f613e7e3211a029e9bd3a3bd24d834ca6838e7a85c2812411783423ba23ccf808d094b6890"}, + {"name": "perl_autorebase", "version": "5.44.0-1", "file": "x86_64/release/perl/perl_autorebase/perl_autorebase-5.44.0-1-x86_64.tar.zst", "bytes": 175, "sha512": "55cd94604cfa3e2491df45e5a3715f32b2dee8298a656ca2da8de2eba4096329c595228a8c950760e9641b2c6097309571350c0ffe1c8d13759eb76ec841a4f8"}, + {"name": "perl_base", "version": "5.44.0-1", "file": "x86_64/release/perl/perl_base/perl_base-5.44.0-1-x86_64.tar.zst", "bytes": 3772189, "sha512": "3039e7a60c950cd682d8372f948ec83f7ebea27e9b425ab7406ed2eff0bebb80f7622423cf82b9ab07e34deb183bdf1b56fe129c8306bd2cfb04e7cd96beb992"}, + {"name": "rebase", "version": "4.6.6-1", "file": "x86_64/release/rebase/rebase-4.6.6-1.tar.xz", "bytes": 248832, "sha512": "9f3751c4687b09f3c286cf5c4522a0d976fce2d79ab471ab37560b15f14af84f7d13162b6102c02d0d60784448ff0586e76e9291675af4fa0c9320a3713633da"}, + {"name": "run", "version": "1.3.4-2", "file": "x86_64/release/run/run-1.3.4-2.tar.xz", "bytes": 34492, "sha512": "7e3156bbddc5f934fde3530879aa5186c9fdccb757ba848fc958e79e96534b336975f2882e413d5c18174628033a422157d25c48f8b5e90f0f1555ff2fa6343f"}, + {"name": "sed", "version": "4.10-1", "file": "x86_64/release/sed/sed-4.10-1-x86_64.tar.xz", "bytes": 359252, "sha512": "da5475344a1693f90fb560773ccca48eaf13af0e6bbcada9dcea658862bf7f46043b2d7365de0e2e235fb9a874fe784ad83bc29eb2fdbf3207cd8ad5e127b8b9"}, + {"name": "tar", "version": "1.35-2", "file": "x86_64/release/tar/tar-1.35-2.tar.zst", "bytes": 997616, "sha512": "007c7df12bc27ca14862a925680e2b2ff616a5149262b153889e6f580f3db492f98da49b5f34a8c4234194ee25021b67e9a84c987c96c5d6baa7ee0b3ea02425"}, + {"name": "terminfo", "version": "6.5+20240427-1", "file": "x86_64/release/ncurses/terminfo/terminfo-6.5+20240427-1.tar.xz", "bytes": 94760, "sha512": "128fb04f188bbf2af3060416193c9879a9cbbf7e5486febf851f35c3d9d00b9d61a0e2bf4be575d2308fd665a224ce5674485dcfd071125c3a8d199a4b88a042"}, + {"name": "texinfo", "version": "7.3-1", "file": "x86_64/release/texinfo/texinfo-7.3-1-x86_64.tar.zst", "bytes": 1853455, "sha512": "a7a0da042d05bbf2978857fcad1887552db9d77bf745fb8a17e156a2616500cf711adce107610f008ddff5556d02823f148d2bf146bd7eaa8a9466f9300cf2e6"}, + {"name": "tzcode", "version": "2026c-1", "file": "x86_64/release/tzcode/tzcode-2026c-1-x86_64.tar.xz", "bytes": 185512, "sha512": "85ab0f5d8d7c02e38dbd945c99ee6841774d84c486f857cd8484605068441b2028eea1f42a7e36c8fd6801c454f8f786a16f9caeb422afb30e5fcf324434878b"}, + {"name": "tzdata", "version": "2026c-1", "file": "noarch/release/tzdata/tzdata-2026c-1-noarch.tar.xz", "bytes": 184092, "sha512": "567adb6da01050a84b3785a1cb518e62a9c838cfe55e8ab33610c6ee64355677486329de5781361d08f03cd355acd64f94c64ec2a2686d48538813cc405b638c"}, + {"name": "util-linux", "version": "2.40.2-2", "file": "x86_64/release/util-linux/util-linux-2.40.2-2.tar.xz", "bytes": 2053568, "sha512": "4f3473472e32bff586401ec31f1423237c9cb112ae1140cdd8b2797040c5c182632f49f5354eaaa409d2dacba7767d5baa8b8926d7638e2725ad80065b46c10b"}, + {"name": "vim-minimal", "version": "9.1.1825-1", "file": "x86_64/release/vim/vim-minimal/vim-minimal-9.1.1825-1-x86_64.tar.xz", "bytes": 794064, "sha512": "6a252210e2ef08a7038d6e5bc8e5312e0c25d8328ab0aefd7f292eb79b7a32dd699f15f3f293cfbb83687e87cb3f68a335e969b117f55e6ff99b34ebdfbc8119"}, + {"name": "which", "version": "2.23-1", "file": "x86_64/release/which/which-2.23-1-x86_64.tar.xz", "bytes": 31108, "sha512": "98dffe802e1137d7b54359314797fd10080e6c261f37668e32e46aa7b1587b4148a1647b4f4a00cd0d23a9e90f153d5c2081d0ffa4ccea657b73aaa31b538d6e"}, + {"name": "xz", "version": "5.8.3-1", "file": "x86_64/release/xz/xz-5.8.3-1-x86_64.tar.zst", "bytes": 767298, "sha512": "8918389508ade8237226170b6dd02104938ec53b2bb15b310f5005dd64f954dfd67a5391757ba8602afe2a423f901894204771d6ae335402095c074d098daa76"}, + {"name": "zlib0", "version": "1.3.2-1", "file": "x86_64/release/zlib/zlib0/zlib0-1.3.2-1-x86_64.tar.zst", "bytes": 46254, "sha512": "d79ba6098cadf6bf2f32f35d9cd54729293c64b3c9daabb9d080f52861a19db71544d5710fdbd61ff45e7c2ae4eecb3f2e3b8a1e3bc05695b8cfe01c7639c53a"}, + {"name": "zstd", "version": "1.5.7-1", "file": "x86_64/release/zstd/zstd-1.5.7-1.tar.zst", "bytes": 394600, "sha512": "5de9a2fdee75345a35592444d467a1b70504f09f3b2ee5aaf8af8a9d9fda1a4e77d37c5c4c4fee9b96995ef78f9708e6c02adea7a70d9f80f1238e507b2bc0fc"} + ] +} diff --git a/.github/scripts/voice_windows_tools.py b/.github/scripts/voice_windows_tools.py new file mode 100644 index 0000000000..7dc26ee3fc --- /dev/null +++ b/.github/scripts/voice_windows_tools.py @@ -0,0 +1,80 @@ +"""Expose verified Windows build-tool installations as local Bazel inputs. + +This declares the installed support trees; the caller authenticates their +inputs and verifies the installed package inventory first. +""" + +import argparse +import itertools +import json +from pathlib import Path +import stat + + +def export_repository(root: Path, target: str, pkg_config: Path): + if target not in ("x86_64-pc-windows-msvc", "aarch64-pc-windows-msvc"): + raise ValueError("unsupported Windows target") + root = root.resolve(strict=True) + pkg_config = pkg_config.resolve(strict=True) + names = { + "shell": "cygwin/bin/bash.exe", + "make": "cygwin/bin/make.exe", + "cygpath": "cygwin/bin/cygpath.exe", + "automake": "cygwin/bin/automake-1.18", + "pkg_config": pkg_config.relative_to(root).as_posix(), + } + if not names["pkg_config"].startswith("pkgconf-image/"): + raise ValueError("pkg-config must belong to the extracted image") + outputs = ("BUILD.bazel", "MODULE.bazel", "voice-tools.json") + if any((root / name).exists() for name in outputs): + raise ValueError("Bazel tool repository outputs must be fresh") + # A nested package boundary would silently remove files from Bazel's glob. + # Reparse points could escape the installation or hide support directories. + for directory in ("cygwin", "pkgconf-image"): + tree = root / directory + if not tree.is_dir(): + raise ValueError(f"missing tool tree: {directory}") + for path in itertools.chain((tree,), tree.rglob("*")): + info = path.lstat() + if ( + stat.S_ISLNK(info.st_mode) + or getattr(info, "st_file_attributes", 0) + & stat.FILE_ATTRIBUTE_REPARSE_POINT + or not (stat.S_ISDIR(info.st_mode) or stat.S_ISREG(info.st_mode)) + or path.name.casefold() in ("build", "build.bazel") + ): + raise ValueError(f"unsupported tool-tree entry: {path}") + if any(not (root / name).is_file() for name in names.values()): + raise ValueError("required Windows build tool missing") + definitions = [ + 'package(default_visibility = ["//visibility:public"])', + 'filegroup(name = "cygwin", srcs = glob(["cygwin/**"], allow_empty = False))', + 'filegroup(name = "pkgconf", srcs = glob(["pkgconf-image/**"], allow_empty = False))', + 'filegroup(name = "tools", srcs = [":cygwin", ":pkgconf", "voice-tools.json"])', + ] + definitions.extend( + f"filegroup(name = {json.dumps(name)}, srcs = [{json.dumps(path)}])" + for name, path in names.items() + ) + metadata = { + "schemaVersion": 1, + "target": target, + "cygwinArchitecture": "x86_64", + "tools": names, + } + for name, contents in ( + ("BUILD.bazel", "\n".join(definitions) + "\n"), + ("MODULE.bazel", 'module(name = "voice_windows_tools")\n'), + ("voice-tools.json", json.dumps(metadata, indent=2) + "\n"), + ): + with (root / name).open("x", encoding="utf-8") as output: + output.write(contents) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--root", type=Path, required=True) + parser.add_argument("--target", required=True) + parser.add_argument("--pkg-config", type=Path, required=True) + args = parser.parse_args() + export_repository(args.root, args.target, args.pkg_config) diff --git a/.github/scripts/watch_voice_bazel.py b/.github/scripts/watch_voice_bazel.py new file mode 100644 index 0000000000..a5903d6730 --- /dev/null +++ b/.github/scripts/watch_voice_bazel.py @@ -0,0 +1,76 @@ +"""Capture one bounded diagnostic when the ARM64 voice build goes silent.""" + +import os +from pathlib import Path +import shutil +import subprocess +import sys +import threading +import time + + +def diagnose(): + root = Path(os.environ.get("BAZEL_OUTPUT_BASE", "")) + if not root.is_absolute(): + print("Bazel output base unavailable; skipping diagnostics", flush=True) + return + for relative in ("command.log", "server/jvm.out"): + try: + with (root / relative).open("rb") as source: + source.seek(max(0, source.seek(0, 2) - 16384)) + print( + f"Bazel {relative} tail:\n{source.read(16384).decode(errors='replace')}", + flush=True, + ) + except OSError as error: + print(f"Cannot read {relative}: {error}", flush=True) + try: + pid = str(int((root / "server/server.pid.txt").read_text().strip())) + jstack = shutil.which("jstack") + if not jstack: + print("jstack unavailable; log tails retained", flush=True) + return + result = subprocess.run([jstack, pid], capture_output=True, timeout=20) + print( + f"jstack exit {result.returncode}:\n{(result.stdout + result.stderr)[-65536:].decode(errors='replace')}", + flush=True, + ) + except (OSError, ValueError, subprocess.TimeoutExpired) as error: + print(f"Cannot capture JVM stacks: {error}", flush=True) + + +def main(): + with subprocess.Popen( + sys.argv[1:], stdout=subprocess.PIPE, stderr=subprocess.STDOUT + ) as process: + last_output = time.monotonic() + + def forward(): + nonlocal last_output + for line in process.stdout: + last_output = time.monotonic() + sys.stdout.buffer.write(line) + sys.stdout.buffer.flush() + + reader = threading.Thread(target=forward) + reader.start() + captured = False + while process.poll() is None: + if ( + not captured + and os.environ.get("VOICE_ARCH") == "aarch64" + and time.monotonic() - last_output >= 600 + ): + captured = True + print( + "ARM64 Bazel silent for ten minutes; capturing diagnostics", + flush=True, + ) + diagnose() + time.sleep(1) + reader.join() + return process.returncode + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/.github/workflows/rust-release-windows.yml b/.github/workflows/rust-release-windows.yml index e9a200ff0f..376b4ce3fa 100644 --- a/.github/workflows/rust-release-windows.yml +++ b/.github/workflows/rust-release-windows.yml @@ -146,6 +146,132 @@ jobs: path: | ${{ env.CARGO_TARGET_DIR }}/${{ matrix.target }}/release/staged-${{ matrix.bundle }}/* + build-windows-voice: + name: Build Windows voice - ${{ matrix.target }} + runs-on: ${{ matrix.runs_on }} + timeout-minutes: 120 + permissions: + contents: read + strategy: + fail-fast: false + matrix: + include: + - target: x86_64-pc-windows-msvc + arch: x86_64 + python_arch: x64 + runs_on: + group: ${{ github.event.repository.name }}-runners + labels: ${{ github.event.repository.name }}-windows-x64 + - target: aarch64-pc-windows-msvc + arch: aarch64 + python_arch: arm64 + runs_on: + group: ${{ github.event.repository.name }}-runners + labels: ${{ github.event.repository.name }}-windows-arm64 + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 + with: + python-version: "3.12" + architecture: ${{ matrix.python_arch }} + - name: Prepare Bazel CI + uses: ./.github/actions/prepare-bazel-ci + with: + target: ${{ matrix.target }} + cache-scope: release-voice-windows + - uses: ./.github/actions/setup-msvc-env + with: + target: ${{ matrix.target }} + - name: Refresh Bazel PATH for the selected MSVC target + shell: pwsh + run: ./.github/scripts/compute-bazel-windows-path.ps1 + - name: Download pinned Windows build inputs + shell: pwsh + env: + GH_TOKEN: ${{ github.token }} + run: | + $manifest = Get-Content -Raw .github/scripts/voice-cygwin-snapshot.json | ConvertFrom-Json + $pin = $manifest.archive + $source = $manifest.sourceArchive + $directory = Join-Path $env:RUNNER_TEMP "voice-cygwin-snapshot" + New-Item -ItemType Directory -Path $directory | Out-Null + gh release download $pin.tag --repo "${{ github.repository }}" --pattern $pin.name --pattern $source.name --dir $directory + if ($LASTEXITCODE -ne 0) { throw "Cannot obtain the public Cygwin inputs and source" } + $sourceFile = Join-Path $directory $source.name + if ((Get-Item $sourceFile).Length -ne $source.bytes -or + (Get-FileHash $sourceFile -Algorithm SHA256).Hash.ToLowerInvariant() -ne $source.sha256) { + throw "Public Cygwin source archive mismatch" + } + "VOICE_CYGWIN_ARCHIVE=$(Join-Path $directory $pin.name)" | Out-File $env:GITHUB_ENV -Encoding utf8 -Append + - name: Verify and install pinned public build tools + shell: pwsh + run: | + & ./.github/scripts/setup-voice-windows.ps1 -Target "${{ matrix.target }}" -SnapshotArchive $env:VOICE_CYGWIN_ARCHIVE + if (-not $env:SystemRoot) { throw "Windows SystemRoot is required for native audio actions" } + "VOICE_WINDOWS_SYSTEM_ROOT=$env:SystemRoot" | Out-File $env:GITHUB_ENV -Encoding utf8 -Append + $hostArch = $env:PROCESSOR_ARCHITEW6432 + if (-not $hostArch) { $hostArch = $env:PROCESSOR_ARCHITECTURE } + if (-not $hostArch) { throw "Windows host architecture is required" } + "VOICE_WINDOWS_HOST_ARCH=$hostArch" | Out-File $env:GITHUB_ENV -Encoding utf8 -Append + - name: Build same-commit native runtime and helper + shell: bash + env: + ARCH: ${{ matrix.arch }} + VOICE_ARCH: ${{ matrix.arch }} + BUILDBUDDY_API_KEY: "" + run: | + (cd codex-rs && cargo update --workspace) + python .github/scripts/watch_voice_bazel.py "$(cygpath -w "$BASH")" ./.github/scripts/run-bazel-ci.sh \ + --remote-download-toplevel \ + --print-failed-action-summary \ + --windows-msvc-host-platform \ + -- \ + test -c opt \ + --platforms=//:local_windows_msvc \ + --extra_toolchains=@local_config_cc//:cc-toolchain-x64_windows,@local_config_cc//:cc-toolchain-arm64_windows,//third_party/voice:windows_pkg_config_toolchain,//third_party/voice:windows_cmake_toolchain \ + --repo_env=BAZEL_DO_NOT_DETECT_CPP_TOOLCHAIN=0 \ + --repo_env=BAZEL_MSVC_RUNTIME_VISUAL_STUDIO_EULA=1 \ + --inject_repository="voice_windows_tools=$VOICE_WINDOWS_BAZEL_REPOSITORY" \ + --//third_party/voice:windows_installed_tools=@voice_windows_tools//:tools \ + --action_env="SystemRoot=$VOICE_WINDOWS_SYSTEM_ROOT" \ + --host_action_env="SystemRoot=$VOICE_WINDOWS_SYSTEM_ROOT" \ + --action_env="PROCESSOR_ARCHITECTURE=$VOICE_WINDOWS_HOST_ARCH" \ + --host_action_env="PROCESSOR_ARCHITECTURE=$VOICE_WINDOWS_HOST_ARCH" \ + --output_groups=default,receipt,sdk \ + --workspace_status_command=./scripts/workspace-status.cmd \ + --build_metadata=COMMIT_SHA="$GITHUB_SHA" \ + -- \ + "//third_party/voice:native_runtime_windows_$ARCH" \ + @rules_rust//cargo/private/cargo_build_script_runner:test \ + //codex-rs/voice-host:codex-voice-host + - name: Stage verified voice build output + shell: bash + env: + TARGET: ${{ matrix.target }} + ARCH: ${{ matrix.arch }} + run: | + set -euo pipefail + runtime="bazel-bin/third_party/voice/native_runtime_windows_${ARCH}" + PYTHONPATH=third_party/voice python - "$runtime" "$TARGET" <<'PY' + from pathlib import Path + import sys + from package_runtime import runtime_files + runtime_files(Path(sys.argv[1]).resolve(strict=True), sys.argv[2]) + PY + output="voice-unsigned/${TARGET}" + mkdir -p "$output" + cp -R "$runtime" "$output/runtime" + cp bazel-bin/codex-rs/voice-host/codex-voice-host.exe "$output/codex-voice-host.exe" + chmod -R u+w "$output" + tar -C "$output" -czf "voice-unsigned-${TARGET}.tar.gz" runtime codex-voice-host.exe + - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 + with: + name: voice-${{ matrix.target }}-unsigned + path: voice-unsigned-${{ matrix.target }}.tar.gz + if-no-files-found: error + build-windows-symbols: needs: - build-windows-binaries @@ -193,12 +319,13 @@ jobs: build-windows: needs: - build-windows-binaries + - build-windows-voice name: Build - ${{ matrix.runner }} - ${{ matrix.target }} runs-on: ${{ matrix.runs_on }} environment: name: azure-artifact-signing deployment: false - timeout-minutes: 90 + timeout-minutes: 120 permissions: contents: read id-token: write @@ -265,6 +392,76 @@ jobs: account-name: ${{ secrets.AZURE_ARTIFACT_SIGNING_ACCOUNT_NAME }} certificate-profile-name: ${{ secrets.AZURE_ARTIFACT_SIGNING_CERTIFICATE_PROFILE_NAME }} + - name: Download matching native voice build + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: voice-${{ matrix.target }}-unsigned + path: ${{ runner.temp }}/unsigned-voice + + - name: Stage voice files for signing + id: voice_files + shell: bash + run: | + set -euo pipefail + target="${{ matrix.target }}" + unsigned="${RUNNER_TEMP}/unsigned-voice/extracted" + signed="${GITHUB_WORKSPACE}/signed-voice/${target}" + mkdir -p "$unsigned" "$signed" + tar -xzf "${RUNNER_TEMP}/unsigned-voice/voice-unsigned-${target}.tar.gz" -C "$unsigned" + python "${GITHUB_WORKSPACE}/third_party/voice/release_runtime.py" stage \ + --source "$unsigned/runtime" --target "$target" --output "$signed/runtime" + cp "$unsigned/codex-voice-host.exe" "$signed/codex-voice-host.exe" + chmod -R u+w "$signed" + { + echo 'files<> "$GITHUB_OUTPUT" + + - name: Sign Windows voice helper and native DLLs + uses: azure/trusted-signing-action@1d365fec12862c4aa68fcac418143d73f0cea293 # v0.5.11 + with: + endpoint: ${{ secrets.AZURE_ARTIFACT_SIGNING_ENDPOINT }} + trusted-signing-account-name: ${{ secrets.AZURE_ARTIFACT_SIGNING_ACCOUNT_NAME }} + certificate-profile-name: ${{ secrets.AZURE_ARTIFACT_SIGNING_CERTIFICATE_PROFILE_NAME }} + exclude-environment-credential: true + exclude-workload-identity-credential: true + exclude-managed-identity-credential: true + exclude-shared-token-cache-credential: true + exclude-visual-studio-credential: true + exclude-visual-studio-code-credential: true + exclude-azure-cli-credential: false + exclude-azure-powershell-credential: true + exclude-azure-developer-cli-credential: true + exclude-interactive-browser-credential: true + cache-dependencies: false + files: ${{ steps.voice_files.outputs.files }} + + - uses: ./.github/actions/setup-msvc-env + with: + target: ${{ matrix.target }} + + - name: Seal and verify signed voice output + shell: pwsh + run: | + $target = "${{ matrix.target }}" + $signed = Join-Path $env:GITHUB_WORKSPACE "signed-voice/$target" + python (Join-Path $env:GITHUB_WORKSPACE 'third_party/voice/windows_crt.py') --root (Join-Path $signed 'runtime') --target $target --helper (Join-Path $signed 'codex-voice-host.exe') + if ($LASTEXITCODE -ne 0) { throw 'Cannot stage verified Microsoft CRT' } + $files = @(Join-Path $signed "codex-voice-host.exe") + @( + Get-ChildItem (Join-Path $signed "runtime/bin") -Filter *.dll -File | + ForEach-Object FullName + ) + if ($files.Count -lt 8) { throw "Voice DLL closure is incomplete" } + foreach ($file in $files) { + $signature = Get-AuthenticodeSignature $file + if ($signature.Status -ne 'Valid') { throw "Unsigned voice file: $file ($($signature.Status))" } + } + python (Join-Path $env:GITHUB_WORKSPACE 'third_party/voice/release_runtime.py') seal --target $target --output (Join-Path $signed 'runtime') + if ($LASTEXITCODE -ne 0) { throw 'Cannot seal signed voice runtime' } + "VOICE_RELEASE_DIR=$signed" | Out-File $env:GITHUB_ENV -Encoding utf8 -Append + - name: Stage artifacts shell: bash run: | @@ -282,22 +479,34 @@ jobs: set -euo pipefail target="${{ matrix.target }}" archive_script="${GITHUB_WORKSPACE}/.github/scripts/build-codex-package-archive.sh" - temp_root="${RUNNER_TEMP}/codex-package-archives" + voice_args=(--voice-release-dir "$VOICE_RELEASE_DIR") + voice_args+=(--release-version "${GITHUB_REF_NAME#rust-v}") + bash "$archive_script" \ + --target "$target" --bundle primary \ + --entrypoint-dir "$CARGO_TARGET_DIR/$target/release" \ + --archive-dir "dist/$target" "${voice_args[@]}" + bash "$archive_script" \ + --target "$target" --bundle app-server \ + --entrypoint-dir "$CARGO_TARGET_DIR/$target/release" \ + --archive-dir "dist/$target" - # The package helper rewrites cached DotSlash executables. Keep the - # concurrent processes in separate temp roots because Windows cannot - # replace an executable while another process is using it. - mkdir -p "$temp_root/primary" "$temp_root/app-server" - printf '%s\0' primary app-server | - xargs -0 -P0 -I{} env \ - TMPDIR="$temp_root/{}" \ - TMP="$temp_root/{}" \ - TEMP="$temp_root/{}" \ - bash "$archive_script" \ - --target "$target" \ - --bundle "{}" \ - --entrypoint-dir "$CARGO_TARGET_DIR/$target/release" \ - --archive-dir "dist/$target" + - name: Verify packaged Windows voice closure + shell: pwsh + run: | + $target = "${{ matrix.target }}" + $package = Join-Path $env:RUNNER_TEMP "verify-voice-archive-$target" + New-Item -ItemType Directory -Path $package | Out-Null + tar -xzf "dist/$target/codex-package-$target.tar.gz" -C $package + if ($LASTEXITCODE -ne 0) { throw "Cannot extract the final Windows package" } + $voice = Join-Path $package 'codex-resources/voice' + $helper = Join-Path $voice 'bin/codex-voice-host.exe' + if (-not (Test-Path $helper) -or -not (Test-Path (Join-Path $voice 'bin/gstreamer-1.0-0.dll'))) { + throw 'Signed Windows voice files are missing from the canonical package' + } + $signature = Get-AuthenticodeSignature $helper + if ($signature.Status -ne 'Valid') { throw "Packaged helper signature: $($signature.Status)" } + python -c 'import sys; from pathlib import Path; sys.path.insert(0, sys.argv[3]); from package_runtime import runtime_files; runtime_files(Path(sys.argv[1]), sys.argv[2], public_release=True)' $voice $target (Join-Path $env:GITHUB_WORKSPACE 'third_party/voice') + if ($LASTEXITCODE -ne 0) { throw 'Packaged runtime receipt verification failed' } - name: Build Python runtime wheel shell: bash @@ -320,12 +529,25 @@ jobs: python -m venv "${RUNNER_TEMP}/python-runtime-build-venv" "${RUNNER_TEMP}/python-runtime-build-venv/Scripts/python.exe" -m pip install build + # The wheel's Windows support floor predates the native voice + # runtime's verified device/VC++ prerequisites. Keep it voice-free. + wheel_archives="${RUNNER_TEMP}/voice-free-wheel/${{ matrix.target }}" + bash "${GITHUB_WORKSPACE}/.github/scripts/build-codex-package-archive.sh" \ + --target "${{ matrix.target }}" --bundle primary \ + --entrypoint-dir "$CARGO_TARGET_DIR/${{ matrix.target }}/release" \ + --archive-dir "$wheel_archives" + python - "$wheel_archives/codex-package-${{ matrix.target }}.tar.gz" <<'PY' + import sys + import tarfile + with tarfile.open(sys.argv[1]) as archive: + assert not any("codex-resources/voice/" in member.name for member in archive) + PY stage_dir="${RUNNER_TEMP}/openai-codex-cli-bin-${{ matrix.target }}" wheel_dir="${GITHUB_WORKSPACE}/python-runtime-dist/${{ matrix.target }}" python "${GITHUB_WORKSPACE}/sdk/python/scripts/update_sdk_artifacts.py" \ stage-runtime \ "$stage_dir" \ - "dist/${{ matrix.target }}/codex-package-${{ matrix.target }}.tar.gz" \ + "$wheel_archives/codex-package-${{ matrix.target }}.tar.gz" \ --codex-version "${GITHUB_REF_NAME}" \ --platform-tag "$platform_tag" "${RUNNER_TEMP}/python-runtime-build-venv/Scripts/python.exe" -m build --wheel --outdir "$wheel_dir" "$stage_dir" diff --git a/MODULE.bazel b/MODULE.bazel index e014c6d32c..7854947538 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -165,6 +165,7 @@ single_version_override( patches = [ "//patches:rules_foreign_cc_make_cppflags.patch", "//patches:rules_foreign_cc_make_xcompile.patch", + "//patches:rules_foreign_cc_make_msvc_stdint.patch", ], version = "0.15.1", ) @@ -208,6 +209,7 @@ rules_rust.patch( "//patches:rules_rust_windows_process_wrapper_skip_temp_outputs.patch", # Group build-script argument files to avoid Windows command-line limits. "//patches:rules_rust_group_build_script_arg_files.patch", + "//patches:rules_rust_windows_execroot_separators.patch", ], strip = 1, ) diff --git a/patches/BUILD.bazel b/patches/BUILD.bazel index 2afcf05d1e..7b58251d8a 100644 --- a/patches/BUILD.bazel +++ b/patches/BUILD.bazel @@ -6,11 +6,13 @@ exports_files([ "llvm_windows_mingw_compat.patch", "rules_rust_build_script_tools_transition.patch", "rules_rust_group_build_script_arg_files.patch", + "rules_rust_windows_execroot_separators.patch", "rules_rust_windows_msvc_direct_link_args.patch", "rules_rust_windows_process_wrapper_skip_temp_outputs.patch", "rules_cc_rusty_v8_custom_libcxx.patch", "rules_foreign_cc_make_cppflags.patch", "rules_foreign_cc_make_xcompile.patch", + "rules_foreign_cc_make_msvc_stdint.patch", "rules_rs_build_script_deps_annotation.patch", "rules_rs_windows_msvc_linker.patch", "rules_rs_zlib_snapshot_urls.patch", diff --git a/patches/ring_windows_msvc_include_dirs.patch b/patches/ring_windows_msvc_include_dirs.patch index 06f944ad34..7bb49cbc2c 100644 --- a/patches/ring_windows_msvc_include_dirs.patch +++ b/patches/ring_windows_msvc_include_dirs.patch @@ -1,8 +1,7 @@ diff --git a/build.rs b/build.rs -index 9843ad8aa..573075489 100644 --- a/build.rs +++ b/build.rs -@@ -346,7 +346,28 @@ fn ring_build_rs_main(c_root_dir: &Path, core_name_and_version: &str) { +@@ -346,7 +346,27 @@ // we want to optimize for minimizing the build tools required: No Perl, // no nasm, etc. let generated_dir = if !is_git { @@ -31,7 +30,22 @@ index 9843ad8aa..573075489 100644 } else { generate_sources_and_preassemble( &out_dir, -@@ -569,6 +591,15 @@ fn configure_cc(c: &mut cc::Build, target: &Target, c_root_dir: &Path, include_d +@@ -561,7 +581,13 @@ + let compiler = c.get_compiler(); + // FIXME: On Windows AArch64 we currently must use Clang to compile C code + let compiler = if target.os == WINDOWS && target.arch == AARCH64 && !compiler.is_like_clang() { +- let _ = c.compiler("clang"); ++ // Keep inherited MSVC flags compatible with the selected Clang driver. ++ let clang = if compiler.is_like_msvc() { ++ "clang-cl" ++ } else { ++ "clang" ++ }; ++ let _ = c.compiler(clang); + c.get_compiler() + } else { + compiler +@@ -569,6 +595,15 @@ let _ = c.include(c_root_dir.join("include")); let _ = c.include(include_dir); diff --git a/patches/rules_foreign_cc_make_msvc_stdint.patch b/patches/rules_foreign_cc_make_msvc_stdint.patch new file mode 100644 index 0000000000..f3af72841a --- /dev/null +++ b/patches/rules_foreign_cc_make_msvc_stdint.patch @@ -0,0 +1,13 @@ +diff --git a/foreign_cc/built_tools/make_build.bzl b/foreign_cc/built_tools/make_build.bzl +--- a/foreign_cc/built_tools/make_build.bzl ++++ b/foreign_cc/built_tools/make_build.bzl +@@ -35,6 +35,9 @@ + build_str += " gcc" + dist_dir = "GccRel" + else: ++ # Modern MSVC provides stdint.h, including the intmax_t types that ++ # Make's legacy Windows config otherwise replaces with macros. ++ build_str = 'export CL="$${CL:-}$$ /DHAVE_STDINT_H=1"\n' + build_str + dist_dir = "WinRel" + + script = [ diff --git a/patches/rules_rs_windows_msvc_linker.patch b/patches/rules_rs_windows_msvc_linker.patch index 66feb78569..5056ade96b 100644 --- a/patches/rules_rs_windows_msvc_linker.patch +++ b/patches/rules_rs_windows_msvc_linker.patch @@ -1,13 +1,14 @@ -# What: use Rust's bundled direct linker for Windows x86_64 MSVC toolchains. -# Scope: standard and bootstrap rules_rs Rust toolchains targeting x86_64 MSVC. +# What: use Rust's bundled direct linker for Windows x86_64 and ARM64 MSVC toolchains. +# Scope: standard and bootstrap rules_rs Rust toolchains targeting x86_64 and ARM64 MSVC. diff --git a/rs/toolchains/declare_rustc_toolchains.bzl b/rs/toolchains/declare_rustc_toolchains.bzl --- a/rs/toolchains/declare_rustc_toolchains.bzl +++ b/rs/toolchains/declare_rustc_toolchains.bzl -@@ -88,6 +88,7 @@ def declare_rustc_toolchains( +@@ -88,6 +88,8 @@ def declare_rustc_toolchains( llvm_cov = "@llvm//tools:llvm-cov", llvm_profdata = "@llvm//tools:llvm-profdata", linker = select({ ++ "@rules_rs//rs/platforms/config:aarch64-pc-windows-msvc": "{}rust-lld".format(rustc_repo_label), + "@rules_rs//rs/platforms/config:x86_64-pc-windows-msvc": "{}rust-lld".format(rustc_repo_label), "@platforms//cpu:wasm32": "{}rust-lld".format(rustc_repo_label), "@platforms//cpu:wasm64": "{}rust-lld".format(rustc_repo_label), diff --git a/patches/rules_rust_windows_execroot_separators.patch b/patches/rules_rust_windows_execroot_separators.patch new file mode 100644 index 0000000000..187b37f48c --- /dev/null +++ b/patches/rules_rust_windows_execroot_separators.patch @@ -0,0 +1,52 @@ +--- a/cargo/private/cargo_build_script_runner/lib.rs ++++ b/cargo/private/cargo_build_script_runner/lib.rs +@@ -337,9 +337,18 @@ + return "${pwd}".to_owned(); + } + +- value ++ let redacted = value + .replace(&format!("{exec_root}/"), "${pwd}/") +- .replace(&format!("{exec_root}\\"), "${pwd}\\") ++ .replace(&format!("{exec_root}\\"), "${pwd}\\"); ++ #[cfg(windows)] ++ let redacted = { ++ // pkg-config can emit forward slashes for the same Windows root. ++ let root = exec_root.replace('\\', "/"); ++ redacted ++ .replace(&format!("{root}/"), "${pwd}/") ++ .replace(&format!("{root}\\"), "${pwd}\\") ++ }; ++ redacted + } + + /// Redact for env vars: uses the generic `${out_dir}` token, resolved +@@ -630,6 +639,28 @@ + link_search_paths: + "-L${pwd}/${bazel-out/cfg/bin/pkg/_bs.out_dir}\n-L${pwd}/other/path".to_owned(), + } ++ ); ++ } ++ ++ #[cfg(windows)] ++ #[test] ++ fn windows_include_metadata_redacts_both_root_spellings() { ++ let root = r"D:\o\execroot\_main"; ++ let forward = root.replace('\\', "/"); ++ let outputs = vec![BuildScriptOutput::DepEnv(format!( ++ "INCLUDE={forward}/include;{root}\\lib;{forward}-other/include" ++ ))]; ++ assert_eq!( ++ BuildScriptOutput::outputs_to_dep_env(&outputs, "glib", root), ++ format!("DEP_GLIB_INCLUDE=${{pwd}}/include;${{pwd}}\\lib;{forward}-other/include") ++ ); ++ let directory = std::env::current_dir().unwrap(); ++ let directory = directory.to_str().unwrap(); ++ let forward = directory.replace('\\', "/"); ++ let outputs = vec![BuildScriptOutput::DepEnv(format!("INCLUDE={forward}/."))]; ++ assert_eq!( ++ BuildScriptOutput::nonhermetic_absolute_paths(&outputs, directory, ""), ++ Vec::::new() + ); + } + diff --git a/patches/rules_rust_windows_msvc_direct_link_args.patch b/patches/rules_rust_windows_msvc_direct_link_args.patch index aa5fb274e1..a8eba2ba6e 100644 --- a/patches/rules_rust_windows_msvc_direct_link_args.patch +++ b/patches/rules_rust_windows_msvc_direct_link_args.patch @@ -4,7 +4,7 @@ use_bpf_linker = toolchain.target_arch in ("bpfeb", "bpfel") and toolchain.linker - if not ld or toolchain.linker_preference == "rust" or use_bpf_linker: + use_windows_msvc_linker = ( -+ toolchain.target_arch == "x86_64" and ++ toolchain.target_arch in ("x86_64", "aarch64") and + toolchain.target_os == "windows" and + toolchain.target_abi == "msvc" and + toolchain.linker != None and diff --git a/third_party/voice/NOTICE.md b/third_party/voice/NOTICE.md index c5b79744a8..3de0cff39b 100644 --- a/third_party/voice/NOTICE.md +++ b/third_party/voice/NOTICE.md @@ -15,3 +15,13 @@ projection and package scripts are in the public Codex source tree under platform-specific runtime directories. Replacements must be compatible with the package and, on macOS, have valid code signatures. Build tools listed in `sources.json` are build inputs, not bundled runtime libraries. + +Windows packages also contain an unmodified Microsoft Visual C++ runtime DLL, +copyright Microsoft Corporation, under Microsoft's applicable software license +terms, separately from the open-source audio libraries. `windows-crt.json` +records its version, official download, hashes, and redistribution references. +The Apache and LGPL licenses for other components do not license this DLL. +Microsoft's runtime terms and separate developer redistribution terms apply +to the Microsoft component; the runtime terms alone do not grant redistribution. +Only retail redistributable files are included; Microsoft signatures are retained. +App-local runtime security updates must be delivered with Codex updates. diff --git a/third_party/voice/README.md b/third_party/voice/README.md index 48ea1fa46c..6cbddf369f 100644 --- a/third_party/voice/README.md +++ b/third_party/voice/README.md @@ -249,7 +249,23 @@ runtime inspection and SDK export. These targets are MSVC-only. They require native Windows execution of the matching architecture; they are not cross builds. The generic Rust-consumer aliases are connected separately, after these inputs. -Provide the complete installed Cygwin/pkgconf repository explicitly. The default +Provide the complete installed Cygwin/pkgconf repository explicitly. For a public +Windows build, `.github/scripts/setup-voice-windows.ps1 -Target +x86_64-pc-windows-msvc -SnapshotArchive ` (under `public/` in +codex-internal) verifies the pinned archive and every input size and SHA-512 +digest, then runs the offline installer against its signed metadata. ARM64 uses +`aarch64-pc-windows-msvc` and the same Cygwin x64 tools under emulation. The +installed tool tree stays in the CI temporary directory and is never added to +a Codex package. The upstream mirror's signed metadata changes over time, so +the archived snapshot must be supplied separately. Private CI validates this +public bootstrap against its existing pinned archive. Public release CI obtains +the same hash-pinned build inputs from the public `openai/codex` release named +by `voice-cygwin-snapshot.json`. That release also makes the corresponding +upstream source archives available under `cygwin-build-sources.tar`, with its +own size and SHA-256 pin. These Cygwin tools run only on the build runner; +neither archive is included in the user's Codex package. + +The default `windows_installed_tools` label setting is empty and fails if a Windows action needs it. This keeps ordinary public dependency queries independent of private provisioning; it does not silently omit tools from a requested Windows build. diff --git a/third_party/voice/assemble_package.py b/third_party/voice/assemble_package.py index af6cc8ca00..a8077d4c5f 100644 --- a/third_party/voice/assemble_package.py +++ b/third_party/voice/assemble_package.py @@ -114,6 +114,7 @@ def assemble( for relative in ( "NOTICE.md", "sources.json", + *(("windows-crt.json",) if suffix else ()), "licenses/LGPL-2.1.txt", "licenses/Opus.txt", "licenses/PCRE2.md", diff --git a/third_party/voice/release_runtime.py b/third_party/voice/release_runtime.py index 6b705ca314..a02cce3448 100644 --- a/third_party/voice/release_runtime.py +++ b/third_party/voice/release_runtime.py @@ -15,6 +15,8 @@ def stage(source: Path, destination: Path, target: str) -> None: "x86_64-apple-darwin", "aarch64-unknown-linux-gnu", "x86_64-unknown-linux-gnu", + "aarch64-pc-windows-msvc", + "x86_64-pc-windows-msvc", }: raise ValueError("unsupported public release voice runtime target") source = source.resolve(strict=True) diff --git a/third_party/voice/test_assemble_package.py b/third_party/voice/test_assemble_package.py index 7758bd4b12..a9ac9a3588 100644 --- a/third_party/voice/test_assemble_package.py +++ b/third_party/voice/test_assemble_package.py @@ -308,6 +308,47 @@ class AssembleTests(unittest.TestCase): digest(staged / "runtime.json"), ) + def test_windows_release_packages_signed_receipt_and_exe_helper(self): + self.commit = "b" * 40 + for target in ("x86_64-pc-windows-msvc", "aarch64-pc-windows-msvc"): + with self.subTest(target=target): + runtime, _ = self.make_runtime(target, "bin/gst{}.dll") + staged = self.root / f"signed-{target}" + stage(runtime, staged, target) + library = staged / "bin/gio-2.0-0.dll" + library.write_bytes(library.read_bytes() + b"signed") + seal(staged, target) + self.metadata.update( + target=target, + entrypoint="bin/codex.exe", + version="0.154.0-beta.2", + ) + (self.package / "bin/codex.exe").write_bytes(b"unchanged app") + (self.package / "codex-package.json").write_text( + json.dumps(self.metadata) + ) + output = self.root / f"windows-{target}" + assemble( + self.package, + self.helper, + target, + self.commit, + output, + runtime=staged, + release_version="0.154.0-beta.2", + ) + voice = output / "codex-resources/voice" + self.assertEqual( + (voice / "bin/codex-voice-host.exe").read_bytes(), + self.helper.read_bytes(), + ) + self.assertEqual( + (voice / "bin/gio-2.0-0.dll").read_bytes(), library.read_bytes() + ) + self.assertTrue( + runtime_files(voice.resolve(), target, public_release=True) + ) + def test_rejects_invalid_runtime_receipts_before_creating_package(self): runtime, original = self.make_runtime() changes = [ diff --git a/third_party/voice/windows-crt.json b/third_party/voice/windows-crt.json new file mode 100644 index 0000000000..c198cb9ea4 --- /dev/null +++ b/third_party/voice/windows-crt.json @@ -0,0 +1,18 @@ +{ + "version": "14.50.35719", + "license": "https://visualstudio.microsoft.com/license-terms/vs2026-ga-visualcpp-v14-redist-runtime/", + "developerLicense": "https://visualstudio.microsoft.com/license-terms/vs2026-ga-community/", + "redistribution": "https://learn.microsoft.com/en-us/visualstudio/releases/2026/redistribution", + "x86_64-pc-windows-msvc": { + "url": "https://download.visualstudio.microsoft.com/download/pr/a424e95b-20f6-4af6-844f-9d9a806080a7/a4ab48822362df6e478eb4ed33bf825a07a6faacd6879c1e3d59929cd38b984b/Microsoft.VC.14.50.18.0.CRT.Redist.X64.base.vsix", + "sha256": "a4ab48822362df6e478eb4ed33bf825a07a6faacd6879c1e3d59929cd38b984b", + "member": "Contents/VC/Redist/MSVC/14.50.35710/x64/Microsoft.VC145.CRT/vcruntime140.dll", + "dllSha256": "184146852727a9db4eea06178716bec3cdbb1015c911f6b0f915b184ad7775b2" + }, + "aarch64-pc-windows-msvc": { + "url": "https://download.visualstudio.microsoft.com/download/pr/a424e95b-20f6-4af6-844f-9d9a806080a7/3544badcbcf09e77c6ec32ed866aae59471a39d8a84611d9ae016861ff60a42f/Microsoft.VC.14.50.18.0.CRT.Redist.ARM64.base.vsix", + "sha256": "3544badcbcf09e77c6ec32ed866aae59471a39d8a84611d9ae016861ff60a42f", + "member": "Contents/VC/Redist/MSVC/14.50.35710/arm64/Microsoft.VC145.CRT/vcruntime140.dll", + "dllSha256": "6d987d8cb2a47cff9c29c1fcbb853e7ef273c545f7d822728f7d2d448ca18758" + } +} diff --git a/third_party/voice/windows_crt.py b/third_party/voice/windows_crt.py new file mode 100644 index 0000000000..a927c1b19e --- /dev/null +++ b/third_party/voice/windows_crt.py @@ -0,0 +1,83 @@ +"""Add pinned, unmodified Microsoft retail CRT files to Windows release staging.""" + +import argparse +import hashlib +import io +import json +from pathlib import Path +import re +import subprocess +import urllib.request +import zipfile + +from windows_runtime import EXTERNAL_IMPORTS, inspect + + +def stage(root: Path, target: str, helper: Path): + pin = json.loads(Path(__file__).with_name("windows-crt.json").read_text())[target] + with urllib.request.urlopen(pin["url"], timeout=90) as response: + archive = response.read(6 * 1024 * 1024 + 1) + if hashlib.sha256(archive).hexdigest() != pin["sha256"]: + raise ValueError("Microsoft CRT archive digest mismatch") + with zipfile.ZipFile(io.BytesIO(archive)) as source: + member = source.getinfo(pin["member"]) + if member.file_size > 1024 * 1024: + raise ValueError("CRT member exceeds size limit") + data = source.read(member) + if hashlib.sha256(data).hexdigest() != pin["dllSha256"]: + raise ValueError("Microsoft CRT DLL digest mismatch") + # Extract exactly one retail member, never debug_nonredist or installer files. + with (root / "bin/vcruntime140.dll").open("xb") as output: + output.write(data) + files = list((root / "bin").glob("*.dll")) + bundled = {path.name.lower() for path in files} + imports = set() + for path in files: + if path.name.lower() != "vcruntime140.dll": + imports.update(inspect(path, target).imports) + # Only Microsoft's hash-pinned CRT may use ARM64X rather than plain ARM64. + for path in (helper, root / "bin/vcruntime140.dll"): + result = subprocess.run( + ["dumpbin", "/nologo", "/dependents", str(path)], + check=True, + capture_output=True, + timeout=30, + ) + imports.update( + re.findall( + r"(?mi)^ +([a-z0-9_+.-]+\.dll)\s*$", result.stdout.decode("ascii") + ) + ) + # Additional Windows OS imports used by the Rust helper, not bundled CRTs. + system = (EXTERNAL_IMPORTS - {"vcruntime140.dll"}) | { + "api-ms-win-core-synch-l1-2-0.dll", + "api-ms-win-core-winrt-error-l1-1-0.dll", + "bcrypt.dll", + "bcryptprimitives.dll", + "combase.dll", + "mmdevapi.dll", + "oleaut32.dll", + "ntdll.dll", + "userenv.dll", + "dbghelp.dll", + } + missing = {name.lower() for name in imports} - bundled - system + if missing: + raise ValueError(f"Unbundled Windows imports: {sorted(missing)}") + manifest_path = root / "runtime.json" + manifest = json.loads(manifest_path.read_text()) + if manifest.get("target") != target or manifest.get("developmentOnly") is not True: + raise ValueError("expected staged development runtime") + manifest["libraries"].append( + {"path": "bin/vcruntime140.dll", "sha256": pin["dllSha256"]} + ) + manifest_path.write_text(json.dumps(manifest, indent=2) + "\n") + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--root", type=Path, required=True) + parser.add_argument("--target", required=True) + parser.add_argument("--helper", type=Path, required=True) + args = parser.parse_args() + stage(args.root, args.target, args.helper)