diff --git a/codex-rs/Cargo.lock b/codex-rs/Cargo.lock index 4d67f63830..801279a005 100644 --- a/codex-rs/Cargo.lock +++ b/codex-rs/Cargo.lock @@ -3392,6 +3392,9 @@ dependencies = [ "codex-utils-absolute-path", "codex-utils-home-dir", "pretty_assertions", + "semver", + "serde", + "serde_json", "tempfile", ] @@ -12256,6 +12259,10 @@ name = "semver" version = "1.0.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" +dependencies = [ + "serde", + "serde_core", +] [[package]] name = "sentry" diff --git a/codex-rs/install-context/Cargo.toml b/codex-rs/install-context/Cargo.toml index 5882a19763..ee84ad2962 100644 --- a/codex-rs/install-context/Cargo.toml +++ b/codex-rs/install-context/Cargo.toml @@ -15,6 +15,9 @@ workspace = true [dependencies] codex-utils-absolute-path = { workspace = true } codex-utils-home-dir = { workspace = true } +semver = { workspace = true, features = ["serde"] } +serde = { workspace = true, features = ["derive"] } +serde_json = { workspace = true } [dev-dependencies] pretty_assertions = { workspace = true } diff --git a/codex-rs/install-context/src/lib.rs b/codex-rs/install-context/src/lib.rs index 4a963e9167..a88e2e2802 100644 --- a/codex-rs/install-context/src/lib.rs +++ b/codex-rs/install-context/src/lib.rs @@ -4,6 +4,8 @@ use std::path::PathBuf; use std::sync::OnceLock; use codex_utils_absolute_path::AbsolutePathBuf; +use semver::Version; +use serde::Deserialize; const BIN_DIRNAME: &str = "bin"; const CODE_MODE_HOST_EXECUTABLE_NAME: &str = if cfg!(windows) { @@ -37,6 +39,12 @@ pub struct CodexPackageLayout { pub path_dir: Option, } +/// Version metadata recorded in a bundled Codex runtime package. +#[derive(Clone, Debug, Deserialize, Eq, PartialEq)] +pub struct CodexPackageManifest { + pub version: Version, +} + #[derive(Clone, Debug, Eq, PartialEq)] pub struct InstallContext { pub method: InstallMethod, @@ -128,6 +136,15 @@ impl InstallContext { }) } + /// Read the manifest for the package that contains the current executable. + pub fn package_manifest(&self) -> Option { + let package_layout = self.package_layout.as_ref()?; + let manifest = + std::fs::read_to_string(package_layout.package_dir.join(PACKAGE_METADATA_FILENAME)) + .ok()?; + serde_json::from_str(&manifest).ok() + } + pub fn rg_command(&self) -> PathBuf { if let Some(package_layout) = &self.package_layout && let Some(path_dir) = &package_layout.path_dir @@ -227,11 +244,18 @@ impl CodexPackageLayout { let exe_dir = canonical_exe.parent()?; match exe_dir.file_name() { Some(name) if name == OsStr::new(BIN_DIRNAME) => Self::from_package_bin_dir(exe_dir), + Some(name) if name == OsStr::new(RESOURCES_DIRNAME) => { + let package_dir = exe_dir.parent()?; + Self::from_package_bin_dir(package_dir.join(BIN_DIRNAME)) + } Some(_) | None => None, } } fn from_package_bin_dir(bin_dir: AbsolutePathBuf) -> Option { + if !bin_dir.is_dir() { + return None; + } let package_dir = bin_dir.parent()?; if !package_dir.join(PACKAGE_METADATA_FILENAME).is_file() { return None; @@ -495,7 +519,19 @@ mod tests { fs::create_dir_all(&bin_dir)?; fs::create_dir_all(&resources_dir)?; fs::create_dir_all(&path_dir)?; - fs::write(package_dir.path().join(PACKAGE_METADATA_FILENAME), "{}")?; + fs::write( + package_dir.path().join(PACKAGE_METADATA_FILENAME), + r#"{ + "layoutVersion": 1, + "version": "1.2.3", + "target": "x86_64-unknown-linux-musl", + "variant": "codex", + "entrypoint": "bin/codex", + "resourcesDir": "codex-resources", + "pathDir": "codex-path" +} +"#, + )?; let exe_path = bin_dir.join(if cfg!(windows) { "codex.exe" } else { "codex" }); fs::write(&exe_path, "")?; fs::write(bin_dir.join(CODE_MODE_HOST_EXECUTABLE_NAME), "")?; @@ -532,6 +568,12 @@ mod tests { package_layout: Some(package_layout), } ); + assert_eq!( + context.package_manifest(), + Some(CodexPackageManifest { + version: Version::new(1, 2, 3), + }) + ); assert_eq!( context.code_mode_host_program_from_exe(Some(&exe_path)), canonical_bin_dir