From bebe24ea77eda338eb0f73b4eca9dcc3a047d2bd Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Sat, 12 Oct 2024 23:23:56 -0700 Subject: [PATCH] Add remote server cross compilation (#19136) This will allow us to compile debug builds of the remote-server for a different architecture than the one we are developing on. This also adds a CI step for building our remote server with minimal dependencies. Release Notes: - N/A --- .github/workflows/ci.yml | 31 +++- Cargo.lock | 1 + Cargo.toml | 3 +- Cross.toml | 2 + Dockerfile-cross | 17 ++ Dockerfile-cross.dockerignore | 16 ++ crates/auto_update/src/auto_update.rs | 1 + crates/gpui/Cargo.toml | 111 +++++++++---- crates/gpui/src/platform.rs | 93 ++++++++++- .../gpui/src/platform/blade/blade_renderer.rs | 2 +- crates/gpui/src/platform/linux.rs | 8 + crates/gpui/src/platform/linux/platform.rs | 152 +++++++++--------- crates/gpui/src/platform/linux/wayland.rs | 32 ++++ crates/gpui/src/platform/linux/x11/client.rs | 2 + .../src/platform/linux/xdg_desktop_portal.rs | 2 + crates/gpui/src/platform/mac.rs | 11 +- crates/gpui/src/platform/mac/platform.rs | 19 ++- crates/gpui/src/scene.rs | 20 +++ crates/recent_projects/src/ssh_connections.rs | 138 ++++++++++++---- crates/remote/src/ssh_session.rs | 14 ++ crates/rpc/Cargo.toml | 5 +- crates/zed/Cargo.toml | 6 +- script/remote-server | 17 ++ 23 files changed, 542 insertions(+), 161 deletions(-) create mode 100644 Cross.toml create mode 100644 Dockerfile-cross create mode 100644 Dockerfile-cross.dockerignore create mode 100755 script/remote-server diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f322d44cca..4222d64437 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -99,7 +99,10 @@ jobs: run: cargo build -p collab - name: Build other binaries and features - run: cargo build --workspace --bins --all-features; cargo check -p gpui --features "macos-blade" + run: | + cargo build --workspace --bins --all-features + cargo check -p gpui --features "macos-blade" + cargo build -p remote_server linux_tests: timeout-minutes: 60 @@ -133,6 +136,32 @@ jobs: - name: Build Zed run: cargo build -p zed + build_remote_server: + timeout-minutes: 60 + name: (Linux) Build Remote Server + runs-on: + - buildjet-16vcpu-ubuntu-2204 + steps: + - name: Add Rust to the PATH + run: echo "$HOME/.cargo/bin" >> $GITHUB_PATH + + - name: Checkout repo + uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4 + with: + clean: false + + - name: Cache dependencies + uses: swatinem/rust-cache@23bce251a8cd2ffc3c1075eaa2367cf899916d84 # v2 + with: + save-if: ${{ github.ref == 'refs/heads/main' }} + cache-provider: "buildjet" + + - name: Install Clang & Mold + run: ./script/remote-server && ./script/install-mold 2.34.0 + + - name: Build Remote Server + run: cargo build -p remote_server + # todo(windows): Actually run the tests windows_tests: timeout-minutes: 60 diff --git a/Cargo.lock b/Cargo.lock index 9f8a1615aa..93ba1988fc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -14669,6 +14669,7 @@ dependencies = [ "winresource", "workspace", "zed_actions", + "zstd", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index c6f7f95cc4..47cd3f915f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -220,7 +220,7 @@ git = { path = "crates/git" } git_hosting_providers = { path = "crates/git_hosting_providers" } go_to_line = { path = "crates/go_to_line" } google_ai = { path = "crates/google_ai" } -gpui = { path = "crates/gpui" } +gpui = { path = "crates/gpui", default-features = false, features = ["http_client"]} gpui_macros = { path = "crates/gpui_macros" } headless = { path = "crates/headless" } html_to_markdown = { path = "crates/html_to_markdown" } @@ -477,6 +477,7 @@ wasmtime = { version = "24", default-features = false, features = [ wasmtime-wasi = "24" which = "6.0.0" wit-component = "0.201" +zstd = "0.11" [workspace.dependencies.async-stripe] git = "https://github.com/zed-industries/async-stripe" diff --git a/Cross.toml b/Cross.toml new file mode 100644 index 0000000000..b5f0f1103a --- /dev/null +++ b/Cross.toml @@ -0,0 +1,2 @@ +[build] +dockerfile = "Dockerfile-cross" diff --git a/Dockerfile-cross b/Dockerfile-cross new file mode 100644 index 0000000000..488309641c --- /dev/null +++ b/Dockerfile-cross @@ -0,0 +1,17 @@ +# syntax=docker/dockerfile:1 + +ARG CROSS_BASE_IMAGE +FROM ${CROSS_BASE_IMAGE} +WORKDIR /app +ARG TZ=Etc/UTC \ + LANG=C.UTF-8 \ + LC_ALL=C.UTF-8 \ + DEBIAN_FRONTEND=noninteractive +ENV CARGO_TERM_COLOR=always + +COPY script/install-mold script/ +RUN ./script/install-mold "2.34.0" +COPY script/remote-server script/ +RUN ./script/remote-server + +COPY . . diff --git a/Dockerfile-cross.dockerignore b/Dockerfile-cross.dockerignore new file mode 100644 index 0000000000..337b4d4262 --- /dev/null +++ b/Dockerfile-cross.dockerignore @@ -0,0 +1,16 @@ +.git +.github +**/.gitignore +**/.gitkeep +.gitattributes +.mailmap +**/target +zed.xcworkspace +.DS_Store +compose.yml +plugins/bin +script/node_modules +styles/node_modules +crates/collab/static/styles.css +vendor/bin +assets/themes/ diff --git a/crates/auto_update/src/auto_update.rs b/crates/auto_update/src/auto_update.rs index 2c93ee4171..bb952990fc 100644 --- a/crates/auto_update/src/auto_update.rs +++ b/crates/auto_update/src/auto_update.rs @@ -464,6 +464,7 @@ impl AutoUpdater { smol::fs::create_dir_all(&platform_dir).await.ok(); let client = this.read_with(cx, |this, _| this.http_client.clone())?; + if smol::fs::metadata(&version_path).await.is_err() { log::info!("downloading zed-remote-server {os} {arch}"); download_remote_server_binary(&version_path, release, client, cx).await?; diff --git a/crates/gpui/Cargo.toml b/crates/gpui/Cargo.toml index 56a419d2de..8e982da855 100644 --- a/crates/gpui/Cargo.toml +++ b/crates/gpui/Cargo.toml @@ -11,16 +11,53 @@ license = "Apache-2.0" workspace = true [features] -default = ["http_client"] +default = ["http_client", "font-kit", "wayland", "x11"] test-support = [ "backtrace", "collections/test-support", "rand", "util/test-support", "http_client?/test-support", + "wayland", + "x11", ] runtime_shaders = [] macos-blade = ["blade-graphics", "blade-macros", "blade-util", "bytemuck"] +wayland = [ + "blade-graphics", + "blade-macros", + "blade-util", + "bytemuck", + "ashpd", + "cosmic-text", + "font-kit", + "calloop-wayland-source", + "wayland-backend", + "wayland-client", + "wayland-cursor", + "wayland-protocols", + "wayland-protocols-plasma", + "filedescriptor", + "xkbcommon", + "open", +] +x11 = [ + "blade-graphics", + "blade-macros", + "blade-util", + "bytemuck", + "ashpd", + "cosmic-text", + "font-kit", + "as-raw-xcb-connection", + "x11rb", + "xkbcommon", + "xim", + "x11-clipboard", + "filedescriptor", + "open", +] + [lib] path = "src/gpui.rs" @@ -95,7 +132,7 @@ core-foundation.workspace = true core-foundation-sys = "0.8" core-graphics = "0.23" core-text = "20.1" -font-kit = { git = "https://github.com/zed-industries/font-kit", rev = "40391b7" } +font-kit = { git = "https://github.com/zed-industries/font-kit", rev = "40391b7", optional = true} foreign-types = "0.5" log.workspace = true media.workspace = true @@ -105,31 +142,45 @@ objc = "0.2" [target.'cfg(any(target_os = "linux", target_os = "macos"))'.dependencies] pathfinder_geometry = "0.5" -[target.'cfg(any(target_os = "linux", target_os = "windows"))'.dependencies] -blade-graphics.workspace = true -blade-macros.workspace = true -blade-util.workspace = true -bytemuck = "1" -flume = "0.11" - [target.'cfg(target_os = "linux")'.dependencies] -as-raw-xcb-connection = "1" -ashpd.workspace = true -calloop = "0.13.0" -calloop-wayland-source = "0.3.0" -cosmic-text = { git = "https://github.com/pop-os/cosmic-text", rev = "542b20c" } -wayland-backend = { version = "0.3.3", features = ["client_system", "dlopen"] } -wayland-client = { version = "0.31.2" } -wayland-cursor = "0.31.1" +# Always used +flume = "0.11" +oo7 = "0.3.0" + +# Used in both windowing options +ashpd = { workspace = true, optional = true } +blade-graphics = { workspace = true, optional = true } +blade-macros = { workspace = true, optional = true } +blade-util = { workspace = true, optional = true } +bytemuck = { version = "1", optional = true } +cosmic-text = { git = "https://github.com/pop-os/cosmic-text", rev = "542b20c", optional = true } +font-kit = { git = "https://github.com/zed-industries/font-kit", rev = "40391b7", features = [ + "source-fontconfig-dlopen", +], optional = true } +calloop = { version = "0.13.0" } +filedescriptor = { version = "0.8.2", optional = true } +open = { version = "5.2.0", optional = true } + +# Wayland +calloop-wayland-source = { version = "0.3.0", optional = true } +wayland-backend = { version = "0.3.3", features = [ + "client_system", + "dlopen", +], optional = true } +wayland-client = { version = "0.31.2", optional = true } +wayland-cursor = { version = "0.31.1", optional = true } wayland-protocols = { version = "0.31.2", features = [ "client", "staging", "unstable", -] } -wayland-protocols-plasma = { version = "0.2.0", features = ["client"] } -oo7 = "0.3.0" -open = "5.2.0" -filedescriptor = "0.8.2" +], optional = true } +wayland-protocols-plasma = { version = "0.2.0", features = [ + "client", +], optional = true } + + +# X11 +as-raw-xcb-connection = { version = "1", optional = true } x11rb = { version = "0.13.0", features = [ "allow-unsafe-code", "xkb", @@ -138,21 +189,23 @@ x11rb = { version = "0.13.0", features = [ "cursor", "resource_manager", "sync", -] } +], optional = true } xkbcommon = { git = "https://github.com/ConradIrwin/xkbcommon-rs", rev = "fcbb4612185cc129ceeff51d22f7fb51810a03b2", features = [ "wayland", "x11", -] } +], optional = true } xim = { git = "https://github.com/XDeme1/xim-rs", rev = "d50d461764c2213655cd9cf65a0ea94c70d3c4fd", features = [ "x11rb-xcb", "x11rb-client", -] } -font-kit = { git = "https://github.com/zed-industries/font-kit", rev = "40391b7", features = [ - "source-fontconfig-dlopen", -] } -x11-clipboard = "0.9.2" +], optional = true } +x11-clipboard = { version = "0.9.2", optional = true } [target.'cfg(windows)'.dependencies] +blade-util.workspace = true +bytemuck = "1" +blade-graphics.workspace = true +blade-macros.workspace = true +flume = "0.11" rand.workspace = true windows.workspace = true windows-core = "0.58" diff --git a/crates/gpui/src/platform.rs b/crates/gpui/src/platform.rs index b5d6ae3127..42441c029e 100644 --- a/crates/gpui/src/platform.rs +++ b/crates/gpui/src/platform.rs @@ -10,7 +10,11 @@ mod linux; #[cfg(target_os = "macos")] mod mac; -#[cfg(any(target_os = "linux", target_os = "windows", feature = "macos-blade"))] +#[cfg(any( + all(target_os = "linux", any(feature = "x11", feature = "wayland")), + target_os = "windows", + feature = "macos-blade" +))] mod blade; #[cfg(any(test, feature = "test-support"))] @@ -26,7 +30,7 @@ use crate::{ RenderGlyphParams, RenderImage, RenderImageParams, RenderSvgParams, ScaledPixels, Scene, SharedString, Size, SvgSize, Task, TaskLabel, WindowContext, DEFAULT_WINDOW_SIZE, }; -use anyhow::Result; +use anyhow::{anyhow, Result}; use async_task::Runnable; use futures::channel::oneshot; use image::codecs::gif::GifDecoder; @@ -75,8 +79,12 @@ pub(crate) fn current_platform(headless: bool) -> Rc { } match guess_compositor() { + #[cfg(feature = "wayland")] "Wayland" => Rc::new(WaylandClient::new()), + + #[cfg(feature = "x11")] "X11" => Rc::new(X11Client::new()), + "Headless" => Rc::new(HeadlessClient::new()), _ => unreachable!(), } @@ -90,8 +98,16 @@ pub fn guess_compositor() -> &'static str { if std::env::var_os("ZED_HEADLESS").is_some() { return "Headless"; } + + #[cfg(feature = "wayland")] let wayland_display = std::env::var_os("WAYLAND_DISPLAY"); + #[cfg(not(feature = "wayland"))] + let wayland_display: Option = None; + + #[cfg(feature = "x11")] let x11_display = std::env::var_os("DISPLAY"); + #[cfg(not(feature = "x11"))] + let x11_display: Option = None; let use_wayland = wayland_display.is_some_and(|display| !display.is_empty()); let use_x11 = x11_display.is_some_and(|display| !display.is_empty()); @@ -426,6 +442,61 @@ pub(crate) trait PlatformTextSystem: Send + Sync { fn layout_line(&self, text: &str, font_size: Pixels, runs: &[FontRun]) -> LineLayout; } +pub(crate) struct NoopTextSystem; + +impl NoopTextSystem { + #[allow(dead_code)] + pub fn new() -> Self { + Self + } +} + +impl PlatformTextSystem for NoopTextSystem { + fn add_fonts(&self, _fonts: Vec>) -> Result<()> { + Ok(()) + } + + fn all_font_names(&self) -> Vec { + Vec::new() + } + + fn font_id(&self, descriptor: &Font) -> Result { + Err(anyhow!("No font found for {:?}", descriptor)) + } + + fn font_metrics(&self, _font_id: FontId) -> FontMetrics { + unimplemented!() + } + + fn typographic_bounds(&self, font_id: FontId, _glyph_id: GlyphId) -> Result> { + Err(anyhow!("No font found for {:?}", font_id)) + } + + fn advance(&self, font_id: FontId, _glyph_id: GlyphId) -> Result> { + Err(anyhow!("No font found for {:?}", font_id)) + } + + fn glyph_for_char(&self, _font_id: FontId, _ch: char) -> Option { + None + } + + fn glyph_raster_bounds(&self, params: &RenderGlyphParams) -> Result> { + Err(anyhow!("No font found for {:?}", params)) + } + + fn rasterize_glyph( + &self, + params: &RenderGlyphParams, + _raster_bounds: Bounds, + ) -> Result<(Size, Vec)> { + Err(anyhow!("No font found for {:?}", params)) + } + + fn layout_line(&self, _text: &str, _font_size: Pixels, _runs: &[FontRun]) -> LineLayout { + unimplemented!() + } +} + #[derive(PartialEq, Eq, Hash, Clone)] pub(crate) enum AtlasKey { Glyph(RenderGlyphParams), @@ -434,6 +505,10 @@ pub(crate) enum AtlasKey { } impl AtlasKey { + #[cfg_attr( + all(target_os = "linux", not(any(feature = "x11", feature = "wayland"))), + allow(dead_code) + )] pub(crate) fn texture_kind(&self) -> AtlasTextureKind { match self { AtlasKey::Glyph(params) => { @@ -494,6 +569,10 @@ pub(crate) struct AtlasTextureId { #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] #[repr(C)] +#[cfg_attr( + all(target_os = "linux", not(any(feature = "x11", feature = "wayland"))), + allow(dead_code) +)] pub(crate) enum AtlasTextureKind { Monochrome = 0, Polychrome = 1, @@ -521,6 +600,10 @@ pub(crate) struct PlatformInputHandler { handler: Box, } +#[cfg_attr( + all(target_os = "linux", not(any(feature = "x11", feature = "wayland"))), + allow(dead_code) +)] impl PlatformInputHandler { pub fn new(cx: AsyncWindowContext, handler: Box) -> Self { Self { cx, handler } @@ -728,10 +811,15 @@ pub struct WindowOptions { /// The variables that can be configured when creating a new window #[derive(Debug)] +#[cfg_attr( + all(target_os = "linux", not(any(feature = "x11", feature = "wayland"))), + allow(dead_code) +)] pub(crate) struct WindowParams { pub bounds: Bounds, /// The titlebar configuration of the window + #[cfg_attr(feature = "wayland", allow(dead_code))] pub titlebar: Option, /// The kind of window to create @@ -748,6 +836,7 @@ pub(crate) struct WindowParams { #[cfg_attr(target_os = "linux", allow(dead_code))] pub show: bool, + #[cfg_attr(feature = "wayland", allow(dead_code))] pub display_id: Option, pub window_min_size: Option>, diff --git a/crates/gpui/src/platform/blade/blade_renderer.rs b/crates/gpui/src/platform/blade/blade_renderer.rs index 65ccfcdd4d..5c37caf2cb 100644 --- a/crates/gpui/src/platform/blade/blade_renderer.rs +++ b/crates/gpui/src/platform/blade/blade_renderer.rs @@ -455,7 +455,7 @@ impl BladeRenderer { } } - #[cfg_attr(target_os = "macos", allow(dead_code))] + #[cfg_attr(any(target_os = "macos", feature = "wayland"), allow(dead_code))] pub fn viewport_size(&self) -> gpu::Extent { self.surface_config.size } diff --git a/crates/gpui/src/platform/linux.rs b/crates/gpui/src/platform/linux.rs index cfba4b4907..0499869361 100644 --- a/crates/gpui/src/platform/linux.rs +++ b/crates/gpui/src/platform/linux.rs @@ -1,14 +1,22 @@ mod dispatcher; mod headless; mod platform; +#[cfg(any(feature = "wayland", feature = "x11"))] mod text_system; +#[cfg(feature = "wayland")] mod wayland; +#[cfg(feature = "x11")] mod x11; + +#[cfg(any(feature = "wayland", feature = "x11"))] mod xdg_desktop_portal; pub(crate) use dispatcher::*; pub(crate) use headless::*; pub(crate) use platform::*; +#[cfg(any(feature = "wayland", feature = "x11"))] pub(crate) use text_system::*; +#[cfg(feature = "wayland")] pub(crate) use wayland::*; +#[cfg(feature = "x11")] pub(crate) use x11::*; diff --git a/crates/gpui/src/platform/linux/platform.rs b/crates/gpui/src/platform/linux/platform.rs index 6e09badb49..c18478aead 100644 --- a/crates/gpui/src/platform/linux/platform.rs +++ b/crates/gpui/src/platform/linux/platform.rs @@ -19,32 +19,26 @@ use std::{ }; use anyhow::anyhow; -use ashpd::desktop::file_chooser::{OpenFileRequest, SaveFileRequest}; -use ashpd::desktop::open_uri::{OpenDirectoryRequest, OpenFileRequest as OpenUriRequest}; -use ashpd::{url, ActivationToken}; use async_task::Runnable; use calloop::channel::Channel; use calloop::{EventLoop, LoopHandle, LoopSignal}; -use filedescriptor::FileDescriptor; use flume::{Receiver, Sender}; use futures::channel::oneshot; use parking_lot::Mutex; use util::ResultExt; -use wayland_client::Connection; -use wayland_protocols::wp::cursor_shape::v1::client::wp_cursor_shape_device_v1::Shape; + +#[cfg(any(feature = "wayland", feature = "x11"))] use xkbcommon::xkb::{self, Keycode, Keysym, State}; -use crate::platform::linux::wayland::WaylandClient; +use crate::platform::NoopTextSystem; use crate::{ - px, Action, AnyWindowHandle, BackgroundExecutor, ClipboardItem, CosmicTextSystem, CursorStyle, - DisplayId, ForegroundExecutor, Keymap, Keystroke, LinuxDispatcher, Menu, MenuItem, Modifiers, - OwnedMenu, PathPromptOptions, Pixels, Platform, PlatformDisplay, PlatformInputHandler, - PlatformTextSystem, PlatformWindow, Point, PromptLevel, Result, SemanticVersion, SharedString, - Size, Task, WindowAppearance, WindowOptions, WindowParams, + px, Action, AnyWindowHandle, BackgroundExecutor, ClipboardItem, CursorStyle, DisplayId, + ForegroundExecutor, Keymap, Keystroke, LinuxDispatcher, Menu, MenuItem, Modifiers, OwnedMenu, + PathPromptOptions, Pixels, Platform, PlatformDisplay, PlatformInputHandler, PlatformTextSystem, + PlatformWindow, Point, PromptLevel, Result, SemanticVersion, SharedString, Size, Task, + WindowAppearance, WindowOptions, WindowParams, }; -use super::x11::X11Client; - pub(crate) const SCROLL_LINES: f32 = 3.0; // Values match the defaults on GTK. @@ -93,7 +87,7 @@ pub(crate) struct PlatformHandlers { pub(crate) struct LinuxCommon { pub(crate) background_executor: BackgroundExecutor, pub(crate) foreground_executor: ForegroundExecutor, - pub(crate) text_system: Arc, + pub(crate) text_system: Arc, pub(crate) appearance: WindowAppearance, pub(crate) auto_hide_scrollbars: bool, pub(crate) callbacks: PlatformHandlers, @@ -104,7 +98,12 @@ pub(crate) struct LinuxCommon { impl LinuxCommon { pub fn new(signal: LoopSignal) -> (Self, Channel) { let (main_sender, main_receiver) = calloop::channel::channel::(); - let text_system = Arc::new(CosmicTextSystem::new()); + #[cfg(any(feature = "wayland", feature = "x11"))] + let text_system = Arc::new(crate::CosmicTextSystem::new()); + + #[cfg(not(any(feature = "wayland", feature = "x11")))] + let text_system = Arc::new(crate::NoopTextSystem::new()); + let callbacks = PlatformHandlers::default(); let dispatcher = Arc::new(LinuxDispatcher::new(main_sender.clone())); @@ -264,6 +263,11 @@ impl Platform for P { options: PathPromptOptions, ) -> oneshot::Receiver>>> { let (done_tx, done_rx) = oneshot::channel(); + + #[cfg(not(any(feature = "wayland", feature = "x11")))] + done_tx.send(Ok(None)); + + #[cfg(any(feature = "wayland", feature = "x11"))] self.foreground_executor() .spawn(async move { let title = if options.directories { @@ -272,7 +276,7 @@ impl Platform for P { "Open File" }; - let request = match OpenFileRequest::default() + let request = match ashpd::desktop::file_chooser::OpenFileRequest::default() .modal(true) .title(title) .multiple(options.multiple) @@ -310,37 +314,47 @@ impl Platform for P { fn prompt_for_new_path(&self, directory: &Path) -> oneshot::Receiver>> { let (done_tx, done_rx) = oneshot::channel(); - let directory = directory.to_owned(); - self.foreground_executor() - .spawn(async move { - let request = match SaveFileRequest::default() - .modal(true) - .title("Save File") - .current_folder(directory) - .expect("pathbuf should not be nul terminated") - .send() - .await - { - Ok(request) => request, - Err(err) => { - let result = match err { - ashpd::Error::PortalNotFound(_) => anyhow!(FILE_PICKER_PORTAL_MISSING), - err => err.into(), - }; - done_tx.send(Err(result)); - return; - } - }; - let result = match request.response() { - Ok(response) => Ok(response - .uris() - .first() - .and_then(|uri| uri.to_file_path().ok())), - Err(ashpd::Error::Response(_)) => Ok(None), - Err(e) => Err(e.into()), - }; - done_tx.send(result); + #[cfg(not(any(feature = "wayland", feature = "x11")))] + done_tx.send(Ok(None)); + + #[cfg(any(feature = "wayland", feature = "x11"))] + self.foreground_executor() + .spawn({ + let directory = directory.to_owned(); + + async move { + let request = match ashpd::desktop::file_chooser::SaveFileRequest::default() + .modal(true) + .title("Save File") + .current_folder(directory) + .expect("pathbuf should not be nul terminated") + .send() + .await + { + Ok(request) => request, + Err(err) => { + let result = match err { + ashpd::Error::PortalNotFound(_) => { + anyhow!(FILE_PICKER_PORTAL_MISSING) + } + err => err.into(), + }; + done_tx.send(Err(result)); + return; + } + }; + + let result = match request.response() { + Ok(response) => Ok(response + .uris() + .first() + .and_then(|uri| uri.to_file_path().ok())), + Err(ashpd::Error::Response(_)) => Ok(None), + Err(e) => Err(e.into()), + }; + done_tx.send(result); + } }) .detach(); @@ -518,16 +532,17 @@ impl Platform for P { fn add_recent_document(&self, _path: &Path) {} } +#[cfg(any(feature = "wayland", feature = "x11"))] pub(super) fn open_uri_internal( executor: BackgroundExecutor, uri: &str, activation_token: Option, ) { - if let Some(uri) = url::Url::parse(uri).log_err() { + if let Some(uri) = ashpd::url::Url::parse(uri).log_err() { executor .spawn(async move { - match OpenUriRequest::default() - .activation_token(activation_token.clone().map(ActivationToken::from)) + match ashpd::desktop::open_uri::OpenFileRequest::default() + .activation_token(activation_token.clone().map(ashpd::ActivationToken::from)) .send_uri(&uri) .await { @@ -551,6 +566,7 @@ pub(super) fn open_uri_internal( } } +#[cfg(any(feature = "x11", feature = "wayland"))] pub(super) fn reveal_path_internal( executor: BackgroundExecutor, path: PathBuf, @@ -559,8 +575,8 @@ pub(super) fn reveal_path_internal( executor .spawn(async move { if let Some(dir) = File::open(path.clone()).log_err() { - match OpenDirectoryRequest::default() - .activation_token(activation_token.map(ActivationToken::from)) + match ashpd::desktop::open_uri::OpenDirectoryRequest::default() + .activation_token(activation_token.map(ashpd::ActivationToken::from)) .send(&dir.as_fd()) .await { @@ -582,6 +598,7 @@ pub(super) fn is_within_click_distance(a: Point, b: Point) -> bo diff.x.abs() <= DOUBLE_CLICK_DISTANCE && diff.y.abs() <= DOUBLE_CLICK_DISTANCE } +#[cfg(any(feature = "wayland", feature = "x11"))] pub(super) fn get_xkb_compose_state(cx: &xkb::Context) -> Option { let mut locales = Vec::default(); if let Some(locale) = std::env::var_os("LC_CTYPE") { @@ -603,7 +620,8 @@ pub(super) fn get_xkb_compose_state(cx: &xkb::Context) -> Option Result> { +#[cfg(any(feature = "wayland", feature = "x11"))] +pub(super) unsafe fn read_fd(mut fd: filedescriptor::FileDescriptor) -> Result> { let mut file = File::from_raw_fd(fd.as_raw_fd()); let mut buffer = Vec::new(); file.read_to_end(&mut buffer)?; @@ -611,32 +629,6 @@ pub(super) unsafe fn read_fd(mut fd: FileDescriptor) -> Result> { } impl CursorStyle { - pub(super) fn to_shape(&self) -> Shape { - match self { - CursorStyle::Arrow => Shape::Default, - CursorStyle::IBeam => Shape::Text, - CursorStyle::Crosshair => Shape::Crosshair, - CursorStyle::ClosedHand => Shape::Grabbing, - CursorStyle::OpenHand => Shape::Grab, - CursorStyle::PointingHand => Shape::Pointer, - CursorStyle::ResizeLeft => Shape::WResize, - CursorStyle::ResizeRight => Shape::EResize, - CursorStyle::ResizeLeftRight => Shape::EwResize, - CursorStyle::ResizeUp => Shape::NResize, - CursorStyle::ResizeDown => Shape::SResize, - CursorStyle::ResizeUpDown => Shape::NsResize, - CursorStyle::ResizeUpLeftDownRight => Shape::NwseResize, - CursorStyle::ResizeUpRightDownLeft => Shape::NeswResize, - CursorStyle::ResizeColumn => Shape::ColResize, - CursorStyle::ResizeRow => Shape::RowResize, - CursorStyle::IBeamCursorForVerticalLayout => Shape::VerticalText, - CursorStyle::OperationNotAllowed => Shape::NotAllowed, - CursorStyle::DragLink => Shape::Alias, - CursorStyle::DragCopy => Shape::Copy, - CursorStyle::ContextualMenu => Shape::ContextMenu, - } - } - pub(super) fn to_icon_name(&self) -> String { // Based on cursor names from https://gitlab.gnome.org/GNOME/adwaita-icon-theme (GNOME) // and https://github.com/KDE/breeze (KDE). Both of them seem to be also derived from @@ -668,6 +660,7 @@ impl CursorStyle { } } +#[cfg(any(feature = "wayland", feature = "x11"))] impl Keystroke { pub(super) fn from_xkb(state: &State, modifiers: Modifiers, keycode: Keycode) -> Self { let mut modifiers = modifiers; @@ -813,6 +806,7 @@ impl Keystroke { } } +#[cfg(any(feature = "wayland", feature = "x11"))] impl Modifiers { pub(super) fn from_xkb(keymap_state: &State) -> Self { let shift = keymap_state.mod_name_is_active(xkb::MOD_NAME_SHIFT, xkb::STATE_MODS_EFFECTIVE); diff --git a/crates/gpui/src/platform/linux/wayland.rs b/crates/gpui/src/platform/linux/wayland.rs index e8594426fa..f178e4b643 100644 --- a/crates/gpui/src/platform/linux/wayland.rs +++ b/crates/gpui/src/platform/linux/wayland.rs @@ -6,3 +6,35 @@ mod serial; mod window; pub(crate) use client::*; + +use wayland_protocols::wp::cursor_shape::v1::client::wp_cursor_shape_device_v1::Shape; + +use crate::CursorStyle; + +impl CursorStyle { + pub(super) fn to_shape(&self) -> Shape { + match self { + CursorStyle::Arrow => Shape::Default, + CursorStyle::IBeam => Shape::Text, + CursorStyle::Crosshair => Shape::Crosshair, + CursorStyle::ClosedHand => Shape::Grabbing, + CursorStyle::OpenHand => Shape::Grab, + CursorStyle::PointingHand => Shape::Pointer, + CursorStyle::ResizeLeft => Shape::WResize, + CursorStyle::ResizeRight => Shape::EResize, + CursorStyle::ResizeLeftRight => Shape::EwResize, + CursorStyle::ResizeUp => Shape::NResize, + CursorStyle::ResizeDown => Shape::SResize, + CursorStyle::ResizeUpDown => Shape::NsResize, + CursorStyle::ResizeUpLeftDownRight => Shape::NwseResize, + CursorStyle::ResizeUpRightDownLeft => Shape::NeswResize, + CursorStyle::ResizeColumn => Shape::ColResize, + CursorStyle::ResizeRow => Shape::RowResize, + CursorStyle::IBeamCursorForVerticalLayout => Shape::VerticalText, + CursorStyle::OperationNotAllowed => Shape::NotAllowed, + CursorStyle::DragLink => Shape::Alias, + CursorStyle::DragCopy => Shape::Copy, + CursorStyle::ContextualMenu => Shape::ContextMenu, + } + } +} diff --git a/crates/gpui/src/platform/linux/x11/client.rs b/crates/gpui/src/platform/linux/x11/client.rs index 459f2045bb..b5a5a00910 100644 --- a/crates/gpui/src/platform/linux/x11/client.rs +++ b/crates/gpui/src/platform/linux/x11/client.rs @@ -1421,10 +1421,12 @@ impl LinuxClient for X11Client { } fn open_uri(&self, uri: &str) { + #[cfg(any(feature = "wayland", feature = "x11"))] open_uri_internal(self.background_executor(), uri, None); } fn reveal_path(&self, path: PathBuf) { + #[cfg(any(feature = "x11", feature = "wayland"))] reveal_path_internal(self.background_executor(), path, None); } diff --git a/crates/gpui/src/platform/linux/xdg_desktop_portal.rs b/crates/gpui/src/platform/linux/xdg_desktop_portal.rs index b36e482639..ade7833eab 100644 --- a/crates/gpui/src/platform/linux/xdg_desktop_portal.rs +++ b/crates/gpui/src/platform/linux/xdg_desktop_portal.rs @@ -11,7 +11,9 @@ use crate::{BackgroundExecutor, WindowAppearance}; pub enum Event { WindowAppearance(WindowAppearance), + #[cfg_attr(feature = "x11", allow(dead_code))] CursorTheme(String), + #[cfg_attr(feature = "x11", allow(dead_code))] CursorSize(u32), } diff --git a/crates/gpui/src/platform/mac.rs b/crates/gpui/src/platform/mac.rs index 2223dd91b4..396fd49d04 100644 --- a/crates/gpui/src/platform/mac.rs +++ b/crates/gpui/src/platform/mac.rs @@ -17,9 +17,14 @@ use metal_renderer as renderer; use crate::platform::blade as renderer; mod attributed_string; + +#[cfg(feature = "font-kit")] mod open_type; -mod platform; + +#[cfg(feature = "font-kit")] mod text_system; + +mod platform; mod window; mod window_appearance; @@ -39,9 +44,11 @@ pub(crate) use dispatcher::*; pub(crate) use display::*; pub(crate) use display_link::*; pub(crate) use platform::*; -pub(crate) use text_system::*; pub(crate) use window::*; +#[cfg(feature = "font-kit")] +pub(crate) use text_system::*; + trait BoolExt { fn to_objc(self) -> BOOL; } diff --git a/crates/gpui/src/platform/mac/platform.rs b/crates/gpui/src/platform/mac/platform.rs index 78465e8f8d..b8b9d17be7 100644 --- a/crates/gpui/src/platform/mac/platform.rs +++ b/crates/gpui/src/platform/mac/platform.rs @@ -6,9 +6,9 @@ use super::{ use crate::{ hash, Action, AnyWindowHandle, BackgroundExecutor, ClipboardEntry, ClipboardItem, ClipboardString, CursorStyle, ForegroundExecutor, Image, ImageFormat, Keymap, MacDispatcher, - MacDisplay, MacTextSystem, MacWindow, Menu, MenuItem, PathPromptOptions, Platform, - PlatformDisplay, PlatformTextSystem, PlatformWindow, Result, SemanticVersion, Task, - WindowAppearance, WindowParams, + MacDisplay, MacWindow, Menu, MenuItem, PathPromptOptions, Platform, PlatformDisplay, + PlatformTextSystem, PlatformWindow, Result, SemanticVersion, Task, WindowAppearance, + WindowParams, }; use anyhow::anyhow; use block::ConcreteBlock; @@ -145,7 +145,7 @@ pub(crate) struct MacPlatform(Mutex); pub(crate) struct MacPlatformState { background_executor: BackgroundExecutor, foreground_executor: ForegroundExecutor, - text_system: Arc, + text_system: Arc, renderer_context: renderer::Context, headless: bool, pasteboard: id, @@ -171,11 +171,18 @@ impl Default for MacPlatform { impl MacPlatform { pub(crate) fn new(headless: bool) -> Self { let dispatcher = Arc::new(MacDispatcher::new()); + + #[cfg(feature = "font-kit")] + let text_system = Arc::new(crate::MacTextSystem::new()); + + #[cfg(not(feature = "font-kit"))] + let text_system = Arc::new(crate::NoopTextSystem::new()); + Self(Mutex::new(MacPlatformState { - background_executor: BackgroundExecutor::new(dispatcher.clone()), headless, + text_system, + background_executor: BackgroundExecutor::new(dispatcher.clone()), foreground_executor: ForegroundExecutor::new(dispatcher), - text_system: Arc::new(MacTextSystem::new()), renderer_context: renderer::Context::default(), pasteboard: unsafe { NSPasteboard::generalPasteboard(nil) }, text_hash_pasteboard_type: unsafe { ns_string("zed-text-hash") }, diff --git a/crates/gpui/src/scene.rs b/crates/gpui/src/scene.rs index 2b0d17a51e..d3eee39aa7 100644 --- a/crates/gpui/src/scene.rs +++ b/crates/gpui/src/scene.rs @@ -40,6 +40,10 @@ impl Scene { self.surfaces.clear(); } + #[cfg_attr( + all(target_os = "linux", not(any(feature = "x11", feature = "wayland"))), + allow(dead_code) + )] pub fn paths(&self) -> &[Path] { &self.paths } @@ -130,6 +134,10 @@ impl Scene { self.surfaces.sort(); } + #[cfg_attr( + all(target_os = "linux", not(any(feature = "x11", feature = "wayland"))), + allow(dead_code) + )] pub(crate) fn batches(&self) -> impl Iterator { BatchIterator { shadows: &self.shadows, @@ -158,6 +166,10 @@ impl Scene { } #[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Default)] +#[cfg_attr( + all(target_os = "linux", not(any(feature = "x11", feature = "wayland"))), + allow(dead_code) +)] pub(crate) enum PrimitiveKind { Shadow, #[default] @@ -212,6 +224,10 @@ impl Primitive { } } +#[cfg_attr( + all(target_os = "linux", not(any(feature = "x11", feature = "wayland"))), + allow(dead_code) +)] struct BatchIterator<'a> { shadows: &'a [Shadow], shadows_start: usize, @@ -398,6 +414,10 @@ impl<'a> Iterator for BatchIterator<'a> { } #[derive(Debug)] +#[cfg_attr( + all(target_os = "linux", not(any(feature = "x11", feature = "wayland"))), + allow(dead_code) +)] pub(crate) enum PrimitiveBatch<'a> { Shadows(&'a [Shadow]), Quads(&'a [Quad]), diff --git a/crates/recent_projects/src/ssh_connections.rs b/crates/recent_projects/src/ssh_connections.rs index 0e33c6d36a..318c2aec6a 100644 --- a/crates/recent_projects/src/ssh_connections.rs +++ b/crates/recent_projects/src/ssh_connections.rs @@ -390,40 +390,11 @@ impl SshClientDelegate { // In dev mode, build the remote server binary from source #[cfg(debug_assertions)] - if release_channel == ReleaseChannel::Dev - && platform.arch == std::env::consts::ARCH - && platform.os == std::env::consts::OS - { - use smol::process::{Command, Stdio}; - - self.update_status(Some("building remote server binary from source"), cx); - log::info!("building remote server binary from source"); - run_cmd(Command::new("cargo").args([ - "build", - "--package", - "remote_server", - "--target-dir", - "target/remote_server", - ])) - .await?; - // run_cmd(Command::new("strip").args(["target/remote_server/debug/remote_server"])) - // .await?; - run_cmd(Command::new("gzip").args([ - "-9", - "-f", - "target/remote_server/debug/remote_server", - ])) - .await?; - - let path = std::env::current_dir()?.join("target/remote_server/debug/remote_server.gz"); - return Ok((path, version)); - - async fn run_cmd(command: &mut Command) -> Result<()> { - let output = command.stderr(Stdio::inherit()).output().await?; - if !output.status.success() { - Err(anyhow::anyhow!("failed to run command: {:?}", command))?; - } - Ok(()) + if release_channel == ReleaseChannel::Dev { + let result = self.build_local(cx, platform, version).await?; + // Fall through to a remote binary if we're not able to compile a local binary + if let Some(result) = result { + return Ok(result); } } @@ -446,6 +417,105 @@ impl SshClientDelegate { Ok((binary_path, version)) } + + #[cfg(debug_assertions)] + async fn build_local( + &self, + cx: &mut AsyncAppContext, + platform: SshPlatform, + version: SemanticVersion, + ) -> Result> { + use smol::process::{Command, Stdio}; + + async fn run_cmd(command: &mut Command) -> Result<()> { + let output = command.stderr(Stdio::inherit()).output().await?; + if !output.status.success() { + Err(anyhow::anyhow!("failed to run command: {:?}", command))?; + } + Ok(()) + } + + if platform.arch == std::env::consts::ARCH && platform.os == std::env::consts::OS { + self.update_status(Some("Building remote server binary from source"), cx); + log::info!("building remote server binary from source"); + run_cmd(Command::new("cargo").args([ + "build", + "--package", + "remote_server", + "--target-dir", + "target/remote_server", + ])) + .await?; + + self.update_status(Some("Compressing binary"), cx); + + run_cmd(Command::new("gzip").args([ + "-9", + "-f", + "target/remote_server/debug/remote_server", + ])) + .await?; + + let path = std::env::current_dir()?.join("target/remote_server/debug/remote_server.gz"); + return Ok(Some((path, version))); + } else if let Some(triple) = platform.triple() { + smol::fs::create_dir_all("target/remote-server").await?; + + self.update_status(Some("Installing cross.rs"), cx); + log::info!("installing cross"); + run_cmd(Command::new("cargo").args([ + "install", + "cross", + "--git", + "https://github.com/cross-rs/cross", + ])) + .await?; + + self.update_status( + Some(&format!( + "Building remote server binary from source for {}", + &triple + )), + cx, + ); + log::info!("building remote server binary from source for {}", &triple); + run_cmd( + Command::new("cross") + .args([ + "build", + "--package", + "remote_server", + "--target-dir", + "target/remote_server", + "--target", + &triple, + ]) + .env( + "CROSS_CONTAINER_OPTS", + "--mount type=bind,src=./target,dst=/app/target", + ), + ) + .await?; + + self.update_status(Some("Compressing binary"), cx); + + run_cmd(Command::new("gzip").args([ + "-9", + "-f", + &format!("target/remote_server/{}/debug/remote_server", triple), + ])) + .await?; + + let path = std::env::current_dir()?.join(format!( + "target/remote_server/{}/debug/remote_server.gz", + triple + )); + + return Ok(Some((path, version))); + } else { + return Ok(None); + } + } } pub fn connect_over_ssh( diff --git a/crates/remote/src/ssh_session.rs b/crates/remote/src/ssh_session.rs index 87756aeb0c..564b94070f 100644 --- a/crates/remote/src/ssh_session.rs +++ b/crates/remote/src/ssh_session.rs @@ -118,6 +118,20 @@ pub struct SshPlatform { pub arch: &'static str, } +impl SshPlatform { + pub fn triple(&self) -> Option { + Some(format!( + "{}-{}", + self.arch, + match self.os { + "linux" => "unknown-linux-gnu", + "macos" => "apple-darwin", + _ => return None, + } + )) + } +} + pub trait SshClientDelegate: Send + Sync { fn ask_password( &self, diff --git a/crates/rpc/Cargo.toml b/crates/rpc/Cargo.toml index f664085f04..ce665a111a 100644 --- a/crates/rpc/Cargo.toml +++ b/crates/rpc/Cargo.toml @@ -35,10 +35,7 @@ sha2.workspace = true strum.workspace = true tracing = { version = "0.1.34", features = ["log"] } util.workspace = true -zstd = "0.11" - -[target.'cfg(target_os = "linux")'.dependencies] -zstd = { version = "0.11", features = [ "pkg-config" ] } +zstd.workspace = true [dev-dependencies] collections = { workspace = true, features = ["test-support"] } diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index a271019573..69ca3aa98d 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -51,7 +51,7 @@ futures.workspace = true git.workspace = true git_hosting_providers.workspace = true go_to_line.workspace = true -gpui.workspace = true +gpui = { workspace = true, features = ["wayland", "x11", "font-kit"] } headless.workspace = true http_client.workspace = true image_viewer.workspace = true @@ -125,6 +125,8 @@ winresource = "0.1" [target.'cfg(target_os = "linux")'.dependencies] ashpd.workspace = true +# We don't use zstd in the zed crate, but we want to add this feature when compiling a desktop build of Zed +zstd = { workspace = true, features = [ "pkg-config" ] } [dev-dependencies] call = { workspace = true, features = ["test-support"] } @@ -169,4 +171,4 @@ osx_info_plist_exts = ["resources/info/*"] osx_url_schemes = ["zed"] [package.metadata.cargo-machete] -ignored = ["profiling"] +ignored = ["profiling", "zstd"] diff --git a/script/remote-server b/script/remote-server new file mode 100755 index 0000000000..1ab2d943e1 --- /dev/null +++ b/script/remote-server @@ -0,0 +1,17 @@ +#!/usr/bin/env bash + +set -xeuo pipefail + +# if root or if sudo/unavailable, define an empty variable +if [ "$(id -u)" -eq 0 ] +then maysudo='' +else maysudo="$(command -v sudo || command -v doas || true)" +fi + +deps=( + clang +) + +$maysudo apt-get update +$maysudo apt-get install -y "${deps[@]}" +exit 0