Bundle native voice runtimes in Windows releases (#44922)

## Why

Windows release packages need the voice helper and native audio libraries. Realtime TLS connections on fresh Windows installations also need platform certificate validation so Windows can retrieve missing trusted roots on demand.

## What changed

- Build and sign the voice helper and audio DLLs for Windows x64 and ARM64, bundle a pinned Microsoft CRT DLL, and verify signatures and runtime receipts before packaging.
- Add verified, pinned Cygwin and native build tools plus MSVC linker, compiler, and path handling fixes for the Windows Bazel builds.
- Include voice resources in primary release archives and WinGet packages. Preserve WinGet executable names, update manifest hashes, and recognize the package root through matching entrypoint metadata. Keep Python runtime wheels voice-free to preserve their existing Windows support floor.
- Use Windows platform TLS validation for realtime WebSockets when no custom CA bundle is configured, preserving custom CA behavior.

## Testing

Add coverage for build-input integrity and unsafe paths, signed Windows runtime assembly, WinGet file and hash preservation, package discovery, and TLS trust selection, untrusted certificate rejection, and hostname validation.

GitOrigin-RevId: 423da35872fa5549d69fd4ca97d922bb49599386
This commit is contained in:
Benjamin Carlsson
2026-09-11 21:59:24 +00:00
committed by copyberry
parent 3f59eb965a
commit ce7fbb373b
38 changed files with 1629 additions and 46 deletions
@@ -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)" \
+3
View File
@@ -47,6 +47,9 @@ if [[ $# -eq 0 ]]; then
fi
bazel_startup_args=()
if [[ -n "${BAZEL_HOST_JVM_ARG:-}" ]]; then
bazel_startup_args+=("--host_jvm_args=${BAZEL_HOST_JVM_ARG}")
fi
if [[ -n "${BAZEL_OUTPUT_USER_ROOT:-}" ]]; then
bazel_startup_args+=("--output_user_root=${BAZEL_OUTPUT_USER_ROOT}")
fi
+96
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
@@ -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
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)
+145
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
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)
+79
View File
@@ -0,0 +1,79 @@
"""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()
if process.returncode and not captured:
print("Bazel command failed; capturing diagnostics", flush=True)
diagnose()
return process.returncode
if __name__ == "__main__":
sys.exit(main())
+248 -35
View File
@@ -109,6 +109,8 @@ jobs:
for binary in ${{ matrix.binaries }}; do
build_args+=(--bin "$binary")
done
STABLE_GIT_COMMIT="$(git rev-parse HEAD)"
export STABLE_GIT_COMMIT
cargo build --target "$target" --release --timings "${build_args[@]}"
- name: Upload Cargo timings
@@ -146,6 +148,134 @@ 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 }}
# Avoid Java JIT crashes in the Windows ARM64 voice build.
BAZEL_HOST_JVM_ARG: ${{ matrix.arch == 'aarch64' && '-Xint' || '' }}
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 +323,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 +396,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 "$(cygpath -u "${RUNNER_TEMP}/unsigned-voice/voice-unsigned-${target}.tar.gz")" -C "$(cygpath -u "$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 +483,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 +533,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"
@@ -378,25 +604,12 @@ jobs:
# Must run from inside the dest dir so 7z does not embed the
# directory path inside the zip.
if [[ "$base" == "codex-${target}.exe" ]]; then
# Bundle the sandbox helper binaries into the main codex zip so
# WinGet installs include the required helpers next to codex.exe.
# Fall back to the single-binary zip if the helpers are missing
# to avoid breaking releases.
# Preserve WinGet executable paths while including the signed
# runtime, resources, and package metadata from the canonical tar.
bundle_dir="$(mktemp -d)"
runner_src="$dest/codex-command-runner-${target}.exe"
setup_src="$dest/codex-windows-sandbox-setup-${target}.exe"
if [[ -f "$runner_src" && -f "$setup_src" ]]; then
cp "$dest/$base" "$bundle_dir/$base"
cp "$runner_src" "$bundle_dir/codex-command-runner.exe"
cp "$setup_src" "$bundle_dir/codex-windows-sandbox-setup.exe"
# Use an absolute path so bundle zips land in the real dist
# dir even when 7z runs from a temp directory.
(cd "$bundle_dir" && 7z a "$repo_root/$dest/${base}.zip" .)
else
echo "warning: missing sandbox binaries; falling back to single-binary zip"
echo "warning: expected $runner_src and $setup_src"
(cd "$dest" && 7z a "${base}.zip" "$base")
fi
tar -xzf "$dest/codex-package-${target}.tar.gz" -C "$bundle_dir"
python "${GITHUB_WORKSPACE}/scripts/build_winget_package.py" "$(cygpath -w "$bundle_dir")"
(cd "$bundle_dir" && 7z a "$repo_root/$dest/${base}.zip" .)
rm -rf "$bundle_dir"
else
(cd "$dest" && 7z a "${base}.zip" "$base")
+2
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,
)
+2
View File
@@ -1663,6 +1663,8 @@
"rustls-native-certs_0.8.3": "{\"dependencies\":[{\"name\":\"openssl-probe\",\"req\":\"^0.2\",\"target\":\"cfg(all(unix, not(target_os = \\\"macos\\\")))\"},{\"features\":[\"std\"],\"name\":\"pki-types\",\"package\":\"rustls-pki-types\",\"req\":\"^1.10\"},{\"kind\":\"dev\",\"name\":\"ring\",\"req\":\"^0.17\"},{\"kind\":\"dev\",\"name\":\"rustls\",\"req\":\"^0.23\"},{\"kind\":\"dev\",\"name\":\"rustls-webpki\",\"req\":\"^0.103\"},{\"name\":\"schannel\",\"req\":\"^0.1\",\"target\":\"cfg(windows)\"},{\"name\":\"security-framework\",\"req\":\"^3\",\"target\":\"cfg(target_os = \\\"macos\\\")\"},{\"kind\":\"dev\",\"name\":\"serial_test\",\"req\":\"^3\"},{\"kind\":\"dev\",\"name\":\"tempfile\",\"req\":\"^3.5\"},{\"kind\":\"dev\",\"name\":\"untrusted\",\"req\":\"^0.9\"},{\"kind\":\"dev\",\"name\":\"webpki-roots\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"x509-parser\",\"req\":\"^0.18\"}],\"features\":{}}",
"rustls-pki-types_1.14.0": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"crabgrind\",\"req\":\"=0.1.9\",\"target\":\"cfg(all(target_os = \\\"linux\\\", target_arch = \\\"x86_64\\\"))\"},{\"name\":\"web-time\",\"optional\":true,\"req\":\"^1\",\"target\":\"cfg(all(target_family = \\\"wasm\\\", target_os = \\\"unknown\\\"))\"},{\"name\":\"zeroize\",\"optional\":true,\"req\":\"^1\"}],\"features\":{\"alloc\":[\"dep:zeroize\"],\"default\":[\"alloc\"],\"std\":[\"alloc\"],\"web\":[\"web-time\"]}}",
"rustls-pki-types_1.14.1": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"crabgrind\",\"req\":\"=0.1.9\",\"target\":\"cfg(all(target_os = \\\"linux\\\", target_arch = \\\"x86_64\\\"))\"},{\"name\":\"web-time\",\"optional\":true,\"req\":\"^1\",\"target\":\"cfg(all(target_family = \\\"wasm\\\", target_os = \\\"unknown\\\"))\"},{\"name\":\"zeroize\",\"optional\":true,\"req\":\"^1\"}],\"features\":{\"alloc\":[\"dep:zeroize\"],\"default\":[\"alloc\"],\"std\":[\"alloc\"],\"web\":[\"web-time\"]}}",
"rustls-platform-verifier-android_0.1.1": "{\"dependencies\":[],\"features\":{}}",
"rustls-platform-verifier_0.7.0": "{\"dependencies\":[{\"name\":\"android_logger\",\"optional\":true,\"req\":\"^0.15\",\"target\":\"cfg(target_os = \\\"android\\\")\"},{\"name\":\"base64\",\"optional\":true,\"req\":\"^0.22\"},{\"name\":\"core-foundation\",\"req\":\"^0.10\",\"target\":\"cfg(any(target_vendor = \\\"apple\\\"))\"},{\"name\":\"core-foundation-sys\",\"req\":\"^0.8\",\"target\":\"cfg(any(target_vendor = \\\"apple\\\"))\"},{\"default_features\":false,\"name\":\"jni\",\"req\":\"^0.22\",\"target\":\"cfg(target_os = \\\"android\\\")\"},{\"default_features\":false,\"name\":\"jni\",\"optional\":true,\"req\":\"^0.22.4\"},{\"name\":\"log\",\"req\":\"^0.4\"},{\"name\":\"once_cell\",\"req\":\"^1.9\",\"target\":\"cfg(target_os = \\\"android\\\")\"},{\"name\":\"once_cell\",\"optional\":true,\"req\":\"^1.9\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"rustls\",\"req\":\"^0.23.27\"},{\"default_features\":false,\"features\":[\"ring\"],\"kind\":\"dev\",\"name\":\"rustls\",\"req\":\"^0.23\"},{\"name\":\"rustls-native-certs\",\"req\":\"^0.8\",\"target\":\"cfg(all(unix, not(target_os = \\\"android\\\"), not(target_vendor = \\\"apple\\\"), not(target_arch = \\\"wasm32\\\")))\"},{\"name\":\"rustls-platform-verifier-android\",\"req\":\"^0.1.0\",\"target\":\"cfg(target_os = \\\"android\\\")\"},{\"name\":\"security-framework\",\"req\":\"^3.5.0\",\"target\":\"cfg(any(target_vendor = \\\"apple\\\"))\"},{\"name\":\"security-framework-sys\",\"req\":\"^2.15\",\"target\":\"cfg(any(target_vendor = \\\"apple\\\"))\"},{\"default_features\":false,\"name\":\"webpki\",\"package\":\"rustls-webpki\",\"req\":\"^0.103\",\"target\":\"cfg(all(unix, not(target_os = \\\"android\\\"), not(target_vendor = \\\"apple\\\"), not(target_arch = \\\"wasm32\\\")))\"},{\"default_features\":false,\"name\":\"webpki\",\"package\":\"rustls-webpki\",\"req\":\"^0.103\",\"target\":\"cfg(target_arch = \\\"wasm32\\\")\"},{\"default_features\":false,\"name\":\"webpki\",\"package\":\"rustls-webpki\",\"req\":\"^0.103\",\"target\":\"cfg(target_os = \\\"android\\\")\"},{\"name\":\"webpki-root-certs\",\"req\":\"^1\",\"target\":\"cfg(target_arch = \\\"wasm32\\\")\"},{\"kind\":\"dev\",\"name\":\"webpki-root-certs\",\"req\":\"^1\"},{\"default_features\":false,\"features\":[\"Win32_Foundation\",\"Win32_Security_Cryptography\"],\"name\":\"windows-sys\",\"req\":\">=0.52.0, <0.62.0\",\"target\":\"cfg(windows)\"}],\"features\":{\"cert-logging\":[\"base64\"],\"dbg\":[],\"docsrs\":[\"jni\",\"once_cell\"],\"ffi-testing\":[\"android_logger\",\"rustls/ring\"]}}",
"rustls-webpki_0.103.13": "{\"dependencies\":[{\"default_features\":false,\"name\":\"aws-lc-rs\",\"optional\":true,\"req\":\"^1.14\"},{\"kind\":\"dev\",\"name\":\"base64\",\"req\":\"^0.22\"},{\"kind\":\"dev\",\"name\":\"bencher\",\"req\":\"^0.1.5\"},{\"kind\":\"dev\",\"name\":\"bzip2\",\"req\":\"^0.6\"},{\"kind\":\"dev\",\"name\":\"once_cell\",\"req\":\"^1.17.2\"},{\"default_features\":false,\"name\":\"pki-types\",\"package\":\"rustls-pki-types\",\"req\":\"^1.12\"},{\"default_features\":false,\"features\":[\"aws_lc_rs\"],\"kind\":\"dev\",\"name\":\"rcgen\",\"req\":\"^0.14.2\"},{\"default_features\":false,\"name\":\"ring\",\"optional\":true,\"req\":\"^0.17\"},{\"features\":[\"derive\"],\"kind\":\"dev\",\"name\":\"serde\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0\"},{\"name\":\"untrusted\",\"req\":\"^0.9\"},{\"kind\":\"dev\",\"name\":\"x509-parser\",\"req\":\"^0.18.1\"}],\"features\":{\"alloc\":[\"ring?/alloc\",\"pki-types/alloc\"],\"aws-lc-rs\":[\"dep:aws-lc-rs\",\"aws-lc-rs/aws-lc-sys\",\"aws-lc-rs/prebuilt-nasm\"],\"aws-lc-rs-fips\":[\"dep:aws-lc-rs\",\"aws-lc-rs/fips\"],\"aws-lc-rs-unstable\":[\"aws-lc-rs\",\"aws-lc-rs/unstable\"],\"default\":[\"std\"],\"ring\":[\"dep:ring\"],\"std\":[\"alloc\",\"pki-types/std\"]}}",
"rustls_0.23.36": "{\"dependencies\":[{\"default_features\":false,\"name\":\"aws-lc-rs\",\"optional\":true,\"req\":\"^1.14\"},{\"kind\":\"dev\",\"name\":\"base64\",\"req\":\"^0.22\"},{\"kind\":\"dev\",\"name\":\"bencher\",\"req\":\"^0.1.5\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"brotli\",\"optional\":true,\"req\":\"^8\"},{\"name\":\"brotli-decompressor\",\"optional\":true,\"req\":\"^5.0.0\"},{\"kind\":\"dev\",\"name\":\"env_logger\",\"req\":\"^0.11\"},{\"default_features\":false,\"features\":[\"default-hasher\",\"inline-more\"],\"name\":\"hashbrown\",\"optional\":true,\"req\":\"^0.15\"},{\"kind\":\"dev\",\"name\":\"hex\",\"req\":\"^0.4\"},{\"name\":\"log\",\"optional\":true,\"req\":\"^0.4.8\"},{\"kind\":\"dev\",\"name\":\"log\",\"req\":\"^0.4.8\"},{\"kind\":\"dev\",\"name\":\"macro_rules_attribute\",\"req\":\"^0.2\"},{\"kind\":\"dev\",\"name\":\"num-bigint\",\"req\":\"^0.4.4\"},{\"default_features\":false,\"features\":[\"alloc\",\"race\"],\"name\":\"once_cell\",\"req\":\"^1.16\"},{\"features\":[\"alloc\"],\"name\":\"pki-types\",\"package\":\"rustls-pki-types\",\"req\":\"^1.12\"},{\"default_features\":false,\"features\":[\"pem\",\"aws_lc_rs\"],\"kind\":\"dev\",\"name\":\"rcgen\",\"req\":\"^0.14\"},{\"name\":\"ring\",\"optional\":true,\"req\":\"^0.17\"},{\"kind\":\"build\",\"name\":\"rustversion\",\"optional\":true,\"req\":\"^1.0.6\"},{\"features\":[\"derive\"],\"kind\":\"dev\",\"name\":\"serde\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1\"},{\"default_features\":false,\"name\":\"subtle\",\"req\":\"^2.5.0\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"time\",\"req\":\"^0.3.6\"},{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"webpki\",\"package\":\"rustls-webpki\",\"req\":\"^0.103.5\"},{\"kind\":\"dev\",\"name\":\"webpki-roots\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"x509-parser\",\"req\":\"^0.17\"},{\"name\":\"zeroize\",\"req\":\"^1.8\"},{\"name\":\"zlib-rs\",\"optional\":true,\"req\":\"^0.5\"}],\"features\":{\"aws-lc-rs\":[\"aws_lc_rs\"],\"aws_lc_rs\":[\"dep:aws-lc-rs\",\"webpki/aws-lc-rs\",\"aws-lc-rs/aws-lc-sys\",\"aws-lc-rs/prebuilt-nasm\"],\"brotli\":[\"dep:brotli\",\"dep:brotli-decompressor\",\"std\"],\"custom-provider\":[],\"default\":[\"aws_lc_rs\",\"logging\",\"prefer-post-quantum\",\"std\",\"tls12\"],\"fips\":[\"aws_lc_rs\",\"aws-lc-rs?/fips\",\"webpki/aws-lc-rs-fips\"],\"logging\":[\"log\"],\"prefer-post-quantum\":[\"aws_lc_rs\"],\"read_buf\":[\"rustversion\",\"std\"],\"ring\":[\"dep:ring\",\"webpki/ring\"],\"std\":[\"webpki/std\",\"pki-types/std\",\"once_cell/std\"],\"tls12\":[],\"zlib\":[\"dep:zlib-rs\"]}}",
"rustls_0.23.40": "{\"dependencies\":[{\"default_features\":false,\"name\":\"aws-lc-rs\",\"optional\":true,\"req\":\"^1.14\"},{\"kind\":\"dev\",\"name\":\"base64\",\"req\":\"^0.22\"},{\"kind\":\"dev\",\"name\":\"bencher\",\"req\":\"^0.1.5\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"brotli\",\"optional\":true,\"req\":\"^8\"},{\"name\":\"brotli-decompressor\",\"optional\":true,\"req\":\"^5.0.0\"},{\"kind\":\"dev\",\"name\":\"env_logger\",\"req\":\"^0.11\"},{\"default_features\":false,\"features\":[\"default-hasher\",\"inline-more\"],\"name\":\"hashbrown\",\"optional\":true,\"req\":\"^0.15\"},{\"kind\":\"dev\",\"name\":\"hex\",\"req\":\"^0.4\"},{\"name\":\"log\",\"optional\":true,\"req\":\"^0.4.8\"},{\"kind\":\"dev\",\"name\":\"log\",\"req\":\"^0.4.8\"},{\"kind\":\"dev\",\"name\":\"macro_rules_attribute\",\"req\":\"^0.2\"},{\"kind\":\"dev\",\"name\":\"num-bigint\",\"req\":\"^0.4.4\"},{\"default_features\":false,\"features\":[\"alloc\",\"race\"],\"name\":\"once_cell\",\"req\":\"^1.16\"},{\"features\":[\"alloc\"],\"name\":\"pki-types\",\"package\":\"rustls-pki-types\",\"req\":\"^1.12\"},{\"default_features\":false,\"features\":[\"pem\",\"aws_lc_rs\"],\"kind\":\"dev\",\"name\":\"rcgen\",\"req\":\"^0.14\"},{\"name\":\"ring\",\"optional\":true,\"req\":\"^0.17\"},{\"kind\":\"build\",\"name\":\"rustversion\",\"optional\":true,\"req\":\"^1.0.6\"},{\"features\":[\"derive\"],\"kind\":\"dev\",\"name\":\"serde\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1\"},{\"default_features\":false,\"name\":\"subtle\",\"req\":\"^2.5.0\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"time\",\"req\":\"^0.3.6\"},{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"webpki\",\"package\":\"rustls-webpki\",\"req\":\"^0.103.5\"},{\"kind\":\"dev\",\"name\":\"webpki-roots\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"x509-parser\",\"req\":\"^0.17\"},{\"name\":\"zeroize\",\"req\":\"^1.8\"},{\"name\":\"zlib-rs\",\"optional\":true,\"req\":\"^0.6\"}],\"features\":{\"aws-lc-rs\":[\"aws_lc_rs\"],\"aws_lc_rs\":[\"dep:aws-lc-rs\",\"webpki/aws-lc-rs\",\"aws-lc-rs/aws-lc-sys\",\"aws-lc-rs/prebuilt-nasm\"],\"brotli\":[\"dep:brotli\",\"dep:brotli-decompressor\",\"std\"],\"custom-provider\":[],\"default\":[\"aws_lc_rs\",\"logging\",\"prefer-post-quantum\",\"std\",\"tls12\"],\"fips\":[\"aws_lc_rs\",\"aws-lc-rs?/fips\",\"webpki/aws-lc-rs-fips\"],\"logging\":[\"log\"],\"prefer-post-quantum\":[\"aws_lc_rs\"],\"read_buf\":[\"rustversion\",\"std\"],\"ring\":[\"dep:ring\",\"webpki/ring\"],\"std\":[\"webpki/std\",\"pki-types/std\",\"once_cell/std\"],\"tls12\":[],\"zlib\":[\"dep:zlib-rs\"]}}",
+31
View File
@@ -2147,10 +2147,13 @@ dependencies = [
"futures",
"http 1.4.0",
"pretty_assertions",
"rcgen",
"regex-lite",
"rustls",
"schemars 0.8.22",
"serde",
"serde_json",
"tempfile",
"thiserror 2.0.18",
"tokio",
"tokio-test",
@@ -3699,6 +3702,7 @@ dependencies = [
"rustls",
"rustls-native-certs",
"rustls-pki-types",
"rustls-platform-verifier",
"serde",
"serde_json",
"sha2 0.10.9",
@@ -13428,6 +13432,33 @@ dependencies = [
"zeroize",
]
[[package]]
name = "rustls-platform-verifier"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26d1e2536ce4f35f4846aa13bff16bd0ff40157cdb14cc056c7b14ba41233ba0"
dependencies = [
"core-foundation 0.10.1",
"core-foundation-sys",
"jni",
"log",
"once_cell",
"rustls",
"rustls-native-certs",
"rustls-platform-verifier-android",
"rustls-webpki",
"security-framework 3.5.1",
"security-framework-sys",
"webpki-root-certs",
"windows-sys 0.61.2",
]
[[package]]
name = "rustls-platform-verifier-android"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f"
[[package]]
name = "rustls-webpki"
version = "0.103.13"
+3
View File
@@ -34,6 +34,9 @@ uuid = { workspace = true }
anyhow = { workspace = true }
assert_matches = { workspace = true }
pretty_assertions = { workspace = true }
rcgen = { workspace = true }
rustls = { workspace = true }
tempfile = { workspace = true }
tokio-test = { workspace = true }
wiremock = { workspace = true }
@@ -965,6 +965,17 @@ impl RealtimeWebsocketClient {
let connector = maybe_build_rustls_client_config_with_custom_ca()
.map_err(|err| ApiError::Stream(format!("failed to configure websocket TLS: {err}")))?
.map(tokio_tungstenite::Connector::Rustls);
// A fresh Windows install may not have downloaded the server's trusted root yet.
// Use platform validation only for system trust, preserving custom CA semantics.
#[cfg(windows)]
let connector = match connector {
Some(connector) => Some(connector),
None => Some(tokio_tungstenite::Connector::Rustls(
codex_http_client::build_windows_platform_tls_config().map_err(|err| {
ApiError::Stream(format!("failed to configure websocket TLS: {err}"))
})?,
)),
};
let (stream, response) = tokio_tungstenite::connect_async_tls_with_config(
request,
Some(websocket_config()),
@@ -0,0 +1,167 @@
#![allow(clippy::expect_used)]
//! Exercise realtime TLS selection without mutating the test process's trust environment.
use std::io;
use std::net::TcpListener;
use std::process::Command;
use std::sync::Arc;
use std::time::Duration;
use std::time::Instant;
use codex_api::Provider;
use codex_api::RealtimeEventParser;
use codex_api::RealtimeOutputModality;
use codex_api::RealtimeSessionConfig;
use codex_api::RealtimeSessionMode;
use codex_api::RealtimeWebsocketClient;
use codex_api::RetryConfig;
use codex_protocol::protocol::RealtimeVoice;
use http::HeaderMap;
use pretty_assertions::assert_eq;
const ADDRESS_ENV: &str = "CODEX_TEST_REALTIME_TLS_ADDRESS";
const TRUST_ENV: &str = "CODEX_TEST_REALTIME_TLS_TRUST";
#[test]
fn realtime_tls_selects_system_and_custom_trust() {
if let Ok(address) = std::env::var(ADDRESS_ENV) {
let runtime = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.unwrap();
runtime.block_on(check_connection(address));
return;
}
codex_utils_rustls_provider::ensure_rustls_crypto_provider();
let certificate = rcgen::generate_simple_self_signed(vec!["localhost".to_string()]).unwrap();
let temp = tempfile::TempDir::new().unwrap();
let ca = temp.path().join("ca.pem");
std::fs::write(&ca, certificate.cert.pem()).unwrap();
let config = Arc::new(
rustls::ServerConfig::builder()
.with_no_client_auth()
.with_single_cert(
vec![certificate.cert.der().clone()],
certificate.signing_key.into(),
)
.unwrap(),
);
for trust in ["system", "custom"] {
let listener = TcpListener::bind("127.0.0.1:0").unwrap();
let address = listener.local_addr().unwrap();
listener.set_nonblocking(/*nonblocking*/ true).unwrap();
let config = config.clone();
let server = std::thread::spawn(move || {
let deadline = Instant::now() + Duration::from_secs(/*secs*/ 30);
let stream = loop {
match listener.accept() {
Ok((stream, _)) => break stream,
Err(error) if error.kind() == io::ErrorKind::WouldBlock => {
assert!(Instant::now() < deadline, "TLS client did not connect");
std::thread::sleep(Duration::from_millis(/*millis*/ 10));
}
Err(error) => panic!("TLS accept failed: {error}"),
}
};
stream.set_nonblocking(/*nonblocking*/ false).unwrap();
stream
.set_read_timeout(Some(Duration::from_secs(/*secs*/ 30)))
.unwrap();
stream
.set_write_timeout(Some(Duration::from_secs(/*secs*/ 30)))
.unwrap();
let tls =
rustls::StreamOwned::new(rustls::ServerConnection::new(config).unwrap(), stream);
match tungstenite::accept(tls) {
Ok(mut socket) => {
assert_eq!(trust, "custom", "system trust must reject the generated CA");
let message = socket.read().unwrap().into_text().unwrap();
let update: serde_json::Value = serde_json::from_str(&message).unwrap();
assert_eq!(update["type"], "session.update");
}
Err(_) => assert_eq!(trust, "system", "custom CA must allow WSS"),
}
});
// Re-execute this test so each connection uses its own CA environment, including on Windows.
let mut child = Command::new(std::env::current_exe().unwrap());
child
.args([
"--exact",
"realtime_tls_selects_system_and_custom_trust",
"--nocapture",
])
.env(ADDRESS_ENV, format!("localhost:{}", address.port()))
.env(TRUST_ENV, trust)
.env_remove("CODEX_CA_CERTIFICATE")
.env_remove("SSL_CERT_FILE");
if trust == "custom" {
child.env("CODEX_CA_CERTIFICATE", &ca).env(
"SSL_CERT_FILE",
temp.path().join("missing-lower-priority-ca.pem"),
);
}
let output = child.output().unwrap();
server.join().unwrap();
assert!(
output.status.success(),
"{trust}: {}\n{}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
}
}
async fn check_connection(address: String) {
let client = RealtimeWebsocketClient::new(Provider {
name: "local TLS test".into(),
base_url: format!("https://{address}"),
query_params: None,
headers: HeaderMap::new(),
retry: RetryConfig {
max_attempts: 1,
base_delay: Duration::from_millis(/*millis*/ 1),
retry_429: false,
retry_5xx: false,
retry_transport: false,
},
stream_idle_timeout: Duration::from_secs(/*secs*/ 5),
});
let result = tokio::time::timeout(
Duration::from_secs(/*secs*/ 20),
client.connect(
RealtimeSessionConfig {
instructions: "TLS test".into(),
initial_items: Vec::new(),
delegation_ack_filler: None,
model: Some("realtime-test-model".into()),
session_id: None,
event_parser: RealtimeEventParser::V1,
session_mode: RealtimeSessionMode::Conversational,
output_modality: RealtimeOutputModality::Audio,
voice: RealtimeVoice::Cove,
},
HeaderMap::new(),
HeaderMap::new(),
),
)
.await
.expect("TLS connection should finish");
match std::env::var(TRUST_ENV)
.expect("trust scenario should be set")
.as_str()
{
"custom" => {
result.expect("configured CA should permit realtime WSS");
}
"system" => {
let error = match result {
Ok(_) => panic!("system trust accepted an untrusted certificate"),
Err(error) => error.to_string(),
};
assert!(error.to_lowercase().contains("certificate"), "{error}");
}
other => panic!("unknown trust scenario: {other}"),
}
}
+1
View File
@@ -28,6 +28,7 @@ zstd = { workspace = true }
system-configuration = { workspace = true }
[target.'cfg(target_os = "windows")'.dependencies]
rustls-platform-verifier = "0.7.0"
windows-sys = { version = "0.52", features = [
"Win32_Foundation",
"Win32_Networking_WinHttp",
+4
View File
@@ -818,3 +818,7 @@ mod tests {
));
}
}
#[cfg(test)]
#[path = "custom_ca_tls_tests.rs"]
mod tls_tests;
@@ -0,0 +1,79 @@
//! Handshake coverage for the custom trust path retained by Windows realtime connections.
use super::CODEX_CA_CERT_ENV;
use super::ConfiguredCaBundle;
use super::build_rustls_client_config;
use pretty_assertions::assert_eq;
use rcgen::BasicConstraints;
use rcgen::CertificateParams;
use rcgen::CertifiedIssuer;
use rcgen::IsCa;
use rcgen::KeyPair;
use std::sync::Arc;
#[test]
fn custom_intermediate_trust_preserves_hostname_validation() {
let mut params = CertificateParams::default();
params.is_ca = IsCa::Ca(BasicConstraints::Unconstrained);
let root = CertifiedIssuer::self_signed(params.clone(), KeyPair::generate().unwrap()).unwrap();
let intermediate =
CertifiedIssuer::signed_by(params, KeyPair::generate().unwrap(), &root).unwrap();
let leaf_key = KeyPair::generate().unwrap();
let leaf = CertificateParams::new(vec!["localhost".to_string()])
.unwrap()
.signed_by(&leaf_key, &intermediate)
.unwrap();
let temp = tempfile::TempDir::new().unwrap();
let path = temp.path().join("intermediate.pem");
std::fs::write(&path, intermediate.pem()).unwrap();
let config = build_rustls_client_config(Some(&ConfiguredCaBundle {
source_env: CODEX_CA_CERT_ENV,
path,
}))
.unwrap();
let server = Arc::new(
rustls::ServerConfig::builder()
.with_no_client_auth()
.with_single_cert(vec![leaf.der().clone()], leaf_key.into())
.unwrap(),
);
assert_eq!(
handshake(config.clone(), server.clone(), "localhost"),
Ok(())
);
let error = handshake(config, server, "wrong.example").unwrap_err();
assert!(
matches!(
error,
rustls::Error::InvalidCertificate(
rustls::CertificateError::NotValidForName
| rustls::CertificateError::NotValidForNameContext { .. }
)
),
"{error:?}"
);
}
fn handshake(
config: Arc<rustls::ClientConfig>,
server: Arc<rustls::ServerConfig>,
hostname: &'static str,
) -> Result<(), rustls::Error> {
let mut client = rustls::ClientConnection::new(config, hostname.try_into().unwrap()).unwrap();
let mut server = rustls::ServerConnection::new(server).unwrap();
for _ in 0..10 {
let mut bytes = Vec::new();
client.write_tls(&mut bytes).unwrap();
server.read_tls(&mut bytes.as_slice()).unwrap();
server.process_new_packets()?;
bytes.clear();
server.write_tls(&mut bytes).unwrap();
client.read_tls(&mut bytes.as_slice()).unwrap();
client.process_new_packets()?;
if !client.is_handshaking() {
return Ok(());
}
}
panic!("handshake did not complete");
}
+6
View File
@@ -57,3 +57,9 @@ pub use crate::transport::ByteStream;
pub use crate::transport::HttpTransport;
pub use crate::transport::ReqwestTransport;
pub use crate::transport::StreamResponse;
#[cfg(windows)]
mod windows_tls;
#[cfg(windows)]
pub use crate::windows_tls::build_windows_platform_tls_config;
+22
View File
@@ -0,0 +1,22 @@
//! Windows certificate validation for callers without a custom CA bundle.
//!
//! Unlike a snapshot of installed roots, Windows can retrieve missing trusted roots on demand.
//! Callers must retain the custom-CA configuration path when a bundle is configured.
use std::sync::Arc;
use codex_utils_rustls_provider::ensure_rustls_crypto_provider;
use rustls::ClientConfig;
use rustls_platform_verifier::ConfigVerifierExt;
/// Builds a TLS configuration that validates server certificates using Windows trust policy.
///
/// This does not load Codex custom CA settings; callers must check those before selecting it.
pub fn build_windows_platform_tls_config() -> Result<Arc<ClientConfig>, rustls::Error> {
ensure_rustls_crypto_provider();
ClientConfig::with_platform_verifier().map(Arc::new)
}
#[cfg(test)]
#[path = "windows_tls_tests.rs"]
mod tests;
@@ -0,0 +1,58 @@
//! Certificate rejection coverage for Windows platform TLS.
use super::build_windows_platform_tls_config;
use std::sync::Arc;
#[test]
fn platform_tls_rejects_a_self_signed_server() {
let client_config =
build_windows_platform_tls_config().expect("configure platform verification");
let certified_key = rcgen::generate_simple_self_signed(vec!["localhost".to_string()])
.expect("generate untrusted certificate");
let server_config = rustls::ServerConfig::builder()
.with_no_client_auth()
.with_single_cert(
vec![certified_key.cert.der().clone()],
certified_key.signing_key.into(),
)
.expect("configure test server");
let mut client = rustls::ClientConnection::new(
client_config,
"localhost".try_into().expect("valid server name"),
)
.expect("create client");
let mut server = rustls::ServerConnection::new(Arc::new(server_config)).expect("create server");
// Drive the real handshake without opening ports or changing system trust.
for _ in 0..10 {
let mut outbound = Vec::new();
client
.write_tls(&mut outbound)
.expect("write client records");
server
.read_tls(&mut outbound.as_slice())
.expect("read client records");
server
.process_new_packets()
.expect("process client records");
outbound.clear();
server
.write_tls(&mut outbound)
.expect("write server records");
client
.read_tls(&mut outbound.as_slice())
.expect("read server records");
if let Err(error) = client.process_new_packets() {
assert!(
matches!(error, rustls::Error::InvalidCertificate(_)),
"{error:?}"
);
return;
}
assert!(
client.is_handshaking(),
"untrusted server must not complete TLS"
);
}
panic!("expected certificate rejection during handshake");
}
@@ -46,3 +46,31 @@ fn bundle_executable_preserves_package_layout_and_install_method() -> std::io::R
);
Ok(())
}
#[cfg(windows)]
#[test]
fn winget_root_requires_metadata_for_the_actual_executable() -> std::io::Result<()> {
let temp = tempfile::tempdir()?;
let package = canonical_absolute_path(temp.path()).unwrap();
let name = "codex-x86_64-pc-windows-msvc.exe";
let executable = package.join(name);
fs::write(&executable, "signed CLI")?;
for directory in [RESOURCES_DIRNAME, PATH_DIRNAME] {
fs::create_dir_all(package.join(directory))?;
}
assert_eq!(CodexPackageLayout::from_exe(executable.as_path()), None);
for entrypoint in ["other.exe", "bin/codex.exe", name] {
fs::write(
package.join(PACKAGE_METADATA_FILENAME),
serde_json::json!({"layoutVersion": 1, "entrypoint": entrypoint}).to_string(),
)?;
let expected = (entrypoint == name).then(|| CodexPackageLayout {
package_dir: package.clone(),
bin_dir: package.clone(),
resources_dir: Some(package.join(RESOURCES_DIRNAME)),
path_dir: Some(package.join(PATH_DIRNAME)),
});
assert_eq!(CodexPackageLayout::from_exe(executable.as_path()), expected);
}
Ok(())
}
+15
View File
@@ -246,6 +246,21 @@ impl CodexPackageLayout {
fn from_exe(exe_path: &Path) -> Option<Self> {
let canonical_exe = canonical_absolute_path(exe_path)?;
let exe_dir = canonical_exe.parent()?;
// WinGet preserves a target-qualified executable at the package root.
// Only recognize that layout when metadata names this exact executable.
#[cfg(windows)]
if let Ok(contents) = std::fs::read(exe_dir.join(PACKAGE_METADATA_FILENAME))
&& let Ok(metadata) = serde_json::from_slice::<serde_json::Value>(&contents)
&& metadata["layoutVersion"] == 1
&& metadata["entrypoint"].as_str().map(OsStr::new) == canonical_exe.file_name()
{
return Some(Self {
resources_dir: existing_dir(exe_dir.join(RESOURCES_DIRNAME)),
path_dir: existing_dir(exe_dir.join(PATH_DIRNAME)),
package_dir: exe_dir.clone(),
bin_dir: exe_dir,
});
}
match exe_dir.file_name() {
Some(name) if name == OsStr::new(BIN_DIRNAME) => Self::from_package_bin_dir(exe_dir),
Some(name) if name == OsStr::new(RESOURCES_DIRNAME) => {
+2
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",
+17 -3
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);
@@ -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 = [
+4 -3
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),
@@ -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()
);
}
@@ -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
+37
View File
@@ -0,0 +1,37 @@
"""Adapt an extracted signed Windows package to WinGet's existing root filenames."""
import argparse
import json
import shutil
from pathlib import Path
def prepare_winget_package(package: Path) -> None:
metadata_path = package / "codex-package.json"
metadata = json.loads(metadata_path.read_text(encoding="utf-8"))
target = metadata["target"]
if target not in ("x86_64-pc-windows-msvc", "aarch64-pc-windows-msvc"):
raise ValueError("WinGet requires a Windows package")
if metadata["entrypoint"] != "bin/codex.exe":
raise ValueError("WinGet requires the canonical Codex entrypoint")
entrypoint = f"codex-{target}.exe"
manifest_path = package / "codex-resources/voice/manifest.json"
manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
manifest["sha256"][entrypoint] = manifest["sha256"].pop("bin/codex.exe")
(package / "bin/codex.exe").rename(package / entrypoint)
(package / "bin/codex-code-mode-host.exe").rename(
package / "codex-code-mode-host.exe"
)
# Keep resources in place for package-aware discovery and provide the root
# filenames declared by the existing WinGet portable installer manifest.
for helper in ("codex-command-runner.exe", "codex-windows-sandbox-setup.exe"):
shutil.copy2(package / "codex-resources" / helper, package / helper)
metadata["entrypoint"] = entrypoint
for path, value in ((metadata_path, metadata), (manifest_path, manifest)):
path.write_text(json.dumps(value, indent=2) + "\n", encoding="utf-8")
if __name__ == "__main__":
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("package", type=Path)
prepare_winget_package(parser.parse_args().package)
+66
View File
@@ -1,12 +1,15 @@
#!/usr/bin/env python3
from pathlib import Path
import hashlib
import json
import sys
import tempfile
import unittest
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
from build_winget_package import prepare_winget_package
from codex_package.layout import build_package_dir
from codex_package.layout import validate_package_dir
from codex_package.targets import PACKAGE_VARIANTS
@@ -15,6 +18,69 @@ from codex_package.targets import TARGET_SPECS
class PackageLayoutTest(unittest.TestCase):
def test_winget_preserves_signed_files_and_voice_hashes(self) -> None:
for target in ("x86_64-pc-windows-msvc", "aarch64-pc-windows-msvc"):
with self.subTest(target=target), tempfile.TemporaryDirectory() as temp:
package = Path(temp)
files = {
"bin/codex.exe": b"signed CLI",
"bin/codex-code-mode-host.exe": b"signed code mode host",
"codex-resources/codex-command-runner.exe": b"signed runner",
"codex-resources/codex-windows-sandbox-setup.exe": b"signed setup",
"codex-resources/voice/bin/codex-voice-host.exe": b"signed voice host",
"codex-resources/voice/bin/gstreamer-1.0-0.dll": b"signed audio DLL",
"codex-resources/voice/NOTICE.md": b"license notices",
"codex-path/rg.exe": b"ripgrep",
}
for name, contents in files.items():
path = package / name
path.parent.mkdir(parents=True, exist_ok=True)
path.write_bytes(contents)
metadata = {
"layoutVersion": 1,
"target": target,
"entrypoint": "bin/codex.exe",
}
(package / "codex-package.json").write_text(json.dumps(metadata))
manifest = {
"schemaVersion": 1,
"sha256": {
name: hashlib.sha256(contents).hexdigest()
for name, contents in files.items()
if name == "bin/codex.exe"
or name.startswith("codex-resources/voice/")
},
}
manifest_path = package / "codex-resources/voice/manifest.json"
manifest_path.write_text(json.dumps(manifest))
prepare_winget_package(package)
entrypoint = f"codex-{target}.exe"
files[entrypoint] = files.pop("bin/codex.exe")
files["codex-code-mode-host.exe"] = files.pop(
"bin/codex-code-mode-host.exe"
)
for helper in (
"codex-command-runner.exe",
"codex-windows-sandbox-setup.exe",
):
files[helper] = files[f"codex-resources/{helper}"]
actual = {
str(path.relative_to(package)).replace("\\", "/"): path.read_bytes()
for path in package.rglob("*")
if path.is_file()
}
actual_metadata = json.loads(actual.pop("codex-package.json"))
actual_manifest = json.loads(
actual.pop("codex-resources/voice/manifest.json")
)
self.assertEqual(actual, files)
metadata["entrypoint"] = entrypoint
self.assertEqual(actual_metadata, metadata)
manifest["sha256"][entrypoint] = manifest["sha256"].pop("bin/codex.exe")
self.assertEqual(actual_manifest, manifest)
for name, digest in actual_manifest["sha256"].items():
self.assertEqual(hashlib.sha256(actual[name]).hexdigest(), digest)
def test_macos_package_preserves_prebuilt_resource_binaries(self) -> None:
for variant_name in ("codex", "codex-app-server"):
for target in ("aarch64-apple-darwin", "x86_64-apple-darwin"):
+10
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.
+17 -1
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.
+1
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",
+2
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)
+41
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
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
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)