mirror of
https://github.com/zed-industries/zed.git
synced 2026-06-01 03:14:56 +07:00
Add SSH remote server for Windows (#47460)
Closes https://github.com/zed-industries/zed/issues/33748 Release Notes: - Windows is now supported as a target platform for SSH remoting. --------- Co-authored-by: Lukas Wirth <me@lukaswirth.dev>
This commit is contained in:
parent
e9d94748e8
commit
9931c6f944
17 changed files with 511 additions and 311 deletions
14
.github/workflows/release.yml
vendored
14
.github/workflows/release.yml
vendored
|
|
@ -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/*
|
||||
|
|
|
|||
14
.github/workflows/release_nightly.yml
vendored
14
.github/workflows/release_nightly.yml
vendored
|
|
@ -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
|
||||
|
|
|
|||
12
.github/workflows/run_bundling.yml
vendored
12
.github/workflows/run_bundling.yml
vendored
|
|
@ -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 }}
|
||||
|
|
|
|||
2
Cargo.lock
generated
2
Cargo.lock
generated
|
|
@ -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();
|
||||
48
crates/remote_server/src/windows.rs
Normal file
48
crates/remote_server/src/windows.rs
Normal file
|
|
@ -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,
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue