mirror of
https://github.com/openai/codex.git
synced 2026-10-19 16:17:49 +00:00
Add receipt-verified native voice SDK export (#43099)
## What changed Add `third_party/voice/sdk.py` to export headers, development libraries, and selected pkg-config metadata from an inspected native build. Verify shared libraries against inspection receipts and record the target, source commit, source manifest hash, and exported file hashes in `sdk.json`. Enable relocatable Meson pkg-config metadata and materialize development library aliases. Reject mismatched receipts, paths escaping the build prefix, nonrelocatable metadata, and unsafe output locations; remove partial output on copy failure. Document SDK consumption as build inputs separate from packaged runtime files. ## Testing Add tests for exported hashes and pkg-config resolution after moving the SDK and deleting the original prefix, alias materialization, invalid inputs, and cleanup after copy failure. GitOrigin-RevId: 6d1575e659a02cd1f56e465e7830a296e8eaf486
This commit is contained in:
committed by
copyberry
parent
64e9a68987
commit
5d35805715
Vendored
+1
@@ -39,6 +39,7 @@ filegroup(
|
||||
"macos_runtime.py",
|
||||
"package_runtime.py",
|
||||
"runtime.py",
|
||||
"sdk.py",
|
||||
"windows_runtime.py",
|
||||
":source_inputs",
|
||||
],
|
||||
|
||||
Vendored
+18
@@ -50,6 +50,24 @@ a shared Opus build with Rust consumers or a reduced dependency count.
|
||||
Rust `opus` 0.4.0 is available through Socket. Adding Rust transport dependencies
|
||||
and establishing a shared Opus build remain separate integration work.
|
||||
|
||||
## Native development inputs
|
||||
|
||||
The private native CI job also emits `sdk.tar.gz` from the same inspected prefix.
|
||||
It contains headers (including the target's GLib configuration), development
|
||||
library names, import/static libraries and pkg-config metadata. `sdk.json`
|
||||
records the target, source commit, pinned manifest and every exported file hash.
|
||||
Shared-library bytes must match the native inspection receipt; other development
|
||||
files are hashed during export. This is provenance, not an authenticity check.
|
||||
|
||||
Meson generates relocatable pkg-config metadata using its standard option.
|
||||
Consumers must restrict `PKG_CONFIG_LIBDIR` to the SDK, clear `PKG_CONFIG_PATH`,
|
||||
and use `pkg-config --define-prefix` for libffi/PCRE2/zlib metadata too. Only the
|
||||
required native metadata is exported; capture Opus uses `opusic-sys`. Native
|
||||
library loader paths are not changed by SDK export. These build inputs do not replace the
|
||||
separate runtime projection and are never copied into users' Codex packages.
|
||||
Cargo/Bazel native providers, final helper linkage and moved-package execution
|
||||
remain separate integration work; exporting an SDK does not enable voice.
|
||||
|
||||
## Native build recipe
|
||||
|
||||
`build_native.py` runs the unmodified upstream build systems in a new output
|
||||
|
||||
Vendored
+1
@@ -248,6 +248,7 @@ class NativeBuild:
|
||||
"--wrap-mode=nofallback",
|
||||
"-Dauto_features=disabled",
|
||||
"-Ddefault_library=shared",
|
||||
"-Dpkgconfig.relocatable=true",
|
||||
*options,
|
||||
],
|
||||
)
|
||||
|
||||
Vendored
+140
@@ -0,0 +1,140 @@
|
||||
"""Export development inputs from the same inspected build as the private runtime.
|
||||
|
||||
SDKs are build inputs, never installed user payloads. Their receipt records byte
|
||||
identity and provenance, not authenticity or approval. Native loader relocation
|
||||
and final helper linking remain the runtime preparer and consumer's jobs.
|
||||
"""
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
import re
|
||||
import shutil
|
||||
|
||||
from runtime import digest
|
||||
|
||||
MODULES = (
|
||||
"glib-2.0",
|
||||
"gobject-2.0",
|
||||
"gmodule-no-export-2.0",
|
||||
"gstreamer-1.0",
|
||||
"gstreamer-base-1.0",
|
||||
"gstreamer-app-1.0",
|
||||
"gstreamer-audio-1.0",
|
||||
"gstreamer-tag-1.0",
|
||||
)
|
||||
|
||||
|
||||
def export_sdk(prefix: Path, receipts: Path, target: str, output: Path):
|
||||
prefix, receipts = prefix.resolve(strict=True), receipts.resolve(strict=True)
|
||||
output = output.absolute()
|
||||
if (
|
||||
output.exists()
|
||||
or output.is_symlink()
|
||||
or any(output.resolve().is_relative_to(root) for root in (prefix, receipts))
|
||||
):
|
||||
raise ValueError("SDK output must be fresh and outside the inputs")
|
||||
if not re.fullmatch(
|
||||
r"(?:aarch64|x86_64)-(?:apple-darwin|unknown-linux-gnu|pc-windows-msvc)",
|
||||
target,
|
||||
):
|
||||
raise ValueError("unsupported SDK target")
|
||||
ci = json.loads((receipts / "ci.json").read_text())
|
||||
source_hash = digest(Path(__file__).with_name("sources.json"))
|
||||
if (
|
||||
ci.get("target") != target
|
||||
or ci.get("build_complete") is not True
|
||||
or ci.get("inspection_complete") is not True
|
||||
or ci.get("manifest_sha256") != source_hash
|
||||
or not re.fullmatch(r"[0-9a-f]{40}", ci.get("commit", ""))
|
||||
):
|
||||
raise ValueError("SDK build receipt does not match the sources and target")
|
||||
inventory = json.loads((receipts / "inspection/binaries.json").read_text())
|
||||
if not 1 <= len(inventory) <= 128:
|
||||
raise ValueError("unexpected native inventory size")
|
||||
binaries = {}
|
||||
for record in inventory:
|
||||
name = record["path"].replace("\\", "/")
|
||||
if not re.fullmatch(r"(?:lib(?:/gstreamer-1\.0)?|bin)/[A-Za-z0-9_+.-]+", name):
|
||||
raise ValueError("invalid native inventory path")
|
||||
path = prefix / name
|
||||
if (
|
||||
name in binaries
|
||||
or path.is_symlink()
|
||||
or not path.resolve(strict=True).is_relative_to(prefix)
|
||||
or record["target"] != target
|
||||
or digest(path) != record["sha256"]
|
||||
):
|
||||
raise ValueError("native SDK library does not match its receipt")
|
||||
binaries[name] = record["sha256"]
|
||||
selected = []
|
||||
names = set()
|
||||
size = 0
|
||||
for path in sorted(prefix.rglob("*")):
|
||||
name = path.relative_to(prefix).as_posix()
|
||||
if path.is_dir():
|
||||
if path.is_symlink():
|
||||
raise ValueError("SDK input directories must not be links")
|
||||
continue
|
||||
if not (
|
||||
name.startswith("include/")
|
||||
or name == "lib/glib-2.0/include/glibconfig.h"
|
||||
or name
|
||||
in {
|
||||
f"lib/pkgconfig/{module}.pc"
|
||||
for module in (*MODULES, "libffi", "libpcre2-8", "zlib")
|
||||
}
|
||||
or re.fullmatch(
|
||||
r"lib/[A-Za-z0-9_+.-]+\.(?:a|lib|dylib|so(?:\.[0-9]+)*)", name
|
||||
)
|
||||
):
|
||||
continue
|
||||
source = path.resolve(strict=True)
|
||||
if not source.is_relative_to(prefix) or not source.is_file():
|
||||
raise ValueError("SDK inputs must be files inside the prefix")
|
||||
if name.casefold() in names:
|
||||
raise ValueError("SDK paths collide across platforms")
|
||||
names.add(name.casefold())
|
||||
size += source.stat().st_size
|
||||
if len(selected) >= 4096 or size > 512 * 1024 * 1024:
|
||||
raise ValueError("SDK inputs exceed the size limit")
|
||||
source_name = source.relative_to(prefix).as_posix()
|
||||
expected = digest(source)
|
||||
if re.search(r"\.(?:dylib|so(?:\.[0-9]+)*)$", name):
|
||||
if binaries.get(source_name) != expected:
|
||||
raise ValueError("SDK shared library is missing from the receipt")
|
||||
selected.append((name, source, expected))
|
||||
for module in MODULES:
|
||||
path = prefix / f"lib/pkgconfig/{module}.pc"
|
||||
if path.relative_to(prefix).as_posix().casefold() not in names or not re.search(
|
||||
r"^prefix=\$\{pcfiledir\}/\.\./\.\.$", path.read_text(), re.MULTILINE
|
||||
):
|
||||
raise ValueError("rebuild the SDK with relocatable Meson pkg-config files")
|
||||
if "lib/glib-2.0/include/glibconfig.h" not in names:
|
||||
raise ValueError("SDK is missing the target's GLib configuration header")
|
||||
output.mkdir()
|
||||
try:
|
||||
files = []
|
||||
for name, source, expected in selected:
|
||||
destination = output / name
|
||||
destination.parent.mkdir(parents=True, exist_ok=True)
|
||||
shutil.copy2(source, destination)
|
||||
if digest(destination) != expected:
|
||||
raise ValueError("SDK input changed while copying")
|
||||
files.append({"path": name, "sha256": expected})
|
||||
(output / "sdk.json").write_text(
|
||||
json.dumps(
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"target": target,
|
||||
"sourceCommit": ci["commit"],
|
||||
"sourceManifestSha256": source_hash,
|
||||
"files": files,
|
||||
},
|
||||
indent=2,
|
||||
)
|
||||
+ "\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
except BaseException:
|
||||
shutil.rmtree(output)
|
||||
raise
|
||||
Vendored
+153
@@ -0,0 +1,153 @@
|
||||
"""Exercise exported bytes, development aliases and moved pkg-config inputs."""
|
||||
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import shlex
|
||||
import shutil
|
||||
import subprocess
|
||||
import tempfile
|
||||
import unittest
|
||||
from unittest.mock import patch
|
||||
|
||||
from runtime import digest
|
||||
from sdk import MODULES, export_sdk
|
||||
|
||||
|
||||
class SdkTests(unittest.TestCase):
|
||||
def setUp(self):
|
||||
temporary = tempfile.TemporaryDirectory(prefix="voice SDK ")
|
||||
self.addCleanup(temporary.cleanup)
|
||||
self.root = Path(temporary.name)
|
||||
self.prefix = self.root / "prefix"
|
||||
self.receipts = self.root / "receipts"
|
||||
self.output = self.root / "sdk"
|
||||
self.target = "aarch64-apple-darwin"
|
||||
for name in ("include/glib-2.0", "lib/glib-2.0/include", "lib/pkgconfig"):
|
||||
(self.prefix / name).mkdir(parents=True)
|
||||
(self.prefix / "include/glib-2.0/glib.h").write_text("/* SDK fixture */")
|
||||
(self.prefix / "lib/glib-2.0/include/glibconfig.h").write_text("/* target */")
|
||||
self.library = self.prefix / "lib/libfixture.0.dylib"
|
||||
self.library.write_bytes(b"receipt-verified fixture bytes")
|
||||
for module in (*MODULES, "zlib"):
|
||||
(self.prefix / f"lib/pkgconfig/{module}.pc").write_text(
|
||||
"prefix=${pcfiledir}/../..\n"
|
||||
f"Name: {module}\nDescription: SDK fixture\nVersion: 1.0\n"
|
||||
"Cflags: -I${prefix}/include\nLibs: -L${prefix}/lib -lfixture\n"
|
||||
)
|
||||
audio = self.prefix / "lib/pkgconfig/gstreamer-audio-1.0.pc"
|
||||
audio.write_text(
|
||||
audio.read_text() + "Requires.private: gstreamer-tag-1.0, zlib\n"
|
||||
)
|
||||
(self.receipts / "inspection").mkdir(parents=True)
|
||||
self.ci = {
|
||||
"target": self.target,
|
||||
"build_complete": True,
|
||||
"inspection_complete": True,
|
||||
"manifest_sha256": digest(Path(__file__).with_name("sources.json")),
|
||||
"commit": "a" * 40,
|
||||
}
|
||||
(self.receipts / "ci.json").write_text(json.dumps(self.ci))
|
||||
(self.receipts / "inspection/binaries.json").write_text(
|
||||
json.dumps(
|
||||
[
|
||||
{
|
||||
"path": "lib/libfixture.0.dylib",
|
||||
"target": self.target,
|
||||
"sha256": digest(self.library),
|
||||
}
|
||||
]
|
||||
)
|
||||
)
|
||||
|
||||
def test_exported_bytes_and_receipt_survive_move_without_original(self):
|
||||
export_sdk(self.prefix, self.receipts, self.target, self.output)
|
||||
moved = self.root / "moved SDK"
|
||||
self.output.rename(moved)
|
||||
shutil.rmtree(self.prefix)
|
||||
manifest = json.loads((moved / "sdk.json").read_text())
|
||||
actual = {
|
||||
path.relative_to(moved).as_posix(): digest(path)
|
||||
for path in moved.rglob("*")
|
||||
if path.is_file() and path.name != "sdk.json"
|
||||
}
|
||||
self.assertEqual(
|
||||
manifest,
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"target": self.target,
|
||||
"sourceCommit": "a" * 40,
|
||||
"sourceManifestSha256": self.ci["manifest_sha256"],
|
||||
"files": [
|
||||
{"path": name, "sha256": actual[name]} for name in sorted(actual)
|
||||
],
|
||||
},
|
||||
)
|
||||
pkg_config = os.environ.get("VOICE_PKG_CONFIG") or shutil.which("pkg-config")
|
||||
if not pkg_config:
|
||||
self.skipTest("pkg-config required for the moved development-input probe")
|
||||
result = subprocess.check_output(
|
||||
[
|
||||
pkg_config,
|
||||
"--define-prefix",
|
||||
"--cflags",
|
||||
"--libs",
|
||||
"gstreamer-audio-1.0",
|
||||
],
|
||||
env={
|
||||
**os.environ,
|
||||
"PKG_CONFIG_PATH": "",
|
||||
"PKG_CONFIG_LIBDIR": str(moved / "lib/pkgconfig"),
|
||||
},
|
||||
text=True,
|
||||
).strip()
|
||||
self.assertEqual(
|
||||
shlex.split(result),
|
||||
[f"-I{moved.as_posix()}/include", f"-L{moved.as_posix()}/lib", "-lfixture"],
|
||||
)
|
||||
|
||||
def test_native_mutation_and_wrong_target_are_rejected(self):
|
||||
with self.assertRaisesRegex(ValueError, "sources and target"):
|
||||
export_sdk(self.prefix, self.receipts, "x86_64-apple-darwin", self.output)
|
||||
self.library.write_bytes(b"changed")
|
||||
with self.assertRaisesRegex(ValueError, "does not match"):
|
||||
export_sdk(self.prefix, self.receipts, self.target, self.output)
|
||||
self.assertFalse(self.output.exists())
|
||||
|
||||
def test_development_alias_is_materialized_and_escape_is_rejected(self):
|
||||
alias = self.prefix / "lib/libfixture.dylib"
|
||||
try:
|
||||
alias.symlink_to(self.library.name)
|
||||
except OSError:
|
||||
self.skipTest("host cannot create the SDK's development symlink")
|
||||
export_sdk(self.prefix, self.receipts, self.target, self.output)
|
||||
self.assertFalse((self.output / "lib/libfixture.dylib").is_symlink())
|
||||
self.assertEqual(
|
||||
(self.output / "lib/libfixture.dylib").read_bytes(),
|
||||
self.library.read_bytes(),
|
||||
)
|
||||
shutil.rmtree(self.output)
|
||||
alias.unlink()
|
||||
alias.symlink_to(self.receipts / "ci.json")
|
||||
with self.assertRaisesRegex(ValueError, "inside the prefix"):
|
||||
export_sdk(self.prefix, self.receipts, self.target, self.output)
|
||||
|
||||
def test_nonrelocatable_metadata_and_failed_copy_leave_no_output(self):
|
||||
metadata = self.prefix / "lib/pkgconfig/gstreamer-1.0.pc"
|
||||
original = metadata.read_text()
|
||||
metadata.write_text(original.replace("${pcfiledir}/../..", "/old/build"))
|
||||
with self.assertRaisesRegex(ValueError, "rebuild the SDK"):
|
||||
export_sdk(self.prefix, self.receipts, self.target, self.output)
|
||||
metadata.write_text(original)
|
||||
with patch("sdk.shutil.copy2", side_effect=OSError("copy failed")):
|
||||
with self.assertRaises(OSError):
|
||||
export_sdk(self.prefix, self.receipts, self.target, self.output)
|
||||
self.assertFalse(self.output.exists())
|
||||
|
||||
def test_output_cannot_overwrite_or_nest_inside_inputs(self):
|
||||
for output in (self.prefix, self.prefix / "sdk", self.receipts / "sdk"):
|
||||
with (
|
||||
self.subTest(output=output),
|
||||
self.assertRaisesRegex(ValueError, "fresh"),
|
||||
):
|
||||
export_sdk(self.prefix, self.receipts, self.target, output)
|
||||
Reference in New Issue
Block a user