Detailed changes
@@ -449,6 +449,12 @@ jobs:
name: Zed-aarch64.exe
path: target/Zed-aarch64.exe
if-no-files-found: error
+ - name: '@actions/upload-artifact zed-remote-server-windows-aarch64.zip'
+ uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
+ with:
+ name: zed-remote-server-windows-aarch64.zip
+ path: target/zed-remote-server-windows-aarch64.zip
+ if-no-files-found: error
timeout-minutes: 60
bundle_windows_x86_64:
needs:
@@ -488,6 +494,12 @@ jobs:
name: Zed-x86_64.exe
path: target/Zed-x86_64.exe
if-no-files-found: error
+ - name: '@actions/upload-artifact zed-remote-server-windows-x86_64.zip'
+ uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
+ with:
+ name: zed-remote-server-windows-x86_64.zip
+ path: target/zed-remote-server-windows-x86_64.zip
+ if-no-files-found: error
timeout-minutes: 60
upload_release_assets:
needs:
@@ -521,6 +533,8 @@ jobs:
mv ./artifacts/zed-remote-server-macos-x86_64.gz/zed-remote-server-macos-x86_64.gz release-artifacts/zed-remote-server-macos-x86_64.gz
mv ./artifacts/zed-remote-server-linux-aarch64.gz/zed-remote-server-linux-aarch64.gz release-artifacts/zed-remote-server-linux-aarch64.gz
mv ./artifacts/zed-remote-server-linux-x86_64.gz/zed-remote-server-linux-x86_64.gz release-artifacts/zed-remote-server-linux-x86_64.gz
+ mv ./artifacts/zed-remote-server-windows-aarch64.zip/zed-remote-server-windows-aarch64.zip release-artifacts/zed-remote-server-windows-aarch64.zip
+ mv ./artifacts/zed-remote-server-windows-x86_64.zip/zed-remote-server-windows-x86_64.zip release-artifacts/zed-remote-server-windows-x86_64.zip
shell: bash -euxo pipefail {0}
- name: gh release upload "$GITHUB_REF_NAME" --repo=zed-industries/zed release-artifacts/*
run: gh release upload "$GITHUB_REF_NAME" --repo=zed-industries/zed release-artifacts/*
@@ -329,6 +329,12 @@ jobs:
name: Zed-aarch64.exe
path: target/Zed-aarch64.exe
if-no-files-found: error
+ - name: '@actions/upload-artifact zed-remote-server-windows-aarch64.zip'
+ uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
+ with:
+ name: zed-remote-server-windows-aarch64.zip
+ path: target/zed-remote-server-windows-aarch64.zip
+ if-no-files-found: error
timeout-minutes: 60
bundle_windows_x86_64:
needs:
@@ -376,6 +382,12 @@ jobs:
name: Zed-x86_64.exe
path: target/Zed-x86_64.exe
if-no-files-found: error
+ - name: '@actions/upload-artifact zed-remote-server-windows-x86_64.zip'
+ uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
+ with:
+ name: zed-remote-server-windows-x86_64.zip
+ path: target/zed-remote-server-windows-x86_64.zip
+ if-no-files-found: error
timeout-minutes: 60
build_nix_linux_x86_64:
needs:
@@ -483,6 +495,8 @@ jobs:
mv ./artifacts/zed-remote-server-macos-x86_64.gz/zed-remote-server-macos-x86_64.gz release-artifacts/zed-remote-server-macos-x86_64.gz
mv ./artifacts/zed-remote-server-linux-aarch64.gz/zed-remote-server-linux-aarch64.gz release-artifacts/zed-remote-server-linux-aarch64.gz
mv ./artifacts/zed-remote-server-linux-x86_64.gz/zed-remote-server-linux-x86_64.gz release-artifacts/zed-remote-server-linux-x86_64.gz
+ mv ./artifacts/zed-remote-server-windows-aarch64.zip/zed-remote-server-windows-aarch64.zip release-artifacts/zed-remote-server-windows-aarch64.zip
+ mv ./artifacts/zed-remote-server-windows-x86_64.zip/zed-remote-server-windows-x86_64.zip release-artifacts/zed-remote-server-windows-x86_64.zip
shell: bash -euxo pipefail {0}
- name: ./script/upload-nightly
run: ./script/upload-nightly
@@ -225,6 +225,12 @@ jobs:
name: Zed-aarch64.exe
path: target/Zed-aarch64.exe
if-no-files-found: error
+ - name: '@actions/upload-artifact zed-remote-server-windows-aarch64.zip'
+ uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
+ with:
+ name: zed-remote-server-windows-aarch64.zip
+ path: target/zed-remote-server-windows-aarch64.zip
+ if-no-files-found: error
timeout-minutes: 60
bundle_windows_x86_64:
if: |-
@@ -263,6 +269,12 @@ jobs:
name: Zed-x86_64.exe
path: target/Zed-x86_64.exe
if-no-files-found: error
+ - name: '@actions/upload-artifact zed-remote-server-windows-x86_64.zip'
+ uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
+ with:
+ name: zed-remote-server-windows-x86_64.zip
+ path: target/zed-remote-server-windows-x86_64.zip
+ if-no-files-found: error
timeout-minutes: 60
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.ref }}
@@ -13677,6 +13677,7 @@ dependencies = [
"log",
"lsp",
"minidumper",
+ "net",
"node_runtime",
"paths",
"pretty_assertions",
@@ -13703,6 +13704,7 @@ dependencies = [
"unindent",
"util",
"watch",
+ "windows 0.61.3",
"workspace",
"worktree",
"zlog",
@@ -132,9 +132,9 @@ pub(crate) fn current_platform(headless: bool) -> Rc<dyn Platform> {
}
#[cfg(target_os = "windows")]
-pub(crate) fn current_platform(_headless: bool) -> Rc<dyn Platform> {
+pub(crate) fn current_platform(headless: bool) -> Rc<dyn Platform> {
Rc::new(
- WindowsPlatform::new()
+ WindowsPlatform::new(headless)
.inspect_err(|err| show_error("Failed to launch", err.to_string()))
.unwrap(),
)
@@ -33,12 +33,14 @@ pub(crate) struct WindowsPlatform {
inner: Rc<WindowsPlatformInner>,
raw_window_handles: Arc<RwLock<SmallVec<[SafeHwnd; 4]>>>,
// The below members will never change throughout the entire lifecycle of the app.
+ headless: bool,
icon: HICON,
background_executor: BackgroundExecutor,
foreground_executor: ForegroundExecutor,
- text_system: Arc<DirectWriteTextSystem>,
+ text_system: Arc<dyn PlatformTextSystem>,
+ direct_write_text_system: Option<Arc<DirectWriteTextSystem>>,
windows_version: WindowsVersion,
- drop_target_helper: IDropTargetHelper,
+ drop_target_helper: Option<IDropTargetHelper>,
/// Flag to instruct the `VSyncProvider` thread to invalidate the directx devices
/// as resizing them has failed, causing us to have lost at least the render target.
invalidate_devices: Arc<AtomicBool>,
@@ -76,11 +78,10 @@ struct PlatformCallbacks {
}
impl WindowsPlatformState {
- fn new(directx_devices: DirectXDevices) -> Self {
+ fn new(directx_devices: Option<DirectXDevices>) -> Self {
let callbacks = PlatformCallbacks::default();
let jump_list = JumpList::new();
let current_cursor = load_cursor(CursorStyle::Arrow);
- let directx_devices = Some(directx_devices);
Self {
callbacks,
@@ -93,11 +94,29 @@ impl WindowsPlatformState {
}
impl WindowsPlatform {
- pub(crate) fn new() -> Result<Self> {
+ pub(crate) fn new(headless: bool) -> Result<Self> {
unsafe {
OleInitialize(None).context("unable to initialize Windows OLE")?;
}
- let directx_devices = DirectXDevices::new().context("Creating DirectX devices")?;
+ let (directx_devices, text_system, direct_write_text_system) = if !headless {
+ let devices = DirectXDevices::new().context("Creating DirectX devices")?;
+ let dw_text_system = Arc::new(
+ DirectWriteTextSystem::new(&devices)
+ .context("Error creating DirectWriteTextSystem")?,
+ );
+ (
+ Some(devices),
+ dw_text_system.clone() as Arc<dyn PlatformTextSystem>,
+ Some(dw_text_system),
+ )
+ } else {
+ (
+ None,
+ Arc::new(crate::NoopTextSystem::new()) as Arc<dyn PlatformTextSystem>,
+ None,
+ )
+ };
+
let (main_sender, main_receiver) = PriorityQueueReceiver::new();
let validation_number = if usize::BITS == 64 {
rand::random::<u64>() as usize
@@ -105,10 +124,7 @@ impl WindowsPlatform {
rand::random::<u32>() as usize
};
let raw_window_handles = Arc::new(RwLock::new(SmallVec::new()));
- let text_system = Arc::new(
- DirectWriteTextSystem::new(&directx_devices)
- .context("Error creating DirectWriteTextSystem")?,
- );
+
register_platform_window_class();
let mut context = PlatformWindowCreateContext {
inner: None,
@@ -116,7 +132,7 @@ impl WindowsPlatform {
validation_number,
main_sender: Some(main_sender),
main_receiver: Some(main_receiver),
- directx_devices: Some(directx_devices),
+ directx_devices,
dispatcher: None,
};
let result = unsafe {
@@ -150,21 +166,31 @@ impl WindowsPlatform {
let background_executor = BackgroundExecutor::new(dispatcher.clone());
let foreground_executor = ForegroundExecutor::new(dispatcher);
- let drop_target_helper: IDropTargetHelper = unsafe {
- CoCreateInstance(&CLSID_DragDropHelper, None, CLSCTX_INPROC_SERVER)
- .context("Error creating drop target helper.")?
+ let drop_target_helper: Option<IDropTargetHelper> = if !headless {
+ Some(unsafe {
+ CoCreateInstance(&CLSID_DragDropHelper, None, CLSCTX_INPROC_SERVER)
+ .context("Error creating drop target helper.")?
+ })
+ } else {
+ None
+ };
+ let icon = if !headless {
+ load_icon().unwrap_or_default()
+ } else {
+ HICON::default()
};
- let icon = load_icon().unwrap_or_default();
let windows_version = WindowsVersion::new().context("Error retrieve windows version")?;
Ok(Self {
inner,
handle,
raw_window_handles,
+ headless,
icon,
background_executor,
foreground_executor,
text_system,
+ direct_write_text_system,
disable_direct_composition,
windows_version,
drop_target_helper,
@@ -196,7 +222,7 @@ impl WindowsPlatform {
executor: self.foreground_executor.clone(),
current_cursor: self.inner.state.current_cursor.get(),
windows_version: self.windows_version,
- drop_target_helper: self.drop_target_helper.clone(),
+ drop_target_helper: self.drop_target_helper.clone().unwrap(),
validation_number: self.inner.validation_number,
main_receiver: self.inner.main_receiver.clone(),
platform_window_handle: self.handle,
@@ -247,11 +273,17 @@ impl WindowsPlatform {
}
fn begin_vsync_thread(&self) {
- let mut directx_device = self.inner.state.directx_devices.borrow().clone().unwrap();
+ let Some(directx_devices) = self.inner.state.directx_devices.borrow().clone() else {
+ return;
+ };
+ let Some(direct_write_text_system) = &self.direct_write_text_system else {
+ return;
+ };
+ let mut directx_device = directx_devices;
let platform_window: SafeHwnd = self.handle.into();
let validation_number = self.inner.validation_number;
let all_windows = Arc::downgrade(&self.raw_window_handles);
- let text_system = Arc::downgrade(&self.text_system);
+ let text_system = Arc::downgrade(direct_write_text_system);
let invalidate_devices = self.invalidate_devices.clone();
std::thread::Builder::new()
@@ -338,7 +370,9 @@ impl Platform for WindowsPlatform {
fn run(&self, on_finish_launching: Box<dyn 'static + FnOnce()>) {
on_finish_launching();
- self.begin_vsync_thread();
+ if !self.headless {
+ self.begin_vsync_thread();
+ }
let mut msg = MSG::default();
unsafe {
@@ -733,12 +767,7 @@ impl Platform for WindowsPlatform {
impl WindowsPlatformInner {
fn new(context: &mut PlatformWindowCreateContext) -> Result<Rc<Self>> {
- let state = WindowsPlatformState::new(
- context
- .directx_devices
- .take()
- .context("missing directx devices")?,
- );
+ let state = WindowsPlatformState::new(context.directx_devices.take());
Ok(Rc::new(Self {
state,
raw_window_handles: context.raw_window_handles.clone(),
@@ -447,9 +447,9 @@ impl RemoteClient {
error.push_str("Client exited with ");
match status {
Ok(exit_code) => {
- error.push_str(&format!(" exit_code {exit_code:?}"))
+ error.push_str(&format!("exit_code {exit_code:?}"))
}
- Err(e) => error.push_str(&format!(" error {e:?}")),
+ Err(e) => error.push_str(&format!("error {e:?}")),
}
} else {
error.push_str("client did not become ready within the timeout");
@@ -678,11 +678,16 @@ impl SshRemoteConnection {
_ => Ok(Some(AppVersion::global(cx))),
})?;
- let tmp_path_gz = remote_server_dir_relative().join(
+ let tmp_path_compressed = remote_server_dir_relative().join(
RelPath::unix(&format!(
- "{}-download-{}.gz",
+ "{}-download-{}.{}",
binary_name,
- std::process::id()
+ std::process::id(),
+ if self.ssh_platform.os.is_windows() {
+ "zip"
+ } else {
+ "gz"
+ }
))
.unwrap(),
);
@@ -697,11 +702,11 @@ impl SshRemoteConnection {
.await?
{
match self
- .download_binary_on_server(&url, &tmp_path_gz, delegate, cx)
+ .download_binary_on_server(&url, &tmp_path_compressed, delegate, cx)
.await
{
Ok(_) => {
- self.extract_server_binary(&dst_path, &tmp_path_gz, delegate, cx)
+ self.extract_server_binary(&dst_path, &tmp_path_compressed, delegate, cx)
.await
.context("extracting server binary")?;
return Ok(dst_path);
@@ -723,10 +728,10 @@ impl SshRemoteConnection {
)
.await
.context("downloading server binary locally")?;
- self.upload_local_server_binary(&src_path, &tmp_path_gz, delegate, cx)
+ self.upload_local_server_binary(&src_path, &tmp_path_compressed, delegate, cx)
.await
.context("uploading server binary")?;
- self.extract_server_binary(&dst_path, &tmp_path_gz, delegate, cx)
+ self.extract_server_binary(&dst_path, &tmp_path_compressed, delegate, cx)
.await
.context("extracting server binary")?;
Ok(dst_path)
@@ -735,11 +740,11 @@ impl SshRemoteConnection {
async fn download_binary_on_server(
&self,
url: &str,
- tmp_path_gz: &RelPath,
+ tmp_path: &RelPath,
delegate: &Arc<dyn RemoteClientDelegate>,
cx: &mut AsyncApp,
) -> Result<()> {
- if let Some(parent) = tmp_path_gz.parent() {
+ if let Some(parent) = tmp_path.parent() {
let res = self
.socket
.run_command(
@@ -776,7 +781,7 @@ impl SshRemoteConnection {
&connection_timeout,
url,
"-o",
- &tmp_path_gz.display(self.path_style()),
+ &tmp_path.display(self.path_style()),
],
true,
)
@@ -806,7 +811,7 @@ impl SshRemoteConnection {
"1",
url,
"-O",
- &tmp_path_gz.display(self.path_style()),
+ &tmp_path.display(self.path_style()),
],
true,
)
@@ -835,11 +840,11 @@ impl SshRemoteConnection {
async fn upload_local_server_binary(
&self,
src_path: &Path,
- tmp_path_gz: &RelPath,
+ tmp_path: &RelPath,
delegate: &Arc<dyn RemoteClientDelegate>,
cx: &mut AsyncApp,
) -> Result<()> {
- if let Some(parent) = tmp_path_gz.parent() {
+ if let Some(parent) = tmp_path.parent() {
let res = self
.socket
.run_command(
@@ -864,10 +869,10 @@ impl SshRemoteConnection {
delegate.set_status(Some("Uploading remote development server"), cx);
log::info!(
"uploading remote development server to {:?} ({}kb)",
- tmp_path_gz,
+ tmp_path,
size / 1024
);
- self.upload_file(src_path, tmp_path_gz)
+ self.upload_file(src_path, tmp_path)
.await
.context("failed to upload server binary")?;
log::info!("uploaded remote development server in {:?}", t0.elapsed());
@@ -882,9 +887,21 @@ impl SshRemoteConnection {
cx: &mut AsyncApp,
) -> Result<()> {
delegate.set_status(Some("Extracting remote development server"), cx);
- let server_mode = 0o755;
+ if self.ssh_platform.os.is_windows() {
+ self.extract_server_binary_windows(dst_path, tmp_path).await
+ } else {
+ self.extract_server_binary_posix(dst_path, tmp_path).await
+ }
+ }
+
+ async fn extract_server_binary_posix(
+ &self,
+ dst_path: &RelPath,
+ tmp_path: &RelPath,
+ ) -> Result<()> {
let shell_kind = ShellKind::Posix;
+ let server_mode = 0o755;
let orig_tmp_path = tmp_path.display(self.path_style());
let server_mode = format!("{:o}", server_mode);
let server_mode = shell_kind
@@ -913,6 +930,39 @@ impl SshRemoteConnection {
Ok(())
}
+ async fn extract_server_binary_windows(
+ &self,
+ dst_path: &RelPath,
+ tmp_path: &RelPath,
+ ) -> Result<()> {
+ let shell_kind = ShellKind::Pwsh;
+ let orig_tmp_path = tmp_path.display(self.path_style());
+ let dst_path = dst_path.display(self.path_style());
+ let dst_path = shell_kind.try_quote(&dst_path).context("shell quoting")?;
+
+ let script = if let Some(tmp_path) = orig_tmp_path.strip_suffix(".zip") {
+ let orig_tmp_path = shell_kind
+ .try_quote(&orig_tmp_path)
+ .context("shell quoting")?;
+ let tmp_path = shell_kind.try_quote(tmp_path).context("shell quoting")?;
+ format!(
+ "Expand-Archive -Force -Path {orig_tmp_path} -DestinationPath {tmp_path} -ErrorAction Stop;
+ Move-Item -Force {tmp_path} {dst_path}",
+ )
+ } else {
+ let orig_tmp_path = shell_kind
+ .try_quote(&orig_tmp_path)
+ .context("shell quoting")?;
+ format!("Move-Item -Force {orig_tmp_path} {dst_path}")
+ };
+
+ let args = shell_kind.args_for_shell(false, script);
+ self.socket
+ .run_command(self.ssh_shell_kind, "powershell", &args, true)
+ .await?;
+ Ok(())
+ }
+
fn build_scp_command(
&self,
src_path: &Path,
@@ -1075,8 +1125,12 @@ impl SshSocket {
to_run.push(' ');
to_run.push_str(&shell_kind.try_quote(arg.as_ref()).expect("shell quoting"));
}
- let separator = shell_kind.sequential_commands_separator();
- let to_run = format!("cd{separator} {to_run}");
+ let to_run = if shell_kind == ShellKind::Cmd {
+ to_run // 'cd' prints the current directory in CMD
+ } else {
+ let separator = shell_kind.sequential_commands_separator();
+ format!("cd{separator} {to_run}")
+ };
self.ssh_options(&mut command, true)
.arg(self.connection_options.ssh_destination());
if !allow_pseudo_tty {
@@ -1188,7 +1242,7 @@ impl SshSocket {
let output = self
.run_command(
shell,
- "cmd",
+ "cmd.exe",
&["/c", "echo", "%PROCESSOR_ARCHITECTURE%"],
false,
)
@@ -1215,7 +1269,7 @@ impl SshSocket {
/// If it succeeds and returns Windows-like output, we assume it's Windows.
async fn probe_is_windows(&self) -> bool {
match self
- .run_command(ShellKind::PowerShell, "cmd", &["/c", "ver"], false)
+ .run_command(ShellKind::Cmd, "cmd.exe", &["/c", "ver"], false)
.await
{
// Windows 'ver' command outputs something like "Microsoft Windows [Version 10.0.19045.5011]"
@@ -1247,10 +1301,31 @@ impl SshSocket {
}
async fn shell_windows(&self) -> String {
- // powershell is always the default, and cannot really be removed from the system
- // so we can rely on that fact and reasonably assume that we will be running in a
- // powershell environment
- "powershell.exe".to_owned()
+ const DEFAULT_SHELL: &str = "cmd.exe";
+
+ // We detect the shell used by the SSH session by running the following command in PowerShell:
+ // (Get-CimInstance Win32_Process -Filter "ProcessId = $((Get-CimInstance Win32_Process -Filter ProcessId=$PID).ParentProcessId)").Name
+ // This prints the name of PowerShell's parent process (which will be the shell that SSH launched).
+ // We pass it as a Base64 encoded string since we don't yet know how to correctly quote that command.
+ // (We'd need to know what the shell is to do that...)
+ match self
+ .run_command(
+ ShellKind::Cmd,
+ "powershell",
+ &[
+ "-E",
+ "KABHAGUAdAAtAEMAaQBtAEkAbgBzAHQAYQBuAGMAZQAgAFcAaQBuADMAMgBfAFAAcgBvAGMAZQBzAHMAIAAtAEYAaQBsAHQAZQByACAAIgBQAHIAbwBjAGUAcwBzAEkAZAAgAD0AIAAkACgAKABHAGUAdAAtAEMAaQBtAEkAbgBzAHQAYQBuAGMAZQAgAFcAaQBuADMAMgBfAFAAcgBvAGMAZQBzAHMAIAAtAEYAaQBsAHQAZQByACAAUAByAG8AYwBlAHMAcwBJAGQAPQAkAFAASQBEACkALgBQAGEAcgBlAG4AdABQAHIAbwBjAGUAcwBzAEkAZAApACIAKQAuAE4AYQBtAGUA",
+ ],
+ false,
+ )
+ .await
+ {
+ Ok(output) => parse_shell(&output, DEFAULT_SHELL),
+ Err(e) => {
+ log::error!("Failed to detect remote shell: {e}");
+ DEFAULT_SHELL.to_owned()
+ }
+ }
}
}
@@ -10,7 +10,7 @@ license = "GPL-3.0-or-later"
workspace = true
[lib]
-path = "src/remote_server.rs"
+path = "src/server.rs"
doctest = false
[[bin]]
@@ -38,7 +38,7 @@ futures.workspace = true
git.workspace = true
git_hosting_providers.workspace = true
git2 = { workspace = true, features = ["vendored-libgit2"] }
-gpui.workspace = true
+gpui = { workspace = true, features = ["windows-manifest"] }
gpui_tokio.workspace = true
http_client.workspace = true
image.workspace = true
@@ -48,6 +48,7 @@ language_extension.workspace = true
languages.workspace = true
log.workspace = true
lsp.workspace = true
+net.workspace = true
node_runtime.workspace = true
paths.workspace = true
project.workspace = true
@@ -77,6 +78,9 @@ fork.workspace = true
libc.workspace = true
minidumper.workspace = true
+[target.'cfg(windows)'.dependencies]
+windows.workspace = true
+
[dev-dependencies]
action_log.workspace = true
agent = { workspace = true, features = ["test-support"] }
@@ -38,9 +38,8 @@ fn main() -> anyhow::Result<()> {
return Ok(());
}
- #[cfg(not(windows))]
if let Some(command) = cli.command {
- use remote_server::unix::ExecuteProxyError;
+ use remote_server::ExecuteProxyError;
let res = remote_server::run(command);
if let Err(e) = &res
@@ -58,13 +57,4 @@ fn main() -> anyhow::Result<()> {
eprintln!("usage: remote <run|proxy|version>");
std::process::exit(1);
}
-
- #[cfg(windows)]
- if let Some(_) = cli.command {
- eprintln!("run is not supported on Windows");
- std::process::exit(2);
- } else {
- eprintln!("usage: remote <run|proxy|version>");
- std::process::exit(1);
- }
}
@@ -1,81 +0,0 @@
-mod headless_project;
-
-#[cfg(not(windows))]
-pub mod unix;
-
-#[cfg(test)]
-mod remote_editing_tests;
-
-use clap::Subcommand;
-use std::path::PathBuf;
-
-pub use headless_project::{HeadlessAppState, HeadlessProject};
-
-#[derive(Subcommand)]
-pub enum Commands {
- Run {
- #[arg(long)]
- log_file: PathBuf,
- #[arg(long)]
- pid_file: PathBuf,
- #[arg(long)]
- stdin_socket: PathBuf,
- #[arg(long)]
- stdout_socket: PathBuf,
- #[arg(long)]
- stderr_socket: PathBuf,
- },
- Proxy {
- #[arg(long)]
- reconnect: bool,
- #[arg(long)]
- identifier: String,
- },
- Version,
-}
-
-#[cfg(not(windows))]
-pub fn run(command: Commands) -> anyhow::Result<()> {
- use anyhow::Context;
- use release_channel::{RELEASE_CHANNEL, ReleaseChannel};
- use unix::{execute_proxy, execute_run};
-
- match command {
- Commands::Run {
- log_file,
- pid_file,
- stdin_socket,
- stdout_socket,
- stderr_socket,
- } => execute_run(
- log_file,
- pid_file,
- stdin_socket,
- stdout_socket,
- stderr_socket,
- ),
- Commands::Proxy {
- identifier,
- reconnect,
- } => execute_proxy(identifier, reconnect).context("running proxy on the remote server"),
- Commands::Version => {
- let release_channel = *RELEASE_CHANNEL;
- match release_channel {
- ReleaseChannel::Stable | ReleaseChannel::Preview => {
- println!("{}", env!("ZED_PKG_VERSION"))
- }
- ReleaseChannel::Nightly | ReleaseChannel::Dev => {
- let commit_sha =
- option_env!("ZED_COMMIT_SHA").unwrap_or(release_channel.dev_name());
- let build_id = option_env!("ZED_BUILD_ID");
- if let Some(build_id) = build_id {
- println!("{}+{}", build_id, commit_sha)
- } else {
- println!("{commit_sha}");
- }
- }
- };
- Ok(())
- }
- }
-}
@@ -1,29 +1,37 @@
-use crate::HeadlessProject;
-use crate::headless_project::HeadlessAppState;
+mod headless_project;
+
+#[cfg(test)]
+mod remote_editing_tests;
+
+#[cfg(windows)]
+pub mod windows;
+
+pub use headless_project::{HeadlessAppState, HeadlessProject};
+
use anyhow::{Context as _, Result, anyhow};
+use clap::Subcommand;
use client::ProxySettings;
use collections::HashMap;
-use project::trusted_worktrees;
-use util::ResultExt;
-
use extension::ExtensionHostProxy;
use fs::{Fs, RealFs};
-use futures::channel::{mpsc, oneshot};
-use futures::{AsyncRead, AsyncWrite, AsyncWriteExt, FutureExt, SinkExt, select, select_biased};
+use futures::{
+ AsyncRead, AsyncWrite, AsyncWriteExt, FutureExt, SinkExt,
+ channel::{mpsc, oneshot},
+ select, select_biased,
+};
use git::GitHostingProviderRegistry;
use gpui::{App, AppContext as _, Context, Entity, UpdateGlobal as _};
use gpui_tokio::Tokio;
use http_client::{Url, read_proxy_from_env};
use language::LanguageRegistry;
+use net::async_net::{UnixListener, UnixStream};
use node_runtime::{NodeBinaryOptions, NodeRuntime};
use paths::logs_dir;
-use project::project_settings::ProjectSettings;
-use util::command::new_smol_command;
-
+use project::{project_settings::ProjectSettings, trusted_worktrees};
use proto::CrashReport;
use release_channel::{AppCommitSha, AppVersion, RELEASE_CHANNEL, ReleaseChannel};
-use remote::RemoteClient;
use remote::{
+ RemoteClient,
json_log::LogRecord,
protocol::{read_message, write_message},
proxy::ProxyLaunchError,
@@ -32,23 +40,90 @@ use reqwest_client::ReqwestClient;
use rpc::proto::{self, Envelope, REMOTE_SERVER_PROJECT_ID};
use rpc::{AnyProtoClient, TypedEnvelope};
use settings::{Settings, SettingsStore, watch_config_file};
-
-use smol::channel::{Receiver, Sender};
-use smol::io::AsyncReadExt;
-use smol::{net::unix::UnixListener, stream::StreamExt as _};
+use smol::{
+ channel::{Receiver, Sender},
+ io::AsyncReadExt,
+ stream::StreamExt as _,
+};
use std::{
env,
ffi::OsStr,
fs::File,
io::Write,
mem,
- ops::ControlFlow,
path::{Path, PathBuf},
- process::ExitStatus,
str::FromStr,
sync::{Arc, LazyLock},
};
use thiserror::Error;
+use util::{ResultExt, command::new_smol_command};
+
+#[derive(Subcommand)]
+pub enum Commands {
+ Run {
+ #[arg(long)]
+ log_file: PathBuf,
+ #[arg(long)]
+ pid_file: PathBuf,
+ #[arg(long)]
+ stdin_socket: PathBuf,
+ #[arg(long)]
+ stdout_socket: PathBuf,
+ #[arg(long)]
+ stderr_socket: PathBuf,
+ },
+ Proxy {
+ #[arg(long)]
+ reconnect: bool,
+ #[arg(long)]
+ identifier: String,
+ },
+ Version,
+}
+
+pub fn run(command: Commands) -> anyhow::Result<()> {
+ use anyhow::Context;
+ use release_channel::{RELEASE_CHANNEL, ReleaseChannel};
+
+ match command {
+ Commands::Run {
+ log_file,
+ pid_file,
+ stdin_socket,
+ stdout_socket,
+ stderr_socket,
+ } => execute_run(
+ log_file,
+ pid_file,
+ stdin_socket,
+ stdout_socket,
+ stderr_socket,
+ ),
+ Commands::Proxy {
+ identifier,
+ reconnect,
+ } => execute_proxy(identifier, reconnect).context("running proxy on the remote server"),
+ Commands::Version => {
+ let release_channel = *RELEASE_CHANNEL;
+ match release_channel {
+ ReleaseChannel::Stable | ReleaseChannel::Preview => {
+ println!("{}", env!("ZED_PKG_VERSION"))
+ }
+ ReleaseChannel::Nightly | ReleaseChannel::Dev => {
+ let commit_sha =
+ option_env!("ZED_COMMIT_SHA").unwrap_or(release_channel.dev_name());
+ let build_id = option_env!("ZED_BUILD_ID");
+ if let Some(build_id) = build_id {
+ println!("{}+{}", build_id, commit_sha)
+ } else {
+ println!("{commit_sha}");
+ }
+ }
+ };
+ Ok(())
+ }
+ }
+}
pub static VERSION: LazyLock<String> = LazyLock::new(|| match *RELEASE_CHANNEL {
ReleaseChannel::Stable | ReleaseChannel::Preview => env!("ZED_PKG_VERSION").to_owned(),
@@ -238,17 +313,17 @@ fn start_server(
.detach();
cx.spawn(async move |cx| {
- let mut stdin_incoming = listeners.stdin.incoming();
- let mut stdout_incoming = listeners.stdout.incoming();
- let mut stderr_incoming = listeners.stderr.incoming();
-
loop {
- let streams = futures::future::join3(stdin_incoming.next(), stdout_incoming.next(), stderr_incoming.next());
+ let streams = futures::future::join3(
+ listeners.stdin.accept(),
+ listeners.stdout.accept(),
+ listeners.stderr.accept(),
+ );
log::info!("accepting new connections");
let result = select! {
streams = streams.fuse() => {
- let (Some(Ok(stdin_stream)), Some(Ok(stdout_stream)), Some(Ok(stderr_stream))) = streams else {
+ let (Ok((stdin_stream, _)), Ok((stdout_stream, _)), Ok((stderr_stream, _))) = streams else {
log::error!("failed to accept new connections");
break;
};
@@ -372,11 +447,6 @@ pub fn execute_run(
) -> Result<()> {
init_paths()?;
- match daemonize()? {
- ControlFlow::Break(_) => return Ok(()),
- ControlFlow::Continue(_) => {}
- }
-
let app = gpui::Application::headless();
let pid = std::process::id();
let id = pid.to_string();
@@ -412,13 +482,19 @@ pub fn execute_run(
.build_global()
.unwrap();
- let (shell_env_loaded_tx, shell_env_loaded_rx) = oneshot::channel();
- app.background_executor()
- .spawn(async {
- util::load_login_shell_environment().await.log_err();
- shell_env_loaded_tx.send(()).ok();
- })
- .detach();
+ #[cfg(unix)]
+ let shell_env_loaded_rx = {
+ let (shell_env_loaded_tx, shell_env_loaded_rx) = oneshot::channel();
+ app.background_executor()
+ .spawn(async {
+ util::load_login_shell_environment().await.log_err();
+ shell_env_loaded_tx.send(()).ok();
+ })
+ .detach();
+ Some(shell_env_loaded_rx)
+ };
+ #[cfg(windows)]
+ let shell_env_loaded_rx: Option<oneshot::Receiver<()>> = None;
let git_hosting_provider_registry = Arc::new(GitHostingProviderRegistry::new());
let run = move |cx: &mut _| {
@@ -476,11 +552,8 @@ pub fn execute_run(
)
};
- let node_runtime = NodeRuntime::new(
- http_client.clone(),
- Some(shell_env_loaded_rx),
- node_settings_rx,
- );
+ let node_runtime =
+ NodeRuntime::new(http_client.clone(), shell_env_loaded_rx, node_settings_rx);
let mut languages = LanguageRegistry::new(cx.background_executor().clone());
languages.set_language_server_download_dir(paths::languages_dir().clone());
@@ -591,14 +664,10 @@ pub enum ExecuteProxyError {
path: PathBuf,
},
- #[error("Failed to kill existing server with pid '{pid}': {source:#}")]
- KillRunningServer {
- #[source]
- source: std::io::Error,
- pid: u32,
- },
+ #[error("Failed to kill existing server with pid '{pid}'")]
+ KillRunningServer { pid: u32 },
- #[error("failed to spawn server: {0:#}")]
+ #[error("failed to spawn server")]
SpawnServer(#[source] SpawnServerError),
#[error("stdin_task failed: {0:#}")]
@@ -639,22 +708,22 @@ pub(crate) fn execute_proxy(
.detach();
log::info!("starting proxy process. PID: {}", std::process::id());
- let server_pid = smol::block_on(async {
- let server_pid = check_pid_file(&server_paths.pid_file)
- .await
- .map_err(|source| ExecuteProxyError::CheckPidFile {
+ let server_pid = {
+ let server_pid = check_pid_file(&server_paths.pid_file).map_err(|source| {
+ ExecuteProxyError::CheckPidFile {
source,
path: server_paths.pid_file.clone(),
- })?;
+ }
+ })?;
if is_reconnecting {
match server_pid {
None => {
log::error!("attempted to reconnect, but no server running");
- Err(ExecuteProxyError::ServerNotRunning(
+ return Err(ExecuteProxyError::ServerNotRunning(
ProxyLaunchError::ServerNotRunning,
- ))
+ ));
}
- Some(server_pid) => Ok(server_pid),
+ Some(server_pid) => server_pid,
}
} else {
if let Some(pid) = server_pid {
@@ -662,11 +731,9 @@ pub(crate) fn execute_proxy(
"proxy found server already running with PID {}. Killing process and cleaning up files...",
pid
);
- kill_running_server(pid, &server_paths).await?;
+ kill_running_server(pid, &server_paths)?;
}
- spawn_server(&server_paths)
- .await
- .map_err(ExecuteProxyError::SpawnServer)?;
+ smol::block_on(spawn_server(&server_paths)).map_err(ExecuteProxyError::SpawnServer)?;
std::fs::read_to_string(&server_paths.pid_file)
.and_then(|contents| {
contents.parse::<u32>().map_err(|_| {
@@ -677,13 +744,13 @@ pub(crate) fn execute_proxy(
})
})
.map_err(SpawnServerError::ProcessStatus)
- .map_err(ExecuteProxyError::SpawnServer)
+ .map_err(ExecuteProxyError::SpawnServer)?
}
- })?;
+ };
let stdin_task = smol::spawn(async move {
let stdin = smol::Unblock::new(std::io::stdin());
- let stream = smol::net::unix::UnixStream::connect(&server_paths.stdin_socket)
+ let stream = UnixStream::connect(&server_paths.stdin_socket)
.await
.with_context(|| {
format!(
@@ -696,7 +763,7 @@ pub(crate) fn execute_proxy(
let stdout_task: smol::Task<Result<()>> = smol::spawn(async move {
let stdout = smol::Unblock::new(std::io::stdout());
- let stream = smol::net::unix::UnixStream::connect(&server_paths.stdout_socket)
+ let stream = UnixStream::connect(&server_paths.stdout_socket)
.await
.with_context(|| {
format!(
@@ -709,7 +776,7 @@ pub(crate) fn execute_proxy(
let stderr_task: smol::Task<Result<()>> = smol::spawn(async move {
let mut stderr = smol::Unblock::new(std::io::stderr());
- let mut stream = smol::net::unix::UnixStream::connect(&server_paths.stderr_socket)
+ let mut stream = UnixStream::connect(&server_paths.stderr_socket)
.await
.with_context(|| {
format!(
@@ -757,13 +824,18 @@ pub(crate) fn execute_proxy(
Ok(())
}
-async fn kill_running_server(pid: u32, paths: &ServerPaths) -> Result<(), ExecuteProxyError> {
+fn kill_running_server(pid: u32, paths: &ServerPaths) -> Result<(), ExecuteProxyError> {
log::info!("killing existing server with PID {}", pid);
- new_smol_command("kill")
- .arg(pid.to_string())
- .output()
- .await
- .map_err(|source| ExecuteProxyError::KillRunningServer { source, pid })?;
+ let system = sysinfo::System::new_with_specifics(
+ sysinfo::RefreshKind::nothing().with_processes(sysinfo::ProcessRefreshKind::nothing()),
+ );
+
+ if let Some(process) = system.process(sysinfo::Pid::from_u32(pid)) {
+ let killed = process.kill();
+ if !killed {
+ return Err(ExecuteProxyError::KillRunningServer { pid });
+ }
+ }
for file in [
&paths.pid_file,
@@ -774,6 +846,7 @@ async fn kill_running_server(pid: u32, paths: &ServerPaths) -> Result<(), Execut
log::debug!("cleaning up file {:?} before starting new server", file);
std::fs::remove_file(file).ok();
}
+
Ok(())
}
@@ -794,9 +867,6 @@ pub enum SpawnServerError {
#[error("failed to launch server process")]
ProcessStatus(#[source] std::io::Error),
- #[error("failed to launch and detach server process: {status}\n{paths}")]
- LaunchStatus { status: ExitStatus, paths: String },
-
#[error("failed to wait for server to be ready to accept connections")]
Timeout,
}
@@ -814,33 +884,15 @@ async fn spawn_server(paths: &ServerPaths) -> Result<(), SpawnServerError> {
}
let binary_name = std::env::current_exe().map_err(SpawnServerError::CurrentExe)?;
- let mut server_process = new_smol_command(binary_name);
- server_process
- .arg("run")
- .arg("--log-file")
- .arg(&paths.log_file)
- .arg("--pid-file")
- .arg(&paths.pid_file)
- .arg("--stdin-socket")
- .arg(&paths.stdin_socket)
- .arg("--stdout-socket")
- .arg(&paths.stdout_socket)
- .arg("--stderr-socket")
- .arg(&paths.stderr_socket);
- let status = server_process
- .status()
- .await
- .map_err(SpawnServerError::ProcessStatus)?;
+ #[cfg(windows)]
+ {
+ spawn_server_windows(&binary_name, paths)?;
+ }
- if !status.success() {
- return Err(SpawnServerError::LaunchStatus {
- status,
- paths: format!(
- "log file: {:?}, pid file: {:?}",
- paths.log_file, paths.pid_file,
- ),
- });
+ #[cfg(not(windows))]
+ {
+ spawn_server_normal(&binary_name, paths)?;
}
let mut total_time_waited = std::time::Duration::from_secs(0);
@@ -865,6 +917,55 @@ async fn spawn_server(paths: &ServerPaths) -> Result<(), SpawnServerError> {
Ok(())
}
+#[cfg(windows)]
+fn spawn_server_windows(binary_name: &Path, paths: &ServerPaths) -> Result<(), SpawnServerError> {
+ let binary_path = binary_name.to_string_lossy().to_string();
+ let parameters = format!(
+ "run --log-file \"{}\" --pid-file \"{}\" --stdin-socket \"{}\" --stdout-socket \"{}\" --stderr-socket \"{}\"",
+ paths.log_file.to_string_lossy(),
+ paths.pid_file.to_string_lossy(),
+ paths.stdin_socket.to_string_lossy(),
+ paths.stdout_socket.to_string_lossy(),
+ paths.stderr_socket.to_string_lossy()
+ );
+
+ let directory = binary_name
+ .parent()
+ .map(|p| p.to_string_lossy().to_string())
+ .unwrap_or_default();
+
+ crate::windows::shell_execute_from_explorer(&binary_path, ¶meters, &directory)
+ .map_err(|e| SpawnServerError::ProcessStatus(std::io::Error::other(e)))?;
+
+ Ok(())
+}
+
+#[cfg(not(windows))]
+fn spawn_server_normal(binary_name: &Path, paths: &ServerPaths) -> Result<(), SpawnServerError> {
+ let mut server_process = new_smol_command(binary_name);
+ server_process
+ .stdin(std::process::Stdio::null())
+ .stdout(std::process::Stdio::null())
+ .stderr(std::process::Stdio::null())
+ .arg("run")
+ .arg("--log-file")
+ .arg(&paths.log_file)
+ .arg("--pid-file")
+ .arg(&paths.pid_file)
+ .arg("--stdin-socket")
+ .arg(&paths.stdin_socket)
+ .arg("--stdout-socket")
+ .arg(&paths.stdout_socket)
+ .arg("--stderr-socket")
+ .arg(&paths.stderr_socket);
+
+ server_process
+ .spawn()
+ .map_err(SpawnServerError::ProcessStatus)?;
+
+ Ok(())
+}
+
#[derive(Debug, Error)]
#[error("Failed to remove PID file for missing process (pid `{pid}`")]
pub struct CheckPidError {
@@ -881,8 +982,8 @@ async fn check_server_running(pid: u32) -> std::io::Result<bool> {
.map(|output| output.status.success())
}
-async fn check_pid_file(path: &Path) -> Result<Option<u32>, CheckPidError> {
- let Some(pid) = std::fs::read_to_string(&path)
+fn check_pid_file(path: &Path) -> Result<Option<u32>, CheckPidError> {
+ let Some(pid) = std::fs::read_to_string(path)
.ok()
.and_then(|contents| contents.parse::<u32>().ok())
else {
@@ -890,21 +991,21 @@ async fn check_pid_file(path: &Path) -> Result<Option<u32>, CheckPidError> {
};
log::debug!("Checking if process with PID {} exists...", pid);
- match check_server_running(pid).await {
- Ok(true) => {
- log::debug!(
- "Process with PID {} exists. NOT spawning new server, but attaching to existing one.",
- pid
- );
- Ok(Some(pid))
- }
- _ => {
- log::debug!(
- "Found PID file, but process with that PID does not exist. Removing PID file."
- );
- std::fs::remove_file(&path).map_err(|source| CheckPidError { source, pid })?;
- Ok(None)
- }
+
+ let system = sysinfo::System::new_with_specifics(
+ sysinfo::RefreshKind::nothing().with_processes(sysinfo::ProcessRefreshKind::nothing()),
+ );
+
+ if system.process(sysinfo::Pid::from_u32(pid)).is_some() {
+ log::debug!(
+ "Process with PID {} exists. NOT spawning new server, but attaching to existing one.",
+ pid
+ );
+ Ok(Some(pid))
+ } else {
+ log::debug!("Found PID file, but process with that PID does not exist. Removing PID file.");
+ std::fs::remove_file(path).map_err(|source| CheckPidError { source, pid })?;
+ Ok(None)
}
}
@@ -1052,46 +1153,6 @@ fn read_proxy_settings(cx: &mut Context<HeadlessProject>) -> Option<Url> {
.or_else(read_proxy_from_env)
}
-fn daemonize() -> Result<ControlFlow<()>> {
- match fork::fork().map_err(|e| anyhow!("failed to call fork with error code {e}"))? {
- fork::Fork::Parent(_) => {
- return Ok(ControlFlow::Break(()));
- }
- fork::Fork::Child => {}
- }
-
- // Once we've detached from the parent, we want to close stdout/stderr/stdin
- // so that the outer SSH process is not attached to us in any way anymore.
- unsafe { redirect_standard_streams() }?;
-
- Ok(ControlFlow::Continue(()))
-}
-
-unsafe fn redirect_standard_streams() -> Result<()> {
- let devnull_fd = unsafe { libc::open(b"/dev/null\0" as *const [u8; 10] as _, libc::O_RDWR) };
- anyhow::ensure!(devnull_fd != -1, "failed to open /dev/null");
-
- let process_stdio = |name, fd| {
- let reopened_fd = unsafe { libc::dup2(devnull_fd, fd) };
- anyhow::ensure!(
- reopened_fd != -1,
- format!("failed to redirect {} to /dev/null", name)
- );
- Ok(())
- };
-
- process_stdio("stdin", libc::STDIN_FILENO)?;
- process_stdio("stdout", libc::STDOUT_FILENO)?;
- process_stdio("stderr", libc::STDERR_FILENO)?;
-
- anyhow::ensure!(
- unsafe { libc::close(devnull_fd) != -1 },
- "failed to close /dev/null fd after redirecting"
- );
-
- Ok(())
-}
-
fn cleanup_old_binaries() -> Result<()> {
let server_dir = paths::remote_server_dir_relative();
let release_channel = release_channel::RELEASE_CHANNEL.dev_name();
@@ -0,0 +1,48 @@
+use windows::Win32::System::Com::{
+ CLSCTX_LOCAL_SERVER, COINIT_APARTMENTTHREADED, CoCreateInstance, CoInitializeEx, IDispatch,
+ IServiceProvider,
+};
+use windows::Win32::System::Variant::VARIANT;
+use windows::Win32::UI::Shell::{
+ CSIDL_DESKTOP, IShellBrowser, IShellDispatch2, IShellFolderViewDual, IShellWindows,
+ SID_STopLevelBrowser, SVGIO_BACKGROUND, SWC_DESKTOP, SWFO_NEEDDISPATCH, ShellWindows,
+};
+use windows::core::{BSTR, Interface};
+
+pub fn shell_execute_from_explorer(
+ file: &str,
+ parameters: &str,
+ directory: &str,
+) -> anyhow::Result<()> {
+ unsafe {
+ CoInitializeEx(None, COINIT_APARTMENTTHREADED).unwrap();
+
+ let mut _hwnd = Default::default();
+ let shell_dispatch: IShellDispatch2 =
+ CoCreateInstance::<_, IShellWindows>(&ShellWindows, None, CLSCTX_LOCAL_SERVER)?
+ .FindWindowSW(
+ &VARIANT::from(CSIDL_DESKTOP as i32),
+ &VARIANT::default(),
+ SWC_DESKTOP,
+ &mut _hwnd,
+ SWFO_NEEDDISPATCH,
+ )?
+ .cast::<IServiceProvider>()?
+ .QueryService::<IShellBrowser>(&SID_STopLevelBrowser)?
+ .QueryActiveShellView()?
+ .GetItemObject::<IDispatch>(SVGIO_BACKGROUND)?
+ .cast::<IShellFolderViewDual>()?
+ .Application()?
+ .cast()?;
+
+ shell_dispatch.ShellExecute(
+ &BSTR::from(file),
+ &VARIANT::from(parameters),
+ &VARIANT::from(directory),
+ &VARIANT::from(""),
+ &VARIANT::from(0i32),
+ )?;
+
+ Ok(())
+ }
+}
@@ -124,12 +124,32 @@ function BuildZedAndItsFriends {
Copy-Item -Path ".\$CargoOutDir\explorer_command_injector.dll" -Destination "$innoDir\zed_explorer_command_injector.dll" -Force
}
+function BuildRemoteServer {
+ Write-Output "Building remote_server for $target"
+ cargo build --release --package remote_server --target $target
+
+ # Create zipped remote server binary
+ $remoteServerSrc = (Resolve-Path ".\$CargoOutDir\remote_server.exe").Path
+
+ if ($env:CI) {
+ Write-Output "Code signing remote_server.exe"
+ & "$innoDir\sign.ps1" $remoteServerSrc
+ }
+
+ $remoteServerDst = "$env:ZED_WORKSPACE\target\zed-remote-server-windows-$Architecture.zip"
+ Write-Output "Compressing remote_server to $remoteServerDst"
+ Compress-Archive -Path $remoteServerSrc -DestinationPath $remoteServerDst -Force
+
+ Write-Output "Remote server compressed successfully"
+}
+
function ZipZedAndItsFriendsDebug {
$items = @(
".\$CargoOutDir\zed.pdb",
".\$CargoOutDir\cli.pdb",
".\$CargoOutDir\auto_update_helper.pdb",
- ".\$CargoOutDir\explorer_command_injector.pdb"
+ ".\$CargoOutDir\explorer_command_injector.pdb",
+ ".\$CargoOutDir\remote_server.pdb"
)
Compress-Archive -Path $items -DestinationPath ".\$CargoOutDir\zed-$env:RELEASE_VERSION-$env:ZED_RELEASE_CHANNEL.dbg.zip" -Force
@@ -352,6 +372,7 @@ CheckEnvironmentVariables
PrepareForBundle
GenerateLicenses
BuildZedAndItsFriends
+BuildRemoteServer
MakeAppx
SignZedAndItsFriends
ZipZedAndItsFriendsDebug
@@ -15,13 +15,13 @@ $bucketName = "zed-nightly-host"
$releaseVersion = & "$PSScriptRoot\get-crate-version.ps1" zed
$version = "$releaseVersion+nightly.$env:GITHUB_RUN_NUMBER.$env:GITHUB_SHA"
-# TODO:
-# Upload remote server files
-# $remoteServerFiles = Get-ChildItem -Path "target" -Filter "zed-remote-server-*.gz" -Recurse -File
-# foreach ($file in $remoteServerFiles) {
-# Upload-ToBlobStore -BucketName $bucketName -FileToUpload $file.FullName -BlobStoreKey "nightly/$($file.Name)"
-# Remove-Item -Path $file.FullName
-# }
+$remoteServerFiles = Get-ChildItem -Path "target" -Filter "zed-remote-server-windows-*.zip" -Recurse -File -ErrorAction SilentlyContinue
+
+foreach ($file in $remoteServerFiles) {
+ UploadToBlobStore -BucketName $bucketName -FileToUpload $file.FullName -BlobStoreKey "nightly/$($file.Name)"
+ UploadToBlobStore -BucketName $bucketName -FileToUpload $file.FullName -BlobStoreKey "$version/$($file.Name)"
+ Remove-Item -Path $file.FullName -ErrorAction SilentlyContinue
+}
UploadToBlobStore -BucketName $bucketName -FileToUpload "target/Zed-$Architecture.exe" -BlobStoreKey "nightly/Zed-$Architecture.exe"
UploadToBlobStore -BucketName $bucketName -FileToUpload "target/Zed-$Architecture.exe" -BlobStoreKey "$version/Zed-$Architecture.exe"
@@ -155,6 +155,10 @@ pub(crate) fn bundle_windows(
Arch::X86_64 => assets::WINDOWS_X86_64,
Arch::AARCH64 => assets::WINDOWS_AARCH64,
};
+ let remote_server_artifact_name = match arch {
+ Arch::X86_64 => assets::REMOTE_SERVER_WINDOWS_X86_64,
+ Arch::AARCH64 => assets::REMOTE_SERVER_WINDOWS_AARCH64,
+ };
NamedJob {
name: format!("bundle_windows_{arch}"),
job: bundle_job(deps)
@@ -166,7 +170,10 @@ pub(crate) fn bundle_windows(
})
.add_step(steps::setup_sentry())
.add_step(bundle_windows(arch))
- .add_step(upload_artifact(&format!("target/{artifact_name}"))),
+ .add_step(upload_artifact(&format!("target/{artifact_name}")))
+ .add_step(upload_artifact(&format!(
+ "target/{remote_server_artifact_name}"
+ ))),
}
}
@@ -337,6 +337,8 @@ pub mod assets {
pub const REMOTE_SERVER_MAC_X86_64: &str = "zed-remote-server-macos-x86_64.gz";
pub const REMOTE_SERVER_LINUX_AARCH64: &str = "zed-remote-server-linux-aarch64.gz";
pub const REMOTE_SERVER_LINUX_X86_64: &str = "zed-remote-server-linux-x86_64.gz";
+ pub const REMOTE_SERVER_WINDOWS_AARCH64: &str = "zed-remote-server-windows-aarch64.zip";
+ pub const REMOTE_SERVER_WINDOWS_X86_64: &str = "zed-remote-server-windows-x86_64.zip";
pub fn all() -> Vec<&'static str> {
vec![
@@ -350,6 +352,8 @@ pub mod assets {
REMOTE_SERVER_MAC_X86_64,
REMOTE_SERVER_LINUX_AARCH64,
REMOTE_SERVER_LINUX_X86_64,
+ REMOTE_SERVER_WINDOWS_AARCH64,
+ REMOTE_SERVER_WINDOWS_X86_64,
]
}
}