Stage Python runtime wheels directly from package directories (#45526)

## Why

The Windows release workflow builds an extra package archive only to extract it again when staging the Python runtime wheel. Reuse the package directory to avoid this round trip.

## What changed

- Allow `stage-runtime` to accept a Codex package directory as well as a `.tar.gz` archive, with the same package layout validation.
- Reject overlapping source and staging directories, symlinks, and non-regular directory entries before staging.
- Stage Windows runtime wheels from the existing package directory and retain the check that voice resources are absent.

## Testing

Add coverage for matching directory and archive output, including file permissions, source preservation, invalid layouts, non-regular entries, overlapping directories, and CLI handling of both source formats.

GitOrigin-RevId: bf6d1e05888367d482d8d51a4a8061f92a9cae8b
This commit is contained in:
Charlie Marsh
2026-09-14 21:58:11 +00:00
committed by copyberry
parent 60e35765c3
commit b0d95427c2
3 changed files with 100 additions and 23 deletions

View File

@@ -188,9 +188,18 @@ def stage_python_sdk_package(
def stage_python_runtime_package(
staging_dir: Path,
codex_version: str,
package_archive: Path,
package_source: Path,
platform_tag: str | None = None,
) -> Path:
if package_source.is_dir():
source = package_source.resolve()
destination = staging_dir.resolve()
if source.is_relative_to(destination) or destination.is_relative_to(source):
raise RuntimeError("Codex package and runtime staging directories must not overlap")
for path in package_source.rglob("*"):
if path.is_symlink() or not (path.is_file() or path.is_dir()):
raise RuntimeError(f"Expected a regular Codex package entry: {path}")
package_version = normalize_codex_version(codex_version)
_copy_package_tree(python_runtime_root(), staging_dir)
@@ -202,7 +211,12 @@ def stage_python_runtime_package(
pyproject_text = _rewrite_runtime_platform_tag(pyproject_text, platform_tag)
pyproject_path.write_text(pyproject_text)
_extract_codex_package_archive(package_archive, staged_runtime_package_root(staging_dir))
runtime_package_root = staged_runtime_package_root(staging_dir)
if package_source.is_dir():
shutil.copytree(package_source, runtime_package_root, dirs_exist_ok=True)
_validate_codex_package_layout(runtime_package_root, package_source)
else:
_extract_codex_package_archive(package_source, runtime_package_root)
return staging_dir
@@ -220,7 +234,7 @@ def _extract_codex_package_archive(package_archive: Path, runtime_package_root:
_validate_codex_package_layout(runtime_package_root, package_archive)
def _validate_codex_package_layout(package_dir: Path, package_archive: Path) -> None:
def _validate_codex_package_layout(package_dir: Path, package_source: Path) -> None:
missing_entries = []
if not (package_dir / CODEX_PACKAGE_METADATA).is_file():
missing_entries.append(CODEX_PACKAGE_METADATA)
@@ -235,7 +249,7 @@ def _validate_codex_package_layout(package_dir: Path, package_archive: Path) ->
missing_entries.append(str(Path("bin") / runtime_code_mode_host_name()))
if missing_entries:
missing = ", ".join(missing_entries)
raise RuntimeError(f"Missing Codex package layout entries in {package_archive}: {missing}")
raise RuntimeError(f"Missing Codex package layout entries in {package_source}: {missing}")
def _flatten_string_enum_one_of(definition: dict[str, Any]) -> bool:
@@ -1405,9 +1419,9 @@ def build_parser() -> argparse.ArgumentParser:
help="Output directory for the staged runtime package",
)
stage_runtime_parser.add_argument(
"package_archive",
"package_source",
type=Path,
help="Path to a Codex package .tar.gz archive for this platform.",
help="Path to a Codex package directory or .tar.gz archive for this platform.",
)
stage_runtime_parser.add_argument(
"--codex-version",
@@ -1462,7 +1476,7 @@ def run_command(args: argparse.Namespace, ops: CliOps) -> None:
ops.stage_python_runtime_package(
args.staging_dir,
normalize_codex_version(args.codex_version),
args.package_archive.resolve(),
args.package_source.resolve(),
args.platform_tag,
)