mirror of
https://github.com/openai/codex.git
synced 2026-10-19 16:17:49 +00:00
Strip symbols from source-built release package binaries (#52178)
## What changed On Linux and macOS, strip the packaged copies of source-built release entrypoint and `codex-code-mode-host` binaries while retaining symbols in the original build outputs. Add `--strip auto|all|none` and `--strip-tool`. The default `auto` preserves prebuilt inputs and other build profiles; `all` also strips those package copies, and `none` disables stripping. Select target-compatible strip tools and fail on missing tools or strip errors. Leave third-party resources unchanged. ## Testing Add a regression test that compiles an executable with debug information, verifies stripped package binaries are smaller and still runnable, and checks that original inputs, preserved package copies, and third-party resources remain byte-for-byte unchanged. GitOrigin-RevId: dc363c04690aa9e772690417a52a8eff31650278
This commit is contained in:
@@ -64,6 +64,24 @@ binary instead of rebuilding it.
|
||||
Release jobs should likewise pass `--code-mode-host-bin` so the package contains
|
||||
the signed host executable beside the signed entrypoint.
|
||||
|
||||
On Linux and macOS, the builder strips symbols from the **package copies** of
|
||||
source-built release-profile entrypoint and code-mode host binaries. Cargo outputs
|
||||
retain their symbols for debugging. The default `--strip auto` also preserves
|
||||
development/profiling builds and prebuilt inputs
|
||||
byte-for-byte, including signatures. Use `--strip all` to also strip prebuilt
|
||||
entrypoint/host copies, or `--strip none` to keep symbols in every package copy.
|
||||
Strip before production signing; `--strip all` is not appropriate for inputs
|
||||
whose release signatures must be preserved. Windows MSVC symbols are separate
|
||||
PDB files and are not included in the package.
|
||||
|
||||
macOS uses `xcrun strip -S -x`, matching the release pipeline and preserving
|
||||
executable ad-hoc signatures. Linux uses `llvm-strip`, a target-prefixed GNU
|
||||
strip, or native GNU strip when the host architecture matches. For cross builds,
|
||||
install `llvm-strip` or pass `--strip-tool /path/to/target-strip`. Missing tools
|
||||
or strip failures fail the build; use `--strip none` to deliberately opt out.
|
||||
Third-party resources are copied unchanged. In particular, never strip `bwrap`
|
||||
after its integrity digest has been embedded in Codex.
|
||||
|
||||
Release jobs that already built package resource binaries should also pass the
|
||||
corresponding resource flags: `--bwrap-bin` for Linux packages, and
|
||||
`--codex-command-runner-bin` plus `--codex-windows-sandbox-setup-bin` for
|
||||
|
||||
@@ -11,6 +11,7 @@ from .layout import build_package_dir
|
||||
from .layout import prepare_package_dir
|
||||
from .layout import validate_package_dir
|
||||
from .ripgrep import resolve_rg_bin
|
||||
from .symbols import strip_binary
|
||||
from .targets import PACKAGE_VARIANTS
|
||||
from .targets import TARGET_SPECS
|
||||
from .targets import PackageInputs
|
||||
@@ -109,6 +110,21 @@ def create_parser() -> argparse.ArgumentParser:
|
||||
"Cargo profile for source-built package artifacts. Use release for release packages."
|
||||
),
|
||||
)
|
||||
parser.add_argument(
|
||||
"--strip",
|
||||
choices=("auto", "all", "none"),
|
||||
default="auto",
|
||||
help=(
|
||||
"Strip Unix first-party package binaries: auto strips source-built "
|
||||
"release-profile inputs, all also strips other profiles and prebuilt "
|
||||
"inputs, none preserves symbols. "
|
||||
"Original inputs and third-party resources are never modified."
|
||||
),
|
||||
)
|
||||
parser.add_argument(
|
||||
"--strip-tool",
|
||||
help="Target-compatible strip executable override, for example llvm-strip.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--entrypoint-bin",
|
||||
type=Path,
|
||||
@@ -236,6 +252,16 @@ def assemble_package(args: argparse.Namespace) -> Path:
|
||||
)
|
||||
prepare_package_dir(package_dir, force=args.force)
|
||||
build_package_dir(package_dir, args.package_version, variant, spec, inputs)
|
||||
for filename, prebuilt in (
|
||||
(variant.entrypoint_name(spec), args.entrypoint_bin),
|
||||
(f"codex-code-mode-host{spec.exe_suffix}", args.code_mode_host_bin),
|
||||
):
|
||||
if args.strip == "all" or (
|
||||
args.strip == "auto"
|
||||
and args.cargo_profile == "release"
|
||||
and prebuilt is None
|
||||
):
|
||||
strip_binary(package_dir / "bin" / filename, spec, tool=args.strip_tool)
|
||||
validate_package_dir(
|
||||
package_dir, variant, spec, include_zsh=inputs.zsh_bin is not None
|
||||
)
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
"""Strip packaged Unix executables without modifying their build outputs.
|
||||
|
||||
Call only on package copies of first-party binaries. Prebuilt release inputs
|
||||
may already be signed, and resources such as bwrap have integrity hashes that
|
||||
must survive staging. Windows MSVC symbols live in separate PDB files.
|
||||
"""
|
||||
|
||||
import platform
|
||||
import shutil
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
from .targets import TargetSpec
|
||||
from .targets import normalize_machine
|
||||
|
||||
|
||||
def strip_binary(binary: Path, spec: TargetSpec, *, tool: str | None = None) -> None:
|
||||
if spec.is_windows:
|
||||
return
|
||||
|
||||
if spec.is_linux:
|
||||
# A host GNU strip may not understand a cross-compiled ELF architecture.
|
||||
# LLVM supports both packaged Linux architectures from any host.
|
||||
machine = spec.target.split("-", 1)[0]
|
||||
command = (
|
||||
tool
|
||||
or shutil.which("llvm-strip")
|
||||
or shutil.which(f"{machine}-linux-gnu-strip")
|
||||
)
|
||||
if (
|
||||
command is None
|
||||
and platform.system() == "Linux"
|
||||
and normalize_machine(platform.machine()) == machine
|
||||
):
|
||||
command = shutil.which("strip")
|
||||
if command is None:
|
||||
raise RuntimeError(
|
||||
f"No strip tool for {spec.target}; install llvm-strip or pass "
|
||||
"--strip-tool /path/to/target-strip (or --strip none to keep symbols)"
|
||||
)
|
||||
args = [command, "--strip-debug", "--strip-unneeded"]
|
||||
else:
|
||||
if tool is None and platform.system() != "Darwin":
|
||||
raise RuntimeError(
|
||||
"Stripping a macOS binary requires a Mach-O strip tool; pass "
|
||||
"--strip-tool /path/to/strip (or --strip none to keep symbols)"
|
||||
)
|
||||
# Match the release pipeline. Apple's strip also updates the ad-hoc
|
||||
# code signature on Cargo-built ARM64 executables so they remain runnable.
|
||||
args = [tool] if tool is not None else ["xcrun", "strip"]
|
||||
args.extend(("-S", "-x"))
|
||||
|
||||
print(f"Stripping package executable {binary}")
|
||||
subprocess.run([*args, str(binary)], check=True)
|
||||
@@ -3,6 +3,8 @@
|
||||
from pathlib import Path
|
||||
import hashlib
|
||||
import json
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import unittest
|
||||
@@ -15,9 +17,81 @@ from codex_package.layout import validate_package_dir
|
||||
from codex_package.targets import PACKAGE_VARIANTS
|
||||
from codex_package.targets import PackageInputs
|
||||
from codex_package.targets import TARGET_SPECS
|
||||
from codex_package.targets import default_target
|
||||
|
||||
|
||||
class PackageLayoutTest(unittest.TestCase):
|
||||
@unittest.skipIf(sys.platform == "win32", "MSVC symbols are separate PDB files")
|
||||
def test_strip_policy_preserves_inputs_and_runs_packaged_executables(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as temp:
|
||||
root = Path(temp)
|
||||
source = root / "input-executable"
|
||||
# System executables may already be stripped. Compile with debug
|
||||
# information so a no-op strip cannot pass this regression test.
|
||||
subprocess.run(
|
||||
["cc", "-g", "-O0", "-x", "c", "-", "-o", str(source)],
|
||||
input=r"""#include <stdio.h>
|
||||
int main(void) {
|
||||
fputs("package-ok", stdout);
|
||||
return 0;
|
||||
}
|
||||
""",
|
||||
text=True,
|
||||
check=True,
|
||||
capture_output=True,
|
||||
)
|
||||
original = source.read_bytes()
|
||||
target = default_target()
|
||||
for mode in ("auto", "all", "none"):
|
||||
with self.subTest(mode=mode):
|
||||
package = root / mode
|
||||
command = [
|
||||
sys.executable,
|
||||
str(
|
||||
Path(__file__).resolve().parents[1]
|
||||
/ "build_codex_package.py"
|
||||
),
|
||||
"--target",
|
||||
target,
|
||||
"--cargo-profile",
|
||||
"release",
|
||||
"--package-dir",
|
||||
str(package),
|
||||
"--strip",
|
||||
mode,
|
||||
]
|
||||
flags = [
|
||||
"--entrypoint-bin",
|
||||
"--code-mode-host-bin",
|
||||
"--rg-bin",
|
||||
"--zsh-bin",
|
||||
]
|
||||
if "linux" in target:
|
||||
flags.append("--bwrap-bin")
|
||||
for flag in flags:
|
||||
command.extend((flag, str(source)))
|
||||
if mode != "all":
|
||||
# Preserved prebuilt inputs must not even need a strip tool.
|
||||
command.extend(("--strip-tool", str(root / "absent-strip")))
|
||||
subprocess.run(command, check=True, capture_output=True)
|
||||
for name in ("codex", "codex-code-mode-host"):
|
||||
executable = package / "bin" / name
|
||||
self.assertEqual(
|
||||
subprocess.check_output([str(executable)]),
|
||||
b"package-ok",
|
||||
)
|
||||
if mode != "all":
|
||||
self.assertEqual(executable.read_bytes(), original)
|
||||
else:
|
||||
self.assertLess(executable.stat().st_size, len(original))
|
||||
self.assertEqual(source.read_bytes(), original)
|
||||
for resource in ("codex-path/rg", "codex-resources/zsh/bin/zsh"):
|
||||
self.assertEqual((package / resource).read_bytes(), original)
|
||||
if "linux" in target:
|
||||
self.assertEqual(
|
||||
(package / "codex-resources/bwrap").read_bytes(), original
|
||||
)
|
||||
|
||||
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:
|
||||
|
||||
Reference in New Issue
Block a user