From 16dfc60ad2c9e5f4f9388acaf8cecb3d45aeafe5 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Fri, 13 Feb 2026 20:16:11 +0100 Subject: [PATCH] util: Always use posix_spawn on macOS even with pre_exec hooks (#49090) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Here's some backstory: * on macOS, @cole-miller and I noticed that since roughly Oct 2025, due to some changes to latest macOS Tahoe, for any spawned child process we needed to reset Mach exception ports (https://github.com/zed-industries/zed/issues/36754 + https://github.com/RemiKalbe/zed/commit/6e8f2d2ebe5a93753625a3026aeb996de8cb436b) * the changes in that PR achieve that via `pre_exec` hook on `std::process::Command` which then abandons `posix_spawn` syscall for `fork` + `execve` dance on macOS (we tracked it down in Rust's std implementation) * as it turns out, `fork` + `execve` is pretty expensive on macOS (apparently way more so than on other OSes like Linux) and `fork` takes a process-wide lock on the allocator which is bad * however, since we wanna reset exception ports on the child, the only official way supported by Rust's std is to use `pre_exec` hook * posix_spawn on macOS exposes this tho via a macOS specific extension to that syscall `posix_spawnattr_setexceptionports_np` but there is no way to use that via any standard interfaces in `std::process::Command` * thus, it seemed like a good idea to instead create our own custom Command wrapper that on non-macOS hosts is a zero-cost wrapper of `smol::process::Command`, while on macOS we reimplement the minimum to achieve `smol::process::Command`  with `posix_spawn` under-the-hood Notably, this changeset improves git-blame in very large repos significantly. Release Notes: - Fixed performance spawning child processes on macOS by always forcing `posix_spawn` no matter what. --------- Co-authored-by: Cole Miller --- crates/auto_update/src/auto_update.rs | 16 +- crates/copilot/src/copilot.rs | 2 +- crates/dap_adapters/src/go.rs | 2 +- crates/dap_adapters/src/python.rs | 8 +- crates/dev_container/src/devcontainer_api.rs | 20 +- crates/eval/src/instance.rs | 4 +- crates/extension/src/extension_builder.rs | 22 +- .../src/wasm_host/wit/since_v0_8_0.rs | 2 +- crates/fs/src/fs.rs | 10 +- crates/git/src/blame.rs | 4 +- crates/git/src/commit.rs | 9 +- crates/git/src/repository.rs | 100 +-- crates/gpui/src/platform/linux/platform.rs | 4 +- crates/gpui/src/platform/mac/platform.rs | 4 +- crates/inspector_ui/src/inspector.rs | 4 +- crates/languages/src/go.rs | 6 +- crates/languages/src/python.rs | 14 +- crates/languages/src/rust.rs | 16 +- crates/lsp/src/lsp.rs | 6 +- crates/node_runtime/src/node_runtime.rs | 10 +- crates/project/src/debugger/locators/cargo.rs | 8 +- crates/project/src/debugger/session.rs | 10 +- crates/project/src/environment.rs | 4 +- crates/project/src/lsp_store.rs | 13 +- .../recent_projects/src/remote_connections.rs | 2 +- crates/remote/src/transport.rs | 23 +- crates/remote/src/transport/docker.rs | 12 +- crates/remote/src/transport/ssh.rs | 26 +- crates/remote/src/transport/wsl.rs | 13 +- crates/remote_server/src/server.rs | 12 +- crates/repl/src/kernels/mod.rs | 2 +- crates/repl/src/kernels/native_kernel.rs | 15 +- crates/repl/src/repl_editor.rs | 2 +- crates/supermaven/src/supermaven.rs | 19 +- crates/util/src/command.rs | 176 ++-- crates/util/src/command/darwin.rs | 825 ++++++++++++++++++ crates/util/src/shell_env.rs | 2 +- crates/util/src/util.rs | 2 - 38 files changed, 1136 insertions(+), 293 deletions(-) create mode 100644 crates/util/src/command/darwin.rs diff --git a/crates/auto_update/src/auto_update.rs b/crates/auto_update/src/auto_update.rs index 4c20f8adb531e47262f1d4115737ec1ee211e547..b506b1b31f7e1840a8a78219c8843687ff85cd2c 100644 --- a/crates/auto_update/src/auto_update.rs +++ b/crates/auto_update/src/auto_update.rs @@ -26,7 +26,7 @@ use std::{ sync::Arc, time::{Duration, SystemTime}, }; -use util::command::new_smol_command; +use util::command::new_command; use workspace::Workspace; const SHOULD_SHOW_UPDATE_NOTIFICATION_KEY: &str = "auto-updater-should-show-updated-notification"; @@ -127,7 +127,7 @@ impl Drop for MacOsUnmounter<'_> { let mount_path = mem::take(&mut self.mount_path); self.background_executor .spawn(async move { - let unmount_output = new_smol_command("hdiutil") + let unmount_output = new_command("hdiutil") .args(["detach", "-force"]) .arg(&mount_path) .output() @@ -902,7 +902,7 @@ async fn install_release_linux( .await .context("failed to create directory into which to extract update")?; - let output = new_smol_command("tar") + let output = new_command("tar") .arg("-xzf") .arg(&downloaded_tar_gz) .arg("-C") @@ -937,7 +937,7 @@ async fn install_release_linux( to = PathBuf::from(prefix); } - let output = new_smol_command("rsync") + let output = new_command("rsync") .args(["-av", "--delete"]) .arg(&from) .arg(&to) @@ -969,7 +969,7 @@ async fn install_release_macos( let mut mounted_app_path: OsString = mount_path.join(running_app_filename).into(); mounted_app_path.push("/"); - let output = new_smol_command("hdiutil") + let output = new_command("hdiutil") .args(["attach", "-nobrowse"]) .arg(&downloaded_dmg) .arg("-mountroot") @@ -989,7 +989,7 @@ async fn install_release_macos( background_executor: cx.background_executor(), }; - let output = new_smol_command("rsync") + let output = new_command("rsync") .args(["-av", "--delete"]) .arg(&mounted_app_path) .arg(&running_app_path) @@ -1020,7 +1020,7 @@ async fn cleanup_windows() -> Result<()> { } async fn install_release_windows(downloaded_installer: PathBuf) -> Result> { - let output = new_smol_command(downloaded_installer) + let output = new_command(downloaded_installer) .arg("/verysilent") .arg("/update=true") .arg("!desktopicon") @@ -1058,7 +1058,7 @@ pub async fn finalize_auto_update_on_quit() { .parent() .map(|p| p.join("tools").join("auto_update_helper.exe")) { - let mut command = util::command::new_smol_command(helper); + let mut command = util::command::new_command(helper); command.arg("--launch"); command.arg("false"); if let Ok(mut cmd) = command.spawn() { diff --git a/crates/copilot/src/copilot.rs b/crates/copilot/src/copilot.rs index 9e27a6f871650f9978357031e91f2f897b361f93..a1bbc26b9c44d0f68e120a10bf11d0f3cae19d73 100644 --- a/crates/copilot/src/copilot.rs +++ b/crates/copilot/src/copilot.rs @@ -1402,7 +1402,7 @@ async fn ensure_node_version_for_copilot(node_path: &Path) -> anyhow::Result<()> log::info!("Checking Node.js version for Copilot at: {:?}", node_path); - let output = util::command::new_smol_command(node_path) + let output = util::command::new_command(node_path) .arg("--version") .output() .await diff --git a/crates/dap_adapters/src/go.rs b/crates/dap_adapters/src/go.rs index d3253d5fe250f7228ebddec15a691ac650a19c89..af81f5cca5390d7e72e1805331e25da0a036d9d8 100644 --- a/crates/dap_adapters/src/go.rs +++ b/crates/dap_adapters/src/go.rs @@ -429,7 +429,7 @@ impl DebugAdapter for GoDebugAdapter { let adapter_path = paths::debug_adapters_dir().join(&Self::ADAPTER_NAME); - let install_output = util::command::new_smol_command(&go) + let install_output = util::command::new_command(&go) .env("GO111MODULE", "on") .env("GOBIN", &adapter_path) .args(&["install", "github.com/go-delve/delve/cmd/dlv@latest"]) diff --git a/crates/dap_adapters/src/python.rs b/crates/dap_adapters/src/python.rs index a45e16dc32180e1e4ed3b2ec01c92ad07ffc5e93..96bdde12672cae471edf1d6e603a06413d1f4b21 100644 --- a/crates/dap_adapters/src/python.rs +++ b/crates/dap_adapters/src/python.rs @@ -20,7 +20,7 @@ use std::{ ffi::OsStr, path::{Path, PathBuf}, }; -use util::command::new_smol_command; +use util::command::new_command; use util::{ResultExt, paths::PathStyle, rel_path::RelPath}; enum DebugpyLaunchMode<'a> { @@ -121,7 +121,7 @@ impl PythonDebugAdapter { std::fs::create_dir_all(&download_dir)?; let venv_python = self.base_venv_path(toolchain, delegate).await?; - let installation_succeeded = util::command::new_smol_command(venv_python.as_ref()) + let installation_succeeded = util::command::new_command(venv_python.as_ref()) .args([ "-m", "pip", @@ -259,7 +259,7 @@ impl PythonDebugAdapter { }; let debug_adapter_path = paths::debug_adapters_dir().join(Self::DEBUG_ADAPTER_NAME.as_ref()); - let output = util::command::new_smol_command(&base_python) + let output = util::command::new_command(&base_python) .args(["-m", "venv", "zed_base_venv"]) .current_dir( &debug_adapter_path, @@ -308,7 +308,7 @@ impl PythonDebugAdapter { // Try to detect situations where `python3` exists but is not a real Python interpreter. // Notably, on fresh Windows installs, `python3` is a shim that opens the Microsoft Store app // when run with no arguments, and just fails otherwise. - let Some(output) = new_smol_command(&path) + let Some(output) = new_command(&path) .args(["-c", "print(1 + 2)"]) .output() .await diff --git a/crates/dev_container/src/devcontainer_api.rs b/crates/dev_container/src/devcontainer_api.rs index dbd694d686ff92e9593ca1b44e7a72d30ed6c9c9..15c39dde119be04e5c58f34f268a98935954d6fe 100644 --- a/crates/dev_container/src/devcontainer_api.rs +++ b/crates/dev_container/src/devcontainer_api.rs @@ -7,7 +7,8 @@ use std::{ use node_runtime::NodeRuntime; use serde::Deserialize; use settings::DevContainerConnection; -use smol::{fs, process::Command}; +use smol::fs; +use util::command::Command; use util::rel_path::RelPath; use workspace::Workspace; use worktree::Snapshot; @@ -73,13 +74,12 @@ pub(crate) struct DevContainerCli { impl DevContainerCli { fn command(&self, use_podman: bool) -> Command { let mut command = if let Some(node_runtime_path) = &self.node_runtime_path { - let mut command = util::command::new_smol_command( - node_runtime_path.as_os_str().display().to_string(), - ); + let mut command = + util::command::new_command(node_runtime_path.as_os_str().display().to_string()); command.arg(self.path.display().to_string()); command } else { - util::command::new_smol_command(self.path.display().to_string()) + util::command::new_command(self.path.display().to_string()) }; if use_podman { @@ -297,9 +297,9 @@ fn dev_container_script() -> String { async fn check_for_docker(use_podman: bool) -> Result<(), DevContainerError> { let mut command = if use_podman { - util::command::new_smol_command("podman") + util::command::new_command("podman") } else { - util::command::new_smol_command("docker") + util::command::new_command("docker") }; command.arg("--version"); @@ -315,7 +315,7 @@ async fn check_for_docker(use_podman: bool) -> Result<(), DevContainerError> { pub(crate) async fn ensure_devcontainer_cli( node_runtime: &NodeRuntime, ) -> Result { - let mut command = util::command::new_smol_command(&dev_container_cli()); + let mut command = util::command::new_command(&dev_container_cli()); command.arg("--version"); if let Err(e) = command.output().await { @@ -340,7 +340,7 @@ pub(crate) async fn ensure_devcontainer_cli( ); let mut command = - util::command::new_smol_command(node_runtime_path.as_os_str().display().to_string()); + util::command::new_command(node_runtime_path.as_os_str().display().to_string()); command.arg(datadir_cli_path.display().to_string()); command.arg("--version"); @@ -385,7 +385,7 @@ pub(crate) async fn ensure_devcontainer_cli( }; let mut command = - util::command::new_smol_command(node_runtime_path.as_os_str().display().to_string()); + util::command::new_command(node_runtime_path.as_os_str().display().to_string()); command.arg(datadir_cli_path.display().to_string()); command.arg("--version"); if let Err(e) = command.output().await { diff --git a/crates/eval/src/instance.rs b/crates/eval/src/instance.rs index 77f28f1d67b9a7c1029633776fa1a18c0270920f..15694d6828fb780274a2a6fab736077eadc33e10 100644 --- a/crates/eval/src/instance.rs +++ b/crates/eval/src/instance.rs @@ -26,7 +26,7 @@ use std::{ time::Duration, }; use unindent::Unindent as _; -use util::{ResultExt as _, command::new_smol_command, markdown::MarkdownCodeBlock}; +use util::{ResultExt as _, command::new_command, markdown::MarkdownCodeBlock}; use crate::{ AgentAppState, ToolMetrics, @@ -1072,7 +1072,7 @@ pub fn repo_path_for_url(repos_dir: &Path, repo_url: &str) -> PathBuf { } pub async fn run_git(repo_path: &Path, args: &[&str]) -> Result { - let output = new_smol_command("git") + let output = new_command("git") .current_dir(repo_path) .args(args) .output() diff --git a/crates/extension/src/extension_builder.rs b/crates/extension/src/extension_builder.rs index 6d7c3206e8811c88781348946b07fd0f81dd5c3b..59e204ea9069f1eb67d13443c2e745ba49df3699 100644 --- a/crates/extension/src/extension_builder.rs +++ b/crates/extension/src/extension_builder.rs @@ -11,10 +11,10 @@ use serde::Deserialize; use std::{ env, fs, mem, path::{Path, PathBuf}, - process::Stdio, str::FromStr, sync::Arc, }; +use util::command::Stdio; use wasm_encoder::{ComponentSectionId, Encode as _, RawSection, Section as _}; use wasmparser::Parser; @@ -157,7 +157,7 @@ impl ExtensionBuilder { "compiling Rust crate for extension {}", extension_dir.display() ); - let output = util::command::new_smol_command("cargo") + let output = util::command::new_command("cargo") .args(["build", "--target", RUST_TARGET]) .args(options.release.then_some("--release")) .arg("--target-dir") @@ -263,7 +263,7 @@ impl ExtensionBuilder { ); } else { log::info!("compiling {grammar_name} parser"); - let clang_output = util::command::new_smol_command(&clang_path) + let clang_output = util::command::new_command(&clang_path) .args(["-fPIC", "-shared", "-Os"]) .arg(format!("-Wl,--export=tree_sitter_{grammar_name}")) .arg("-o") @@ -292,7 +292,7 @@ impl ExtensionBuilder { let git_dir = directory.join(".git"); if directory.exists() { - let remotes_output = util::command::new_smol_command("git") + let remotes_output = util::command::new_command("git") .arg("--git-dir") .arg(&git_dir) .args(["remote", "-v"]) @@ -316,7 +316,7 @@ impl ExtensionBuilder { fs::create_dir_all(directory).with_context(|| { format!("failed to create grammar directory {}", directory.display(),) })?; - let init_output = util::command::new_smol_command("git") + let init_output = util::command::new_command("git") .arg("init") .current_dir(directory) .output() @@ -328,7 +328,7 @@ impl ExtensionBuilder { ); } - let remote_add_output = util::command::new_smol_command("git") + let remote_add_output = util::command::new_command("git") .arg("--git-dir") .arg(&git_dir) .args(["remote", "add", "origin", url]) @@ -343,7 +343,7 @@ impl ExtensionBuilder { } } - let fetch_output = util::command::new_smol_command("git") + let fetch_output = util::command::new_command("git") .arg("--git-dir") .arg(&git_dir) .args(["fetch", "--depth", "1", "origin", rev]) @@ -351,7 +351,7 @@ impl ExtensionBuilder { .await .context("failed to execute `git fetch`")?; - let checkout_output = util::command::new_smol_command("git") + let checkout_output = util::command::new_command("git") .arg("--git-dir") .arg(&git_dir) .args(["checkout", rev]) @@ -379,7 +379,7 @@ impl ExtensionBuilder { } async fn install_rust_wasm_target_if_needed(&self) -> Result<()> { - let rustc_output = util::command::new_smol_command("rustc") + let rustc_output = util::command::new_command("rustc") .arg("--print") .arg("sysroot") .output() @@ -397,7 +397,7 @@ impl ExtensionBuilder { return Ok(()); } - let output = util::command::new_smol_command("rustup") + let output = util::command::new_command("rustup") .args(["target", "add", RUST_TARGET]) .stderr(Stdio::piped()) .stdout(Stdio::inherit()) @@ -452,7 +452,7 @@ impl ExtensionBuilder { log::info!("un-tarring wasi-sdk to {}", tar_out_dir.display()); // Shell out to tar to extract the archive - let tar_output = util::command::new_smol_command("tar") + let tar_output = util::command::new_command("tar") .arg("-xzf") .arg(&tar_gz_path) .arg("-C") diff --git a/crates/extension_host/src/wasm_host/wit/since_v0_8_0.rs b/crates/extension_host/src/wasm_host/wit/since_v0_8_0.rs index d22054913ac089ca3596dee17bb43baa942b897e..d7cf29ad0a3fcc7448d5bf44a8a2612d55e07a88 100644 --- a/crates/extension_host/src/wasm_host/wit/since_v0_8_0.rs +++ b/crates/extension_host/src/wasm_host/wit/since_v0_8_0.rs @@ -868,7 +868,7 @@ impl process::Host for WasmState { self.capability_granter .grant_exec(&command.command, &command.args)?; - let output = util::command::new_smol_command(command.command.as_str()) + let output = util::command::new_command(command.command.as_str()) .args(&command.args) .envs(command.env) .output() diff --git a/crates/fs/src/fs.rs b/crates/fs/src/fs.rs index d540dd7e3ec7d4413ef52a2c7f48d7b82578db79..75ce789aafd38b9cc2b97e0fc4ad50a9496797e0 100644 --- a/crates/fs/src/fs.rs +++ b/crates/fs/src/fs.rs @@ -15,7 +15,7 @@ use gpui::Global; use gpui::ReadGlobal as _; use gpui::SharedString; use std::borrow::Cow; -use util::command::new_smol_command; +use util::command::new_command; #[cfg(unix)] use std::os::fd::{AsFd, AsRawFd}; @@ -516,7 +516,7 @@ impl Fs for RealFs { #[cfg(windows)] if smol::fs::metadata(&target).await?.is_dir() { - let status = new_smol_command("cmd") + let status = new_command("cmd") .args(["/C", "mklink", "/J"]) .args([path, target.as_path()]) .status() @@ -1057,7 +1057,7 @@ impl Fs for RealFs { abs_work_directory_path: &Path, fallback_branch_name: String, ) -> Result<()> { - let config = new_smol_command("git") + let config = new_command("git") .current_dir(abs_work_directory_path) .args(&["config", "--global", "--get", "init.defaultBranch"]) .output() @@ -1071,7 +1071,7 @@ impl Fs for RealFs { branch_name = Cow::Borrowed(fallback_branch_name.as_str()); } - new_smol_command("git") + new_command("git") .current_dir(abs_work_directory_path) .args(&["init", "-b"]) .arg(branch_name.trim()) @@ -1091,7 +1091,7 @@ impl Fs for RealFs { let _job_tracker = JobTracker::new(job_info, self.job_event_subscribers.clone()); - let output = new_smol_command("git") + let output = new_command("git") .current_dir(abs_work_directory) .args(&["clone", repo_url]) .output() diff --git a/crates/git/src/blame.rs b/crates/git/src/blame.rs index 548aa321ffd27a53104bbb9ef76ede43b9fa761c..9dc184bf2ac253c8bc24f6203f13d6654ac2b64b 100644 --- a/crates/git/src/blame.rs +++ b/crates/git/src/blame.rs @@ -5,12 +5,12 @@ use anyhow::{Context as _, Result}; use collections::{HashMap, HashSet}; use futures::AsyncWriteExt; use serde::{Deserialize, Serialize}; -use std::process::Stdio; use std::{ops::Range, path::Path}; use text::{LineEnding, Rope}; use time::OffsetDateTime; use time::UtcOffset; use time::macros::format_description; +use util::command::Stdio; pub use git2 as libgit; @@ -61,7 +61,7 @@ async fn run_git_blame( let mut child = { let span = ztracing::debug_span!("spawning git-blame command", path = path.as_unix_str()); let _enter = span.enter(); - util::command::new_smol_command(git_binary) + util::command::new_command(git_binary) .current_dir(working_directory) .arg("blame") .arg("--incremental") diff --git a/crates/git/src/commit.rs b/crates/git/src/commit.rs index 1b450a3dffb9e9956e5b43aa2797ae02f90e731c..3f3526afc4ba8fa146592684a6d3acfc44ce7e73 100644 --- a/crates/git/src/commit.rs +++ b/crates/git/src/commit.rs @@ -80,16 +80,15 @@ pub async fn get_messages(working_directory: &Path, shas: &[Oid]) -> Result Result> { const MARKER: &str = ""; - let mut cmd = util::command::new_smol_command("git"); - cmd.current_dir(working_directory) + let output = util::command::new_command("git") + .current_dir(working_directory) .arg("show") .arg("-s") .arg(format!("--format=%B{}", MARKER)) - .args(shas.iter().map(ToString::to_string)); - let output = cmd + .args(shas.iter().map(ToString::to_string)) .output() .await - .with_context(|| format!("starting git blame process: {:?}", cmd))?; + .context("starting git show process")?; anyhow::ensure!( output.status.success(), "'git show' failed with error {:?}", diff --git a/crates/git/src/repository.rs b/crates/git/src/repository.rs index 05c0da4611ff7d410a1c947c8a42c066a7247b69..304a0f2c95dc8e26eb22368eeb281d235a7f458a 100644 --- a/crates/git/src/repository.rs +++ b/crates/git/src/repository.rs @@ -21,7 +21,7 @@ use text::LineEnding; use std::collections::HashSet; use std::ffi::{OsStr, OsString}; -use std::process::{ExitStatus, Stdio}; +use std::process::ExitStatus; use std::str::FromStr; use std::{ cmp::Ordering, @@ -31,7 +31,7 @@ use std::{ }; use sum_tree::MapSeekTarget; use thiserror::Error; -use util::command::new_smol_command; +use util::command::{Stdio, new_command}; use util::paths::PathStyle; use util::rel_path::RelPath; use util::{ResultExt, paths}; @@ -954,7 +954,7 @@ impl GitRepository for RealGitRepository { self.executor .spawn(async move { let working_directory = working_directory?; - let output = new_smol_command(git_binary_path) + let output = new_command(git_binary_path) .current_dir(&working_directory) .args([ "--no-optional-locks", @@ -993,7 +993,7 @@ impl GitRepository for RealGitRepository { }; let git_binary_path = self.any_git_binary_path.clone(); cx.background_spawn(async move { - let show_output = util::command::new_smol_command(&git_binary_path) + let show_output = util::command::new_command(&git_binary_path) .current_dir(&working_directory) .args([ "--no-optional-locks", @@ -1016,7 +1016,7 @@ impl GitRepository for RealGitRepository { let changes = parse_git_diff_name_status(&show_stdout); let parent_sha = format!("{}^", commit); - let mut cat_file_process = util::command::new_smol_command(&git_binary_path) + let mut cat_file_process = util::command::new_command(&git_binary_path) .current_dir(&working_directory) .args(["--no-optional-locks", "cat-file", "--batch=%(objectsize)"]) .stdin(Stdio::piped()) @@ -1133,7 +1133,7 @@ impl GitRepository for RealGitRepository { ResetMode::Soft => "--soft", }; - let output = new_smol_command(&self.any_git_binary_path) + let output = new_command(&self.any_git_binary_path) .envs(env.iter()) .current_dir(&working_directory?) .args(["reset", mode_flag, &commit]) @@ -1162,7 +1162,7 @@ impl GitRepository for RealGitRepository { return Ok(()); } - let output = new_smol_command(&git_binary_path) + let output = new_command(&git_binary_path) .current_dir(&working_directory?) .envs(env.iter()) .args(["checkout", &commit, "--"]) @@ -1260,7 +1260,7 @@ impl GitRepository for RealGitRepository { let mode = if is_executable { "100755" } else { "100644" }; if let Some(content) = content { - let mut child = new_smol_command(&git_binary_path) + let mut child = new_command(&git_binary_path) .current_dir(&working_directory) .envs(env.iter()) .args(["hash-object", "-w", "--stdin"]) @@ -1276,7 +1276,7 @@ impl GitRepository for RealGitRepository { log::debug!("indexing SHA: {sha}, path {path:?}"); - let output = new_smol_command(&git_binary_path) + let output = new_command(&git_binary_path) .current_dir(&working_directory) .envs(env.iter()) .args(["update-index", "--add", "--cacheinfo", mode, sha]) @@ -1291,7 +1291,7 @@ impl GitRepository for RealGitRepository { ); } else { log::debug!("removing path {path:?} from the index"); - let output = new_smol_command(&git_binary_path) + let output = new_command(&git_binary_path) .current_dir(&working_directory) .envs(env.iter()) .args(["update-index", "--force-remove"]) @@ -1328,7 +1328,7 @@ impl GitRepository for RealGitRepository { self.executor .spawn(async move { let working_directory = working_directory?; - let mut process = new_smol_command(&git_binary_path) + let mut process = new_command(&git_binary_path) .current_dir(&working_directory) .args([ "--no-optional-locks", @@ -1391,7 +1391,7 @@ impl GitRepository for RealGitRepository { let args = git_status_args(path_prefixes); log::debug!("Checking for git status in {path_prefixes:?}"); self.executor.spawn(async move { - let output = new_smol_command(&git_binary_path) + let output = new_command(&git_binary_path) .current_dir(working_directory) .args(args) .output() @@ -1434,7 +1434,7 @@ impl GitRepository for RealGitRepository { self.executor .spawn(async move { - let output = new_smol_command(&git_binary_path) + let output = new_command(&git_binary_path) .current_dir(working_directory) .args(args) .output() @@ -1455,7 +1455,7 @@ impl GitRepository for RealGitRepository { let working_directory = self.working_directory(); self.executor .spawn(async move { - let output = new_smol_command(&git_binary_path) + let output = new_command(&git_binary_path) .current_dir(working_directory?) .args(&["stash", "list", "--pretty=format:%gd%x00%H%x00%ct%x00%s"]) .output() @@ -1496,7 +1496,7 @@ impl GitRepository for RealGitRepository { &fields, ]; let working_directory = working_directory?; - let output = new_smol_command(&git_binary_path) + let output = new_command(&git_binary_path) .current_dir(&working_directory) .args(args) .output() @@ -1514,7 +1514,7 @@ impl GitRepository for RealGitRepository { if branches.is_empty() { let args = vec!["symbolic-ref", "--quiet", "HEAD"]; - let output = new_smol_command(&git_binary_path) + let output = new_command(&git_binary_path) .current_dir(&working_directory) .args(args) .output() @@ -1544,7 +1544,7 @@ impl GitRepository for RealGitRepository { let working_directory = self.working_directory(); self.executor .spawn(async move { - let output = new_smol_command(&git_binary_path) + let output = new_command(&git_binary_path) .current_dir(working_directory?) .args(&["--no-optional-locks", "worktree", "list", "--porcelain"]) .output() @@ -1584,7 +1584,7 @@ impl GitRepository for RealGitRepository { } self.executor .spawn(async move { - let output = new_smol_command(&git_binary_path) + let output = new_command(&git_binary_path) .current_dir(working_directory?) .args(args) .output() @@ -1768,7 +1768,7 @@ impl GitRepository for RealGitRepository { args.push("--"); - let output = new_smol_command(&git_binary_path) + let output = new_command(&git_binary_path) .current_dir(&working_directory) .args(&args) .arg(path.as_unix_str()) @@ -1824,7 +1824,7 @@ impl GitRepository for RealGitRepository { DiffType::HeadToWorktree => None, }; - let output = new_smol_command(&git_binary_path) + let output = new_command(&git_binary_path) .current_dir(&working_directory?) .args(["diff"]) .args(args) @@ -1851,7 +1851,7 @@ impl GitRepository for RealGitRepository { self.executor .spawn(async move { if !paths.is_empty() { - let output = new_smol_command(&git_binary_path) + let output = new_command(&git_binary_path) .current_dir(&working_directory?) .envs(env.iter()) .args(["update-index", "--add", "--remove", "--"]) @@ -1880,7 +1880,7 @@ impl GitRepository for RealGitRepository { self.executor .spawn(async move { if !paths.is_empty() { - let output = new_smol_command(&git_binary_path) + let output = new_command(&git_binary_path) .current_dir(&working_directory?) .envs(env.iter()) .args(["reset", "--quiet", "--"]) @@ -1908,7 +1908,7 @@ impl GitRepository for RealGitRepository { let git_binary_path = self.any_git_binary_path.clone(); self.executor .spawn(async move { - let mut cmd = new_smol_command(&git_binary_path); + let mut cmd = new_command(&git_binary_path); cmd.current_dir(&working_directory?) .envs(env.iter()) .args(["stash", "push", "--quiet"]) @@ -1937,7 +1937,7 @@ impl GitRepository for RealGitRepository { let git_binary_path = self.any_git_binary_path.clone(); self.executor .spawn(async move { - let mut cmd = new_smol_command(git_binary_path); + let mut cmd = new_command(git_binary_path); let mut args = vec!["stash".to_string(), "pop".to_string()]; if let Some(index) = index { args.push(format!("stash@{{{}}}", index)); @@ -1967,7 +1967,7 @@ impl GitRepository for RealGitRepository { let git_binary_path = self.any_git_binary_path.clone(); self.executor .spawn(async move { - let mut cmd = new_smol_command(git_binary_path); + let mut cmd = new_command(git_binary_path); let mut args = vec!["stash".to_string(), "apply".to_string()]; if let Some(index) = index { args.push(format!("stash@{{{}}}", index)); @@ -1997,7 +1997,7 @@ impl GitRepository for RealGitRepository { let git_binary_path = self.any_git_binary_path.clone(); self.executor .spawn(async move { - let mut cmd = new_smol_command(git_binary_path); + let mut cmd = new_command(git_binary_path); let mut args = vec!["stash".to_string(), "drop".to_string()]; if let Some(index) = index { args.push(format!("stash@{{{}}}", index)); @@ -2032,15 +2032,15 @@ impl GitRepository for RealGitRepository { // Note: Do not spawn this command on the background thread, it might pop open the credential helper // which we want to block on. async move { - let mut cmd = new_smol_command(git_binary_path); + let mut cmd = new_command(git_binary_path); cmd.current_dir(&working_directory?) .envs(env.iter()) .args(["commit", "--quiet", "-m"]) .arg(&message.to_string()) .arg("--cleanup=strip") .arg("--no-verify") - .stdout(smol::process::Stdio::piped()) - .stderr(smol::process::Stdio::piped()); + .stdout(Stdio::piped()) + .stderr(Stdio::piped()); if options.amend { cmd.arg("--amend"); @@ -2079,7 +2079,7 @@ impl GitRepository for RealGitRepository { async move { let git_binary_path = git_binary_path.context("git not found on $PATH, can't push")?; let working_directory = working_directory?; - let mut command = new_smol_command(git_binary_path); + let mut command = new_command(git_binary_path); command .envs(env.iter()) .current_dir(&working_directory) @@ -2090,9 +2090,9 @@ impl GitRepository for RealGitRepository { })) .arg(remote_name) .arg(format!("{}:{}", branch_name, remote_branch_name)) - .stdin(smol::process::Stdio::null()) - .stdout(smol::process::Stdio::piped()) - .stderr(smol::process::Stdio::piped()); + .stdin(Stdio::null()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()); run_git_command(env, ask_pass, command, executor).await } @@ -2115,7 +2115,7 @@ impl GitRepository for RealGitRepository { // which we want to block on. async move { let git_binary_path = git_binary_path.context("git not found on $PATH, can't pull")?; - let mut command = new_smol_command(git_binary_path); + let mut command = new_command(git_binary_path); command .envs(env.iter()) .current_dir(&working_directory?) @@ -2128,8 +2128,8 @@ impl GitRepository for RealGitRepository { command .arg(remote_name) .args(branch_name) - .stdout(smol::process::Stdio::piped()) - .stderr(smol::process::Stdio::piped()); + .stdout(Stdio::piped()) + .stderr(Stdio::piped()); run_git_command(env, ask_pass, command, executor).await } @@ -2151,13 +2151,13 @@ impl GitRepository for RealGitRepository { // which we want to block on. async move { let git_binary_path = git_binary_path.context("git not found on $PATH, can't fetch")?; - let mut command = new_smol_command(git_binary_path); + let mut command = new_command(git_binary_path); command .envs(env.iter()) .current_dir(&working_directory?) .args(["fetch", &remote_name]) - .stdout(smol::process::Stdio::piped()) - .stderr(smol::process::Stdio::piped()); + .stdout(Stdio::piped()) + .stderr(Stdio::piped()); run_git_command(env, ask_pass, command, executor).await } @@ -2170,7 +2170,7 @@ impl GitRepository for RealGitRepository { self.executor .spawn(async move { let working_directory = working_directory?; - let output = new_smol_command(&git_binary_path) + let output = new_command(&git_binary_path) .current_dir(&working_directory) .args(["rev-parse", "--abbrev-ref"]) .arg(format!("{branch}@{{push}}")) @@ -2197,7 +2197,7 @@ impl GitRepository for RealGitRepository { self.executor .spawn(async move { let working_directory = working_directory?; - let output = new_smol_command(&git_binary_path) + let output = new_command(&git_binary_path) .current_dir(&working_directory) .args(["config", "--get"]) .arg(format!("branch.{branch}.remote")) @@ -2221,7 +2221,7 @@ impl GitRepository for RealGitRepository { self.executor .spawn(async move { let working_directory = working_directory?; - let output = new_smol_command(&git_binary_path) + let output = new_command(&git_binary_path) .current_dir(&working_directory) .args(["remote", "-v"]) .output() @@ -2280,7 +2280,7 @@ impl GitRepository for RealGitRepository { .spawn(async move { let working_directory = working_directory?; let git_cmd = async |args: &[&str]| -> Result { - let output = new_smol_command(&git_binary_path) + let output = new_command(&git_binary_path) .current_dir(&working_directory) .args(args) .output() @@ -2540,7 +2540,7 @@ impl GitRepository for RealGitRepository { { let hook_abs_path = repository.lock().path().join("hooks").join(hook.as_str()); if hook_abs_path.is_file() { - let output = new_smol_command(&hook_abs_path) + let output = new_command(&hook_abs_path) .envs(env.iter()) .current_dir(&working_directory) .output() @@ -2706,8 +2706,8 @@ async fn run_commit_data_reader( Ok(()) } -async fn read_single_commit_response( - stdout: &mut BufReader, +async fn read_single_commit_response( + stdout: &mut R, sha: &Oid, ) -> Result { let mut header_bytes = Vec::new(); @@ -2964,11 +2964,11 @@ impl GitBinary { Ok(String::from_utf8(output.stdout)?) } - fn build_command(&self, args: impl IntoIterator) -> smol::process::Command + fn build_command(&self, args: impl IntoIterator) -> util::command::Command where S: AsRef, { - let mut command = new_smol_command(&self.git_binary_path); + let mut command = new_command(&self.git_binary_path); command.current_dir(&self.working_directory); command.args(args); if let Some(index_file_path) = self.index_file_path.as_ref() { @@ -2990,7 +2990,7 @@ struct GitBinaryCommandError { async fn run_git_command( env: Arc>, ask_pass: AskPassDelegate, - mut command: smol::process::Command, + mut command: util::command::Command, executor: BackgroundExecutor, ) -> Result { if env.contains_key("GIT_ASKPASS") { @@ -3019,7 +3019,7 @@ async fn run_git_command( async fn run_askpass_command( mut ask_pass: AskPassSession, - git_process: smol::process::Child, + git_process: util::command::Child, ) -> anyhow::Result { select_biased! { result = ask_pass.run().fuse() => { diff --git a/crates/gpui/src/platform/linux/platform.rs b/crates/gpui/src/platform/linux/platform.rs index 429c7c86035f01233e3f7612d35a855e48f2fd5d..f891aa35adbc62d4d384970fb9d997e60cbfc848 100644 --- a/crates/gpui/src/platform/linux/platform.rs +++ b/crates/gpui/src/platform/linux/platform.rs @@ -17,7 +17,7 @@ use anyhow::{Context as _, anyhow}; use calloop::LoopSignal; use futures::channel::oneshot; use util::ResultExt as _; -use util::command::{new_smol_command, new_std_command}; +use util::command::{new_command, new_std_command}; #[cfg(any(feature = "wayland", feature = "x11"))] use xkbcommon::xkb::{self, Keycode, Keysym, State}; @@ -475,7 +475,7 @@ impl Platform for P { let path = path.to_owned(); self.background_executor() .spawn(async move { - let _ = new_smol_command("xdg-open") + let _ = new_command("xdg-open") .arg(path) .spawn() .context("invoking xdg-open") diff --git a/crates/gpui/src/platform/mac/platform.rs b/crates/gpui/src/platform/mac/platform.rs index 041a914251127b202cd29d4ae773c4eaef12964a..310c8958452eca0e67f0cc65720cf49224c78fb0 100644 --- a/crates/gpui/src/platform/mac/platform.rs +++ b/crates/gpui/src/platform/mac/platform.rs @@ -54,7 +54,7 @@ use std::{ }; use util::{ ResultExt, - command::{new_smol_command, new_std_command}, + command::{new_command, new_std_command}, }; #[allow(non_upper_case_globals)] @@ -848,7 +848,7 @@ impl Platform for MacPlatform { .lock() .background_executor .spawn(async move { - if let Some(mut child) = new_smol_command("open") + if let Some(mut child) = new_command("open") .arg(path) .spawn() .context("invoking open command") diff --git a/crates/inspector_ui/src/inspector.rs b/crates/inspector_ui/src/inspector.rs index f1f3ed8d38e4f0947741a0eeb72481e225904929..3c90bd7d6c6d550140df85c4c7547bd5b5700149 100644 --- a/crates/inspector_ui/src/inspector.rs +++ b/crates/inspector_ui/src/inspector.rs @@ -2,7 +2,7 @@ use anyhow::{Context as _, anyhow}; use gpui::{App, DivInspectorState, Inspector, InspectorElementId, IntoElement, Window}; use std::{cell::OnceCell, path::Path, sync::Arc}; use ui::{Label, Tooltip, prelude::*, utils::platform_title_bar_height}; -use util::{ResultExt as _, command::new_smol_command}; +use util::{ResultExt as _, command::new_command}; use workspace::AppState; use crate::div_inspector::DivInspector; @@ -169,7 +169,7 @@ async fn open_zed_source_location( location.column() ); - let output = new_smol_command("zed") + let output = new_command("zed") .arg(&path_arg) .output() .await diff --git a/crates/languages/src/go.rs b/crates/languages/src/go.rs index 7a287923c833f1fc8d92900e3d923bbb871f0a7b..abcb890566d9c0d0d6d9fe85b565c74825775250 100644 --- a/crates/languages/src/go.rs +++ b/crates/languages/src/go.rs @@ -111,7 +111,7 @@ impl LspInstaller for GoLspAdapter { delegate: &dyn LspAdapterDelegate, ) -> Result { let go = delegate.which("go".as_ref()).await.unwrap_or("go".into()); - let go_version_output = util::command::new_smol_command(&go) + let go_version_output = util::command::new_command(&go) .args(["version"]) .output() .await @@ -140,7 +140,7 @@ impl LspInstaller for GoLspAdapter { let gobin_dir = container_dir.join("gobin"); fs::create_dir_all(&gobin_dir).await?; - let install_output = util::command::new_smol_command(go) + let install_output = util::command::new_command(go) .env("GO111MODULE", "on") .env("GOBIN", &gobin_dir) .args(["install", "golang.org/x/tools/gopls@latest"]) @@ -159,7 +159,7 @@ impl LspInstaller for GoLspAdapter { } let installed_binary_path = gobin_dir.join(BINARY); - let version_output = util::command::new_smol_command(&installed_binary_path) + let version_output = util::command::new_command(&installed_binary_path) .arg("version") .output() .await diff --git a/crates/languages/src/python.rs b/crates/languages/src/python.rs index 9f9d31ac861886614e98af1b5cacf84a957e4e19..435dcba16c8a615f17d6a3d401fef6f3ada3b0ef 100644 --- a/crates/languages/src/python.rs +++ b/crates/languages/src/python.rs @@ -30,9 +30,9 @@ use terminal::terminal_settings::TerminalSettings; use smol::lock::OnceCell; use std::cmp::{Ordering, Reverse}; use std::env::consts; -use std::process::Stdio; +use util::command::Stdio; -use util::command::new_smol_command; +use util::command::new_command; use util::fs::{make_file_executable, remove_matching}; use util::paths::PathStyle; use util::rel_path::RelPath; @@ -1620,7 +1620,7 @@ impl PyLspAdapter { let mut path = PathBuf::from(work_dir.as_ref()); path.push("pylsp-venv"); if !path.exists() { - util::command::new_smol_command(python_path) + util::command::new_command(python_path) .arg("-m") .arg("venv") .arg("pylsp-venv") @@ -1641,7 +1641,7 @@ impl PyLspAdapter { // Try to detect situations where `python3` exists but is not a real Python interpreter. // Notably, on fresh Windows installs, `python3` is a shim that opens the Microsoft Store app // when run with no arguments, and just fails otherwise. - let Some(output) = new_smol_command(&path) + let Some(output) = new_command(&path) .args(["-c", "print(1 + 2)"]) .output() .await @@ -1853,7 +1853,7 @@ impl LspInstaller for PyLspAdapter { let venv = self.base_venv(delegate).await.map_err(|e| anyhow!(e))?; let pip_path = venv.join(BINARY_DIR).join("pip3"); ensure!( - util::command::new_smol_command(pip_path.as_path()) + util::command::new_command(pip_path.as_path()) .arg("install") .arg("python-lsp-server[all]") .arg("--upgrade") @@ -1864,7 +1864,7 @@ impl LspInstaller for PyLspAdapter { "python-lsp-server[all] installation failed" ); ensure!( - util::command::new_smol_command(pip_path) + util::command::new_command(pip_path) .arg("install") .arg("pylsp-mypy") .arg("--upgrade") @@ -2421,7 +2421,7 @@ impl LspAdapter for RuffLspAdapter { .0 .ok()?; - let mut command = util::command::new_smol_command(&binary.path); + let mut command = util::command::new_command(&binary.path); command .args(&["config", "--output-format", "json"]) .stdout(Stdio::piped()) diff --git a/crates/languages/src/rust.rs b/crates/languages/src/rust.rs index f79fea7798747c4f7feeca12ccddd4dc9c60d2b9..e463a6c62dd6a0625c8ee7c6d314b296b881157e 100644 --- a/crates/languages/src/rust.rs +++ b/crates/languages/src/rust.rs @@ -19,13 +19,13 @@ use smol::fs::{self}; use std::cmp::Reverse; use std::fmt::Display; use std::ops::Range; -use std::process::Stdio; use std::{ borrow::Cow, path::{Path, PathBuf}, sync::{Arc, LazyLock}, }; use task::{TaskTemplate, TaskTemplates, TaskVariables, VariableName}; +use util::command::Stdio; use util::fs::{make_file_executable, remove_matching}; use util::merge_json_value_into; use util::rel_path::RelPath; @@ -144,13 +144,9 @@ impl RustLspAdapter { use futures::pin_mut; async fn from_ldd_version() -> Option { - use util::command::new_smol_command; + use util::command::new_command; - let ldd_output = new_smol_command("ldd") - .arg("--version") - .output() - .await - .ok()?; + let ldd_output = new_command("ldd").arg("--version").output().await.ok()?; let ldd_version = String::from_utf8_lossy(&ldd_output.stdout); if ldd_version.contains("GNU libc") || ldd_version.contains("GLIBC") { @@ -543,7 +539,7 @@ impl LspAdapter for RustLspAdapter { .0 .ok()?; - let mut command = util::command::new_smol_command(&binary.path); + let mut command = util::command::new_command(&binary.path); command .arg("--print-config-schema") .stdout(Stdio::piped()) @@ -1140,7 +1136,7 @@ async fn target_info_from_abs_path( abs_path: &Path, project_env: Option<&HashMap>, ) -> Option<(Option, Arc)> { - let mut command = util::command::new_smol_command("cargo"); + let mut command = util::command::new_command("cargo"); if let Some(envs) = project_env { command.envs(envs); } @@ -1215,7 +1211,7 @@ async fn human_readable_package_name( package_directory: &Path, project_env: Option<&HashMap>, ) -> Option { - let mut command = util::command::new_smol_command("cargo"); + let mut command = util::command::new_command("cargo"); if let Some(envs) = project_env { command.envs(envs); } diff --git a/crates/lsp/src/lsp.rs b/crates/lsp/src/lsp.rs index 5993bf7e373550be7c28759ab98c5842ac55be6c..e552c21d701cefa8aa1f4b6e14e826892e3b25b6 100644 --- a/crates/lsp/src/lsp.rs +++ b/crates/lsp/src/lsp.rs @@ -22,9 +22,10 @@ use serde_json::{Value, json, value::RawValue}; use smol::{ channel, io::{AsyncBufReadExt, AsyncWriteExt, BufReader}, - process::Child, }; +use util::command::{Child, Stdio}; +use std::path::Path; use std::{ any::TypeId, collections::BTreeSet, @@ -41,7 +42,6 @@ use std::{ task::Poll, time::{Duration, Instant}, }; -use std::{path::Path, process::Stdio}; use util::{ConnectionResult, ResultExt, TryFutureExt, redact}; const JSON_RPC_VERSION: &str = "2.0"; @@ -418,7 +418,7 @@ impl LanguageServer { working_dir, &binary.arguments ); - let mut command = util::command::new_smol_command(&binary.path); + let mut command = util::command::new_command(&binary.path); command .current_dir(working_dir) .args(&binary.arguments) diff --git a/crates/node_runtime/src/node_runtime.rs b/crates/node_runtime/src/node_runtime.rs index acd4ebc5221d0fdb8cbdffd745f9d8f0944c5349..8d838cbe7505a696fbfbeebfa53e96dbd3b19e29 100644 --- a/crates/node_runtime/src/node_runtime.rs +++ b/crates/node_runtime/src/node_runtime.rs @@ -427,7 +427,7 @@ impl ManagedNodeRuntime { let node_ca_certs = env::var(NODE_CA_CERTS_ENV_VAR).unwrap_or_else(|_| String::new()); let valid = if fs::metadata(&node_binary).await.is_ok() { - let result = util::command::new_smol_command(&node_binary) + let result = util::command::new_command(&node_binary) .env(NODE_CA_CERTS_ENV_VAR, node_ca_certs) .arg(npm_file) .arg("--version") @@ -555,7 +555,7 @@ impl NodeRuntimeTrait for ManagedNodeRuntime { ) -> Result { let attempt = || async { let npm_command = self.npm_command(proxy, subcommand, args).await?; - let mut command = util::command::new_smol_command(npm_command.path); + let mut command = util::command::new_command(npm_command.path); command.args(npm_command.args); command.envs(npm_command.env); configure_npm_command(&mut command, directory); @@ -640,7 +640,7 @@ pub struct SystemNodeRuntime { impl SystemNodeRuntime { const MIN_VERSION: semver::Version = Version::new(22, 0, 0); async fn new(node: PathBuf, npm: PathBuf) -> Result { - let output = util::command::new_smol_command(&node) + let output = util::command::new_command(&node) .arg("--version") .output() .await @@ -723,7 +723,7 @@ impl NodeRuntimeTrait for SystemNodeRuntime { args: &[&str], ) -> anyhow::Result { let npm_command = self.npm_command(proxy, subcommand, args).await?; - let mut command = util::command::new_smol_command(npm_command.path); + let mut command = util::command::new_command(npm_command.path); command.args(npm_command.args); command.envs(npm_command.env); configure_npm_command(&mut command, directory); @@ -841,7 +841,7 @@ impl NodeRuntimeTrait for UnavailableNodeRuntime { } } -fn configure_npm_command(command: &mut smol::process::Command, directory: Option<&Path>) { +fn configure_npm_command(command: &mut util::command::Command, directory: Option<&Path>) { if let Some(directory) = directory { command.current_dir(directory); command.args(["--prefix".into(), directory.to_path_buf()]); diff --git a/crates/project/src/debugger/locators/cargo.rs b/crates/project/src/debugger/locators/cargo.rs index 66af7a2fe62fa06478680d23dd35575a64fd3c0f..4206e130a23ac74f3c2a50e376d3b972db81e7b3 100644 --- a/crates/project/src/debugger/locators/cargo.rs +++ b/crates/project/src/debugger/locators/cargo.rs @@ -3,10 +3,10 @@ use async_trait::async_trait; use dap::{DapLocator, DebugRequest, adapters::DebugAdapterName}; use gpui::{BackgroundExecutor, SharedString}; use serde_json::{Value, json}; -use smol::{io::AsyncReadExt, process::Stdio}; +use smol::{io::AsyncReadExt, process::Stdio as SmolStdio}; use std::time::Duration; use task::{BuildTaskDefinition, DebugScenario, ShellBuilder, SpawnInTerminal, TaskTemplate}; -use util::command::new_smol_command; +use util::command::{Stdio, new_command}; pub(crate) struct CargoLocator; @@ -19,7 +19,7 @@ async fn find_best_executable( return executables.first().cloned(); } for executable in executables { - let Some(mut child) = new_smol_command(&executable) + let Some(mut child) = new_command(&executable) .arg("--list") .stdout(Stdio::piped()) .spawn() @@ -136,7 +136,7 @@ impl DapLocator for CargoLocator { ) .envs(build_config.env.iter().map(|(k, v)| (k.clone(), v.clone()))) .current_dir(cwd) - .stdout(Stdio::piped()) + .stdout(SmolStdio::piped()) .spawn()?; let mut output = String::new(); diff --git a/crates/project/src/debugger/session.rs b/crates/project/src/debugger/session.rs index c3fd611e0f8b0f1d9403d91f150fbd185ca61c71..2430d6c1024c61bb9af984c914df9c308c4cb64f 100644 --- a/crates/project/src/debugger/session.rs +++ b/crates/project/src/debugger/session.rs @@ -51,7 +51,6 @@ use std::collections::{BTreeMap, VecDeque}; use std::net::Ipv4Addr; use std::ops::RangeInclusive; use std::path::PathBuf; -use std::process::Stdio; use std::time::Duration; use std::u64; use std::{ @@ -64,7 +63,8 @@ use std::{ use task::SharedTaskContext; use text::{PointUtf16, ToPointUtf16}; use url::Url; -use util::command::new_smol_command; +use util::command::Stdio; +use util::command::new_command; use util::{ResultExt, debug_panic, maybe}; use worktree::Worktree; @@ -2883,7 +2883,7 @@ impl Session { let child = remote_client.update(cx, |client, _| { let command = client.build_forward_ports_command(port_forwards)?; - let child = new_smol_command(command.program) + let child = new_command(command.program) .args(command.args) .envs(command.env) .spawn() @@ -3067,7 +3067,7 @@ struct KillCompanionBrowserParams { async fn spawn_companion( node_runtime: NodeRuntime, cx: &mut AsyncApp, -) -> Result<(u16, smol::process::Child)> { +) -> Result<(u16, util::command::Child)> { let binary_path = node_runtime .binary_path() .await @@ -3089,7 +3089,7 @@ async fn spawn_companion( .to_string_lossy() .to_string(); - let child = new_smol_command(binary_path) + let child = new_command(binary_path) .arg(path) .args([ format!("--listen=127.0.0.1:{port}"), diff --git a/crates/project/src/environment.rs b/crates/project/src/environment.rs index 0d590d6adcb4ff5321de7c44bd419ebceaa80ee6..6a7f0311d04f14941b21ee9e32bda0faec2783b5 100644 --- a/crates/project/src/environment.rs +++ b/crates/project/src/environment.rs @@ -6,7 +6,7 @@ use rpc::proto::{self, REMOTE_SERVER_PROJECT_ID}; use std::{collections::VecDeque, path::Path, sync::Arc}; use task::{Shell, shell_to_proto}; use terminal::terminal_settings::TerminalSettings; -use util::{ResultExt, command::new_smol_command, rel_path::RelPath}; +use util::{ResultExt, command::new_command, rel_path::RelPath}; use worktree::Worktree; use collections::HashMap; @@ -402,7 +402,7 @@ async fn load_direnv_environment( }; let args = &["export", "json"]; - let direnv_output = new_smol_command(&direnv_path) + let direnv_output = new_command(&direnv_path) .args(args) .envs(env) .env("TERM", "dumb") diff --git a/crates/project/src/lsp_store.rs b/crates/project/src/lsp_store.rs index f8f78e95c6a00cc023ed898e6a84ff23ad449167..276ecc70c10f157f6fcef37f508fb4d9c16d3f04 100644 --- a/crates/project/src/lsp_store.rs +++ b/crates/project/src/lsp_store.rs @@ -2373,7 +2373,8 @@ impl LocalLspStore { Some(worktree_path) }); - let mut child = util::command::new_smol_command(command); + use util::command::Stdio; + let mut child = util::command::new_command(command); if let Some(buffer_env) = buffer.env.as_ref() { child.envs(buffer_env); @@ -2394,9 +2395,9 @@ impl LocalLspStore { } let mut child = child - .stdin(smol::process::Stdio::piped()) - .stdout(smol::process::Stdio::piped()) - .stderr(smol::process::Stdio::piped()) + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) .spawn()?; let stdin = child.stdin.as_mut().context("failed to acquire stdin")?; @@ -14100,7 +14101,7 @@ impl LspAdapterDelegate for LocalLspAdapterDelegate { }; let env = self.shell_env().await; - let output = util::command::new_smol_command(&npm) + let output = util::command::new_command(&npm) .args(["root", "-g"]) .envs(env) .current_dir(local_package_directory) @@ -14135,7 +14136,7 @@ impl LspAdapterDelegate for LocalLspAdapterDelegate { if self.fs.is_file(&working_dir).await { working_dir.pop(); } - let output = util::command::new_smol_command(&command.path) + let output = util::command::new_command(&command.path) .args(command.arguments) .envs(command.env.clone().unwrap_or_default()) .current_dir(working_dir) diff --git a/crates/recent_projects/src/remote_connections.rs b/crates/recent_projects/src/remote_connections.rs index 52304c211a4d38ef1e408093d1fbdc3c8f07c1bf..90bce513500eadf77e198cc4745fb0c40dda88d6 100644 --- a/crates/recent_projects/src/remote_connections.rs +++ b/crates/recent_projects/src/remote_connections.rs @@ -422,7 +422,7 @@ async fn path_exists(connection: &Arc, path: &Path) -> boo ) else { return false; }; - let Ok(mut child) = util::command::new_smol_command(command.program) + let Ok(mut child) = util::command::new_command(command.program) .args(command.args) .envs(command.env) .spawn() diff --git a/crates/remote/src/transport.rs b/crates/remote/src/transport.rs index e4707dee815cf86e021c8844a2dfd338710d56b0..09bb22ddbe2b303b767255fd7ab02b54d9b17b2f 100644 --- a/crates/remote/src/transport.rs +++ b/crates/remote/src/transport.rs @@ -10,7 +10,7 @@ use futures::{ }; use gpui::{AppContext as _, AsyncApp, Task}; use rpc::proto::Envelope; -use smol::process::Child; +use util::command::Child; pub mod docker; #[cfg(any(test, feature = "test-support"))] @@ -181,10 +181,9 @@ async fn build_remote_server_from_source( binary_exists_on_server: bool, cx: &mut AsyncApp, ) -> Result> { - use smol::process::{Command, Stdio}; use std::env::VarError; use std::path::Path; - use util::command::new_smol_command; + use util::command::{Command, Stdio, new_command}; if let Ok(path) = std::env::var("ZED_COPY_REMOTE_SERVER") { let path = std::path::PathBuf::from(path); @@ -267,7 +266,7 @@ async fn build_remote_server_from_source( delegate.set_status(Some("Building remote server binary from source"), cx); log::info!("building remote server binary from source"); run_cmd( - new_smol_command("cargo") + new_command("cargo") .current_dir(concat!(env!("CARGO_MANIFEST_DIR"), "/../..")) .args([ "build", @@ -297,18 +296,12 @@ async fn build_remote_server_from_source( .context("rustup not found on $PATH, install rustup (see https://rustup.rs/)")?; delegate.set_status(Some("Adding rustup target for cross-compilation"), cx); log::info!("adding rustup target"); - run_cmd( - new_smol_command(rustup) - .args(["target", "add"]) - .arg(&triple), - ) - .await?; + run_cmd(new_command(rustup).args(["target", "add"]).arg(&triple)).await?; if which("cargo-zigbuild", cx).await?.is_none() { delegate.set_status(Some("Installing cargo-zigbuild for cross-compilation"), cx); log::info!("installing cargo-zigbuild"); - run_cmd(new_smol_command("cargo").args(["install", "--locked", "cargo-zigbuild"])) - .await?; + run_cmd(new_command("cargo").args(["install", "--locked", "cargo-zigbuild"])).await?; } delegate.set_status( @@ -319,7 +312,7 @@ async fn build_remote_server_from_source( ); log::info!("building remote binary from source for {triple} with Zig"); run_cmd( - new_smol_command("cargo") + new_command("cargo") .args([ "zigbuild", "--package", @@ -347,7 +340,7 @@ async fn build_remote_server_from_source( #[cfg(not(target_os = "windows"))] let archive_path = { - run_cmd(new_smol_command("gzip").arg("-f").arg(&bin_path)).await?; + run_cmd(new_command("gzip").arg("-f").arg(&bin_path)).await?; bin_path.with_extension("gz") }; @@ -362,7 +355,7 @@ async fn build_remote_server_from_source( bin_path.display(), zip_path.display(), ); - run_cmd(new_smol_command("powershell.exe").args([ + run_cmd(new_command("powershell.exe").args([ "-NoProfile", "-Command", &compress_command, diff --git a/crates/remote/src/transport/docker.rs b/crates/remote/src/transport/docker.rs index 7a8c22e0fc4389c6702260ac94623349c51a6609..1bcf80880ab17ddea63bd56fb54acfddc48db2dd 100644 --- a/crates/remote/src/transport/docker.rs +++ b/crates/remote/src/transport/docker.rs @@ -9,10 +9,10 @@ use semver::Version as SemanticVersion; use std::time::Instant; use std::{ path::{Path, PathBuf}, - process::Stdio, sync::Arc, }; use util::ResultExt; +use util::command::Stdio; use util::shell::ShellKind; use util::{ paths::{PathStyle, RemotePathBuf}, @@ -400,7 +400,7 @@ impl DockerExecConnection { src_path: String, dst_path: String, ) -> Result<()> { - let mut command = util::command::new_smol_command(&docker_cli); + let mut command = util::command::new_command(&docker_cli); command.arg("cp"); command.arg("-a"); command.arg(&src_path); @@ -419,7 +419,7 @@ impl DockerExecConnection { ); } - let mut chown_command = util::command::new_smol_command(&docker_cli); + let mut chown_command = util::command::new_command(&docker_cli); chown_command.arg("exec"); chown_command.arg(connection_options.container_id); chown_command.arg("chown"); @@ -469,7 +469,7 @@ impl DockerExecConnection { subcommand: &str, args: &[impl AsRef], ) -> Result { - let mut command = util::command::new_smol_command(self.docker_cli()); + let mut command = util::command::new_command(self.docker_cli()); command.arg(subcommand); for arg in args { command.arg(arg.as_ref()); @@ -589,7 +589,7 @@ impl DockerExecConnection { fn kill_inner(&self) -> Result<()> { if let Some(pid) = self.proxy_process.lock().take() { - if let Ok(_) = util::command::new_smol_command("kill") + if let Ok(_) = util::command::new_command("kill") .arg(pid.to_string()) .spawn() { @@ -658,7 +658,7 @@ impl RemoteConnection for DockerExecConnection { if reconnect { docker_args.push("--reconnect".to_string()); } - let mut command = util::command::new_smol_command(self.docker_cli()); + let mut command = util::command::new_command(self.docker_cli()); command .kill_on_drop(true) .stdin(Stdio::piped()) diff --git a/crates/remote/src/transport/ssh.rs b/crates/remote/src/transport/ssh.rs index 811e675ca3aa011cd7713cc018797833b3f98580..8bbc2be57dbf2b47a8fc370702c18931767370de 100644 --- a/crates/remote/src/transport/ssh.rs +++ b/crates/remote/src/transport/ssh.rs @@ -18,10 +18,7 @@ use release_channel::{AppVersion, ReleaseChannel}; use rpc::proto::Envelope; use semver::Version; pub use settings::SshPortForwardOption; -use smol::{ - fs, - process::{self, Child, Stdio}, -}; +use smol::fs; use std::{ net::IpAddr, path::{Path, PathBuf}, @@ -29,6 +26,7 @@ use std::{ time::Instant, }; use tempfile::TempDir; +use util::command::{Child, Stdio}; use util::{ paths::{PathStyle, RemotePathBuf}, rel_path::RelPath, @@ -157,7 +155,7 @@ impl MasterProcess { "-o", ]; - let mut master_process = util::command::new_smol_command("ssh"); + let mut master_process = util::command::new_command("ssh"); master_process .kill_on_drop(true) .stdin(Stdio::null()) @@ -206,7 +204,7 @@ impl MasterProcess { &format!("echo '{}'; exec $0", Self::CONNECTION_ESTABLISHED_MAGIC), ]; - let mut master_process = util::command::new_smol_command("ssh"); + let mut master_process = util::command::new_command("ssh"); master_process .kill_on_drop(true) .stdin(Stdio::null()) @@ -992,8 +990,8 @@ impl SshRemoteConnection { src_path: &Path, dest_path_str: &str, args: Option<&[&str]>, - ) -> process::Command { - let mut command = util::command::new_smol_command("scp"); + ) -> util::command::Command { + let mut command = util::command::new_command("scp"); self.socket.ssh_options(&mut command, false).args( self.socket .connection_options @@ -1012,8 +1010,8 @@ impl SshRemoteConnection { command } - fn build_sftp_command(&self) -> process::Command { - let mut command = util::command::new_smol_command("sftp"); + fn build_sftp_command(&self) -> util::command::Command { + let mut command = util::command::new_command("sftp"); self.socket.ssh_options(&mut command, false).args( self.socket .connection_options @@ -1133,8 +1131,8 @@ impl SshSocket { program: &str, args: &[impl AsRef], allow_pseudo_tty: bool, - ) -> process::Command { - let mut command = util::command::new_smol_command("ssh"); + ) -> util::command::Command { + let mut command = util::command::new_command("ssh"); let program = shell_kind.prepend_command_prefix(program); let mut to_run = shell_kind .try_quote_prefix_aware(&program) @@ -1185,9 +1183,9 @@ impl SshSocket { fn ssh_options<'a>( &self, - command: &'a mut process::Command, + command: &'a mut util::command::Command, include_port_forwards: bool, - ) -> &'a mut process::Command { + ) -> &'a mut util::command::Command { let args = if include_port_forwards { self.connection_options.additional_args() } else { diff --git a/crates/remote/src/transport/wsl.rs b/crates/remote/src/transport/wsl.rs index 45fbe77bd01c6ad21c745d5d63968423c6aea26c..69619f13c2c93ed77b782696f0bd85bcb55e2427 100644 --- a/crates/remote/src/transport/wsl.rs +++ b/crates/remote/src/transport/wsl.rs @@ -11,16 +11,17 @@ use gpui::{App, AppContext as _, AsyncApp, Task}; use release_channel::{AppVersion, ReleaseChannel}; use rpc::proto::Envelope; use semver::Version; -use smol::{fs, process}; +use smol::fs; use std::{ ffi::OsStr, fmt::Write as _, path::{Path, PathBuf}, - process::Stdio, sync::Arc, time::Instant, }; + use util::{ + command::Stdio, paths::{PathStyle, RemotePathBuf}, rel_path::RelPath, shell::{Shell, ShellKind}, @@ -595,7 +596,9 @@ pub fn wsl_path_to_windows_path( } } -fn run_wsl_command_impl(mut command: process::Command) -> impl Future> { +fn run_wsl_command_impl( + mut command: util::command::Command, +) -> impl Future> { async move { let output = command .output() @@ -622,8 +625,8 @@ fn wsl_command_impl( program: &str, args: &[impl AsRef], exec: bool, -) -> process::Command { - let mut command = util::command::new_smol_command("wsl.exe"); +) -> util::command::Command { + let mut command = util::command::new_command("wsl.exe"); if let Some(user) = &options.user { command.arg("--user").arg(user); diff --git a/crates/remote_server/src/server.rs b/crates/remote_server/src/server.rs index f656d476a7c47139272717a45828fc0dfa9ce4c5..f88cf9faa70c96ddcfdbbc3d291195e57597c674 100644 --- a/crates/remote_server/src/server.rs +++ b/crates/remote_server/src/server.rs @@ -56,7 +56,7 @@ use std::{ sync::{Arc, LazyLock}, }; use thiserror::Error; -use util::{ResultExt, command::new_smol_command}; +use util::{ResultExt, command::new_command}; #[derive(Subcommand)] pub enum Commands { @@ -945,11 +945,11 @@ fn spawn_server_windows(binary_name: &Path, paths: &ServerPaths) -> Result<(), S #[cfg(not(windows))] fn spawn_server_normal(binary_name: &Path, paths: &ServerPaths) -> Result<(), SpawnServerError> { - let mut server_process = new_smol_command(binary_name); + let mut server_process = new_command(binary_name); server_process - .stdin(std::process::Stdio::null()) - .stdout(std::process::Stdio::null()) - .stderr(std::process::Stdio::null()) + .stdin(util::command::Stdio::null()) + .stdout(util::command::Stdio::null()) + .stderr(util::command::Stdio::null()) .arg("run") .arg("--log-file") .arg(&paths.log_file) @@ -977,7 +977,7 @@ pub struct CheckPidError { pid: u32, } async fn check_server_running(pid: u32) -> std::io::Result { - new_smol_command("kill") + new_command("kill") .arg("-0") .arg(pid.to_string()) .output() diff --git a/crates/repl/src/kernels/mod.rs b/crates/repl/src/kernels/mod.rs index 03d352ecac15cd61d8b9592ecf36b9110913c86e..b0c768904153545ba9ee2dab767f101547f3cec1 100644 --- a/crates/repl/src/kernels/mod.rs +++ b/crates/repl/src/kernels/mod.rs @@ -190,7 +190,7 @@ pub fn python_env_kernel_specifications( let python_path = toolchain.path.to_string(); let environment_kind = extract_environment_kind(&toolchain.as_json); - let has_ipykernel = util::command::new_smol_command(&python_path) + let has_ipykernel = util::command::new_command(&python_path) .args(&["-c", "import ipykernel"]) .output() .await diff --git a/crates/repl/src/kernels/native_kernel.rs b/crates/repl/src/kernels/native_kernel.rs index bf7a1effc059d5b9fa67e8cd926d27b0c9137923..c2696cd24d550b80bb788c90822130606d768983 100644 --- a/crates/repl/src/kernels/native_kernel.rs +++ b/crates/repl/src/kernels/native_kernel.rs @@ -12,7 +12,7 @@ use jupyter_protocol::{ }; use project::Fs; use runtimelib::{RuntimeError, dirs}; -use smol::{net::TcpListener, process::Command}; +use smol::net::TcpListener; use std::{ env, fmt::Debug, @@ -20,6 +20,7 @@ use std::{ path::PathBuf, sync::Arc, }; +use util::command::Command; use uuid::Uuid; use super::{KernelSession, RunningKernel}; @@ -52,7 +53,7 @@ impl LocalKernelSpecification { self.name ); - let mut cmd = util::command::new_smol_command(&argv[0]); + let mut cmd = util::command::new_command(&argv[0]); for arg in &argv[1..] { if arg == "{connection_file}" { @@ -85,7 +86,7 @@ async fn peek_ports(ip: IpAddr) -> Result<[u16; 5]> { } pub struct NativeRunningKernel { - pub process: smol::process::Child, + pub process: util::command::Child, connection_path: PathBuf, _process_status_task: Option>, pub working_directory: PathBuf, @@ -143,9 +144,9 @@ impl NativeRunningKernel { let mut process = cmd .current_dir(&working_directory) - .stdout(std::process::Stdio::piped()) - .stderr(std::process::Stdio::piped()) - .stdin(std::process::Stdio::piped()) + .stdout(util::command::Stdio::piped()) + .stderr(util::command::Stdio::piped()) + .stdin(util::command::Stdio::piped()) .kill_on_drop(true) .spawn() .context("failed to start the kernel process")?; @@ -490,7 +491,7 @@ pub async fn local_kernel_specifications(fs: Arc) -> Result, cx: &mut Context, ) -> Result { - let mut process = util::command::new_smol_command(&binary_path) + let mut process = util::command::new_command(&binary_path) .arg("stdio") .stdin(Stdio::piped()) .stdout(Stdio::piped()) @@ -308,9 +307,9 @@ impl SupermavenAgent { }) } - async fn handle_outgoing_messages( + async fn handle_outgoing_messages( mut outgoing: mpsc::UnboundedReceiver, - mut stdin: ChildStdin, + mut stdin: W, ) -> Result<()> { while let Some(message) = outgoing.next().await { let bytes = serde_json::to_vec(&message)?; @@ -320,9 +319,9 @@ impl SupermavenAgent { Ok(()) } - async fn handle_incoming_messages( + async fn handle_incoming_messages( this: WeakEntity, - stdout: ChildStdout, + stdout: R, cx: &mut AsyncApp, ) -> Result<()> { const MESSAGE_PREFIX: &str = "SM-MESSAGE "; diff --git a/crates/util/src/command.rs b/crates/util/src/command.rs index 40f1ec323f6dd799bdda07da2540741d46c99cea..44db592640bc70362b924ffca674fd02a4126f3a 100644 --- a/crates/util/src/command.rs +++ b/crates/util/src/command.rs @@ -1,8 +1,20 @@ use std::ffi::OsStr; +#[cfg(not(target_os = "macos"))] +use std::path::Path; + +#[cfg(target_os = "macos")] +mod darwin; + +#[cfg(target_os = "macos")] +pub use darwin::{Child, Command, Stdio}; #[cfg(target_os = "windows")] const CREATE_NO_WINDOW: u32 = 0x0800_0000_u32; +pub fn new_command(program: impl AsRef) -> Command { + Command::new(program) +} + #[cfg(target_os = "windows")] pub fn new_std_command(program: impl AsRef) -> std::process::Command { use std::os::windows::process::CommandExt; @@ -17,86 +29,104 @@ pub fn new_std_command(program: impl AsRef) -> std::process::Command { std::process::Command::new(program) } -#[cfg(target_os = "windows")] -pub fn new_smol_command(program: impl AsRef) -> smol::process::Command { - use smol::process::windows::CommandExt; +#[cfg(not(target_os = "macos"))] +pub type Child = smol::process::Child; - let mut command = smol::process::Command::new(program); - command.creation_flags(CREATE_NO_WINDOW); - command -} +#[cfg(not(target_os = "macos"))] +pub use std::process::Stdio; -#[cfg(target_os = "macos")] -pub fn new_smol_command(program: impl AsRef) -> smol::process::Command { - use std::os::unix::process::CommandExt; - - // Create a std::process::Command first so we can use pre_exec - let mut std_cmd = std::process::Command::new(program); - - // WORKAROUND: Reset exception ports before exec to prevent inheritance of - // crash handler exception ports. Due to a timing issue, child processes can - // inherit the parent's exception ports before they're fully stabilized, - // which can block child process spawning. - // See: https://github.com/zed-industries/zed/issues/36754 - unsafe { - std_cmd.pre_exec(|| { - // Reset all exception ports to system defaults for this task. - // This prevents the child from inheriting the parent's crash handler - // exception ports. - reset_exception_ports(); - Ok(()) - }); +#[cfg(not(target_os = "macos"))] +#[derive(Debug)] +pub struct Command(smol::process::Command); + +#[cfg(not(target_os = "macos"))] +impl Command { + #[inline] + pub fn new(program: impl AsRef) -> Self { + #[cfg(target_os = "windows")] + { + use smol::process::windows::CommandExt; + let mut cmd = smol::process::Command::new(program); + cmd.creation_flags(CREATE_NO_WINDOW); + Self(cmd) + } + #[cfg(not(target_os = "windows"))] + Self(smol::process::Command::new(program)) } - // Convert to async_process::Command via From trait - smol::process::Command::from(std_cmd) -} + pub fn arg(&mut self, arg: impl AsRef) -> &mut Self { + self.0.arg(arg); + self + } -#[cfg(all(not(target_os = "windows"), not(target_os = "macos")))] -pub fn new_smol_command(program: impl AsRef) -> smol::process::Command { - smol::process::Command::new(program) -} + pub fn args(&mut self, args: I) -> &mut Self + where + I: IntoIterator, + S: AsRef, + { + self.0.args(args); + self + } -#[cfg(target_os = "macos")] -pub fn reset_exception_ports() { - use mach2::exception_types::{ - EXC_MASK_ALL, EXCEPTION_DEFAULT, exception_behavior_t, exception_mask_t, - }; - use mach2::kern_return::{KERN_SUCCESS, kern_return_t}; - use mach2::mach_types::task_t; - use mach2::port::{MACH_PORT_NULL, mach_port_t}; - use mach2::thread_status::{THREAD_STATE_NONE, thread_state_flavor_t}; - use mach2::traps::mach_task_self; - - // FFI binding for task_set_exception_ports (not exposed by mach2 crate) - unsafe extern "C" { - fn task_set_exception_ports( - task: task_t, - exception_mask: exception_mask_t, - new_port: mach_port_t, - behavior: exception_behavior_t, - new_flavor: thread_state_flavor_t, - ) -> kern_return_t; + pub fn env(&mut self, key: impl AsRef, val: impl AsRef) -> &mut Self { + self.0.env(key, val); + self } - unsafe { - let task = mach_task_self(); - // Reset all exception ports to MACH_PORT_NULL (system default) - // This prevents the child process from inheriting the parent's crash handler - let kr = task_set_exception_ports( - task, - EXC_MASK_ALL, - MACH_PORT_NULL, - EXCEPTION_DEFAULT as exception_behavior_t, - THREAD_STATE_NONE, - ); - - if kr != KERN_SUCCESS { - // Log but don't fail - the process can still work without this workaround - eprintln!( - "Warning: failed to reset exception ports in child process (kern_return: {})", - kr - ); - } + pub fn envs(&mut self, vars: I) -> &mut Self + where + I: IntoIterator, + K: AsRef, + V: AsRef, + { + self.0.envs(vars); + self + } + + pub fn env_remove(&mut self, key: impl AsRef) -> &mut Self { + self.0.env_remove(key); + self + } + + pub fn env_clear(&mut self) -> &mut Self { + self.0.env_clear(); + self + } + + pub fn current_dir(&mut self, dir: impl AsRef) -> &mut Self { + self.0.current_dir(dir); + self + } + + pub fn stdin(&mut self, cfg: impl Into) -> &mut Self { + self.0.stdin(cfg.into()); + self + } + + pub fn stdout(&mut self, cfg: impl Into) -> &mut Self { + self.0.stdout(cfg.into()); + self + } + + pub fn stderr(&mut self, cfg: impl Into) -> &mut Self { + self.0.stderr(cfg.into()); + self + } + + pub fn kill_on_drop(&mut self, kill_on_drop: bool) -> &mut Self { + self.0.kill_on_drop(kill_on_drop); + self + } + + pub fn spawn(&mut self) -> std::io::Result { + self.0.spawn() + } + + pub async fn output(&mut self) -> std::io::Result { + self.0.output().await + } + + pub async fn status(&mut self) -> std::io::Result { + self.0.status().await } } diff --git a/crates/util/src/command/darwin.rs b/crates/util/src/command/darwin.rs new file mode 100644 index 0000000000000000000000000000000000000000..347fc8180ed9325d4f36a3fcce2f3c68964321d5 --- /dev/null +++ b/crates/util/src/command/darwin.rs @@ -0,0 +1,825 @@ +use mach2::exception_types::{ + EXC_MASK_ALL, EXCEPTION_DEFAULT, exception_behavior_t, exception_mask_t, +}; +use mach2::port::{MACH_PORT_NULL, mach_port_t}; +use mach2::thread_status::{THREAD_STATE_NONE, thread_state_flavor_t}; +use smol::Unblock; +use std::collections::BTreeMap; +use std::ffi::{CString, OsStr, OsString}; +use std::io; +use std::os::unix::ffi::OsStrExt; +use std::os::unix::io::FromRawFd; +use std::os::unix::process::ExitStatusExt; +use std::path::{Path, PathBuf}; +use std::process::{ExitStatus, Output}; +use std::ptr; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] +pub enum Stdio { + /// A new pipe should be arranged to connect the parent and child processes. + #[default] + Piped, + /// The child inherits from the corresponding parent descriptor. + Inherit, + /// This stream will be ignored (redirected to `/dev/null`). + Null, +} + +impl Stdio { + pub fn piped() -> Self { + Self::Piped + } + + pub fn inherit() -> Self { + Self::Inherit + } + + pub fn null() -> Self { + Self::Null + } +} + +unsafe extern "C" { + fn posix_spawnattr_setexceptionports_np( + attr: *mut libc::posix_spawnattr_t, + mask: exception_mask_t, + new_port: mach_port_t, + behavior: exception_behavior_t, + new_flavor: thread_state_flavor_t, + ) -> libc::c_int; + + fn posix_spawn_file_actions_addchdir_np( + file_actions: *mut libc::posix_spawn_file_actions_t, + path: *const libc::c_char, + ) -> libc::c_int; + + fn posix_spawn_file_actions_addinherit_np( + file_actions: *mut libc::posix_spawn_file_actions_t, + filedes: libc::c_int, + ) -> libc::c_int; + + static environ: *const *mut libc::c_char; +} + +#[derive(Debug)] +pub struct Command { + program: OsString, + args: Vec, + envs: BTreeMap>, + env_clear: bool, + current_dir: Option, + stdin_cfg: Option, + stdout_cfg: Option, + stderr_cfg: Option, + kill_on_drop: bool, +} + +impl Command { + pub fn new(program: impl AsRef) -> Self { + Self { + program: program.as_ref().to_owned(), + args: Vec::new(), + envs: BTreeMap::new(), + env_clear: false, + current_dir: None, + stdin_cfg: None, + stdout_cfg: None, + stderr_cfg: None, + kill_on_drop: false, + } + } + + pub fn arg(&mut self, arg: impl AsRef) -> &mut Self { + self.args.push(arg.as_ref().to_owned()); + self + } + + pub fn args(&mut self, args: I) -> &mut Self + where + I: IntoIterator, + S: AsRef, + { + self.args + .extend(args.into_iter().map(|a| a.as_ref().to_owned())); + self + } + + pub fn env(&mut self, key: impl AsRef, val: impl AsRef) -> &mut Self { + self.envs + .insert(key.as_ref().to_owned(), Some(val.as_ref().to_owned())); + self + } + + pub fn envs(&mut self, vars: I) -> &mut Self + where + I: IntoIterator, + K: AsRef, + V: AsRef, + { + for (key, val) in vars { + self.envs + .insert(key.as_ref().to_owned(), Some(val.as_ref().to_owned())); + } + self + } + + pub fn env_remove(&mut self, key: impl AsRef) -> &mut Self { + let key = key.as_ref().to_owned(); + if self.env_clear { + self.envs.remove(&key); + } else { + self.envs.insert(key, None); + } + self + } + + pub fn env_clear(&mut self) -> &mut Self { + self.env_clear = true; + self.envs.clear(); + self + } + + pub fn current_dir(&mut self, dir: impl AsRef) -> &mut Self { + self.current_dir = Some(dir.as_ref().to_owned()); + self + } + + pub fn stdin(&mut self, cfg: Stdio) -> &mut Self { + self.stdin_cfg = Some(cfg); + self + } + + pub fn stdout(&mut self, cfg: Stdio) -> &mut Self { + self.stdout_cfg = Some(cfg); + self + } + + pub fn stderr(&mut self, cfg: Stdio) -> &mut Self { + self.stderr_cfg = Some(cfg); + self + } + + pub fn kill_on_drop(&mut self, kill_on_drop: bool) -> &mut Self { + self.kill_on_drop = kill_on_drop; + self + } + + pub fn spawn(&mut self) -> io::Result { + let current_dir = self + .current_dir + .as_deref() + .unwrap_or_else(|| Path::new(".")); + + // Optimization: if no environment modifications were requested, pass None + // to spawn_posix so it uses the `environ` global directly, avoiding a + // full copy of the environment. This matches std::process::Command behavior. + let envs = if self.env_clear || !self.envs.is_empty() { + let mut result = BTreeMap::::new(); + if !self.env_clear { + for (key, val) in std::env::vars_os() { + result.insert(key, val); + } + } + for (key, maybe_val) in &self.envs { + if let Some(val) = maybe_val { + result.insert(key.clone(), val.clone()); + } else { + result.remove(key); + } + } + Some(result.into_iter().collect::>()) + } else { + None + }; + + spawn_posix_spawn( + &self.program, + &self.args, + current_dir, + envs.as_deref(), + self.stdin_cfg.unwrap_or_default(), + self.stdout_cfg.unwrap_or_default(), + self.stderr_cfg.unwrap_or_default(), + self.kill_on_drop, + ) + } + + pub async fn output(&mut self) -> io::Result { + self.stdin_cfg.get_or_insert(Stdio::null()); + self.stdout_cfg.get_or_insert(Stdio::piped()); + self.stderr_cfg.get_or_insert(Stdio::piped()); + + let child = self.spawn()?; + child.output().await + } + + pub async fn status(&mut self) -> io::Result { + let mut child = self.spawn()?; + child.status().await + } +} + +#[derive(Debug)] +pub struct Child { + pid: libc::pid_t, + pub stdin: Option>, + pub stdout: Option>, + pub stderr: Option>, + kill_on_drop: bool, + status: Option, +} + +impl Drop for Child { + fn drop(&mut self) { + if self.kill_on_drop && self.status.is_none() { + let _ = self.kill(); + } + } +} + +impl Child { + pub fn id(&self) -> u32 { + self.pid as u32 + } + + pub fn kill(&mut self) -> io::Result<()> { + let result = unsafe { libc::kill(self.pid, libc::SIGKILL) }; + if result == -1 { + Err(io::Error::last_os_error()) + } else { + Ok(()) + } + } + + pub fn try_status(&mut self) -> io::Result> { + if let Some(status) = self.status { + return Ok(Some(status)); + } + + let mut status: libc::c_int = 0; + let result = unsafe { libc::waitpid(self.pid, &mut status, libc::WNOHANG) }; + + if result == -1 { + Err(io::Error::last_os_error()) + } else if result == 0 { + Ok(None) + } else { + let exit_status = ExitStatus::from_raw(status); + self.status = Some(exit_status); + Ok(Some(exit_status)) + } + } + + pub fn status( + &mut self, + ) -> impl std::future::Future> + Send + 'static { + self.stdin.take(); + + let pid = self.pid; + let cached_status = self.status; + + async move { + if let Some(status) = cached_status { + return Ok(status); + } + + smol::unblock(move || { + let mut status: libc::c_int = 0; + let result = unsafe { libc::waitpid(pid, &mut status, 0) }; + if result == -1 { + Err(io::Error::last_os_error()) + } else { + Ok(ExitStatus::from_raw(status)) + } + }) + .await + } + } + + pub async fn output(mut self) -> io::Result { + use futures_lite::AsyncReadExt; + + let status = self.status(); + + let stdout = self.stdout.take(); + let stdout_future = async move { + let mut data = Vec::new(); + if let Some(mut stdout) = stdout { + stdout.read_to_end(&mut data).await?; + } + io::Result::Ok(data) + }; + + let stderr = self.stderr.take(); + let stderr_future = async move { + let mut data = Vec::new(); + if let Some(mut stderr) = stderr { + stderr.read_to_end(&mut data).await?; + } + io::Result::Ok(data) + }; + + let (stdout_data, stderr_data) = + futures_lite::future::try_zip(stdout_future, stderr_future).await?; + let status = status.await?; + + Ok(Output { + status, + stdout: stdout_data, + stderr: stderr_data, + }) + } +} + +fn spawn_posix_spawn( + program: &OsStr, + args: &[OsString], + current_dir: &Path, + envs: Option<&[(OsString, OsString)]>, + stdin_cfg: Stdio, + stdout_cfg: Stdio, + stderr_cfg: Stdio, + kill_on_drop: bool, +) -> io::Result { + let program_cstr = CString::new(program.as_bytes()).map_err(|_| invalid_input_error())?; + + let current_dir_cstr = + CString::new(current_dir.as_os_str().as_bytes()).map_err(|_| invalid_input_error())?; + + let mut argv_cstrs = vec![program_cstr.clone()]; + for arg in args { + let cstr = CString::new(arg.as_bytes()).map_err(|_| invalid_input_error())?; + argv_cstrs.push(cstr); + } + let mut argv_ptrs: Vec<*mut libc::c_char> = argv_cstrs + .iter() + .map(|s| s.as_ptr() as *mut libc::c_char) + .collect(); + argv_ptrs.push(ptr::null_mut()); + + let envp: Vec = if let Some(envs) = envs { + envs.iter() + .map(|(key, value)| { + let mut env_str = key.as_bytes().to_vec(); + env_str.push(b'='); + env_str.extend_from_slice(value.as_bytes()); + CString::new(env_str) + }) + .collect::, _>>() + .map_err(|_| invalid_input_error())? + } else { + Vec::new() + }; + let mut envp_ptrs: Vec<*mut libc::c_char> = envp + .iter() + .map(|s| s.as_ptr() as *mut libc::c_char) + .collect(); + envp_ptrs.push(ptr::null_mut()); + + let (stdin_read, stdin_write) = match stdin_cfg { + Stdio::Piped => { + let (r, w) = create_pipe()?; + (Some(r), Some(w)) + } + Stdio::Null => { + let fd = open_dev_null(libc::O_RDONLY)?; + (Some(fd), None) + } + Stdio::Inherit => (None, None), + }; + + let (stdout_read, stdout_write) = match stdout_cfg { + Stdio::Piped => { + let (r, w) = create_pipe()?; + (Some(r), Some(w)) + } + Stdio::Null => { + let fd = open_dev_null(libc::O_WRONLY)?; + (None, Some(fd)) + } + Stdio::Inherit => (None, None), + }; + + let (stderr_read, stderr_write) = match stderr_cfg { + Stdio::Piped => { + let (r, w) = create_pipe()?; + (Some(r), Some(w)) + } + Stdio::Null => { + let fd = open_dev_null(libc::O_WRONLY)?; + (None, Some(fd)) + } + Stdio::Inherit => (None, None), + }; + + let mut attr: libc::posix_spawnattr_t = ptr::null_mut(); + let mut file_actions: libc::posix_spawn_file_actions_t = ptr::null_mut(); + + unsafe { + cvt_nz(libc::posix_spawnattr_init(&mut attr))?; + cvt_nz(libc::posix_spawn_file_actions_init(&mut file_actions))?; + + cvt_nz(libc::posix_spawnattr_setflags( + &mut attr, + libc::POSIX_SPAWN_CLOEXEC_DEFAULT as libc::c_short, + ))?; + + cvt_nz(posix_spawnattr_setexceptionports_np( + &mut attr, + EXC_MASK_ALL, + MACH_PORT_NULL, + EXCEPTION_DEFAULT as exception_behavior_t, + THREAD_STATE_NONE, + ))?; + + cvt_nz(posix_spawn_file_actions_addchdir_np( + &mut file_actions, + current_dir_cstr.as_ptr(), + ))?; + + if let Some(fd) = stdin_read { + cvt_nz(libc::posix_spawn_file_actions_adddup2( + &mut file_actions, + fd, + libc::STDIN_FILENO, + ))?; + cvt_nz(posix_spawn_file_actions_addinherit_np( + &mut file_actions, + libc::STDIN_FILENO, + ))?; + } + + if let Some(fd) = stdout_write { + cvt_nz(libc::posix_spawn_file_actions_adddup2( + &mut file_actions, + fd, + libc::STDOUT_FILENO, + ))?; + cvt_nz(posix_spawn_file_actions_addinherit_np( + &mut file_actions, + libc::STDOUT_FILENO, + ))?; + } + + if let Some(fd) = stderr_write { + cvt_nz(libc::posix_spawn_file_actions_adddup2( + &mut file_actions, + fd, + libc::STDERR_FILENO, + ))?; + cvt_nz(posix_spawn_file_actions_addinherit_np( + &mut file_actions, + libc::STDERR_FILENO, + ))?; + } + + let mut pid: libc::pid_t = 0; + + let spawn_result = libc::posix_spawnp( + &mut pid, + program_cstr.as_ptr(), + &file_actions, + &attr, + argv_ptrs.as_ptr(), + if envs.is_some() { + envp_ptrs.as_ptr() + } else { + environ + }, + ); + + libc::posix_spawnattr_destroy(&mut attr); + libc::posix_spawn_file_actions_destroy(&mut file_actions); + + if let Some(fd) = stdin_read { + libc::close(fd); + } + if let Some(fd) = stdout_write { + libc::close(fd); + } + if let Some(fd) = stderr_write { + libc::close(fd); + } + + cvt_nz(spawn_result)?; + + Ok(Child { + pid, + stdin: stdin_write.map(|fd| Unblock::new(std::fs::File::from_raw_fd(fd))), + stdout: stdout_read.map(|fd| Unblock::new(std::fs::File::from_raw_fd(fd))), + stderr: stderr_read.map(|fd| Unblock::new(std::fs::File::from_raw_fd(fd))), + kill_on_drop, + status: None, + }) + } +} + +fn create_pipe() -> io::Result<(libc::c_int, libc::c_int)> { + let mut fds: [libc::c_int; 2] = [0; 2]; + let result = unsafe { libc::pipe(fds.as_mut_ptr()) }; + if result == -1 { + return Err(io::Error::last_os_error()); + } + Ok((fds[0], fds[1])) +} + +fn open_dev_null(flags: libc::c_int) -> io::Result { + let fd = unsafe { libc::open(c"/dev/null".as_ptr() as *const libc::c_char, flags) }; + if fd == -1 { + return Err(io::Error::last_os_error()); + } + Ok(fd) +} + +/// Zero means `Ok()`, all other values are treated as raw OS errors. Does not look at `errno`. +/// Mirrored after Rust's std `cvt_nz` function. +fn cvt_nz(error: libc::c_int) -> io::Result<()> { + if error == 0 { + Ok(()) + } else { + Err(io::Error::from_raw_os_error(error)) + } +} + +fn invalid_input_error() -> io::Error { + io::Error::new( + io::ErrorKind::InvalidInput, + "invalid argument: path or argument contains null byte", + ) +} + +#[cfg(test)] +mod tests { + use super::*; + use futures_lite::AsyncWriteExt; + + #[test] + fn test_spawn_echo() { + smol::block_on(async { + let output = Command::new("/bin/echo") + .args(["-n", "hello world"]) + .output() + .await + .expect("failed to run command"); + + assert!(output.status.success()); + assert_eq!(output.stdout, b"hello world"); + }); + } + + #[test] + fn test_spawn_cat_stdin() { + smol::block_on(async { + let mut child = Command::new("/bin/cat") + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .spawn() + .expect("failed to spawn"); + + if let Some(ref mut stdin) = child.stdin { + stdin + .write_all(b"hello from stdin") + .await + .expect("failed to write"); + stdin.close().await.expect("failed to close"); + } + drop(child.stdin.take()); + + let output = child.output().await.expect("failed to get output"); + assert!(output.status.success()); + assert_eq!(output.stdout, b"hello from stdin"); + }); + } + + #[test] + fn test_spawn_stderr() { + smol::block_on(async { + let output = Command::new("/bin/sh") + .args(["-c", "echo error >&2"]) + .output() + .await + .expect("failed to run command"); + + assert!(output.status.success()); + assert_eq!(output.stderr, b"error\n"); + }); + } + + #[test] + fn test_spawn_exit_code() { + smol::block_on(async { + let output = Command::new("/bin/sh") + .args(["-c", "exit 42"]) + .output() + .await + .expect("failed to run command"); + + assert!(!output.status.success()); + assert_eq!(output.status.code(), Some(42)); + }); + } + + #[test] + fn test_spawn_current_dir() { + smol::block_on(async { + let output = Command::new("/bin/pwd") + .current_dir("/tmp") + .output() + .await + .expect("failed to run command"); + + assert!(output.status.success()); + let pwd = String::from_utf8_lossy(&output.stdout); + assert!(pwd.trim() == "/tmp" || pwd.trim() == "/private/tmp"); + }); + } + + #[test] + fn test_spawn_env() { + smol::block_on(async { + let output = Command::new("/bin/sh") + .args(["-c", "echo $MY_TEST_VAR"]) + .env("MY_TEST_VAR", "test_value") + .output() + .await + .expect("failed to run command"); + + assert!(output.status.success()); + assert_eq!(String::from_utf8_lossy(&output.stdout).trim(), "test_value"); + }); + } + + #[test] + fn test_spawn_status() { + smol::block_on(async { + let status = Command::new("/usr/bin/true") + .status() + .await + .expect("failed to run command"); + + assert!(status.success()); + + let status = Command::new("/usr/bin/false") + .status() + .await + .expect("failed to run command"); + + assert!(!status.success()); + }); + } + + #[test] + fn test_env_remove_removes_set_env() { + smol::block_on(async { + let output = Command::new("/bin/sh") + .args(["-c", "echo ${MY_VAR:-unset}"]) + .env("MY_VAR", "set_value") + .env_remove("MY_VAR") + .output() + .await + .expect("failed to run command"); + + assert!(output.status.success()); + assert_eq!(String::from_utf8_lossy(&output.stdout).trim(), "unset"); + }); + } + + #[test] + fn test_env_remove_removes_inherited_env() { + smol::block_on(async { + // SAFETY: This test is single-threaded and we clean up the var at the end + unsafe { std::env::set_var("TEST_INHERITED_VAR", "inherited_value") }; + + let output = Command::new("/bin/sh") + .args(["-c", "echo ${TEST_INHERITED_VAR:-unset}"]) + .env_remove("TEST_INHERITED_VAR") + .output() + .await + .expect("failed to run command"); + + assert!(output.status.success()); + assert_eq!(String::from_utf8_lossy(&output.stdout).trim(), "unset"); + + // SAFETY: Cleaning up test env var + unsafe { std::env::remove_var("TEST_INHERITED_VAR") }; + }); + } + + #[test] + fn test_env_after_env_remove() { + smol::block_on(async { + let output = Command::new("/bin/sh") + .args(["-c", "echo ${MY_VAR:-unset}"]) + .env_remove("MY_VAR") + .env("MY_VAR", "new_value") + .output() + .await + .expect("failed to run command"); + + assert!(output.status.success()); + assert_eq!(String::from_utf8_lossy(&output.stdout).trim(), "new_value"); + }); + } + + #[test] + fn test_env_remove_after_env_clear() { + smol::block_on(async { + let output = Command::new("/bin/sh") + .args(["-c", "echo ${MY_VAR:-unset}"]) + .env_clear() + .env("MY_VAR", "set_value") + .env_remove("MY_VAR") + .output() + .await + .expect("failed to run command"); + + assert!(output.status.success()); + assert_eq!(String::from_utf8_lossy(&output.stdout).trim(), "unset"); + }); + } + + #[test] + fn test_stdio_null_stdin() { + smol::block_on(async { + let child = Command::new("/bin/cat") + .stdin(Stdio::null()) + .stdout(Stdio::piped()) + .spawn() + .expect("failed to spawn"); + + let output = child.output().await.expect("failed to get output"); + assert!(output.status.success()); + assert!( + output.stdout.is_empty(), + "stdin from /dev/null should produce no output from cat" + ); + }); + } + + #[test] + fn test_stdio_null_stdout() { + smol::block_on(async { + let mut child = Command::new("/bin/echo") + .args(["hello"]) + .stdout(Stdio::null()) + .spawn() + .expect("failed to spawn"); + + assert!( + child.stdout.is_none(), + "stdout should be None when Stdio::null() is used" + ); + + let status = child.status().await.expect("failed to get status"); + assert!(status.success()); + }); + } + + #[test] + fn test_stdio_null_stderr() { + smol::block_on(async { + let mut child = Command::new("/bin/sh") + .args(["-c", "echo error >&2"]) + .stderr(Stdio::null()) + .spawn() + .expect("failed to spawn"); + + assert!( + child.stderr.is_none(), + "stderr should be None when Stdio::null() is used" + ); + + let status = child.status().await.expect("failed to get status"); + assert!(status.success()); + }); + } + + #[test] + fn test_stdio_piped_stdin() { + smol::block_on(async { + let mut child = Command::new("/bin/cat") + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .spawn() + .expect("failed to spawn"); + + assert!( + child.stdin.is_some(), + "stdin should be Some when Stdio::piped() is used" + ); + + if let Some(ref mut stdin) = child.stdin { + stdin + .write_all(b"piped input") + .await + .expect("failed to write"); + stdin.close().await.expect("failed to close"); + } + drop(child.stdin.take()); + + let output = child.output().await.expect("failed to get output"); + assert!(output.status.success()); + assert_eq!(output.stdout, b"piped input"); + }); + } +} diff --git a/crates/util/src/shell_env.rs b/crates/util/src/shell_env.rs index c41a28b46953bdb87f319bfbbb84b7ebd22be245..4fc9fd2d69b608c1215495d84c340f11e5be8179 100644 --- a/crates/util/src/shell_env.rs +++ b/crates/util/src/shell_env.rs @@ -141,7 +141,7 @@ async fn capture_windows( std::env::current_exe().context("Failed to determine current zed executable path.")?; let shell_kind = ShellKind::new(shell_path, true); - let mut cmd = crate::command::new_smol_command(shell_path); + let mut cmd = crate::command::new_command(shell_path); cmd.args(args); let cmd = match shell_kind { ShellKind::Csh diff --git a/crates/util/src/util.rs b/crates/util/src/util.rs index 87b8a1615d86c74fa4b3b1cb7c89fc94939558a0..47247b67084f485f24a7411842a0cff877e6ddf4 100644 --- a/crates/util/src/util.rs +++ b/crates/util/src/util.rs @@ -409,8 +409,6 @@ pub fn set_pre_exec_to_start_new_session( use std::os::unix::process::CommandExt; command.pre_exec(|| { libc::setsid(); - #[cfg(target_os = "macos")] - crate::command::reset_exception_ports(); Ok(()) }); };