diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cfbdc2ca02..fd92792714 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -32,6 +32,11 @@ jobs: with: clean: false + - name: Download rust-analyzer + run: | + script/download-rust-analyzer + echo "$PWD/vendor/bin" >> $GITHUB_PATH + - name: Run tests run: cargo test --workspace --no-fail-fast @@ -63,6 +68,9 @@ jobs: with: clean: false + - name: Download rust-analyzer + run: script/download-rust-analyzer + - name: Create app bundle run: script/bundle diff --git a/.gitignore b/.gitignore index d096dc01da..379d197a5c 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ /script/node_modules /server/.env.toml /server/static/styles.css +/vendor/bin diff --git a/crates/gpui/src/platform.rs b/crates/gpui/src/platform.rs index cd972021a5..6f776524c2 100644 --- a/crates/gpui/src/platform.rs +++ b/crates/gpui/src/platform.rs @@ -53,6 +53,8 @@ pub trait Platform: Send + Sync { fn set_cursor_style(&self, style: CursorStyle); fn local_timezone(&self) -> UtcOffset; + + fn path_for_resource(&self, name: Option<&str>, extension: Option<&str>) -> Result; } pub(crate) trait ForegroundPlatform { diff --git a/crates/gpui/src/platform/mac/platform.rs b/crates/gpui/src/platform/mac/platform.rs index c956a19998..8a4dc8cdf9 100644 --- a/crates/gpui/src/platform/mac/platform.rs +++ b/crates/gpui/src/platform/mac/platform.rs @@ -14,7 +14,9 @@ use cocoa::{ NSPasteboardTypeString, NSSavePanel, NSWindow, }, base::{id, nil, selector, YES}, - foundation::{NSArray, NSAutoreleasePool, NSData, NSInteger, NSString, NSURL}, + foundation::{ + NSArray, NSAutoreleasePool, NSBundle, NSData, NSInteger, NSString, NSUInteger, NSURL, + }, }; use core_foundation::{ base::{CFType, CFTypeRef, OSStatus, TCFType as _}, @@ -45,6 +47,9 @@ use std::{ }; use time::UtcOffset; +#[allow(non_upper_case_globals)] +const NSUTF8StringEncoding: NSUInteger = 4; + const MAC_PLATFORM_IVAR: &'static str = "platform"; static mut APP_CLASS: *const Class = ptr::null(); static mut APP_DELEGATE_CLASS: *const Class = ptr::null(); @@ -588,6 +593,27 @@ impl platform::Platform for MacPlatform { UtcOffset::from_whole_seconds(seconds_from_gmt.try_into().unwrap()).unwrap() } } + + fn path_for_resource(&self, name: Option<&str>, extension: Option<&str>) -> Result { + unsafe { + let bundle: id = NSBundle::mainBundle(); + if bundle.is_null() { + Err(anyhow!("app is not running inside a bundle")) + } else { + let name = name.map_or(nil, |name| ns_string(name)); + let extension = extension.map_or(nil, |extension| ns_string(extension)); + let path: id = msg_send![bundle, pathForResource: name ofType: extension]; + if path.is_null() { + Err(anyhow!("resource could not be found")) + } else { + let len = msg_send![path, lengthOfBytesUsingEncoding: NSUTF8StringEncoding]; + let bytes = path.UTF8String() as *const u8; + let path = str::from_utf8(slice::from_raw_parts(bytes, len)).unwrap(); + Ok(PathBuf::from(path)) + } + } + } + } } unsafe fn get_foreground_platform(object: &mut Object) -> &MacForegroundPlatform { diff --git a/crates/gpui/src/platform/test.rs b/crates/gpui/src/platform/test.rs index d705a277e5..c866a5d23f 100644 --- a/crates/gpui/src/platform/test.rs +++ b/crates/gpui/src/platform/test.rs @@ -1,6 +1,6 @@ use super::CursorStyle; use crate::{AnyAction, ClipboardItem}; -use anyhow::Result; +use anyhow::{anyhow, Result}; use parking_lot::Mutex; use pathfinder_geometry::vector::Vector2F; use std::{ @@ -148,6 +148,10 @@ impl super::Platform for Platform { fn local_timezone(&self) -> UtcOffset { UtcOffset::UTC } + + fn path_for_resource(&self, _name: Option<&str>, _extension: Option<&str>) -> Result { + Err(anyhow!("app not running inside a bundle")) + } } impl Window { diff --git a/crates/lsp/Cargo.toml b/crates/lsp/Cargo.toml index 9d4c4850e9..897c8c8224 100644 --- a/crates/lsp/Cargo.toml +++ b/crates/lsp/Cargo.toml @@ -13,3 +13,6 @@ parking_lot = "0.11" serde = { version = "1.0", features = ["derive"] } serde_json = { version = "1.0", features = ["raw_value"] } smol = "1.2" + +[dev-dependencies] +gpui = { path = "../gpui", features = ["test-support"] } diff --git a/crates/lsp/build.rs b/crates/lsp/build.rs index a29aa4b016..0aedf6d1d2 100644 --- a/crates/lsp/build.rs +++ b/crates/lsp/build.rs @@ -1,36 +1,10 @@ -use std::{ - env, - fs::{self, Permissions}, - os::unix::prelude::PermissionsExt, - process::Command, -}; +use std::env; fn main() { let target = env::var("TARGET").unwrap(); - let rust_analyzer_filename = format!("rust-analyzer-{}", target); - let rust_analyzer_url = format!( - "https://github.com/rust-analyzer/rust-analyzer/releases/download/2021-10-18/{}.gz", - rust_analyzer_filename - ); - println!( - "cargo:rustc-env=RUST_ANALYZER_FILENAME={}", - rust_analyzer_filename - ); + println!("cargo:rustc-env=TARGET={}", target); - let target_dir = env::var("CARGO_MANIFEST_DIR").unwrap(); - let rust_analyzer_target_path = format!("{}/{}", target_dir, rust_analyzer_filename); - assert!( - Command::new("/bin/sh") - .arg("-c") - .arg(format!( - "curl -L {} | gunzip > {}", - rust_analyzer_url, rust_analyzer_target_path - )) - .status() - .unwrap() - .success(), - "failed to download rust-analyzer" - ); - fs::set_permissions(rust_analyzer_target_path, Permissions::from_mode(0x755)) - .expect("failed to make rust-analyzer executable"); + if let Ok(bundled) = env::var("BUNDLE") { + println!("cargo:rustc-env=BUNDLE={}", bundled); + } } diff --git a/crates/lsp/src/lib.rs b/crates/lsp/src/lib.rs index 53e85a373a..c2fdf75182 100644 --- a/crates/lsp/src/lib.rs +++ b/crates/lsp/src/lib.rs @@ -1,5 +1,5 @@ use anyhow::{anyhow, Context, Result}; -use gpui::{executor, Task}; +use gpui::{executor, AppContext, Task}; use parking_lot::Mutex; use serde::{Deserialize, Serialize}; use serde_json::value::RawValue; @@ -71,6 +71,21 @@ struct Error { } impl LanguageServer { + pub fn rust(cx: &AppContext) -> Result> { + const BUNDLE: Option<&'static str> = option_env!("BUNDLE"); + const TARGET: &'static str = env!("TARGET"); + + let rust_analyzer_name = format!("rust-analyzer-{}", TARGET); + if BUNDLE.map_or(Ok(false), |b| b.parse())? { + let rust_analyzer_path = cx + .platform() + .path_for_resource(Some(&rust_analyzer_name), None)?; + Self::new(&rust_analyzer_path, cx.background()) + } else { + Self::new(Path::new(&rust_analyzer_name), cx.background()) + } + } + pub fn new(path: &Path, background: &executor::Background) -> Result> { let mut server = Command::new(path) .stdin(Stdio::piped()) @@ -143,12 +158,7 @@ impl LanguageServer { _input_task, _output_task, }); - let init = this.clone().init(); - background - .spawn(async move { - init.log_err().await; - }) - .detach(); + background.spawn(this.clone().init().log_err()).detach(); Ok(this) } @@ -229,6 +239,6 @@ mod tests { #[gpui::test] async fn test_basic(cx: TestAppContext) { - let server = LanguageServer::new(); + let server = cx.read(|cx| LanguageServer::rust(cx).unwrap()); } } diff --git a/crates/project/src/lib.rs b/crates/project/src/lib.rs index d4e41b4f28..f9926e0416 100644 --- a/crates/project/src/lib.rs +++ b/crates/project/src/lib.rs @@ -49,7 +49,7 @@ impl Project { languages: Arc, rpc: Arc, fs: Arc, - background: &executor::Background, + cx: &AppContext, ) -> Self { Self { worktrees: Default::default(), @@ -57,11 +57,7 @@ impl Project { languages, client: rpc, fs, - language_server: LanguageServer::new( - Path::new("/Users/as-cii/Downloads/rust-analyzer-x86_64-apple-darwin"), - background, - ) - .unwrap(), + language_server: LanguageServer::rust(cx).unwrap(), } } @@ -420,6 +416,6 @@ mod tests { let languages = Arc::new(LanguageRegistry::new()); let fs = Arc::new(RealFs); let rpc = client::Client::new(); - cx.add_model(|cx| Project::new(languages, rpc, fs, cx.background())) + cx.add_model(|cx| Project::new(languages, rpc, fs, cx)) } } diff --git a/crates/project_panel/src/lib.rs b/crates/project_panel/src/lib.rs index 422484e74d..0a8c6e6fbb 100644 --- a/crates/project_panel/src/lib.rs +++ b/crates/project_panel/src/lib.rs @@ -622,7 +622,7 @@ mod tests { params.languages.clone(), params.client.clone(), params.fs.clone(), - cx.background(), + cx, ) }); let root1 = project diff --git a/crates/workspace/src/lib.rs b/crates/workspace/src/lib.rs index 9fafd433bb..1b53d15862 100644 --- a/crates/workspace/src/lib.rs +++ b/crates/workspace/src/lib.rs @@ -327,7 +327,7 @@ impl Workspace { params.languages.clone(), params.client.clone(), params.fs.clone(), - cx.background(), + cx, ) }); cx.observe(&project, |_, _, cx| cx.notify()).detach(); diff --git a/script/bundle b/script/bundle index e86f80755e..e77f5407dd 100755 --- a/script/bundle +++ b/script/bundle @@ -2,6 +2,8 @@ set -e +export BUNDLE=true + # Install cargo-bundle 0.5.0 if it's not already installed cargo install cargo-bundle --version 0.5.0 @@ -11,10 +13,14 @@ cargo bundle --release --target x86_64-apple-darwin popd > /dev/null # Build the binary for aarch64 (Apple M1) -cargo build --release --target aarch64-apple-darwin +# cargo build --release --target aarch64-apple-darwin # Replace the bundle's binary with a "fat binary" that combines the two architecture-specific binaries -lipo -create target/x86_64-apple-darwin/release/Zed target/aarch64-apple-darwin/release/Zed -output target/x86_64-apple-darwin/release/bundle/osx/Zed.app/Contents/MacOS/zed +# lipo -create target/x86_64-apple-darwin/release/Zed target/aarch64-apple-darwin/release/Zed -output target/x86_64-apple-darwin/release/bundle/osx/Zed.app/Contents/MacOS/zed + +# Bundle rust-analyzer +cp vendor/bin/rust-analyzer-x86_64-apple-darwin target/x86_64-apple-darwin/release/bundle/osx/Zed.app/Contents/Resources/ +cp vendor/bin/rust-analyzer-aarch64-apple-darwin target/x86_64-apple-darwin/release/bundle/osx/Zed.app/Contents/Resources/ # Sign the app bundle with an ad-hoc signature so it runs on the M1. We need a real certificate but this works for now. if [[ -n $MACOS_CERTIFICATE && -n $MACOS_CERTIFICATE_PASSWORD && -n $APPLE_NOTARIZATION_USERNAME && -n $APPLE_NOTARIZATION_PASSWORD ]]; then diff --git a/script/download-rust-analyzer b/script/download-rust-analyzer new file mode 100755 index 0000000000..9a64f9ed69 --- /dev/null +++ b/script/download-rust-analyzer @@ -0,0 +1,15 @@ +#!/bin/bash + +set -e + +export RUST_ANALYZER_URL="https://github.com/rust-analyzer/rust-analyzer/releases/download/2021-10-18/" + +function download { + local filename="rust-analyzer-$1" + curl -L $RUST_ANALYZER_URL/$filename.gz | gunzip > vendor/bin/$filename + chmod +x vendor/bin/$filename +} + +mkdir -p vendor/bin +download "x86_64-apple-darwin" +download "aarch64-apple-darwin"