diff --git a/codex-rs/git-utils/src/errors.rs b/codex-rs/git-utils/src/errors.rs index 41f7b6df49..8c57a97e25 100644 --- a/codex-rs/git-utils/src/errors.rs +++ b/codex-rs/git-utils/src/errors.rs @@ -30,6 +30,8 @@ pub enum GitToolingError { PathPrefix(#[from] std::path::StripPrefixError), #[error(transparent)] Walkdir(#[from] WalkdirError), + #[error("{0}")] + UnsupportedPlatform(String), #[error(transparent)] Io(#[from] std::io::Error), } diff --git a/codex-rs/git-utils/src/platform.rs b/codex-rs/git-utils/src/platform.rs index 41d4e2eae8..11a23f8310 100644 --- a/codex-rs/git-utils/src/platform.rs +++ b/codex-rs/git-utils/src/platform.rs @@ -34,4 +34,12 @@ pub fn create_symlink( } #[cfg(not(any(unix, windows)))] -compile_error!("codex-git symlink support is only implemented for Unix and Windows"); +pub fn create_symlink( + _source: &Path, + _link_target: &Path, + _destination: &Path, +) -> Result<(), GitToolingError> { + Err(GitToolingError::UnsupportedPlatform( + "symlink creation is unavailable on this target".to_string(), + )) +} diff --git a/codex-rs/utils/absolute-path/Cargo.toml b/codex-rs/utils/absolute-path/Cargo.toml index f4fa8bf789..b6e16b9828 100644 --- a/codex-rs/utils/absolute-path/Cargo.toml +++ b/codex-rs/utils/absolute-path/Cargo.toml @@ -10,7 +10,6 @@ workspace = true [dependencies] dirs = { workspace = true } -path-absolutize = { workspace = true } schemars = { workspace = true } serde = { workspace = true, features = ["derive"] } ts-rs = { workspace = true, features = [ @@ -22,3 +21,6 @@ ts-rs = { workspace = true, features = [ pretty_assertions = { workspace = true } serde_json = { workspace = true } tempfile = { workspace = true } + +[target.'cfg(not(target_arch = "wasm32"))'.dependencies] +path-absolutize = { workspace = true } diff --git a/codex-rs/utils/absolute-path/src/lib.rs b/codex-rs/utils/absolute-path/src/lib.rs index 0de0f4e023..32e3c44267 100644 --- a/codex-rs/utils/absolute-path/src/lib.rs +++ b/codex-rs/utils/absolute-path/src/lib.rs @@ -1,4 +1,5 @@ use dirs::home_dir; +#[cfg(not(target_arch = "wasm32"))] use path_absolutize::Absolutize; use schemars::JsonSchema; use serde::Deserialize; @@ -6,6 +7,10 @@ use serde::Deserializer; use serde::Serialize; use serde::de::Error as SerdeError; use std::cell::RefCell; +#[cfg(target_arch = "wasm32")] +use std::ffi::OsString; +#[cfg(target_arch = "wasm32")] +use std::path::Component; use std::path::Display; use std::path::Path; use std::path::PathBuf; @@ -45,13 +50,24 @@ impl AbsolutePathBuf { base_path: B, ) -> std::io::Result { let expanded = Self::maybe_expand_home_directory(path.as_ref()); + #[cfg(not(target_arch = "wasm32"))] let absolute_path = expanded.absolutize_from(base_path.as_ref())?; + #[cfg(target_arch = "wasm32")] + let absolute_path = + normalize_absolute_path(join_against_base(expanded, base_path.as_ref())?); Ok(Self(absolute_path.into_owned())) } pub fn from_absolute_path>(path: P) -> std::io::Result { let expanded = Self::maybe_expand_home_directory(path.as_ref()); + #[cfg(not(target_arch = "wasm32"))] let absolute_path = expanded.absolutize()?; + #[cfg(target_arch = "wasm32")] + let absolute_path = normalize_absolute_path(if expanded.is_absolute() { + expanded + } else { + join_against_base(expanded, &std::env::current_dir()?)? + }); Ok(Self(absolute_path.into_owned())) } @@ -101,6 +117,56 @@ impl AbsolutePathBuf { } } +#[cfg(target_arch = "wasm32")] +fn join_against_base(path: PathBuf, base_path: &Path) -> std::io::Result { + if path.is_absolute() { + return Ok(path); + } + if !base_path.is_absolute() { + return Err(std::io::Error::new( + std::io::ErrorKind::InvalidInput, + "base path must be absolute", + )); + } + Ok(base_path.join(path)) +} + +#[cfg(target_arch = "wasm32")] +fn normalize_absolute_path(path: PathBuf) -> std::borrow::Cow<'static, Path> { + let mut prefix: Option = None; + let mut has_root = false; + let mut parts: Vec = Vec::new(); + + for component in path.components() { + match component { + Component::Prefix(value) => { + prefix = Some(value.as_os_str().to_os_string()); + } + Component::RootDir => { + has_root = true; + } + Component::CurDir => {} + Component::ParentDir => { + let _ = parts.pop(); + } + Component::Normal(part) => parts.push(part.to_os_string()), + } + } + + let mut normalized = PathBuf::new(); + if let Some(prefix) = prefix { + normalized.push(prefix); + } + if has_root { + normalized.push(Path::new(std::path::MAIN_SEPARATOR_STR)); + } + for part in parts { + normalized.push(part); + } + + std::borrow::Cow::Owned(normalized) +} + impl AsRef for AbsolutePathBuf { fn as_ref(&self) -> &Path { &self.0 diff --git a/codex-rs/utils/cache/Cargo.toml b/codex-rs/utils/cache/Cargo.toml index c034ad1346..e8292c2959 100644 --- a/codex-rs/utils/cache/Cargo.toml +++ b/codex-rs/utils/cache/Cargo.toml @@ -10,7 +10,9 @@ workspace = true [dependencies] lru = { workspace = true } sha1 = { workspace = true } -tokio = { workspace = true, features = ["sync", "rt", "rt-multi-thread"] } [dev-dependencies] tokio = { workspace = true, features = ["macros", "rt", "rt-multi-thread"] } + +[target.'cfg(not(target_arch = "wasm32"))'.dependencies] +tokio = { workspace = true, features = ["sync", "rt", "rt-multi-thread"] } diff --git a/codex-rs/utils/cache/src/lib.rs b/codex-rs/utils/cache/src/lib.rs index ed4b926687..40d720fbcd 100644 --- a/codex-rs/utils/cache/src/lib.rs +++ b/codex-rs/utils/cache/src/lib.rs @@ -5,7 +5,13 @@ use std::num::NonZeroUsize; use lru::LruCache; use sha1::Digest; use sha1::Sha1; +#[cfg(target_arch = "wasm32")] +use std::sync::Mutex; +#[cfg(target_arch = "wasm32")] +use std::sync::MutexGuard; +#[cfg(not(target_arch = "wasm32"))] use tokio::sync::Mutex; +#[cfg(not(target_arch = "wasm32"))] use tokio::sync::MutexGuard; /// A minimal LRU cache protected by a Tokio mutex. @@ -119,6 +125,7 @@ where } } +#[cfg(not(target_arch = "wasm32"))] fn lock_if_runtime(m: &Mutex>) -> Option>> where K: Eq + Hash, @@ -127,6 +134,14 @@ where Some(tokio::task::block_in_place(|| m.blocking_lock())) } +#[cfg(target_arch = "wasm32")] +fn lock_if_runtime(m: &Mutex>) -> Option>> +where + K: Eq + Hash, +{ + m.lock().ok() +} + /// Computes the SHA-1 digest of `bytes`. /// /// Useful for content-based cache keys when you want to avoid staleness @@ -141,6 +156,7 @@ pub fn sha1_digest(bytes: &[u8]) -> [u8; 20] { out } +#[cfg(not(target_arch = "wasm32"))] #[cfg(test)] mod tests { use super::BlockingLruCache;