mirror of
https://github.com/openai/codex.git
synced 2026-05-23 20:44:50 +00:00
runtime: detect Codex package layout (#23596)
## Why The package-builder stack now creates a canonical Codex package directory where the entrypoint lives under `bin/`, bundled helper resources live under `codex-resources/`, and bundled PATH-style tools live under `codex-path/`. That layout is not specific to the standalone installer: npm, brew, install scripts, and manually unpacked artifacts should all be able to use the same package shape. The Rust runtime still only knew about the legacy standalone release layout, where resources sit next to the executable. A packaged binary therefore would not identify its package root or prefer the bundled `rg` from `codex-path/`. ## What changed - Adds `CodexPackageLayout` to `codex-install-context` and detects it from an executable path shaped like `<package>/bin/<entrypoint>` when `<package>/codex-package.json` is present. - Splits `InstallContext` into an install `method` plus an optional package layout so the layout is shared across npm, bun, brew, standalone, and other launch contexts. - Stores package-layout paths as `AbsolutePathBuf` values. - Keeps `codex-resources/` and `codex-path/` optional so Codex can still run with degraded behavior if sidecar directories are missing. - Updates `InstallContext::rg_command()` to prefer bundled `codex-path/rg` or `rg.exe`, then fall back to the legacy standalone resources location, then system `rg`. - Updates `codex doctor` reporting so package installs show package, bin, resources, and path directories, and so bundled search detection recognizes `codex-path/` for any install method. ## Test plan - `cargo test -p codex-install-context` - `cargo test -p codex-cli` - `cargo test -p codex-tui update_action::tests::maps_install_context_to_update_action` - `just bazel-lock-check`
This commit is contained in:
@@ -39,7 +39,9 @@ use codex_core::config::ConfigBuilder;
|
||||
use codex_core::config::ConfigOverrides;
|
||||
use codex_core::config::find_codex_home;
|
||||
use codex_features::FEATURES;
|
||||
use codex_install_context::CodexPackageLayout;
|
||||
use codex_install_context::InstallContext;
|
||||
use codex_install_context::InstallMethod;
|
||||
use codex_install_context::StandalonePlatform;
|
||||
use codex_login::AuthDotJson;
|
||||
use codex_login::AuthManager;
|
||||
@@ -812,7 +814,10 @@ fn installation_check(show_details: bool) -> DoctorCheck {
|
||||
|
||||
fn doctor_install_context(current_exe: Option<&Path>) -> InstallContext {
|
||||
if inherited_managed_env_for_cargo_binary(current_exe) {
|
||||
InstallContext::Other
|
||||
InstallContext {
|
||||
method: InstallMethod::Other,
|
||||
package_layout: None,
|
||||
}
|
||||
} else {
|
||||
InstallContext::current().clone()
|
||||
}
|
||||
@@ -843,8 +848,8 @@ fn inherited_managed_env_for_cargo_binary(current_exe: Option<&Path>) -> bool {
|
||||
}
|
||||
|
||||
fn describe_install_context(context: &InstallContext) -> String {
|
||||
match context {
|
||||
InstallContext::Standalone {
|
||||
match &context.method {
|
||||
InstallMethod::Standalone {
|
||||
release_dir,
|
||||
resources_dir,
|
||||
platform,
|
||||
@@ -853,22 +858,63 @@ fn describe_install_context(context: &InstallContext) -> String {
|
||||
StandalonePlatform::Unix => "unix",
|
||||
StandalonePlatform::Windows => "windows",
|
||||
};
|
||||
let resources = resources_dir
|
||||
.as_ref()
|
||||
.map(|path| path.display().to_string())
|
||||
.unwrap_or_else(|| "none".to_string());
|
||||
match &context.package_layout {
|
||||
Some(package_layout) => {
|
||||
let resources = display_optional_path(package_layout.resources_dir.as_deref());
|
||||
let path = display_optional_path(package_layout.path_dir.as_deref());
|
||||
format!(
|
||||
"standalone ({platform}, package {}, bin {}, resources {resources}, path {path})",
|
||||
package_layout.package_dir.display(),
|
||||
package_layout.bin_dir.display()
|
||||
)
|
||||
}
|
||||
None => {
|
||||
let resources = display_optional_path(resources_dir.as_deref());
|
||||
format!(
|
||||
"standalone ({platform}, release {}, resources {resources})",
|
||||
release_dir.display()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
InstallMethod::Npm => {
|
||||
describe_method_with_package_layout("npm", context.package_layout.as_ref())
|
||||
}
|
||||
InstallMethod::Bun => {
|
||||
describe_method_with_package_layout("bun", context.package_layout.as_ref())
|
||||
}
|
||||
InstallMethod::Brew => {
|
||||
describe_method_with_package_layout("brew", context.package_layout.as_ref())
|
||||
}
|
||||
InstallMethod::Other => {
|
||||
describe_method_with_package_layout("other", context.package_layout.as_ref())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn describe_method_with_package_layout(
|
||||
method: &str,
|
||||
package_layout: Option<&CodexPackageLayout>,
|
||||
) -> String {
|
||||
match package_layout {
|
||||
Some(package_layout) => {
|
||||
let resources = display_optional_path(package_layout.resources_dir.as_deref());
|
||||
let path = display_optional_path(package_layout.path_dir.as_deref());
|
||||
format!(
|
||||
"standalone ({platform}, release {}, resources {resources})",
|
||||
release_dir.display()
|
||||
"{method} (package {}, bin {}, resources {resources}, path {path})",
|
||||
package_layout.package_dir.display(),
|
||||
package_layout.bin_dir.display()
|
||||
)
|
||||
}
|
||||
InstallContext::Npm => "npm".to_string(),
|
||||
InstallContext::Bun => "bun".to_string(),
|
||||
InstallContext::Brew => "brew".to_string(),
|
||||
InstallContext::Other => "other".to_string(),
|
||||
None => method.to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
fn display_optional_path(path: Option<&Path>) -> String {
|
||||
path.map(|path| path.display().to_string())
|
||||
.unwrap_or_else(|| "none".to_string())
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
enum NpmRootCheck {
|
||||
Match {
|
||||
@@ -2791,13 +2837,14 @@ fn path_readiness(details: &mut Vec<String>, label: &str, path: &Path) {
|
||||
}
|
||||
|
||||
fn standalone_release_cache_details(details: &mut Vec<String>) {
|
||||
let InstallContext::Standalone { release_dir, .. } = InstallContext::current() else {
|
||||
let context = InstallContext::current();
|
||||
let InstallMethod::Standalone { release_dir, .. } = &context.method else {
|
||||
return;
|
||||
};
|
||||
let Some(releases_dir) = release_dir.parent() else {
|
||||
return;
|
||||
};
|
||||
let Ok(entries) = std::fs::read_dir(releases_dir) else {
|
||||
let Ok(entries) = std::fs::read_dir(&releases_dir) else {
|
||||
return;
|
||||
};
|
||||
let release_count = entries.filter_map(Result::ok).count();
|
||||
|
||||
@@ -3,12 +3,13 @@
|
||||
//! Runtime diagnostics answer provenance questions that are hard to infer from
|
||||
//! user reports: which binary is running, which install channel it resembles,
|
||||
//! which platform it targets, and whether the search command comes from bundled
|
||||
//! standalone resources or from PATH.
|
||||
//! package files or from PATH.
|
||||
|
||||
use std::env;
|
||||
use std::process::Command;
|
||||
|
||||
use codex_install_context::InstallContext;
|
||||
use codex_install_context::InstallMethod;
|
||||
|
||||
use super::CheckStatus;
|
||||
use super::DoctorCheck;
|
||||
@@ -49,9 +50,10 @@ pub(super) fn runtime_check() -> DoctorCheck {
|
||||
|
||||
/// Verifies that the search command selected by the install context is usable.
|
||||
///
|
||||
/// Standalone installs should point at a bundled ripgrep binary, while local or
|
||||
/// package-managed installs usually resolve rg from PATH. A warning here means
|
||||
/// features that depend on file search may degrade even when the CLI launches.
|
||||
/// Package-layout installs should point at a bundled ripgrep binary, while local
|
||||
/// installs without that layout usually resolve rg from PATH. A warning here
|
||||
/// means features that depend on file search may degrade even when the CLI
|
||||
/// launches.
|
||||
pub(super) fn search_check() -> DoctorCheck {
|
||||
let current_exe = env::current_exe().ok();
|
||||
let install_context = doctor_install_context(current_exe.as_deref());
|
||||
@@ -109,28 +111,40 @@ pub(super) fn search_check() -> DoctorCheck {
|
||||
};
|
||||
let mut check = DoctorCheck::new("runtime.search", "search", status, summary).details(details);
|
||||
if status != CheckStatus::Ok {
|
||||
check = check.remediation("Install ripgrep or repair the bundled standalone resources.");
|
||||
check = check.remediation("Install ripgrep or repair the bundled Codex package.");
|
||||
}
|
||||
check
|
||||
}
|
||||
|
||||
fn install_method_name(context: &InstallContext) -> &'static str {
|
||||
match context {
|
||||
InstallContext::Standalone { .. } => "standalone",
|
||||
InstallContext::Npm => "npm",
|
||||
InstallContext::Bun => "bun",
|
||||
InstallContext::Brew => "brew",
|
||||
InstallContext::Other => "local build",
|
||||
match &context.method {
|
||||
InstallMethod::Standalone { .. } => "standalone",
|
||||
InstallMethod::Npm => "npm",
|
||||
InstallMethod::Bun => "bun",
|
||||
InstallMethod::Brew => "brew",
|
||||
InstallMethod::Other => "local build",
|
||||
}
|
||||
}
|
||||
|
||||
fn search_provider(context: &InstallContext) -> &'static str {
|
||||
match context {
|
||||
InstallContext::Standalone {
|
||||
let rg_command = context.rg_command();
|
||||
let from_package_layout = context
|
||||
.package_layout
|
||||
.as_ref()
|
||||
.and_then(|package_layout| package_layout.path_dir.as_ref())
|
||||
.is_some_and(|path_dir| rg_command.starts_with(path_dir));
|
||||
let from_legacy_standalone = matches!(
|
||||
&context.method,
|
||||
InstallMethod::Standalone {
|
||||
resources_dir: Some(resources_dir),
|
||||
..
|
||||
} if context.rg_command().starts_with(resources_dir) => "bundled",
|
||||
_ => "system",
|
||||
} if rg_command.starts_with(resources_dir)
|
||||
);
|
||||
|
||||
if from_package_layout || from_legacy_standalone {
|
||||
"bundled"
|
||||
} else {
|
||||
"system"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ use std::path::Path;
|
||||
|
||||
use codex_core::config::Config;
|
||||
use codex_install_context::InstallContext;
|
||||
use codex_install_context::InstallMethod;
|
||||
use serde::Deserialize;
|
||||
|
||||
use super::CheckStatus;
|
||||
@@ -129,22 +130,22 @@ fn push_cached_version_details(details: &mut Vec<String>, version_file: &Path) {
|
||||
}
|
||||
|
||||
fn update_action_label(context: &InstallContext) -> &'static str {
|
||||
match context {
|
||||
InstallContext::Npm => "npm install -g @openai/codex",
|
||||
InstallContext::Bun => "bun install -g @openai/codex",
|
||||
InstallContext::Brew => "brew upgrade --cask codex",
|
||||
InstallContext::Standalone { .. } => "standalone installer",
|
||||
InstallContext::Other => "manual or unknown",
|
||||
match &context.method {
|
||||
InstallMethod::Npm => "npm install -g @openai/codex",
|
||||
InstallMethod::Bun => "bun install -g @openai/codex",
|
||||
InstallMethod::Brew => "brew upgrade --cask codex",
|
||||
InstallMethod::Standalone { .. } => "standalone installer",
|
||||
InstallMethod::Other => "manual or unknown",
|
||||
}
|
||||
}
|
||||
|
||||
fn fetch_latest_version(context: &InstallContext) -> Result<String, String> {
|
||||
match context {
|
||||
InstallContext::Brew => fetch_homebrew_cask_version(),
|
||||
InstallContext::Npm
|
||||
| InstallContext::Bun
|
||||
| InstallContext::Standalone { .. }
|
||||
| InstallContext::Other => fetch_latest_github_release_version(),
|
||||
match &context.method {
|
||||
InstallMethod::Brew => fetch_homebrew_cask_version(),
|
||||
InstallMethod::Npm
|
||||
| InstallMethod::Bun
|
||||
| InstallMethod::Standalone { .. }
|
||||
| InstallMethod::Other => fetch_latest_github_release_version(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -216,11 +217,17 @@ mod tests {
|
||||
#[test]
|
||||
fn update_action_labels_install_contexts() {
|
||||
assert_eq!(
|
||||
update_action_label(&InstallContext::Npm),
|
||||
update_action_label(&InstallContext {
|
||||
method: InstallMethod::Npm,
|
||||
package_layout: None,
|
||||
}),
|
||||
"npm install -g @openai/codex"
|
||||
);
|
||||
assert_eq!(
|
||||
update_action_label(&InstallContext::Other),
|
||||
update_action_label(&InstallContext {
|
||||
method: InstallMethod::Other,
|
||||
package_layout: None,
|
||||
}),
|
||||
"manual or unknown"
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user