mirror of
https://github.com/openai/codex.git
synced 2026-09-20 12:47:38 +00:00
## Why Linux voice needs system ALSA plugins and enough buffering to accommodate PipeWire graph cycles without losing capture samples. Voice startup failures also need actionable diagnostics without exposing native error details. ## What changed - Build and bundle GNU voice helpers and runtimes with primary Linux musl release archives, and sign the archives. Keep Python wheels free of these libraries to preserve `manylinux_2_17` compatibility. - Discover ALSA plugins in fixed system directories and increase Linux capture and playback buffering to support larger PipeWire graph cycles. - Report voice failures by stage, preserve negotiation timeout classification, and discard native error sources. Suppress the misleading `requested` closure message after failure cleanup. - Add explicit Windows MSVC, pkgconf, and CMake toolchain configuration and preserve host architecture in native build environments. ## Testing Add coverage for Linux release assembly, ALSA plugin discovery, PipeWire capture and playback, classified startup failures, failure cleanup rendering, and Windows build environment handling. GitOrigin-RevId: d805eace96a669ce3a4489f12e2db6f68f9f7f53
176 lines
6.7 KiB
Python
176 lines
6.7 KiB
Python
"""Adapt declared Windows tool paths to the existing native build/runtime recipes."""
|
|
|
|
import hashlib
|
|
import json
|
|
import os
|
|
import re
|
|
import shutil
|
|
import sys
|
|
import tarfile
|
|
import tempfile
|
|
from pathlib import Path
|
|
from types import SimpleNamespace
|
|
|
|
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
|
from build_native import NativeBuild
|
|
from windows_build_inputs import build_environment
|
|
|
|
|
|
def selected_inputs(config, root):
|
|
inputs = dict(config["inputs"])
|
|
tools = {name: (root / path).absolute() for name, path in inputs["tools"].items()}
|
|
manifest = (root / config["manifest"]).absolute()
|
|
declared = {(root / path).absolute() for path in config["installed_files"]}
|
|
if manifest.stat().st_size > 65536:
|
|
raise ValueError("installed Windows tool metadata exceeds limits")
|
|
installed = json.loads(manifest.read_text(encoding="utf-8"))
|
|
expected = {"shell", "make", "cygpath", "automake", "pkg_config"}
|
|
if (
|
|
installed.get("schemaVersion") != 1
|
|
or installed.get("target") != inputs["target"]
|
|
or installed.get("cygwinArchitecture") != "x86_64"
|
|
or set(installed.get("tools", {})) != expected
|
|
):
|
|
raise ValueError("installed Windows tools do not match the selected target")
|
|
for name in expected:
|
|
relative = Path(installed["tools"][name])
|
|
source = (manifest.parent / relative).absolute()
|
|
if (
|
|
relative.is_absolute()
|
|
or ".." in relative.parts
|
|
or source not in declared
|
|
or (name == "pkg_config" and tools.get(name) != source)
|
|
or not source.resolve(strict=True).is_relative_to(
|
|
manifest.parent.resolve(strict=True)
|
|
)
|
|
):
|
|
raise ValueError(f"installed Windows tool selection differs: {name}")
|
|
tools[name] = source
|
|
inputs["tools"] = {name: str(path) for name, path in tools.items()}
|
|
for name in ("INCLUDE", "LIB"):
|
|
inputs[name] = [str((root / path).absolute()) for path in inputs[name]]
|
|
return inputs
|
|
|
|
|
|
def main():
|
|
operation, config_path = sys.argv[1:]
|
|
config = json.loads(Path(config_path).read_text(encoding="utf-8"))
|
|
root = Path.cwd()
|
|
with tempfile.TemporaryDirectory(prefix="voice-windows-") as temporary:
|
|
temporary = Path(temporary)
|
|
document = temporary / "windows-inputs.json"
|
|
inputs = selected_inputs(config, root)
|
|
document.write_text(json.dumps(inputs), encoding="utf-8")
|
|
environment, _ = build_environment(
|
|
document, inputs["target"], {"python": Path(sys.executable)}
|
|
)
|
|
# Keep host identity and scratch directories, never developer tool search paths.
|
|
environment.update(
|
|
{
|
|
name: os.environ[name]
|
|
for name in ("TMP", "TEMP", "PROCESSOR_ARCHITECTURE")
|
|
if name in os.environ
|
|
}
|
|
)
|
|
home = temporary / "home"
|
|
home.mkdir()
|
|
environment.update({"HOME": str(home), "USERPROFILE": str(home)})
|
|
os.environ.clear()
|
|
os.environ.update(environment)
|
|
if operation == "prepare":
|
|
from prepare_built_runtime import prepare_archive
|
|
|
|
output = root / config["output"]
|
|
sdk = root / config["sdk"]
|
|
for path in (output, sdk):
|
|
if path.exists():
|
|
path.rmdir()
|
|
try:
|
|
prepare_archive(
|
|
root / config["prefix"],
|
|
root / config["receipt"],
|
|
root / config["status"],
|
|
inputs["target"],
|
|
output,
|
|
sdk_output=sdk,
|
|
)
|
|
except ValueError as exc:
|
|
if str(exc) != "native build receipt is incomplete or mismatched":
|
|
raise
|
|
receipt = json.loads((root / config["receipt"]).read_text())
|
|
commits = [
|
|
line
|
|
for line in (root / config["status"]).read_text().splitlines()
|
|
if line.startswith("STABLE_GIT_COMMIT ")
|
|
]
|
|
steps = receipt.get("steps", [])
|
|
checks = {
|
|
"commit_count": len(commits) == 1,
|
|
"commit_format": len(commits) == 1
|
|
and bool(
|
|
re.fullmatch(r"STABLE_GIT_COMMIT [0-9a-f]{40}", commits[0])
|
|
),
|
|
"target": receipt.get("target") == inputs["target"],
|
|
"manifest": receipt.get("manifest_sha256")
|
|
== hashlib.sha256(
|
|
Path(__file__).with_name("sources.json").read_bytes()
|
|
).hexdigest(),
|
|
"step_count": 1 <= len(steps) <= 128,
|
|
"step_exit": all(step.get("exit_code") == 0 for step in steps),
|
|
"last_step": bool(steps)
|
|
and steps[-1].get("name") == "gst-plugins-good-install",
|
|
}
|
|
print(
|
|
"Receipt checks failed: "
|
|
+ ", ".join(name for name, valid in checks.items() if not valid),
|
|
file=sys.stderr,
|
|
)
|
|
raise
|
|
return
|
|
if operation != "build":
|
|
raise ValueError("unknown Windows native action")
|
|
archives = temporary / "archives"
|
|
archives.mkdir()
|
|
for path in config["archives"]:
|
|
source = root / path
|
|
shutil.copyfile(source, archives / source.name)
|
|
tools = inputs["tools"]
|
|
builder = NativeBuild(
|
|
SimpleNamespace(
|
|
target=inputs["target"],
|
|
deployment_target=None,
|
|
jobs=8,
|
|
output=temporary / "build",
|
|
archives=archives,
|
|
windows_build_inputs=document,
|
|
**{
|
|
name: Path(tools[name])
|
|
for name in (
|
|
"cc",
|
|
"cxx",
|
|
"cmake",
|
|
"make",
|
|
"pkg_config",
|
|
"shell",
|
|
"bootstrap_make",
|
|
)
|
|
},
|
|
),
|
|
environment,
|
|
)
|
|
try:
|
|
builder.build()
|
|
except Exception:
|
|
if builder.record["steps"]:
|
|
log = builder.output / (builder.record["steps"][-1]["name"] + ".log")
|
|
if log.is_file():
|
|
print(log.read_text(errors="replace"), file=sys.stderr)
|
|
raise
|
|
with tarfile.open(root / config["prefix"], "w") as archive:
|
|
archive.add(builder.prefix, arcname=".")
|
|
shutil.copyfile(builder.output / "built.json", root / config["receipt"])
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|