Stage publishable Python runtime wheels (#18865)

This is PR 2 of the Python SDK PyPI publishing split. [PR
1](https://github.com/openai/codex/pull/18862) refreshed the generated
SDK bindings; this PR makes the runtime package itself publishable, and
PR 3 will wire the SDK package/version pinning to this runtime package.

## Summary
- Rename the runtime distribution to `openai-codex-cli-bin` while
keeping the import package as `codex_cli_bin`.
- Make the runtime package wheel-only and build `py3-none-<platform>`
wheels instead of interpreter-specific wheels.
- Add `stage-runtime --codex-version` and `--platform-tag` so release
staging can produce the platform wheel matrix from Codex release tags.
- Add focused artifact workflow tests for version normalization,
platform tag injection, and runtime wheel metadata.

## Why Rename
There is already an unofficial PyPI package,
[`codex-bin`](https://pypi.org/project/codex-bin/), distributing OpenAI
Codex binaries. Publishing the official SDK runtime dependency as
`openai-codex-cli-bin` makes the ownership clear, avoids confusing the
SDK-pinned runtime wheel with that unowned wrapper, and keeps the import
package unchanged as `codex_cli_bin`.

## Tests
- `uv run --extra dev pytest
tests/test_artifact_workflow_and_binaries.py` -> 21 passed
- `uv run --extra dev python scripts/update_sdk_artifacts.py
stage-runtime /tmp/codex-python-pr2-rebased/runtime-stage
/tmp/codex-python-pr2-rebased/codex --codex-version
rust-v0.116.0-alpha.1 --platform-tag macosx_11_0_arm64`
- `uv run --with build --extra dev python -m build --wheel
/tmp/codex-python-pr2-rebased/runtime-stage`
- `uv run --with twine --extra dev twine check
/tmp/codex-python-pr2-rebased/runtime-stage/dist/openai_codex_cli_bin-0.116.0a1-py3-none-macosx_11_0_arm64.whl`

## Note
- Full `uv run --extra dev pytest` currently fails because regenerating
from schemas already on `main` adds new DeviceKey Python types. I left
that generated catch-up out of this runtime-only PR.
This commit is contained in:
Steve Coffey
2026-04-22 08:14:48 -07:00
committed by GitHub
parent 0ebe69a8c3
commit 0127cef5db
12 changed files with 228 additions and 54 deletions

View File

@@ -168,6 +168,19 @@ def test_examples_readme_matches_pinned_runtime_version() -> None:
)
def test_runtime_distribution_name_is_consistent() -> None:
script = _load_update_script_module()
runtime_setup = _load_runtime_setup_module()
from codex_app_server import client as client_module
assert script.RUNTIME_DISTRIBUTION_NAME == "openai-codex-cli-bin"
assert runtime_setup.PACKAGE_NAME == "openai-codex-cli-bin"
assert client_module.RUNTIME_PKG_NAME == "openai-codex-cli-bin"
assert "importlib.metadata.version('codex-cli-bin')" not in (
ROOT / "_runtime_setup.py"
).read_text()
def test_release_metadata_retries_without_invalid_auth(monkeypatch: pytest.MonkeyPatch) -> None:
runtime_setup = _load_runtime_setup_module()
authorizations: list[str | None] = []
@@ -222,19 +235,24 @@ def test_runtime_package_is_wheel_only_and_builds_platform_specific_wheels() ->
),
None,
)
build_data_assignments = {
node.targets[0].slice.value: node.value.value
for node in initialize_fn.body
if isinstance(node, ast.Assign)
and len(node.targets) == 1
and isinstance(node.targets[0], ast.Subscript)
and isinstance(node.targets[0].value, ast.Name)
and node.targets[0].value.id == "build_data"
and isinstance(node.targets[0].slice, ast.Constant)
and isinstance(node.targets[0].slice.value, str)
and isinstance(node.value, ast.Constant)
}
build_data_assignments = {}
for node in initialize_fn.body:
if (
not isinstance(node, ast.Assign)
or len(node.targets) != 1
or not isinstance(node.targets[0], ast.Subscript)
or not isinstance(node.targets[0].value, ast.Name)
or node.targets[0].value.id != "build_data"
or not isinstance(node.targets[0].slice, ast.Constant)
or not isinstance(node.targets[0].slice.value, str)
):
continue
if isinstance(node.value, ast.Constant):
build_data_assignments[node.targets[0].slice.value] = node.value.value
elif isinstance(node.value, ast.JoinedStr):
build_data_assignments[node.targets[0].slice.value] = "joined-string"
assert pyproject["project"]["name"] == "openai-codex-cli-bin"
assert pyproject["tool"]["hatch"]["build"]["targets"]["wheel"] == {
"packages": ["src/codex_cli_bin"],
"include": ["src/codex_cli_bin/bin/**"],
@@ -244,7 +262,11 @@ def test_runtime_package_is_wheel_only_and_builds_platform_specific_wheels() ->
"hooks": {"custom": {}},
}
assert sdist_guard is not None
assert build_data_assignments == {"pure_python": False, "infer_tag": True}
assert build_data_assignments == {
"pure_python": False,
"infer_tag": False,
"tag": "joined-string",
}
def test_stage_runtime_release_copies_binary_and_sets_version(tmp_path: Path) -> None:
@@ -260,9 +282,19 @@ def test_stage_runtime_release_copies_binary_and_sets_version(tmp_path: Path) ->
assert staged == tmp_path / "runtime-stage"
assert script.staged_runtime_bin_path(staged).read_text() == "fake codex\n"
assert 'name = "openai-codex-cli-bin"' in (staged / "pyproject.toml").read_text()
assert 'version = "1.2.3"' in (staged / "pyproject.toml").read_text()
def test_normalize_codex_version_accepts_release_tags_and_pep440_versions() -> None:
script = _load_update_script_module()
assert script.normalize_codex_version("rust-v0.116.0-alpha.1") == "0.116.0a1"
assert script.normalize_codex_version("v0.116.0-beta.2") == "0.116.0b2"
assert script.normalize_codex_version("0.116.0rc3") == "0.116.0rc3"
assert script.normalize_codex_version("0.116.0") == "0.116.0"
def test_stage_runtime_release_replaces_existing_staging_dir(tmp_path: Path) -> None:
script = _load_update_script_module()
staging_dir = tmp_path / "runtime-stage"
@@ -284,13 +316,30 @@ def test_stage_runtime_release_replaces_existing_staging_dir(tmp_path: Path) ->
assert script.staged_runtime_bin_path(staged).read_text() == "fake codex\n"
def test_stage_runtime_release_can_pin_wheel_platform_tag(tmp_path: Path) -> None:
script = _load_update_script_module()
fake_binary = tmp_path / script.runtime_binary_name()
fake_binary.write_text("fake codex\n")
staged = script.stage_python_runtime_package(
tmp_path / "runtime-stage",
"0.116.0a1",
fake_binary,
platform_tag="musllinux_1_1_x86_64",
)
pyproject = (staged / "pyproject.toml").read_text()
assert 'platform-tag = "musllinux_1_1_x86_64"' in pyproject
def test_stage_sdk_release_injects_exact_runtime_pin(tmp_path: Path) -> None:
script = _load_update_script_module()
staged = script.stage_python_sdk_package(tmp_path / "sdk-stage", "0.2.1", "1.2.3")
pyproject = (staged / "pyproject.toml").read_text()
assert 'version = "0.2.1"' in pyproject
assert '"codex-cli-bin==1.2.3"' in pyproject
assert '"openai-codex-cli-bin==1.2.3"' in pyproject
assert '"codex-cli-bin==1.2.3"' not in pyproject
assert not any((staged / "src" / "codex_app_server").glob("bin/**"))
@@ -329,7 +378,10 @@ def test_stage_sdk_runs_type_generation_before_staging(tmp_path: Path) -> None:
return tmp_path / "sdk-stage"
def fake_stage_runtime_package(
_staging_dir: Path, _runtime_version: str, _runtime_binary: Path
_staging_dir: Path,
_runtime_version: str,
_runtime_binary: Path,
_platform_tag: str | None,
) -> Path:
raise AssertionError("runtime staging should not run for stage-sdk")
@@ -358,8 +410,10 @@ def test_stage_runtime_stages_binary_without_type_generation(tmp_path: Path) ->
"stage-runtime",
str(tmp_path / "runtime-stage"),
str(fake_binary),
"--runtime-version",
"1.2.3",
"--codex-version",
"rust-v0.116.0-alpha.1",
"--platform-tag",
"musllinux_1_1_x86_64",
]
)
@@ -372,9 +426,12 @@ def test_stage_runtime_stages_binary_without_type_generation(tmp_path: Path) ->
raise AssertionError("sdk staging should not run for stage-runtime")
def fake_stage_runtime_package(
_staging_dir: Path, _runtime_version: str, _runtime_binary: Path
_staging_dir: Path,
codex_version: str,
_runtime_binary: Path,
platform_tag: str | None,
) -> Path:
calls.append("stage_runtime")
calls.append(f"stage_runtime:{codex_version}:{platform_tag}")
return tmp_path / "runtime-stage"
def fake_current_sdk_version() -> str:
@@ -389,7 +446,7 @@ def test_stage_runtime_stages_binary_without_type_generation(tmp_path: Path) ->
script.run_command(args, ops)
assert calls == ["stage_runtime"]
assert calls == ["stage_runtime:0.116.0a1:musllinux_1_1_x86_64"]
def test_default_runtime_is_resolved_from_installed_runtime_package(