mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
package: include zsh fork in Codex package (#23756)
## Why The package layout gives Codex a stable place for runtime helpers that should travel with the entrypoint. `shell_zsh_fork` still required users to configure `zsh_path` manually, even though we already publish prebuilt zsh fork artifacts. This PR builds on #24129 and uses the shared DotSlash artifact fetcher to include the zsh fork in Codex packages when a matching target artifact exists. Packaged Codex builds can then discover the bundled fork automatically; the user/profile `zsh_path` override is removed so the feature uses the package-managed artifact instead of a legacy path knob. ## What Changed - Added `scripts/codex_package/codex-zsh`, a checked-in DotSlash manifest for the current macOS arm64 and Linux zsh fork artifacts. - Taught `scripts/build_codex_package.py` to fetch the matching zsh fork artifact and install it at `codex-resources/zsh/bin/zsh` when available for the selected target. - Added package layout validation for the optional bundled zsh resource. - Added `InstallContext::bundled_zsh_path()` and `InstallContext::bundled_zsh_bin_dir()` for package-layout resource discovery. - Threaded the packaged zsh path through config loading as the runtime `zsh_path` for packaged installs, and removed the config/profile/CLI override path. - Kept the packaged default zsh override typed as `AbsolutePathBuf` until the existing runtime `Config::zsh_path` boundary. - Updated app-server zsh-fork integration tests to spawn `codex-app-server` from a temporary package layout with `codex-resources/zsh/bin/zsh`, matching the new packaged discovery path instead of setting `zsh_path` in config. - Switched package executable copying from metadata-preserving `copy2()` to `copyfile()` plus explicit executable bits, which avoids macOS file-flag failures when local smoke tests use system binaries as inputs. ## Testing To verify that the `zsh` executable from the Codex package is picked up correctly, first I ran: ```shell ./scripts/build_codex_package.py ``` which created: ``` /private/var/folders/vw/x2knqmks50sfhfpy27nftl900000gp/T/codex-package-pms94kdp/ ``` so then I ran: ``` /private/var/folders/vw/x2knqmks50sfhfpy27nftl900000gp/T/codex-package-pms94kdp/bin/codex exec --enable shell_zsh_fork 'run `echo $0`' ``` which reported the following, as expected: ``` /private/var/folders/vw/x2knqmks50sfhfpy27nftl900000gp/T/codex-package-pms94kdp/codex-resources/zsh/bin/zsh ``` --- [//]: # (BEGIN SAPLING FOOTER) Stack created with [Sapling](https://sapling-scm.com). Best reviewed with [ReviewStack](https://reviewstack.dev/openai/codex/pull/23756). * #23768 * __->__ #23756
This commit is contained in:
committed by
GitHub
Unverified
parent
03e6c5f600
commit
c7bcb90f9b
Generated
+1
@@ -2507,6 +2507,7 @@ dependencies = [
|
||||
"codex-feedback",
|
||||
"codex-git-utils",
|
||||
"codex-hooks",
|
||||
"codex-install-context",
|
||||
"codex-login",
|
||||
"codex-mcp",
|
||||
"codex-memories-read",
|
||||
|
||||
@@ -173,6 +173,20 @@ impl McpProcess {
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn new_with_program_and_env(
|
||||
codex_home: &Path,
|
||||
program: &Path,
|
||||
env_overrides: &[(&str, Option<&str>)],
|
||||
) -> anyhow::Result<Self> {
|
||||
Self::new_with_program_env_and_args(
|
||||
codex_home,
|
||||
program,
|
||||
env_overrides,
|
||||
&[DISABLE_PLUGIN_STARTUP_TASKS_ARG],
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn new_with_env_and_args(
|
||||
codex_home: &Path,
|
||||
env_overrides: &[(&str, Option<&str>)],
|
||||
@@ -180,6 +194,15 @@ impl McpProcess {
|
||||
) -> anyhow::Result<Self> {
|
||||
let program = codex_utils_cargo_bin::cargo_bin("codex-app-server")
|
||||
.context("should find binary for codex-app-server")?;
|
||||
Self::new_with_program_env_and_args(codex_home, &program, env_overrides, args).await
|
||||
}
|
||||
|
||||
async fn new_with_program_env_and_args(
|
||||
codex_home: &Path,
|
||||
program: &Path,
|
||||
env_overrides: &[(&str, Option<&str>)],
|
||||
args: &[&str],
|
||||
) -> anyhow::Result<Self> {
|
||||
let mut cmd = Command::new(program);
|
||||
|
||||
cmd.stdin(Stdio::piped());
|
||||
|
||||
@@ -37,6 +37,7 @@ use core_test_support::skip_if_no_network;
|
||||
use pretty_assertions::assert_eq;
|
||||
use std::collections::BTreeMap;
|
||||
use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
use tempfile::TempDir;
|
||||
use tokio::time::timeout;
|
||||
|
||||
@@ -94,10 +95,9 @@ async fn turn_start_shell_zsh_fork_executes_command_v2() -> Result<()> {
|
||||
(Feature::UnifiedExec, false),
|
||||
(Feature::ShellSnapshot, false),
|
||||
]),
|
||||
&zsh_path,
|
||||
)?;
|
||||
|
||||
let mut mcp = create_zsh_test_mcp_process(&codex_home, &workspace).await?;
|
||||
let mut mcp = create_zsh_test_mcp_process(&codex_home, &workspace, &zsh_path).await?;
|
||||
timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??;
|
||||
|
||||
let start_id = mcp
|
||||
@@ -162,7 +162,7 @@ async fn turn_start_shell_zsh_fork_executes_command_v2() -> Result<()> {
|
||||
};
|
||||
assert_eq!(id, "call-zsh-fork");
|
||||
assert_eq!(status, CommandExecutionStatus::InProgress);
|
||||
assert!(command.starts_with(&zsh_path.display().to_string()));
|
||||
assert!(command.starts_with(&command_packaged_zsh_path(&codex_home).display().to_string()));
|
||||
assert!(command.contains("/bin/sh -c"));
|
||||
assert!(command.contains("sleep 0.01"));
|
||||
assert!(command.contains(&release_marker.display().to_string()));
|
||||
@@ -213,10 +213,9 @@ async fn turn_start_shell_zsh_fork_exec_approval_decline_v2() -> Result<()> {
|
||||
(Feature::UnifiedExec, false),
|
||||
(Feature::ShellSnapshot, false),
|
||||
]),
|
||||
&zsh_path,
|
||||
)?;
|
||||
|
||||
let mut mcp = create_zsh_test_mcp_process(&codex_home, &workspace).await?;
|
||||
let mut mcp = create_zsh_test_mcp_process(&codex_home, &workspace, &zsh_path).await?;
|
||||
timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??;
|
||||
|
||||
let start_id = mcp
|
||||
@@ -346,10 +345,9 @@ async fn turn_start_shell_zsh_fork_exec_approval_cancel_v2() -> Result<()> {
|
||||
(Feature::UnifiedExec, false),
|
||||
(Feature::ShellSnapshot, false),
|
||||
]),
|
||||
&zsh_path,
|
||||
)?;
|
||||
|
||||
let mut mcp = create_zsh_test_mcp_process(&codex_home, &workspace).await?;
|
||||
let mut mcp = create_zsh_test_mcp_process(&codex_home, &workspace, &zsh_path).await?;
|
||||
timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??;
|
||||
|
||||
let start_id = mcp
|
||||
@@ -505,10 +503,9 @@ async fn turn_start_shell_zsh_fork_subcommand_decline_marks_parent_declined_v2()
|
||||
(Feature::UnifiedExec, false),
|
||||
(Feature::ShellSnapshot, false),
|
||||
]),
|
||||
&zsh_path,
|
||||
)?;
|
||||
|
||||
let mut mcp = create_zsh_test_mcp_process(&codex_home, &workspace).await?;
|
||||
let mut mcp = create_zsh_test_mcp_process(&codex_home, &workspace, &zsh_path).await?;
|
||||
timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??;
|
||||
|
||||
let start_id = mcp
|
||||
@@ -603,7 +600,8 @@ async fn turn_start_shell_zsh_fork_subcommand_decline_marks_parent_declined_v2()
|
||||
);
|
||||
approved_subcommand_strings.push(approval_command.to_string());
|
||||
}
|
||||
let is_parent_approval = approval_command.contains(&zsh_path.display().to_string())
|
||||
let is_parent_approval = approval_command
|
||||
.contains(&command_packaged_zsh_path(&codex_home).display().to_string())
|
||||
&& (approval_command.contains(&shell_command)
|
||||
|| (has_first_file && has_second_file)
|
||||
|| approval_command.contains(&parent_shell_hint));
|
||||
@@ -738,9 +736,58 @@ async fn turn_start_shell_zsh_fork_subcommand_decline_marks_parent_declined_v2()
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn create_zsh_test_mcp_process(codex_home: &Path, zdotdir: &Path) -> Result<McpProcess> {
|
||||
async fn create_zsh_test_mcp_process(
|
||||
codex_home: &Path,
|
||||
zdotdir: &Path,
|
||||
zsh_path: &Path,
|
||||
) -> Result<McpProcess> {
|
||||
let app_server = create_test_package_app_server(codex_home, zsh_path)?;
|
||||
let zdotdir = zdotdir.to_string_lossy().into_owned();
|
||||
McpProcess::new_with_env(codex_home, &[("ZDOTDIR", Some(zdotdir.as_str()))]).await
|
||||
McpProcess::new_with_program_and_env(
|
||||
codex_home,
|
||||
&app_server,
|
||||
&[("ZDOTDIR", Some(zdotdir.as_str()))],
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
fn create_test_package_app_server(codex_home: &Path, zsh_path: &Path) -> Result<PathBuf> {
|
||||
let package_dir = codex_home.join("test-package");
|
||||
let bin_dir = package_dir.join("bin");
|
||||
let package_zsh_path = packaged_zsh_path(codex_home);
|
||||
let Some(zsh_bin_dir) = package_zsh_path.parent() else {
|
||||
anyhow::bail!("packaged zsh path should have parent");
|
||||
};
|
||||
std::fs::create_dir_all(&bin_dir)?;
|
||||
std::fs::create_dir_all(zsh_bin_dir)?;
|
||||
std::fs::write(package_dir.join("codex-package.json"), "{}")?;
|
||||
|
||||
let app_server = bin_dir.join("codex-app-server");
|
||||
copy_with_permissions(
|
||||
&codex_utils_cargo_bin::cargo_bin("codex-app-server")?,
|
||||
&app_server,
|
||||
)?;
|
||||
copy_with_permissions(zsh_path, &package_zsh_path)?;
|
||||
Ok(app_server)
|
||||
}
|
||||
|
||||
fn packaged_zsh_path(codex_home: &Path) -> PathBuf {
|
||||
codex_home
|
||||
.join("test-package")
|
||||
.join("codex-resources")
|
||||
.join("zsh")
|
||||
.join("bin")
|
||||
.join("zsh")
|
||||
}
|
||||
|
||||
fn command_packaged_zsh_path(codex_home: &Path) -> PathBuf {
|
||||
let path = packaged_zsh_path(codex_home);
|
||||
std::fs::canonicalize(&path).unwrap_or(path)
|
||||
}
|
||||
|
||||
fn copy_with_permissions(source: &Path, destination: &Path) -> std::io::Result<()> {
|
||||
std::fs::copy(source, destination)?;
|
||||
std::fs::set_permissions(destination, std::fs::metadata(source)?.permissions())
|
||||
}
|
||||
|
||||
fn create_config_toml(
|
||||
@@ -748,7 +795,6 @@ fn create_config_toml(
|
||||
server_uri: &str,
|
||||
approval_policy: &str,
|
||||
feature_flags: &BTreeMap<Feature, bool>,
|
||||
zsh_path: &Path,
|
||||
) -> std::io::Result<()> {
|
||||
let mut features = BTreeMap::from([(Feature::RemoteModels, false)]);
|
||||
for (feature, enabled) in feature_flags {
|
||||
@@ -774,7 +820,6 @@ fn create_config_toml(
|
||||
model = "mock-model"
|
||||
approval_policy = "{approval_policy}"
|
||||
sandbox_mode = "read-only"
|
||||
zsh_path = "{zsh_path}"
|
||||
|
||||
model_provider = "mock_provider"
|
||||
|
||||
@@ -787,9 +832,7 @@ base_url = "{server_uri}/v1"
|
||||
wire_api = "responses"
|
||||
request_max_retries = 0
|
||||
stream_max_retries = 0
|
||||
"#,
|
||||
approval_policy = approval_policy,
|
||||
zsh_path = zsh_path.display()
|
||||
"#
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -291,9 +291,6 @@ pub struct ConfigToml {
|
||||
#[schemars(skip)]
|
||||
pub js_repl_node_module_dirs: Option<Vec<AbsolutePathBuf>>,
|
||||
|
||||
/// Optional absolute path to patched zsh used by zsh-exec-bridge-backed shell execution.
|
||||
pub zsh_path: Option<AbsolutePathBuf>,
|
||||
|
||||
/// Profile to use from the `profiles` map.
|
||||
pub profile: Option<String>,
|
||||
|
||||
|
||||
@@ -48,8 +48,6 @@ pub struct ConfigProfile {
|
||||
/// Deprecated: ignored.
|
||||
#[schemars(skip)]
|
||||
pub js_repl_node_module_dirs: Option<Vec<AbsolutePathBuf>>,
|
||||
/// Optional absolute path to patched zsh used by zsh-exec-bridge-backed shell execution.
|
||||
pub zsh_path: Option<AbsolutePathBuf>,
|
||||
pub experimental_compact_prompt_file: Option<AbsolutePathBuf>,
|
||||
pub include_permissions_instructions: Option<bool>,
|
||||
pub include_apps_instructions: Option<bool>,
|
||||
|
||||
@@ -47,6 +47,7 @@ codex-shell-command = { workspace = true }
|
||||
codex-execpolicy = { workspace = true }
|
||||
codex-git-utils = { workspace = true }
|
||||
codex-hooks = { workspace = true }
|
||||
codex-install-context = { workspace = true }
|
||||
codex-network-proxy = { workspace = true }
|
||||
codex-otel = { workspace = true }
|
||||
codex-plugin = { workspace = true }
|
||||
|
||||
@@ -711,14 +711,6 @@
|
||||
}
|
||||
],
|
||||
"default": null
|
||||
},
|
||||
"zsh_path": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/AbsolutePathBuf"
|
||||
}
|
||||
],
|
||||
"description": "Optional absolute path to patched zsh used by zsh-exec-bridge-backed shell execution."
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
@@ -4986,14 +4978,6 @@
|
||||
],
|
||||
"default": null,
|
||||
"description": "Windows-specific configuration."
|
||||
},
|
||||
"zsh_path": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/AbsolutePathBuf"
|
||||
}
|
||||
],
|
||||
"description": "Optional absolute path to patched zsh used by zsh-exec-bridge-backed shell execution."
|
||||
}
|
||||
},
|
||||
"title": "ConfigToml",
|
||||
|
||||
@@ -4470,6 +4470,25 @@ async fn add_dir_override_extends_workspace_writable_roots() -> std::io::Result<
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn default_zsh_path_sets_runtime_zsh_path() -> std::io::Result<()> {
|
||||
let codex_home = TempDir::new()?;
|
||||
let default_zsh_path = codex_home.path().join("packaged-zsh");
|
||||
|
||||
let config = Config::load_from_base_config_with_overrides(
|
||||
ConfigToml::default(),
|
||||
ConfigOverrides {
|
||||
default_zsh_path: Some(default_zsh_path.abs()),
|
||||
..Default::default()
|
||||
},
|
||||
codex_home.abs(),
|
||||
)
|
||||
.await?;
|
||||
assert_eq!(config.zsh_path, Some(default_zsh_path));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn sqlite_home_defaults_to_codex_home_for_workspace_write() -> std::io::Result<()> {
|
||||
let codex_home = TempDir::new()?;
|
||||
|
||||
@@ -67,6 +67,7 @@ use codex_features::FeaturesToml;
|
||||
use codex_features::MultiAgentV2ConfigToml;
|
||||
use codex_features::NetworkProxyConfigToml;
|
||||
use codex_git_utils::resolve_root_git_project_for_trust;
|
||||
use codex_install_context::InstallContext;
|
||||
use codex_login::AuthManagerConfig;
|
||||
use codex_mcp::McpConfig;
|
||||
use codex_memories_read::memory_root;
|
||||
@@ -1392,11 +1393,18 @@ impl Config {
|
||||
.effective_config()
|
||||
.try_into()
|
||||
.map_err(|err| std::io::Error::new(std::io::ErrorKind::InvalidData, err))?;
|
||||
let default_zsh_path = refreshed_config
|
||||
.zsh_path
|
||||
.clone()
|
||||
.map(AbsolutePathBuf::try_from)
|
||||
.transpose()?;
|
||||
|
||||
Self::load_config_with_layer_stack(
|
||||
LOCAL_FS.as_ref(),
|
||||
cfg,
|
||||
ConfigOverrides {
|
||||
cwd: Some(self.cwd.to_path_buf()),
|
||||
default_zsh_path,
|
||||
..Default::default()
|
||||
},
|
||||
refreshed_config.codex_home.clone(),
|
||||
@@ -2128,7 +2136,7 @@ pub struct ConfigOverrides {
|
||||
pub codex_self_exe: Option<PathBuf>,
|
||||
pub codex_linux_sandbox_exe: Option<PathBuf>,
|
||||
pub main_execve_wrapper_exe: Option<PathBuf>,
|
||||
pub zsh_path: Option<PathBuf>,
|
||||
pub default_zsh_path: Option<AbsolutePathBuf>,
|
||||
pub base_instructions: Option<String>,
|
||||
pub developer_instructions: Option<String>,
|
||||
pub personality: Option<Personality>,
|
||||
@@ -2480,7 +2488,7 @@ impl Config {
|
||||
codex_self_exe,
|
||||
codex_linux_sandbox_exe,
|
||||
main_execve_wrapper_exe,
|
||||
zsh_path: zsh_path_override,
|
||||
default_zsh_path,
|
||||
base_instructions,
|
||||
developer_instructions,
|
||||
personality,
|
||||
@@ -3199,7 +3207,9 @@ impl Config {
|
||||
)
|
||||
.await?;
|
||||
let compact_prompt = compact_prompt.or(file_compact_prompt);
|
||||
let zsh_path = zsh_path_override.or(cfg.zsh_path.map(Into::into));
|
||||
let zsh_path = default_zsh_path
|
||||
.or_else(|| InstallContext::current().bundled_zsh_path())
|
||||
.map(AbsolutePathBuf::into_path_buf);
|
||||
|
||||
let review_model = override_review_model.or(cfg.review_model);
|
||||
|
||||
|
||||
@@ -82,7 +82,7 @@ async fn restricted_read_implicitly_allows_helper_executables() -> std::io::Resu
|
||||
},
|
||||
ConfigOverrides {
|
||||
cwd: Some(cwd.clone()),
|
||||
zsh_path: Some(zsh_path.clone()),
|
||||
default_zsh_path: Some(AbsolutePathBuf::try_from(zsh_path.clone())?),
|
||||
main_execve_wrapper_exe: Some(execve_wrapper),
|
||||
..Default::default()
|
||||
},
|
||||
|
||||
@@ -826,13 +826,13 @@ impl Session {
|
||||
} else if use_zsh_fork_shell {
|
||||
let zsh_path = config.zsh_path.as_ref().ok_or_else(|| {
|
||||
anyhow::anyhow!(
|
||||
"zsh fork feature enabled, but `zsh_path` is not configured; set `zsh_path` in config.toml"
|
||||
"zsh fork feature enabled, but no packaged zsh fork is available for this install"
|
||||
)
|
||||
})?;
|
||||
let zsh_path = zsh_path.to_path_buf();
|
||||
shell::get_shell(shell::ShellType::Zsh, Some(&zsh_path)).ok_or_else(|| {
|
||||
anyhow::anyhow!(
|
||||
"zsh fork feature enabled, but zsh_path `{}` is not usable; set `zsh_path` to a valid zsh executable",
|
||||
"zsh fork feature enabled, but packaged zsh fork `{}` is not usable",
|
||||
zsh_path.display()
|
||||
)
|
||||
})?
|
||||
|
||||
@@ -4319,7 +4319,7 @@ async fn absolute_cwd_update_with_turn_environment_is_allowed() {
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn session_new_fails_when_zsh_fork_enabled_without_zsh_path() {
|
||||
async fn session_new_fails_when_zsh_fork_enabled_without_packaged_zsh() {
|
||||
let codex_home = tempfile::tempdir().expect("create temp dir");
|
||||
let mut config = build_test_config(codex_home.path()).await;
|
||||
config
|
||||
@@ -4420,7 +4420,7 @@ async fn session_new_fails_when_zsh_fork_enabled_without_zsh_path() {
|
||||
Err(err) => err,
|
||||
};
|
||||
let msg = format!("{err:#}");
|
||||
assert!(msg.contains("zsh fork feature enabled, but `zsh_path` is not configured"));
|
||||
assert!(msg.contains("zsh fork feature enabled, but no packaged zsh fork is available"));
|
||||
}
|
||||
|
||||
// todo: use online model info
|
||||
|
||||
@@ -416,7 +416,7 @@ pub async fn run_main(cli: Cli, arg0_paths: Arg0DispatchPaths) -> anyhow::Result
|
||||
codex_self_exe: arg0_paths.codex_self_exe.clone(),
|
||||
codex_linux_sandbox_exe: arg0_paths.codex_linux_sandbox_exe.clone(),
|
||||
main_execve_wrapper_exe: arg0_paths.main_execve_wrapper_exe.clone(),
|
||||
zsh_path: None,
|
||||
default_zsh_path: None,
|
||||
base_instructions: None,
|
||||
developer_instructions: None,
|
||||
personality: None,
|
||||
|
||||
@@ -11,6 +11,7 @@ const PATH_DIRNAME: &str = "codex-path";
|
||||
const RELEASES_DIRNAME: &str = "releases";
|
||||
const RESOURCES_DIRNAME: &str = "codex-resources";
|
||||
const STANDALONE_PACKAGES_DIRNAME: &str = "standalone";
|
||||
const ZSH_DIRNAME: &str = "zsh";
|
||||
static INSTALL_CONTEXT: OnceLock<InstallContext> = OnceLock::new();
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
@@ -166,6 +167,18 @@ impl InstallContext {
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
pub fn bundled_zsh_path(&self) -> Option<AbsolutePathBuf> {
|
||||
if cfg!(windows) {
|
||||
None
|
||||
} else {
|
||||
self.bundled_resource(zsh_resource_path())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn bundled_zsh_bin_dir(&self) -> Option<AbsolutePathBuf> {
|
||||
self.bundled_zsh_path()?.parent()
|
||||
}
|
||||
}
|
||||
|
||||
impl CodexPackageLayout {
|
||||
@@ -260,6 +273,10 @@ fn default_rg_command() -> PathBuf {
|
||||
}
|
||||
}
|
||||
|
||||
fn zsh_resource_path() -> PathBuf {
|
||||
PathBuf::from(ZSH_DIRNAME).join(BIN_DIRNAME).join("zsh")
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -345,6 +362,11 @@ mod tests {
|
||||
fs::write(&exe_path, "")?;
|
||||
fs::write(resources_dir.join(TEST_RESOURCE_NAME), "")?;
|
||||
fs::write(path_dir.join(default_rg_command()), "")?;
|
||||
if !cfg!(windows) {
|
||||
let zsh_path = resources_dir.join(zsh_resource_path());
|
||||
fs::create_dir_all(zsh_path.parent().expect("zsh path should have parent"))?;
|
||||
fs::write(&zsh_path, "")?;
|
||||
}
|
||||
let canonical_package_dir =
|
||||
AbsolutePathBuf::from_absolute_path(package_dir.path().canonicalize()?)?;
|
||||
let canonical_bin_dir = AbsolutePathBuf::from_absolute_path(bin_dir.canonicalize()?)?;
|
||||
@@ -382,6 +404,19 @@ mod tests {
|
||||
context.bundled_resource(TEST_RESOURCE_NAME),
|
||||
Some(canonical_resources_dir.join(TEST_RESOURCE_NAME))
|
||||
);
|
||||
if cfg!(windows) {
|
||||
assert_eq!(context.bundled_zsh_path(), None);
|
||||
assert_eq!(context.bundled_zsh_bin_dir(), None);
|
||||
} else {
|
||||
assert_eq!(
|
||||
context.bundled_zsh_path(),
|
||||
Some(canonical_resources_dir.join(zsh_resource_path()))
|
||||
);
|
||||
assert_eq!(
|
||||
context.bundled_zsh_bin_dir(),
|
||||
Some(canonical_resources_dir.join(ZSH_DIRNAME).join(BIN_DIRNAME))
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ The builder creates a canonical Codex package directory:
|
||||
│ └── <entrypoint>[.exe]
|
||||
├── codex-resources
|
||||
│ ├── bwrap # Linux only
|
||||
│ ├── zsh/bin/zsh # supported Unix targets only
|
||||
│ ├── codex-command-runner.exe # Windows only
|
||||
│ └── codex-windows-sandbox-setup.exe # Windows only
|
||||
└── codex-path
|
||||
@@ -67,3 +68,9 @@ DotSlash manifest at `scripts/codex_package/rg`. Downloaded archives are cached
|
||||
under `$TMPDIR/codex-package/<target>-rg` and are reused only after the recorded
|
||||
size and SHA-256 digest have been verified. Pass `--rg-bin` to use a local
|
||||
ripgrep executable instead.
|
||||
|
||||
The patched zsh fork used by `shell_zsh_fork` is fetched from the DotSlash
|
||||
manifest at `scripts/codex_package/codex-zsh` when the selected target has a
|
||||
matching prebuilt artifact. Downloaded archives are cached under
|
||||
`$TMPDIR/codex-package/<target>-zsh` and installed at
|
||||
`codex-resources/zsh/bin/zsh`.
|
||||
|
||||
@@ -15,6 +15,7 @@ from .targets import TARGET_SPECS
|
||||
from .targets import PackageInputs
|
||||
from .targets import default_target
|
||||
from .targets import resolve_input_path
|
||||
from .zsh import resolve_zsh_bin
|
||||
from .version import read_workspace_version
|
||||
|
||||
|
||||
@@ -161,13 +162,14 @@ def main() -> int:
|
||||
inputs = PackageInputs(
|
||||
entrypoint_bin=source_outputs.entrypoint_bin,
|
||||
rg_bin=resolve_rg_bin(spec, args.rg_bin),
|
||||
zsh_bin=resolve_zsh_bin(spec),
|
||||
bwrap_bin=source_outputs.bwrap_bin,
|
||||
codex_command_runner_bin=source_outputs.codex_command_runner_bin,
|
||||
codex_windows_sandbox_setup_bin=source_outputs.codex_windows_sandbox_setup_bin,
|
||||
)
|
||||
prepare_package_dir(package_dir, force=args.force)
|
||||
build_package_dir(package_dir, version, variant, spec, inputs)
|
||||
validate_package_dir(package_dir, variant, spec)
|
||||
validate_package_dir(package_dir, variant, spec, include_zsh=inputs.zsh_bin is not None)
|
||||
|
||||
for archive_output in args.archive_output:
|
||||
archive_path = archive_output.resolve()
|
||||
|
||||
Executable
+43
@@ -0,0 +1,43 @@
|
||||
#!/usr/bin/env dotslash
|
||||
|
||||
{
|
||||
"name": "codex-zsh",
|
||||
"platforms": {
|
||||
"macos-aarch64": {
|
||||
"size": 358776,
|
||||
"hash": "sha256",
|
||||
"digest": "c6dbb063a0135b947ab1cacc655b2b750874699472f412ec7daba97543a90c3c",
|
||||
"format": "tar.gz",
|
||||
"path": "codex-zsh/bin/zsh",
|
||||
"providers": [
|
||||
{
|
||||
"url": "https://github.com/openai/codex/releases/download/rust-v0.132.0/codex-zsh-aarch64-apple-darwin.tar.gz"
|
||||
}
|
||||
]
|
||||
},
|
||||
"linux-x86_64": {
|
||||
"size": 433413,
|
||||
"hash": "sha256",
|
||||
"digest": "5f42d9fc8e9c8c399a727512002906006ae9de966ea7b3d87ca36b47efc59938",
|
||||
"format": "tar.gz",
|
||||
"path": "codex-zsh/bin/zsh",
|
||||
"providers": [
|
||||
{
|
||||
"url": "https://github.com/openai/codex/releases/download/rust-v0.132.0/codex-zsh-x86_64-unknown-linux-musl.tar.gz"
|
||||
}
|
||||
]
|
||||
},
|
||||
"linux-aarch64": {
|
||||
"size": 411653,
|
||||
"hash": "sha256",
|
||||
"digest": "6c6e32c297425db02b4dbffb10925895875d14647fc3eb2f18767be97dc6a945",
|
||||
"format": "tar.gz",
|
||||
"path": "codex-zsh/bin/zsh",
|
||||
"providers": [
|
||||
{
|
||||
"url": "https://github.com/openai/codex/releases/download/rust-v0.132.0/codex-zsh-aarch64-unknown-linux-musl.tar.gz"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@ from pathlib import Path
|
||||
from .targets import PackageInputs
|
||||
from .targets import PackageVariant
|
||||
from .targets import TargetSpec
|
||||
from .zsh import ZSH_RESOURCE_PATH
|
||||
|
||||
|
||||
LAYOUT_VERSION = 1
|
||||
@@ -50,6 +51,13 @@ def build_package_dir(
|
||||
)
|
||||
copy_executable(inputs.rg_bin, path_dir / spec.rg_name, is_windows=spec.is_windows)
|
||||
|
||||
if inputs.zsh_bin is not None:
|
||||
copy_executable(
|
||||
inputs.zsh_bin,
|
||||
resources_dir / ZSH_RESOURCE_PATH,
|
||||
is_windows=False,
|
||||
)
|
||||
|
||||
if inputs.bwrap_bin is not None:
|
||||
copy_executable(inputs.bwrap_bin, resources_dir / "bwrap", is_windows=False)
|
||||
|
||||
@@ -83,6 +91,8 @@ def validate_package_dir(
|
||||
package_dir: Path,
|
||||
variant: PackageVariant,
|
||||
spec: TargetSpec,
|
||||
*,
|
||||
include_zsh: bool,
|
||||
) -> None:
|
||||
required_dirs = [
|
||||
Path("bin"),
|
||||
@@ -122,6 +132,11 @@ def validate_package_dir(
|
||||
]
|
||||
executable_files = list(required_files)
|
||||
|
||||
if include_zsh:
|
||||
zsh_path = Path("codex-resources") / ZSH_RESOURCE_PATH
|
||||
required_files.append(zsh_path)
|
||||
executable_files.append(zsh_path)
|
||||
|
||||
if spec.is_linux:
|
||||
required_files.append(Path("codex-resources") / "bwrap")
|
||||
executable_files.append(Path("codex-resources") / "bwrap")
|
||||
@@ -148,7 +163,7 @@ def validate_package_dir(
|
||||
|
||||
def copy_executable(src: Path, dest: Path, *, is_windows: bool) -> None:
|
||||
dest.parent.mkdir(parents=True, exist_ok=True)
|
||||
shutil.copy2(src, dest)
|
||||
shutil.copyfile(src, dest)
|
||||
if not is_windows:
|
||||
mode = dest.stat().st_mode
|
||||
dest.chmod(mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)
|
||||
|
||||
@@ -40,6 +40,7 @@ class PackageVariant:
|
||||
class PackageInputs:
|
||||
entrypoint_bin: Path
|
||||
rg_bin: Path
|
||||
zsh_bin: Path | None
|
||||
bwrap_bin: Path | None
|
||||
codex_command_runner_bin: Path | None
|
||||
codex_windows_sandbox_setup_bin: Path | None
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
"""Fetch the patched zsh fork used by shell_zsh_fork."""
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from .dotslash import fetch_dotslash_executable
|
||||
from .targets import REPO_ROOT
|
||||
from .targets import TargetSpec
|
||||
|
||||
|
||||
ZSH_MANIFEST = REPO_ROOT / "scripts" / "codex_package" / "codex-zsh"
|
||||
ZSH_RESOURCE_PATH = Path("zsh") / "bin" / "zsh"
|
||||
|
||||
|
||||
def resolve_zsh_bin(spec: TargetSpec) -> Path | None:
|
||||
return fetch_dotslash_executable(
|
||||
spec,
|
||||
manifest_path=ZSH_MANIFEST,
|
||||
artifact_label="codex-zsh",
|
||||
cache_key=f"{spec.target}-zsh",
|
||||
dest_name="zsh",
|
||||
missing_ok=True,
|
||||
)
|
||||
Reference in New Issue
Block a user