Bundle Windows voice runtime for release rehearsal

This commit is contained in:
Benjamin Carlsson
2026-09-10 20:17:17 -07:00
parent 231e6844cc
commit cfa72fd776
22 changed files with 1084 additions and 28 deletions

View File

@@ -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)" \

96
.github/scripts/setup-voice-windows.ps1 vendored Normal file
View File

@@ -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

View File

@@ -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()

98
.github/scripts/voice-cygwin-inputs.py vendored Normal file
View File

@@ -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)

View File

@@ -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"}
]
}

80
.github/scripts/voice_windows_tools.py vendored Normal file
View File

@@ -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)

76
.github/scripts/watch_voice_bazel.py vendored Normal file
View File

@@ -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())

View File

@@ -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<<EOF'
echo "$signed/codex-voice-host.exe"
find "$signed/runtime/bin" -name '*.dll' -type f -print
echo 'EOF'
} >> "$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"

View File

@@ -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,
)

View File

@@ -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",

View File

@@ -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);

View File

@@ -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 = [

View File

@@ -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),

View File

@@ -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::<String>::new()
);
}

View File

@@ -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

View File

@@ -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.

View File

@@ -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 <path>` (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.

View File

@@ -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",

View File

@@ -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)

View File

@@ -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 = [

18
third_party/voice/windows-crt.json vendored Normal file
View File

@@ -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"
}
}

83
third_party/voice/windows_crt.py vendored Normal file
View File

@@ -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)