diff --git a/Cargo.lock b/Cargo.lock index df0af75abe..dcfded036a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4056,6 +4056,7 @@ dependencies = [ "paths", "serde", "serde_json", + "smol", "task", "util", "workspace-hack", @@ -10077,7 +10078,6 @@ dependencies = [ "async-tar", "async-trait", "async-watch", - "async_zip", "futures 0.3.31", "http_client", "log", @@ -10086,9 +10086,7 @@ dependencies = [ "serde", "serde_json", "smol", - "tempfile", "util", - "walkdir", "which 6.0.3", "workspace-hack", ] @@ -17030,6 +17028,7 @@ version = "0.1.0" dependencies = [ "anyhow", "async-fs", + "async_zip", "collections", "dirs 4.0.0", "dunce", @@ -17051,6 +17050,7 @@ dependencies = [ "tendril", "unicase", "util_macros", + "walkdir", "workspace-hack", ] @@ -19138,6 +19138,7 @@ dependencies = [ "aho-corasick", "anstream", "arrayvec", + "async-compression", "async-std", "async-tungstenite", "aws-config", diff --git a/Cargo.toml b/Cargo.toml index bc9a203d5c..bd70e9f9dd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -598,7 +598,7 @@ unindent = "0.2.0" url = "2.2" urlencoding = "2.1.2" uuid = { version = "1.1.2", features = ["v4", "v5", "v7", "serde"] } -walkdir = "2.3" +walkdir = "2.5" wasi-preview1-component-adapter-provider = "29" wasm-encoder = "0.221" wasmparser = "0.221" diff --git a/crates/dap/src/adapters.rs b/crates/dap/src/adapters.rs index 7ae70e0820..f60c47d6b7 100644 --- a/crates/dap/src/adapters.rs +++ b/crates/dap/src/adapters.rs @@ -12,7 +12,7 @@ use language::{LanguageName, LanguageToolchainStore}; use node_runtime::NodeRuntime; use serde::{Deserialize, Serialize}; use settings::WorktreeId; -use smol::{self, fs::File}; +use smol::fs::File; use std::{ borrow::Borrow, ffi::OsStr, @@ -23,6 +23,7 @@ use std::{ sync::Arc, }; use task::{AttachRequest, DebugRequest, DebugScenario, LaunchRequest, TcpArgumentsTemplate}; +use util::archive::extract_zip; #[derive(Clone, Debug, PartialEq, Eq)] pub enum DapStatus { @@ -358,17 +359,13 @@ pub async fn download_adapter_from_github( } DownloadedFileType::Zip | DownloadedFileType::Vsix => { let zip_path = version_path.with_extension("zip"); - let mut file = File::create(&zip_path).await?; futures::io::copy(response.body_mut(), &mut file).await?; - - // we cannot check the status as some adapter include files with names that trigger `Illegal byte sequence` - util::command::new_smol_command("unzip") - .arg(&zip_path) - .arg("-d") - .arg(&version_path) - .output() - .await?; + let file = File::open(&zip_path).await?; + extract_zip(&version_path, BufReader::new(file)) + .await + // we cannot check the status as some adapter include files with names that trigger `Illegal byte sequence` + .ok(); util::fs::remove_matching(&adapter_path, |entry| { entry diff --git a/crates/dap_adapters/Cargo.toml b/crates/dap_adapters/Cargo.toml index 77dbe40088..7aaa9d9529 100644 --- a/crates/dap_adapters/Cargo.toml +++ b/crates/dap_adapters/Cargo.toml @@ -30,6 +30,7 @@ language.workspace = true paths.workspace = true serde.workspace = true serde_json.workspace = true +smol.workspace = true task.workspace = true util.workspace = true workspace-hack.workspace = true diff --git a/crates/dap_adapters/src/codelldb.rs b/crates/dap_adapters/src/codelldb.rs index cc06714d7f..fc1c9099b1 100644 --- a/crates/dap_adapters/src/codelldb.rs +++ b/crates/dap_adapters/src/codelldb.rs @@ -136,6 +136,34 @@ impl DebugAdapter for CodeLldbDebugAdapter { }; let adapter_dir = version_path.join("extension").join("adapter"); let path = adapter_dir.join("codelldb").to_string_lossy().to_string(); + // todo("windows") + #[cfg(not(windows))] + { + use smol::fs; + + fs::set_permissions( + &path, + ::from_mode(0o755), + ) + .await + .with_context(|| format!("Settings executable permissions to {path:?}"))?; + + let lldb_binaries_dir = version_path.join("extension").join("lldb").join("bin"); + let mut lldb_binaries = + fs::read_dir(&lldb_binaries_dir).await.with_context(|| { + format!("reading lldb binaries dir contents {lldb_binaries_dir:?}") + })?; + while let Some(binary) = lldb_binaries.next().await { + let binary_entry = binary?; + let path = binary_entry.path(); + fs::set_permissions( + &path, + ::from_mode(0o755), + ) + .await + .with_context(|| format!("Settings executable permissions to {path:?}"))?; + } + } self.path_to_codelldb.set(path.clone()).ok(); command = Some(path); }; diff --git a/crates/extension_host/src/wasm_host/wit/since_v0_1_0.rs b/crates/extension_host/src/wasm_host/wit/since_v0_1_0.rs index 64ce50dbb9..d94c493822 100644 --- a/crates/extension_host/src/wasm_host/wit/since_v0_1_0.rs +++ b/crates/extension_host/src/wasm_host/wit/since_v0_1_0.rs @@ -15,6 +15,7 @@ use std::{ path::{Path, PathBuf}, sync::{Arc, OnceLock}, }; +use util::archive::extract_zip; use util::maybe; use wasmtime::component::{Linker, Resource}; @@ -543,9 +544,9 @@ impl ExtensionImports for WasmState { } DownloadedFileType::Zip => { futures::pin_mut!(body); - node_runtime::extract_zip(&destination_path, body) + extract_zip(&destination_path, body) .await - .with_context(|| format!("failed to unzip {} archive", path.display()))?; + .with_context(|| format!("unzipping {path:?} archive"))?; } } diff --git a/crates/extension_host/src/wasm_host/wit/since_v0_6_0.rs b/crates/extension_host/src/wasm_host/wit/since_v0_6_0.rs index 5d2013fbe3..947cfc4777 100644 --- a/crates/extension_host/src/wasm_host/wit/since_v0_6_0.rs +++ b/crates/extension_host/src/wasm_host/wit/since_v0_6_0.rs @@ -27,7 +27,7 @@ use std::{ path::{Path, PathBuf}, sync::{Arc, OnceLock}, }; -use util::maybe; +use util::{archive::extract_zip, maybe}; use wasmtime::component::{Linker, Resource}; pub const MIN_VERSION: SemanticVersion = SemanticVersion::new(0, 6, 0); @@ -906,9 +906,9 @@ impl ExtensionImports for WasmState { } DownloadedFileType::Zip => { futures::pin_mut!(body); - node_runtime::extract_zip(&destination_path, body) + extract_zip(&destination_path, body) .await - .with_context(|| format!("failed to unzip {} archive", path.display()))?; + .with_context(|| format!("unzipping {path:?} archive"))?; } } diff --git a/crates/languages/src/c.rs b/crates/languages/src/c.rs index deaca84f96..3128810124 100644 --- a/crates/languages/src/c.rs +++ b/crates/languages/src/c.rs @@ -7,9 +7,9 @@ pub use language::*; use lsp::{DiagnosticTag, InitializeParams, LanguageServerBinary, LanguageServerName}; use project::lsp_store::clangd_ext; use serde_json::json; -use smol::fs::{self, File}; +use smol::{fs, io::BufReader}; use std::{any::Any, env::consts, path::PathBuf, sync::Arc}; -use util::{ResultExt, fs::remove_matching, maybe, merge_json_value_into}; +use util::{ResultExt, archive::extract_zip, fs::remove_matching, maybe, merge_json_value_into}; pub struct CLspAdapter; @@ -32,7 +32,7 @@ impl super::LspAdapter for CLspAdapter { let path = delegate.which(Self::SERVER_NAME.as_ref()).await?; Some(LanguageServerBinary { path, - arguments: vec![], + arguments: Vec::new(), env: None, }) } @@ -69,7 +69,6 @@ impl super::LspAdapter for CLspAdapter { delegate: &dyn LspAdapterDelegate, ) -> Result { let version = version.downcast::().unwrap(); - let zip_path = container_dir.join(format!("clangd_{}.zip", version.name)); let version_dir = container_dir.join(format!("clangd_{}", version.name)); let binary_path = version_dir.join("bin/clangd"); @@ -79,28 +78,31 @@ impl super::LspAdapter for CLspAdapter { .get(&version.url, Default::default(), true) .await .context("error downloading release")?; - let mut file = File::create(&zip_path).await?; anyhow::ensure!( response.status().is_success(), "download failed with status {}", response.status().to_string() ); - futures::io::copy(response.body_mut(), &mut file).await?; - - let unzip_status = util::command::new_smol_command("unzip") - .current_dir(&container_dir) - .arg(&zip_path) - .output() - .await? - .status; - anyhow::ensure!(unzip_status.success(), "failed to unzip clangd archive"); + extract_zip(&container_dir, BufReader::new(response.body_mut())) + .await + .with_context(|| format!("unzipping clangd archive to {container_dir:?}"))?; remove_matching(&container_dir, |entry| entry != version_dir).await; + + // todo("windows") + #[cfg(not(windows))] + { + fs::set_permissions( + &binary_path, + ::from_mode(0o755), + ) + .await?; + } } Ok(LanguageServerBinary { path: binary_path, env: None, - arguments: vec![], + arguments: Vec::new(), }) } @@ -306,7 +308,7 @@ impl super::LspAdapter for CLspAdapter { .map(move |diag| { let range = language::range_to_lsp(diag.range.to_point_utf16(&snapshot)).unwrap(); - let mut tags = vec![]; + let mut tags = Vec::with_capacity(1); if diag.diagnostic.is_unnecessary { tags.push(DiagnosticTag::UNNECESSARY); } @@ -344,7 +346,7 @@ async fn get_cached_server_binary(container_dir: PathBuf) -> Option { - node_runtime::extract_zip( - &destination_path, - BufReader::new(response.body_mut()), - ) - .await - .with_context(|| { - format!("unzipping {} to {:?}", version.url, destination_path) - })?; + extract_zip(&destination_path, BufReader::new(response.body_mut())) + .await + .with_context(|| { + format!("unzipping {} to {:?}", version.url, destination_path) + })?; } }; diff --git a/crates/languages/src/typescript.rs b/crates/languages/src/typescript.rs index d0740cb4a6..39a810ecc0 100644 --- a/crates/languages/src/typescript.rs +++ b/crates/languages/src/typescript.rs @@ -19,6 +19,7 @@ use std::{ sync::Arc, }; use task::{TaskTemplate, TaskTemplates, VariableName}; +use util::archive::extract_zip; use util::{ResultExt, fs::remove_matching, maybe}; pub(super) fn typescript_task_context() -> ContextProviderWithTasks { @@ -514,14 +515,11 @@ impl LspAdapter for EsLintLspAdapter { })?; } AssetKind::Zip => { - node_runtime::extract_zip( - &destination_path, - BufReader::new(response.body_mut()), - ) - .await - .with_context(|| { - format!("unzipping {} to {:?}", version.url, destination_path) - })?; + extract_zip(&destination_path, BufReader::new(response.body_mut())) + .await + .with_context(|| { + format!("unzipping {} to {:?}", version.url, destination_path) + })?; } } diff --git a/crates/node_runtime/Cargo.toml b/crates/node_runtime/Cargo.toml index 1c03214d5c..71d281a801 100644 --- a/crates/node_runtime/Cargo.toml +++ b/crates/node_runtime/Cargo.toml @@ -13,7 +13,7 @@ path = "src/node_runtime.rs" doctest = false [features] -test-support = ["tempfile"] +test-support = [] [dependencies] anyhow.workspace = true @@ -21,7 +21,6 @@ async-compression.workspace = true async-watch.workspace = true async-tar.workspace = true async-trait.workspace = true -async_zip.workspace = true futures.workspace = true http_client.workspace = true log.workspace = true @@ -30,14 +29,9 @@ semver.workspace = true serde.workspace = true serde_json.workspace = true smol.workspace = true -tempfile = { workspace = true, optional = true } util.workspace = true -walkdir = "2.5.0" which.workspace = true workspace-hack.workspace = true [target.'cfg(windows)'.dependencies] async-std = { version = "1.12.0", features = ["unstable"] } - -[dev-dependencies] -tempfile.workspace = true diff --git a/crates/node_runtime/src/node_runtime.rs b/crates/node_runtime/src/node_runtime.rs index 41597062f7..5a62a4f804 100644 --- a/crates/node_runtime/src/node_runtime.rs +++ b/crates/node_runtime/src/node_runtime.rs @@ -1,7 +1,4 @@ -mod archive; - use anyhow::{Context as _, Result, anyhow, bail}; -pub use archive::extract_zip; use async_compression::futures::bufread::GzipDecoder; use async_tar::Archive; use futures::{AsyncReadExt, FutureExt as _, channel::oneshot, future::Shared}; @@ -19,6 +16,7 @@ use std::{ sync::Arc, }; use util::ResultExt; +use util::archive::extract_zip; const NODE_CA_CERTS_ENV_VAR: &str = "NODE_EXTRA_CA_CERTS"; @@ -353,7 +351,7 @@ impl ManagedNodeRuntime { let archive = Archive::new(decompressed_bytes); archive.unpack(&node_containing_dir).await?; } - ArchiveType::Zip => archive::extract_zip(&node_containing_dir, body).await?, + ArchiveType::Zip => extract_zip(&node_containing_dir, body).await?, } } diff --git a/crates/project/src/lsp_store.rs b/crates/project/src/lsp_store.rs index b4324fc63a..d4601a20b1 100644 --- a/crates/project/src/lsp_store.rs +++ b/crates/project/src/lsp_store.rs @@ -348,11 +348,11 @@ impl LocalLspStore { delegate.update_status( adapter.name(), BinaryStatus::Failed { - error: format!("{err}\n-- stderr--\n{}", log), + error: format!("{err}\n-- stderr--\n{log}"), }, ); - log::error!("Failed to start language server {server_name:?}: {err}"); - log::error!("server stderr: {:?}", log); + log::error!("Failed to start language server {server_name:?}: {err:#?}"); + log::error!("server stderr: {log}"); None } } diff --git a/crates/project/src/yarn.rs b/crates/project/src/yarn.rs index 64349e2911..52a35f3203 100644 --- a/crates/project/src/yarn.rs +++ b/crates/project/src/yarn.rs @@ -15,7 +15,7 @@ use anyhow::Result; use collections::HashMap; use fs::Fs; use gpui::{App, AppContext as _, Context, Entity, Task}; -use util::ResultExt; +use util::{ResultExt, archive::extract_zip}; pub(crate) struct YarnPathStore { temp_dirs: HashMap, tempfile::TempDir>, @@ -131,7 +131,7 @@ fn zip_path(path: &Path) -> Option<&Path> { async fn dump_zip(path: Arc, fs: Arc) -> Result { let dir = tempfile::tempdir()?; let contents = fs.load_bytes(&path).await?; - node_runtime::extract_zip(dir.path(), futures::io::Cursor::new(contents)).await?; + extract_zip(dir.path(), futures::io::Cursor::new(contents)).await?; Ok(dir) } diff --git a/crates/util/Cargo.toml b/crates/util/Cargo.toml index 696dfb9af0..e0ae034b1c 100644 --- a/crates/util/Cargo.toml +++ b/crates/util/Cargo.toml @@ -18,6 +18,7 @@ test-support = ["tempfile", "git2", "rand", "util_macros"] [dependencies] anyhow.workspace = true async-fs.workspace = true +async_zip.workspace = true collections.workspace = true dirs.workspace = true futures-lite.workspace = true @@ -36,6 +37,7 @@ take-until.workspace = true tempfile = { workspace = true, optional = true } unicase.workspace = true util_macros = { workspace = true, optional = true } +walkdir.workspace = true workspace-hack.workspace = true [target.'cfg(unix)'.dependencies] diff --git a/crates/node_runtime/src/archive.rs b/crates/util/src/archive.rs similarity index 100% rename from crates/node_runtime/src/archive.rs rename to crates/util/src/archive.rs diff --git a/crates/util/src/util.rs b/crates/util/src/util.rs index 09521d2d95..c61e3f7d25 100644 --- a/crates/util/src/util.rs +++ b/crates/util/src/util.rs @@ -1,4 +1,5 @@ pub mod arc_cow; +pub mod archive; pub mod command; pub mod fs; pub mod markdown; diff --git a/tooling/workspace-hack/Cargo.toml b/tooling/workspace-hack/Cargo.toml index 273ffa72f6..90c7eec31e 100644 --- a/tooling/workspace-hack/Cargo.toml +++ b/tooling/workspace-hack/Cargo.toml @@ -19,6 +19,7 @@ ahash = { version = "0.8", features = ["serde"] } aho-corasick = { version = "1" } anstream = { version = "0.6" } arrayvec = { version = "0.7", features = ["serde"] } +async-compression = { version = "0.4", default-features = false, features = ["deflate", "deflate64", "futures-io", "gzip"] } async-std = { version = "1", features = ["attributes", "unstable"] } async-tungstenite = { version = "0.29", features = ["tokio-rustls-manual-roots"] } aws-config = { version = "1", features = ["behavior-version-latest"] } @@ -145,6 +146,7 @@ ahash = { version = "0.8", features = ["serde"] } aho-corasick = { version = "1" } anstream = { version = "0.6" } arrayvec = { version = "0.7", features = ["serde"] } +async-compression = { version = "0.4", default-features = false, features = ["deflate", "deflate64", "futures-io", "gzip"] } async-std = { version = "1", features = ["attributes", "unstable"] } async-tungstenite = { version = "0.29", features = ["tokio-rustls-manual-roots"] } aws-config = { version = "1", features = ["behavior-version-latest"] }