fix code mode host packaging compatibility

This commit is contained in:
Channing Conger
2026-07-09 23:57:01 +00:00
parent 955fa324da
commit 8fc5ae3ddf
10 changed files with 169 additions and 121 deletions
+3 -3
View File
@@ -352,13 +352,13 @@ jobs:
rm -rf "$bundle_root"
mkdir -p "$bundle_root/codex-resources"
cp "$dest/codex-${{ matrix.target }}" "$bundle_root/codex"
cp "$dest/codex-code-mode-host-${{ matrix.target }}" "$bundle_root/codex-resources/codex-code-mode-host"
cp "$dest/codex-code-mode-host-${{ matrix.target }}" "$bundle_root/codex-code-mode-host"
cp "$dest/bwrap-${{ matrix.target }}" "$bundle_root/codex-resources/bwrap"
chmod 0755 \
"$bundle_root/codex" \
"$bundle_root/codex-resources/codex-code-mode-host" \
"$bundle_root/codex-code-mode-host" \
"$bundle_root/codex-resources/bwrap"
tar -C "$bundle_root" -cf - codex codex-resources/codex-code-mode-host codex-resources/bwrap |
tar -C "$bundle_root" -cf - codex codex-code-mode-host codex-resources/bwrap |
zstd -T0 -19 -o "$dest/codex-${{ matrix.target }}-bundle.tar.zst"
fi
+23 -4
View File
@@ -1,3 +1,4 @@
use std::io;
use std::path::PathBuf;
use std::sync::Arc;
use std::sync::Mutex as StdMutex;
@@ -63,7 +64,7 @@ impl ProcessOwnedCodeModeSessionProvider {
impl Default for ProcessOwnedCodeModeSessionProvider {
fn default() -> Self {
Self::with_host_program(InstallContext::current().code_mode_host_program())
Self::with_host_program(default_host_program())
}
}
@@ -192,9 +193,7 @@ impl ProcessOwnedCodeModeSession {
pub fn new() -> Self {
Self::with_process_host(
Arc::new(NoopCodeModeSessionDelegate),
Arc::new(OwnedProcessHost::new(
InstallContext::current().code_mode_host_program(),
)),
Arc::new(OwnedProcessHost::new(default_host_program())),
)
}
@@ -493,6 +492,26 @@ impl CodeModeSession for ProcessOwnedCodeModeSession {
}
}
fn default_host_program() -> PathBuf {
InstallContext::current()
.code_mode_host_program()
.unwrap_or_else(|| resolve_host_program(std::env::current_exe()))
}
fn resolve_host_program(current_exe: io::Result<PathBuf>) -> PathBuf {
let executable_name = if cfg!(windows) {
"codex-code-mode-host.exe"
} else {
"codex-code-mode-host"
};
if let Ok(current_exe) = current_exe
&& let Some(parent) = current_exe.parent()
{
return parent.join(executable_name);
}
PathBuf::from(executable_name)
}
#[cfg(test)]
#[path = "remote_session_tests.rs"]
mod tests;
@@ -1,3 +1,5 @@
use std::io;
use std::path::PathBuf;
use std::sync::Arc;
use codex_code_mode_protocol::CodeModeSessionProvider;
@@ -8,6 +10,7 @@ use pretty_assertions::assert_eq;
use super::ProcessOwnedCodeModeSession;
use super::ProcessOwnedCodeModeSessionProvider;
use super::resolve_host_program;
use crate::NoopCodeModeSessionDelegate;
#[test]
@@ -20,6 +23,37 @@ fn provider_reuses_its_live_process_host() {
assert!(Arc::ptr_eq(&first, &second));
}
#[test]
fn host_program_is_next_to_the_main_executable_even_when_missing() {
let executable_name = if cfg!(windows) {
"codex-code-mode-host.exe"
} else {
"codex-code-mode-host"
};
assert_eq!(
resolve_host_program(Ok(PathBuf::from("/opt/codex/bin/codex"))),
PathBuf::from("/opt/codex/bin").join(executable_name)
);
}
#[test]
fn host_program_falls_back_to_its_name_when_main_executable_is_unknown() {
let executable_name = if cfg!(windows) {
"codex-code-mode-host.exe"
} else {
"codex-code-mode-host"
};
assert_eq!(
resolve_host_program(Err(io::Error::new(
io::ErrorKind::NotFound,
"missing executable"
))),
PathBuf::from(executable_name)
);
}
#[tokio::test]
async fn provider_falls_back_to_in_process_session_when_host_is_missing() {
let provider = ProcessOwnedCodeModeSessionProvider::with_host_program(
+8 -71
View File
@@ -1,6 +1,4 @@
use std::ffi::OsStr;
use std::ffi::OsString;
use std::io;
use std::path::Path;
use std::path::PathBuf;
use std::sync::OnceLock;
@@ -8,7 +6,6 @@ use std::sync::OnceLock;
use codex_utils_absolute_path::AbsolutePathBuf;
const BIN_DIRNAME: &str = "bin";
const CODE_MODE_HOST_PATH_ENV: &str = "CODEX_CODE_MODE_HOST_PATH";
const PACKAGE_METADATA_FILENAME: &str = "codex-package.json";
const PATH_DIRNAME: &str = "codex-path";
const RELEASES_DIRNAME: &str = "releases";
@@ -150,35 +147,10 @@ impl InstallContext {
default_rg_command()
}
/// Resolves the code-mode host executable for the current installation.
pub fn code_mode_host_program(&self) -> PathBuf {
self.resolve_code_mode_host_program(
std::env::var_os(CODE_MODE_HOST_PATH_ENV),
std::env::current_exe(),
)
}
fn resolve_code_mode_host_program(
&self,
override_path: Option<OsString>,
current_exe: io::Result<PathBuf>,
) -> PathBuf {
if let Some(path) = override_path {
return PathBuf::from(path);
}
let executable_name = code_mode_host_executable_name();
if let Some(path) = self.bundled_resource(executable_name) {
return path.into_path_buf();
}
if let Ok(current_exe) = current_exe
&& let Some(parent) = current_exe.parent()
{
return parent.join(executable_name);
}
PathBuf::from(executable_name)
/// Returns the code-mode host when it is part of the detected package layout.
pub fn code_mode_host_program(&self) -> Option<PathBuf> {
self.bundled_resource(code_mode_host_executable_name())
.map(AbsolutePathBuf::into_path_buf)
}
pub fn bundled_resource(&self, file_name: impl AsRef<Path>) -> Option<AbsolutePathBuf> {
@@ -334,7 +306,7 @@ mod tests {
const TEST_RESOURCE_NAME: &str = "codex-test-helper";
#[test]
fn code_mode_host_program_prefers_override_then_bundled_resource() -> std::io::Result<()> {
fn code_mode_host_program_returns_bundled_resource() -> std::io::Result<()> {
let package_dir = tempfile::tempdir()?;
let bin_dir = package_dir.path().join(BIN_DIRNAME);
let resources_dir = package_dir.path().join(RESOURCES_DIRNAME);
@@ -352,53 +324,18 @@ mod tests {
/*method_override*/ None,
);
assert_eq!(
context.resolve_code_mode_host_program(
Some("custom-code-mode-host".into()),
Ok(exe_path.clone()),
),
PathBuf::from("custom-code-mode-host")
);
assert_eq!(
context.resolve_code_mode_host_program(/*override_path*/ None, Ok(exe_path),),
canonical_host_path
);
assert_eq!(context.code_mode_host_program(), Some(canonical_host_path));
Ok(())
}
#[test]
fn code_mode_host_program_uses_adjacent_binary_for_legacy_layout() {
fn code_mode_host_program_is_none_without_bundled_resource() {
let context = InstallContext {
method: InstallMethod::Other,
package_layout: None,
};
assert_eq!(
context.resolve_code_mode_host_program(
/*override_path*/ None,
Ok(PathBuf::from("/opt/codex/bin/codex")),
),
PathBuf::from("/opt/codex/bin").join(code_mode_host_executable_name())
);
}
#[test]
fn code_mode_host_program_falls_back_to_executable_name() {
let context = InstallContext {
method: InstallMethod::Other,
package_layout: None,
};
assert_eq!(
context.resolve_code_mode_host_program(
/*override_path*/ None,
Err(io::Error::new(
io::ErrorKind::NotFound,
"missing executable"
)),
),
PathBuf::from(code_mode_host_executable_name())
);
assert_eq!(context.code_mode_host_program(), None);
}
#[test]
+1 -5
View File
@@ -17,16 +17,12 @@ from codex_package.targets import TARGET_SPECS
class PackageLayoutTest(unittest.TestCase):
def test_app_server_package_places_code_mode_host_in_resources(self) -> None:
with tempfile.TemporaryDirectory() as temp_dir:
for target in (
"x86_64-unknown-linux-musl",
"x86_64-pc-windows-msvc",
):
for target, spec in TARGET_SPECS.items():
with self.subTest(target=target):
root = Path(temp_dir) / target
root.mkdir()
package_dir = root / "package"
package_dir.mkdir()
spec = TARGET_SPECS[target]
inputs = PackageInputs(
entrypoint_bin=touch_executable(root / "codex-app-server"),
code_mode_host_bin=touch_executable(
+11 -2
View File
@@ -539,7 +539,6 @@ function Test-PackageContentsAreComplete {
"codex-package.json",
"bin\codex.exe",
"codex-path\rg.exe",
"codex-resources\codex-code-mode-host.exe",
"codex-resources\codex-command-runner.exe",
"codex-resources\codex-windows-sandbox-setup.exe"
)
@@ -549,7 +548,17 @@ function Test-PackageContentsAreComplete {
}
}
return $true
$hostPaths = @(
"codex-resources\codex-code-mode-host.exe",
"bin\codex-code-mode-host.exe"
)
foreach ($name in $hostPaths) {
if (Test-Path -LiteralPath (Join-Path $PackageDir $name) -PathType Leaf) {
return $true
}
}
return $false
}
function Test-LegacyPlatformNpmContentsAreComplete {
+24 -7
View File
@@ -760,6 +760,15 @@ handle_conflicting_install() {
fi
}
package_code_mode_host_relative_path() {
package_dir="$1"
if [ -f "$package_dir/codex-resources/codex-code-mode-host" ]; then
printf 'codex-resources/codex-code-mode-host\n'
else
printf 'bin/codex-code-mode-host\n'
fi
}
install_package_release() {
release_dir="$1"
archive_path="$2"
@@ -769,9 +778,10 @@ install_package_release() {
rm -rf "$stage_release"
mkdir -p "$stage_release"
tar -xzf "$archive_path" -C "$stage_release"
code_mode_host_relative_path="$(package_code_mode_host_relative_path "$stage_release")"
chmod 0755 \
"$stage_release/bin/codex" \
"$stage_release/codex-resources/codex-code-mode-host" \
"$stage_release/$code_mode_host_relative_path" \
"$stage_release/codex-path/rg"
if [ -f "$stage_release/codex-resources/bwrap" ]; then
chmod 0755 "$stage_release/codex-resources/bwrap"
@@ -824,7 +834,8 @@ release_dir_is_complete() {
package)
[ -f "$release_dir/codex-package.json" ] &&
[ -x "$release_dir/bin/codex" ] &&
[ -x "$release_dir/codex-resources/codex-code-mode-host" ] &&
{ [ -x "$release_dir/codex-resources/codex-code-mode-host" ] ||
[ -x "$release_dir/bin/codex-code-mode-host" ]; } &&
[ -x "$release_dir/codex-path/rg" ] ||
return 1
;;
@@ -866,17 +877,23 @@ update_visible_command() {
mkdir -p "$BIN_DIR"
tmp_link="$BIN_DIR/.codex.$$"
codex_relative_path="$(release_codex_relative_path "$release_dir")"
code_mode_host_relative_path="$(package_code_mode_host_relative_path "$release_dir")"
replace_path_with_symlink "$BIN_PATH" "$CURRENT_LINK/$codex_relative_path" "$tmp_link"
if [ "$os" = "darwin" ] && [ -x "$release_dir/bin/codex-code-mode-host" ]; then
if [ "$os" = "darwin" ] && [ -x "$release_dir/$code_mode_host_relative_path" ]; then
replace_path_with_symlink \
"$CODE_MODE_HOST_BIN_PATH" \
"$CURRENT_LINK/bin/codex-code-mode-host" \
"$CURRENT_LINK/$code_mode_host_relative_path" \
"$tmp_link"
elif [ "$(readlink "$CODE_MODE_HOST_BIN_PATH" 2>/dev/null || true)" = \
"$CURRENT_LINK/bin/codex-code-mode-host" ]; then
rm -f "$CODE_MODE_HOST_BIN_PATH"
else
existing_code_mode_host_target="$(readlink "$CODE_MODE_HOST_BIN_PATH" 2>/dev/null || true)"
case "$existing_code_mode_host_target" in
"$CURRENT_LINK/bin/codex-code-mode-host" | \
"$CURRENT_LINK/codex-resources/codex-code-mode-host")
rm -f "$CODE_MODE_HOST_BIN_PATH"
;;
esac
fi
}
+35 -24
View File
@@ -89,30 +89,38 @@ class InstallShTest(unittest.TestCase):
self.assertNotIn("codex-package_SHA256SUMS", requests[1])
def test_macos_install_exposes_code_mode_host_beside_codex(self) -> None:
with tempfile.TemporaryDirectory() as temp_dir:
root = Path(temp_dir)
archive_path, checksum_path, metadata_json = create_package_release(root)
for host_dir in ("codex-resources", "bin"):
with (
self.subTest(host_dir=host_dir),
tempfile.TemporaryDirectory() as temp_dir,
):
root = Path(temp_dir)
archive_path, checksum_path, metadata_json = create_package_release(
root, host_dir=host_dir
)
result, _requests = run_installer_in(
root,
VERSION,
metadata_json=metadata_json,
archive_path=archive_path,
checksum_path=checksum_path,
force_macos=True,
)
result, _requests = run_installer_in(
root,
VERSION,
metadata_json=metadata_json,
archive_path=archive_path,
checksum_path=checksum_path,
force_macos=True,
)
self.assertEqual(result.returncode, 0, result.stderr)
install_bin = root / "install-bin"
current = root / "codex-home" / "packages" / "standalone" / "current"
codex_path = install_bin / "codex"
host_path = install_bin / "codex-code-mode-host"
self.assertEqual(os.readlink(codex_path), str(current / "bin" / "codex"))
self.assertEqual(
os.readlink(host_path),
str(current / "bin" / "codex-code-mode-host"),
)
self.assertTrue(os.access(host_path, os.X_OK))
self.assertEqual(result.returncode, 0, result.stderr)
install_bin = root / "install-bin"
current = root / "codex-home" / "packages" / "standalone" / "current"
codex_path = install_bin / "codex"
host_path = install_bin / "codex-code-mode-host"
self.assertEqual(
os.readlink(codex_path), str(current / "bin" / "codex")
)
self.assertEqual(
os.readlink(host_path),
str(current / host_dir / "codex-code-mode-host"),
)
self.assertTrue(os.access(host_path, os.X_OK))
def run_installer(
@@ -241,9 +249,12 @@ def run_installer_in(
return result, requests
def create_package_release(root: Path) -> tuple[Path, Path, str]:
def create_package_release(
root: Path, *, host_dir: str = "codex-resources"
) -> tuple[Path, Path, str]:
package_dir = root / "package"
(package_dir / "bin").mkdir(parents=True)
(package_dir / host_dir).mkdir(exist_ok=True)
(package_dir / "codex-path").mkdir()
(package_dir / "codex-package.json").write_text("{}\n", encoding="utf-8")
write_executable(
@@ -251,7 +262,7 @@ def create_package_release(root: Path) -> tuple[Path, Path, str]:
f"#!/bin/sh\nprintf 'codex-cli {VERSION}\\n'\n",
)
write_executable(
package_dir / "bin" / "codex-code-mode-host",
package_dir / host_dir / "codex-code-mode-host",
"#!/bin/sh\nexit 0\n",
)
write_executable(package_dir / "codex-path" / "rg", "#!/bin/sh\nexit 0\n")
+6 -3
View File
@@ -280,9 +280,12 @@ def _validate_codex_package_layout(package_dir: Path, package_archive: Path) ->
package_binary = package_dir / "bin" / runtime_binary_name()
if not package_binary.is_file():
missing_entries.append(str(Path("bin") / runtime_binary_name()))
code_mode_host = package_dir / "codex-resources" / runtime_code_mode_host_name()
if not code_mode_host.is_file():
missing_entries.append(str(Path("codex-resources") / runtime_code_mode_host_name()))
code_mode_host_paths = (
Path("codex-resources") / runtime_code_mode_host_name(),
Path("bin") / runtime_code_mode_host_name(),
)
if not any((package_dir / path).is_file() for path in code_mode_host_paths):
missing_entries.append(" or ".join(map(str, code_mode_host_paths)))
if missing_entries:
missing = ", ".join(missing_entries)
raise RuntimeError(f"Missing Codex package layout entries in {package_archive}: {missing}")
@@ -51,13 +51,15 @@ def _load_runtime_setup_module():
return module
def _write_fake_codex_package(package_dir: Path, script) -> Path:
def _write_fake_codex_package(
package_dir: Path, script, *, host_dir: str = "codex-resources"
) -> Path:
(package_dir / "bin").mkdir(parents=True)
(package_dir / "codex-resources").mkdir()
(package_dir / "codex-path").mkdir()
(package_dir / "codex-package.json").write_text('{"variant":"codex"}\n')
(package_dir / "bin" / script.runtime_binary_name()).write_text("fake codex\n")
(package_dir / "codex-resources" / script.runtime_code_mode_host_name()).write_text(
(package_dir / host_dir / script.runtime_code_mode_host_name()).write_text(
"fake code mode host\n"
)
(package_dir / "codex-resources" / "bwrap").write_text("fake bwrap\n")
@@ -701,6 +703,26 @@ def test_stage_runtime_release_copies_package_layout_and_sets_version(
assert 'version = "1.2.3"' in (staged / "pyproject.toml").read_text()
def test_stage_runtime_release_accepts_legacy_code_mode_host_location(
tmp_path: Path,
) -> None:
script = _load_update_script_module()
package_dir = _write_fake_codex_package(tmp_path / "codex-package", script, host_dir="bin")
package_archive = tmp_path / "codex-package.tar.gz"
_write_package_archive(package_dir, package_archive)
staged = script.stage_python_runtime_package(
tmp_path / "runtime-stage",
"1.2.3",
package_archive,
)
package_root = script.staged_runtime_package_root(staged)
assert (
package_root / "bin" / script.runtime_code_mode_host_name()
).read_text() == "fake code mode host\n"
def test_normalize_codex_version_accepts_release_tags_and_pep440_versions() -> None:
script = _load_update_script_module()