Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rework download adapters and allow multiple download formats #72

Merged
merged 4 commits into from
Dec 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions crates/dap/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ workspace = true

[dependencies]
anyhow.workspace = true
async-compression.workspace = true
async-tar.workspace = true
async-trait.workspace = true
collections.workspace = true
dap-types = { git = "https://github.com/zed-industries/dap-types", rev = "b95818130022bfc72bbcd639bdd0c0358c7549fc" }
Expand Down
243 changes: 107 additions & 136 deletions crates/dap/src/adapters.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
use crate::transport::Transport;
use ::fs::Fs;
use anyhow::{anyhow, Context, Result};
use anyhow::{anyhow, Context as _, Result};
use async_compression::futures::bufread::GzipDecoder;
use async_tar::Archive;
use async_trait::async_trait;
use futures::io::BufReader;
use gpui::SharedString;
use http_client::{github::latest_github_release, HttpClient};
pub use http_client::{github::latest_github_release, HttpClient};
use node_runtime::NodeRuntime;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use smol::{self, fs::File, lock::Mutex, process};
use std::{
Expand All @@ -17,6 +21,7 @@ use std::{
};
use sysinfo::{Pid, Process};
use task::DebugAdapterConfig;
use util::ResultExt;

#[derive(Clone, Debug, PartialEq, Eq)]
pub enum DapStatus {
Expand All @@ -37,7 +42,7 @@ pub trait DapDelegate {
async fn shell_env(&self) -> collections::HashMap<String, String>;
}

#[derive(PartialEq, Eq, Hash, Debug)]
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Deserialize, Serialize)]
pub struct DebugAdapterName(pub Arc<str>);

impl Deref for DebugAdapterName {
Expand Down Expand Up @@ -72,20 +77,31 @@ impl From<DebugAdapterName> for SharedString {
}
}

impl<'a> From<&'a str> for DebugAdapterName {
fn from(str: &'a str) -> DebugAdapterName {
DebugAdapterName(str.to_string().into())
}
}

#[derive(Debug, Clone)]
pub struct DebugAdapterBinary {
pub command: String,
pub arguments: Option<Vec<OsString>>,
pub envs: Option<HashMap<String, String>>,
pub cwd: Option<PathBuf>,
pub version: String,
}

pub struct AdapterVersion {
pub tag_name: String,
pub url: String,
}

pub enum DownloadedFileType {
Vsix,
GzipTar,
Zip,
}

pub struct GithubRepo {
pub repo_name: String,
pub repo_owner: String,
Expand All @@ -94,89 +110,79 @@ pub struct GithubRepo {
pub async fn download_adapter_from_github(
adapter_name: DebugAdapterName,
github_version: AdapterVersion,
file_type: DownloadedFileType,
delegate: &dyn DapDelegate,
) -> Result<PathBuf> {
let adapter_path = paths::debug_adapters_dir().join(&adapter_name);
let version_dir = adapter_path.join(format!("{}_{}", adapter_name, github_version.tag_name));
let version_path = adapter_path.join(format!("{}_{}", adapter_name, github_version.tag_name));
let fs = delegate.fs();

let http_client = delegate
.http_client()
.ok_or_else(|| anyhow!("Failed to download adapter: couldn't connect to GitHub"))?;

if !adapter_path.exists() {
fs.create_dir(&adapter_path.as_path()).await?;
if version_path.exists() {
return Ok(version_path);
}

if version_dir.exists() {
return Ok(version_dir);
if !adapter_path.exists() {
fs.create_dir(&adapter_path.as_path())
.await
.context("Failed creating adapter path")?;
}

let asset_name = format!("{}_{}.zip", &adapter_name, github_version.tag_name);
let zip_path = adapter_path.join(&asset_name);
fs.remove_file(
zip_path.as_path(),
fs::RemoveOptions {
recursive: true,
ignore_if_not_exists: true,
},
)
.await?;
log::debug!(
"Downloading adapter {} from {}",
adapter_name,
&github_version.url,
);

let http_client = delegate
.http_client()
.ok_or_else(|| anyhow!("Failed to download adapter: couldn't connect to GitHub"))?;
let mut response = http_client
.get(&github_version.url, Default::default(), true)
.await
.context("Error downloading release")?;
if !response.status().is_success() {
Err(anyhow!(
"download failed with status {}",
response.status().to_string()
))?;
}

let mut file = File::create(&zip_path).await?;
futures::io::copy(response.body_mut(), &mut file).await?;

let old_files: HashSet<_> = util::fs::collect_matching(&adapter_path.as_path(), |file_path| {
file_path != zip_path.as_path()
})
.await
.into_iter()
.filter_map(|file_path| {
file_path
.file_name()
.and_then(|f| f.to_str())
.map(|f| f.to_string())
})
.collect();

let _unzip_status = process::Command::new("unzip")
.current_dir(&adapter_path)
.arg(&zip_path)
.output()
.await?
.status;
match file_type {
DownloadedFileType::GzipTar => {
let decompressed_bytes = GzipDecoder::new(BufReader::new(response.body_mut()));
let archive = Archive::new(decompressed_bytes);
archive.unpack(&version_path).await?;
}
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`
process::Command::new("unzip")
.arg(&zip_path)
.arg("-d")
.arg(&version_path)
.output()
.await?;

util::fs::remove_matching(&adapter_path, |entry| {
entry
.file_name()
.is_some_and(|file| file.to_string_lossy().ends_with(".zip"))
})
.await;
}
}

let file_name = util::fs::find_file_name_in_dir(&adapter_path.as_path(), |file_name| {
!file_name.ends_with(".zip") && !old_files.contains(file_name)
// remove older versions
util::fs::remove_matching(&adapter_path, |entry| {
entry.to_string_lossy() != version_path.to_string_lossy()
})
.await
.ok_or_else(|| anyhow!("Unzipped directory not found"));

let file_name = file_name?;
let downloaded_path = adapter_path
.join(format!("{}_{}", adapter_name, github_version.tag_name))
.to_owned();

fs.rename(
file_name.as_path(),
downloaded_path.as_path(),
Default::default(),
)
.await?;
.await;

util::fs::remove_matching(&adapter_path, |entry| entry != version_dir).await;

// if !unzip_status.success() {
// dbg!(unzip_status);
// Err(anyhow!("failed to unzip downloaded dap archive"))?;
// }

Ok(downloaded_path)
Ok(version_path)
}

pub async fn fetch_latest_adapter_version_from_github(
Expand All @@ -186,8 +192,14 @@ pub async fn fetch_latest_adapter_version_from_github(
let http_client = delegate
.http_client()
.ok_or_else(|| anyhow!("Failed to download adapter: couldn't connect to GitHub"))?;
let repo_name_with_owner = format!("{}/{}", github_repo.repo_owner, github_repo.repo_name);
let release = latest_github_release(&repo_name_with_owner, false, false, http_client).await?;

let release = latest_github_release(
&format!("{}/{}", github_repo.repo_owner, github_repo.repo_name),
false,
false,
http_client,
)
.await?;

Ok(AdapterVersion {
tag_name: release.tag_name,
Expand All @@ -203,39 +215,8 @@ pub trait DebugAdapter: 'static + Send + Sync {
&self,
delegate: &dyn DapDelegate,
config: &DebugAdapterConfig,
adapter_path: Option<PathBuf>,
user_installed_path: Option<PathBuf>,
) -> Result<DebugAdapterBinary> {
if let Some(adapter_path) = adapter_path {
if adapter_path.exists() {
log::info!(
"Using adapter path from settings\n debug adapter name: {}\n adapter_path: {:?}",
self.name(),
&adapter_path,
);

let binary = self
.get_installed_binary(delegate, &config, Some(adapter_path))
.await;

if binary.is_ok() {
return binary;
} else {
log::info!(
"Failed to get debug adapter path from user's setting.\n adapter_name: {}",
self.name()
);
}
} else {
log::warn!(
r#"User downloaded adapter path does not exist
Debug Adapter: {},
User Adapter Path: {:?}"#,
self.name(),
&adapter_path
)
}
}

if delegate
.updated_adapters()
.lock()
Expand All @@ -244,49 +225,39 @@ pub trait DebugAdapter: 'static + Send + Sync {
{
log::info!("Using cached debug adapter binary {}", self.name());

return self.get_installed_binary(delegate, &config, None).await;
if let Some(binary) = self
.get_installed_binary(delegate, &config, user_installed_path.clone())
.await
.log_err()
{
return Ok(binary);
}

log::info!(
"Cached binary {} is corrupt falling back to install",
self.name()
);
}

log::info!("Getting latest version of debug adapter {}", self.name());
delegate.update_status(self.name(), DapStatus::CheckingForUpdate);
let version = self.fetch_latest_adapter_version(delegate).await.ok();

let mut binary = self.get_installed_binary(delegate, &config, None).await;

if let Some(version) = version {
if binary
.as_ref()
.is_ok_and(|binary| binary.version == version.tag_name)
{
delegate
.updated_adapters()
.lock_arc()
.await
.insert(self.name());

return binary;
}

if let Some(version) = self.fetch_latest_adapter_version(delegate).await.log_err() {
log::info!(
"Installiing latest version of debug adapter {}",
self.name()
);
delegate.update_status(self.name(), DapStatus::Downloading);
self.install_binary(version, delegate).await?;

binary = self.get_installed_binary(delegate, &config, None).await;
} else {
log::error!(
"Failed getting latest version of debug adapter {}",
self.name()
);
delegate
.updated_adapters()
.lock_arc()
.await
.insert(self.name());
}

let binary = binary?;

delegate
.updated_adapters()
.lock_arc()
self.get_installed_binary(delegate, &config, user_installed_path)
.await
.insert(self.name());

Ok(binary)
}

fn transport(&self) -> Box<dyn Transport>;
Expand Down
7 changes: 7 additions & 0 deletions crates/dap/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ pub struct DebugAdapterClientId(pub usize);
pub struct DebugAdapterClient {
id: DebugAdapterClientId,
sequence_count: AtomicU64,
binary: DebugAdapterBinary,
executor: BackgroundExecutor,
adapter: Arc<Box<dyn DebugAdapter>>,
transport_delegate: TransportDelegate,
Expand All @@ -49,12 +50,14 @@ impl DebugAdapterClient {
id: DebugAdapterClientId,
config: DebugAdapterConfig,
adapter: Arc<Box<dyn DebugAdapter>>,
binary: DebugAdapterBinary,
cx: &AsyncAppContext,
) -> Self {
let transport_delegate = TransportDelegate::new(adapter.transport());

Self {
id,
binary,
adapter,
transport_delegate,
sequence_count: AtomicU64::new(1),
Expand Down Expand Up @@ -196,6 +199,10 @@ impl DebugAdapterClient {
&self.adapter
}

pub fn binary(&self) -> &DebugAdapterBinary {
&self.binary
}

pub fn adapter_id(&self) -> String {
self.adapter.name().to_string()
}
Expand Down
Loading