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:
Michael Bolin
2026-10-08 17:12:09 +00:00
committed by copyberry
parent bc1f22455d
commit e9e6cf6349
4 changed files with 172 additions and 0 deletions
+18
View File
@@ -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
+26
View File
@@ -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
)
+54
View File
@@ -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)
+74
View File
@@ -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: