From 5c2238c7a5573ff836e8f3f3bbee37256561ca26 Mon Sep 17 00:00:00 2001 From: Thorsten Ball Date: Fri, 25 Oct 2024 16:27:36 +0200 Subject: [PATCH 1/3] ssh remoting: Use matching versions of remote server binary (#19740) This changes the download logic to not fetch the latest version, but to fetch the version matching the current version of Zed. Release Notes: - Changed the update logic of the SSH remote server to not fetch the latest version for a current channel, but to fetch the version matching the current Zed version. If Zed is updated, the server is updated too. If the server is newer than the Zed version an error will be displayed. --- crates/auto_update/src/auto_update.rs | 112 ++++++++++-------- crates/recent_projects/src/ssh_connections.rs | 25 +++- crates/remote/src/ssh_session.rs | 29 +++-- 3 files changed, 105 insertions(+), 61 deletions(-) diff --git a/crates/auto_update/src/auto_update.rs b/crates/auto_update/src/auto_update.rs index a45eb3a05b..61154cb504 100644 --- a/crates/auto_update/src/auto_update.rs +++ b/crates/auto_update/src/auto_update.rs @@ -432,10 +432,11 @@ impl AutoUpdater { cx.notify(); } - pub async fn get_latest_remote_server_release( + pub async fn download_remote_server_release( os: &str, arch: &str, - mut release_channel: ReleaseChannel, + release_channel: ReleaseChannel, + version: Option, cx: &mut AsyncAppContext, ) -> Result { let this = cx.update(|cx| { @@ -445,15 +446,12 @@ impl AutoUpdater { .ok_or_else(|| anyhow!("auto-update not initialized")) })??; - if release_channel == ReleaseChannel::Dev { - release_channel = ReleaseChannel::Nightly; - } - - let release = Self::get_latest_release( + let release = Self::get_release( &this, "zed-remote-server", os, arch, + version, Some(release_channel), cx, ) @@ -468,17 +466,21 @@ impl AutoUpdater { let client = this.read_with(cx, |this, _| this.http_client.clone())?; if smol::fs::metadata(&version_path).await.is_err() { - log::info!("downloading zed-remote-server {os} {arch}"); + log::info!( + "downloading zed-remote-server {os} {arch} version {}", + release.version + ); download_remote_server_binary(&version_path, release, client, cx).await?; } Ok(version_path) } - pub async fn get_latest_remote_server_release_url( + pub async fn get_remote_server_release_url( os: &str, arch: &str, - mut release_channel: ReleaseChannel, + release_channel: ReleaseChannel, + version: Option, cx: &mut AsyncAppContext, ) -> Result<(String, String)> { let this = cx.update(|cx| { @@ -488,15 +490,12 @@ impl AutoUpdater { .ok_or_else(|| anyhow!("auto-update not initialized")) })??; - if release_channel == ReleaseChannel::Dev { - release_channel = ReleaseChannel::Nightly; - } - - let release = Self::get_latest_release( + let release = Self::get_release( &this, "zed-remote-server", os, arch, + version, Some(release_channel), cx, ) @@ -508,6 +507,56 @@ impl AutoUpdater { Ok((release.url, body)) } + async fn get_release( + this: &Model, + asset: &str, + os: &str, + arch: &str, + version: Option, + release_channel: Option, + cx: &mut AsyncAppContext, + ) -> Result { + let client = this.read_with(cx, |this, _| this.http_client.clone())?; + + if let Some(version) = version { + let channel = release_channel.map(|c| c.dev_name()).unwrap_or("stable"); + + let url = format!("/api/releases/{channel}/{version}/{asset}-{os}-{arch}.gz?update=1",); + + Ok(JsonRelease { + version: version.to_string(), + url: client.build_url(&url), + }) + } else { + let mut url_string = client.build_url(&format!( + "/api/releases/latest?asset={}&os={}&arch={}", + asset, os, arch + )); + if let Some(param) = release_channel.and_then(|c| c.release_query_param()) { + url_string += "&"; + url_string += param; + } + + let mut response = client.get(&url_string, Default::default(), true).await?; + let mut body = Vec::new(); + response.body_mut().read_to_end(&mut body).await?; + + if !response.status().is_success() { + return Err(anyhow!( + "failed to fetch release: {:?}", + String::from_utf8_lossy(&body), + )); + } + + serde_json::from_slice(body.as_slice()).with_context(|| { + format!( + "error deserializing release {:?}", + String::from_utf8_lossy(&body), + ) + }) + } + } + async fn get_latest_release( this: &Model, asset: &str, @@ -516,38 +565,7 @@ impl AutoUpdater { release_channel: Option, cx: &mut AsyncAppContext, ) -> Result { - let client = this.read_with(cx, |this, _| this.http_client.clone())?; - let mut url_string = client.build_url(&format!( - "/api/releases/latest?asset={}&os={}&arch={}", - asset, os, arch - )); - if let Some(param) = release_channel.and_then(|c| c.release_query_param()) { - url_string += "&"; - url_string += param; - } - - let mut response = client.get(&url_string, Default::default(), true).await?; - - let mut body = Vec::new(); - response - .body_mut() - .read_to_end(&mut body) - .await - .context("error reading release")?; - - if !response.status().is_success() { - Err(anyhow!( - "failed to fetch release: {:?}", - String::from_utf8_lossy(&body), - ))?; - } - - serde_json::from_slice(body.as_slice()).with_context(|| { - format!( - "error deserializing release {:?}", - String::from_utf8_lossy(&body), - ) - }) + Self::get_release(this, asset, os, arch, None, release_channel, cx).await } async fn update(this: Model, mut cx: AsyncAppContext) -> Result<()> { diff --git a/crates/recent_projects/src/ssh_connections.rs b/crates/recent_projects/src/ssh_connections.rs index 0718c4f983..47e4c91dbd 100644 --- a/crates/recent_projects/src/ssh_connections.rs +++ b/crates/recent_projects/src/ssh_connections.rs @@ -517,17 +517,31 @@ impl SshClientDelegate { } } + // For nightly channel, always get latest + let current_version = if release_channel == ReleaseChannel::Nightly { + None + } else { + Some(version) + }; + + self.update_status( + Some(&format!("Checking remote server release {}", version)), + cx, + ); + if download_binary_on_host { - let (request_url, request_body) = AutoUpdater::get_latest_remote_server_release_url( + let (request_url, request_body) = AutoUpdater::get_remote_server_release_url( platform.os, platform.arch, release_channel, + current_version, cx, ) .await .map_err(|e| { anyhow!( - "Failed to get remote server binary download url (os: {}, arch: {}): {}", + "Failed to get remote server binary download url (version: {}, os: {}, arch: {}): {}", + version, platform.os, platform.arch, e @@ -542,17 +556,18 @@ impl SshClientDelegate { version, )) } else { - self.update_status(Some("Checking for latest version of remote server"), cx); - let binary_path = AutoUpdater::get_latest_remote_server_release( + let binary_path = AutoUpdater::download_remote_server_release( platform.os, platform.arch, release_channel, + current_version, cx, ) .await .map_err(|e| { anyhow!( - "Failed to download remote server binary (os: {}, arch: {}): {}", + "Failed to download remote server binary (version: {}, os: {}, arch: {}): {}", + version, platform.os, platform.arch, e diff --git a/crates/remote/src/ssh_session.rs b/crates/remote/src/ssh_session.rs index 8e0c345f74..656560f0b6 100644 --- a/crates/remote/src/ssh_session.rs +++ b/crates/remote/src/ssh_session.rs @@ -1707,21 +1707,32 @@ impl SshRemoteConnection { let (binary, version) = delegate.get_server_binary(platform, cx).await??; - let mut server_binary_exists = false; - if !server_binary_exists && cfg!(not(debug_assertions)) { + let mut remote_version = None; + if cfg!(not(debug_assertions)) { if let Ok(installed_version) = run_cmd(self.socket.ssh_command(dst_path).arg("version")).await { - if installed_version.trim() == version.to_string() { - server_binary_exists = true; + if let Ok(version) = installed_version.trim().parse::() { + remote_version = Some(version); + } else { + log::warn!("failed to parse version of remote server: {installed_version:?}",); } - log::info!("checked remote server binary for version. latest version: {}. remote server version: {}", version.to_string(), installed_version.trim()); } - } - if server_binary_exists { - log::info!("remote development server already present",); - return Ok(()); + if let Some(remote_version) = remote_version { + if remote_version == version { + log::info!("remote development server present and matching client version"); + return Ok(()); + } else if remote_version > version { + let error = anyhow!("The version of the remote server ({}) is newer than the Zed version ({}). Please update Zed.", remote_version, version); + return Err(error); + } else { + log::info!( + "remote development server has older version: {}. updating...", + remote_version + ); + } + } } match binary { From 5f9a1482f1aab3296a9e417a801ef1b4a5a742ce Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Fri, 25 Oct 2024 11:02:27 -0400 Subject: [PATCH 2/3] assistant: Make `/file` emit events as they occur (#19743) This PR updates the `/file` command to emit its `SlashCommandEvent`s in a way that can actually be streamed. Previously it was buffering up all of the events and then returning them all at once. Note that we still don't yet support streaming in the context editor on `main`, so there won't be any visible changes just yet. Release Notes: - N/A --- .../src/slash_command/file_command.rs | 53 ++++++++++--------- 1 file changed, 29 insertions(+), 24 deletions(-) diff --git a/crates/assistant/src/slash_command/file_command.rs b/crates/assistant/src/slash_command/file_command.rs index 0a1794cae1..1d0fa2bf3e 100644 --- a/crates/assistant/src/slash_command/file_command.rs +++ b/crates/assistant/src/slash_command/file_command.rs @@ -4,6 +4,7 @@ use assistant_slash_command::{ SlashCommandOutput, SlashCommandOutputSection, SlashCommandResult, }; use futures::channel::mpsc; +use futures::Stream; use fuzzy::PathMatch; use gpui::{AppContext, Model, Task, View, WeakView}; use language::{BufferSnapshot, CodeLabel, HighlightId, LineEnding, LspAdapterDelegate}; @@ -196,7 +197,12 @@ impl SlashCommand for FileSlashCommand { return Task::ready(Err(anyhow!("missing path"))); }; - collect_files(workspace.read(cx).project().clone(), arguments, cx) + Task::ready(Ok(collect_files( + workspace.read(cx).project().clone(), + arguments, + cx, + ) + .boxed())) } } @@ -204,7 +210,7 @@ fn collect_files( project: Model, glob_inputs: &[String], cx: &mut AppContext, -) -> Task { +) -> impl Stream> { let Ok(matchers) = glob_inputs .into_iter() .map(|glob_input| { @@ -213,7 +219,7 @@ fn collect_files( }) .collect::>>() else { - return Task::ready(Err(anyhow!("invalid path"))); + return futures::stream::once(async { Err(anyhow!("invalid path")) }).boxed(); }; let project_handle = project.downgrade(); @@ -357,8 +363,12 @@ fn collect_files( events_tx.unbounded_send(Ok(SlashCommandEvent::EndSection { metadata: None }))?; } } - Ok(events_rx.boxed()) + + anyhow::Ok(()) }) + .detach_and_log_err(cx); + + events_rx.boxed() } pub fn codeblock_fence_for_path( @@ -550,6 +560,7 @@ mod test { use project::Project; use serde_json::json; use settings::SettingsStore; + use smol::stream::StreamExt; use crate::slash_command::file_command::collect_files; @@ -590,11 +601,9 @@ mod test { let project = Project::test(fs, ["/root".as_ref()], cx).await; - let result_1 = cx - .update(|cx| collect_files(project.clone(), &["root/dir".to_string()], cx)) - .await - .unwrap(); - let result_1 = SlashCommandOutput::from_event_stream(result_1) + let result_1 = + cx.update(|cx| collect_files(project.clone(), &["root/dir".to_string()], cx)); + let result_1 = SlashCommandOutput::from_event_stream(result_1.boxed()) .await .unwrap(); @@ -602,20 +611,16 @@ mod test { // 4 files + 2 directories assert_eq!(result_1.sections.len(), 6); - let result_2 = cx - .update(|cx| collect_files(project.clone(), &["root/dir/".to_string()], cx)) - .await - .unwrap(); - let result_2 = SlashCommandOutput::from_event_stream(result_2) + let result_2 = + cx.update(|cx| collect_files(project.clone(), &["root/dir/".to_string()], cx)); + let result_2 = SlashCommandOutput::from_event_stream(result_2.boxed()) .await .unwrap(); assert_eq!(result_1, result_2); - let result = cx - .update(|cx| collect_files(project.clone(), &["root/dir*".to_string()], cx)) - .await - .unwrap(); + let result = + cx.update(|cx| collect_files(project.clone(), &["root/dir*".to_string()], cx).boxed()); let result = SlashCommandOutput::from_event_stream(result).await.unwrap(); assert!(result.text.starts_with("root/dir")); @@ -659,11 +664,11 @@ mod test { let project = Project::test(fs, ["/zed".as_ref()], cx).await; - let result = cx - .update(|cx| collect_files(project.clone(), &["zed/assets/themes".to_string()], cx)) + let result = + cx.update(|cx| collect_files(project.clone(), &["zed/assets/themes".to_string()], cx)); + let result = SlashCommandOutput::from_event_stream(result.boxed()) .await .unwrap(); - let result = SlashCommandOutput::from_event_stream(result).await.unwrap(); // Sanity check assert!(result.text.starts_with("zed/assets/themes\n")); @@ -721,11 +726,11 @@ mod test { let project = Project::test(fs, ["/zed".as_ref()], cx).await; - let result = cx - .update(|cx| collect_files(project.clone(), &["zed/assets/themes".to_string()], cx)) + let result = + cx.update(|cx| collect_files(project.clone(), &["zed/assets/themes".to_string()], cx)); + let result = SlashCommandOutput::from_event_stream(result.boxed()) .await .unwrap(); - let result = SlashCommandOutput::from_event_stream(result).await.unwrap(); assert!(result.text.starts_with("zed/assets/themes\n")); assert_eq!(result.sections[0].label, "zed/assets/themes/LICENSE"); From d40ea8fc81026b46a8a5ac244db87863ab9b6e99 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Fri, 25 Oct 2024 19:04:38 +0300 Subject: [PATCH 3/3] Make macOS bundle script compatible with GNU sed (#19745) Closes https://github.com/zed-industries/zed/issues/19742 Release Notes: - N/A --- script/bundle-mac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/script/bundle-mac b/script/bundle-mac index bc95e1dd6a..230722ecfa 100755 --- a/script/bundle-mac +++ b/script/bundle-mac @@ -97,7 +97,7 @@ popd pushd crates/zed cp Cargo.toml Cargo.toml.backup sed \ - -i .backup \ + -i.backup \ "s/package.metadata.bundle-${channel}/package.metadata.bundle/" \ Cargo.toml