mirror of
https://github.com/openai/codex.git
synced 2026-11-05 20:10:33 +00:00
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:
committed by
copyberry
parent
3f59eb965a
commit
ce7fbb373b
@@ -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)" \
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
@@ -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)
|
||||
@@ -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"}
|
||||
]
|
||||
}
|
||||
@@ -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)
|
||||
@@ -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())
|
||||
@@ -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")
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
Generated
+2
@@ -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\"]}}",
|
||||
|
||||
Generated
+31
@@ -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"
|
||||
|
||||
@@ -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}"),
|
||||
}
|
||||
}
|
||||
@@ -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",
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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 = [
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
@@ -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"):
|
||||
|
||||
Vendored
+10
@@ -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.
|
||||
|
||||
Vendored
+17
-1
@@ -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
@@ -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",
|
||||
|
||||
Vendored
+2
@@ -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
@@ -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 = [
|
||||
|
||||
Vendored
+18
@@ -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"
|
||||
}
|
||||
}
|
||||
Vendored
+83
@@ -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)
|
||||
Reference in New Issue
Block a user