Fix unzipping clangd and codelldb on Windows (#31080)

Closes https://github.com/zed-industries/zed/pull/30454

Release Notes:

- N/A
This commit is contained in:
Kirill Bulatov 2025-05-21 21:17:14 +03:00 committed by GitHub
parent c8f56e38b1
commit 6e5996a815
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
19 changed files with 93 additions and 70 deletions

7
Cargo.lock generated
View file

@ -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",

View file

@ -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"

View file

@ -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

View file

@ -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

View file

@ -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,
<fs::Permissions as fs::unix::PermissionsExt>::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,
<fs::Permissions as fs::unix::PermissionsExt>::from_mode(0o755),
)
.await
.with_context(|| format!("Settings executable permissions to {path:?}"))?;
}
}
self.path_to_codelldb.set(path.clone()).ok();
command = Some(path);
};

View file

@ -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"))?;
}
}

View file

@ -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"))?;
}
}

View file

@ -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<LanguageServerBinary> {
let version = version.downcast::<GitHubLspBinaryVersion>().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,
<fs::Permissions as fs::unix::PermissionsExt>::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<LanguageServ
Ok(LanguageServerBinary {
path: clangd_bin,
env: None,
arguments: vec![],
arguments: Vec::new(),
})
})
.await

View file

@ -26,7 +26,7 @@ use std::{
sync::Arc,
};
use task::{TaskTemplate, TaskTemplates, VariableName};
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};
const SERVER_PATH: &str =
"node_modules/vscode-langservers-extracted/bin/vscode-json-language-server";
@ -429,7 +429,7 @@ impl LspAdapter for NodeVersionAdapter {
.await
.context("downloading release")?;
if version.url.ends_with(".zip") {
node_runtime::extract_zip(
extract_zip(
&destination_container_path,
BufReader::new(response.body_mut()),
)

View file

@ -22,6 +22,7 @@ use std::{
sync::{Arc, LazyLock},
};
use task::{TaskTemplate, TaskTemplates, TaskVariables, VariableName};
use util::archive::extract_zip;
use util::merge_json_value_into;
use util::{ResultExt, fs::remove_matching, maybe};
@ -215,14 +216,11 @@ impl LspAdapter for RustLspAdapter {
})?;
}
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)
})?;
}
};

View file

@ -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)
})?;
}
}

View file

@ -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

View file

@ -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?,
}
}

View file

@ -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
}
}

View file

@ -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<Arc<Path>, tempfile::TempDir>,
@ -131,7 +131,7 @@ fn zip_path(path: &Path) -> Option<&Path> {
async fn dump_zip(path: Arc<Path>, fs: Arc<dyn Fs>) -> Result<tempfile::TempDir> {
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)
}

View file

@ -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]

View file

@ -1,4 +1,5 @@
pub mod arc_cow;
pub mod archive;
pub mod command;
pub mod fs;
pub mod markdown;

View file

@ -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"] }