Skip to content

Commit

Permalink
Rework download adapters and allow multiple download formats (#72)
Browse files Browse the repository at this point in the history
* Rework download adapters and allow multiple download formats

* Move version path check up, so we don't have to check always 2 paths

* Add user installed binary back

* Fix dynamic ports don't work for javascript when using start debugging path
  • Loading branch information
RemcoSmitsDev authored Dec 6, 2024
1 parent 61e8d0c commit 2b8ae36
Show file tree
Hide file tree
Showing 16 changed files with 411 additions and 407 deletions.
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

0 comments on commit 2b8ae36

Please sign in to comment.