Add `install_cli2`

Antonio Scandurra , Mikayla , and Kirill created

Co-authored-by: Mikayla <mikayla@zed.dev>
Co-Authored-By: Kirill <kirill@zed.dev>

Change summary

Cargo.lock                              | 11 +++++
Cargo.toml                              |  1 
crates/gpui2/src/app.rs                 |  5 ++
crates/install_cli2/Cargo.toml          | 18 ++++++++
crates/install_cli2/src/install_cli2.rs | 57 +++++++++++++++++++++++++++
5 files changed, 92 insertions(+)

Detailed changes

Cargo.lock 🔗

@@ -3929,6 +3929,17 @@ dependencies = [
  "util",
 ]
 
+[[package]]
+name = "install_cli2"
+version = "0.1.0"
+dependencies = [
+ "anyhow",
+ "gpui2",
+ "log",
+ "smol",
+ "util",
+]
+
 [[package]]
 name = "instant"
 version = "0.1.12"

Cargo.toml 🔗

@@ -46,6 +46,7 @@ members = [
     "crates/gpui2",
     "crates/gpui2_macros",
     "crates/install_cli",
+    "crates/install_cli2",
     "crates/journal",
     "crates/language",
     "crates/language2",

crates/gpui2/src/app.rs 🔗

@@ -30,6 +30,7 @@ use std::{
     marker::PhantomData,
     mem,
     ops::{Deref, DerefMut},
+    path::PathBuf,
     sync::{atomic::Ordering::SeqCst, Arc, Weak},
     time::Duration,
 };
@@ -722,6 +723,10 @@ where
     pub fn open_url(&self, url: &str) {
         self.platform().open_url(url);
     }
+
+    pub fn path_for_auxiliary_executable(&self, name: &str) -> Result<PathBuf> {
+        self.platform().path_for_auxiliary_executable(name)
+    }
 }
 
 impl MainThread<AppContext> {

crates/install_cli2/Cargo.toml 🔗

@@ -0,0 +1,18 @@
+[package]
+name = "install_cli2"
+version = "0.1.0"
+edition = "2021"
+publish = false
+
+[lib]
+path = "src/install_cli2.rs"
+
+[features]
+test-support = []
+
+[dependencies]
+smol.workspace = true
+anyhow.workspace = true
+log.workspace = true
+gpui2 = { path = "../gpui2" }
+util = { path = "../util" }

crates/install_cli2/src/install_cli2.rs 🔗

@@ -0,0 +1,57 @@
+use anyhow::{anyhow, Result};
+use gpui2::AsyncAppContext;
+use std::path::Path;
+use util::ResultExt;
+
+// todo!()
+// actions!(cli, [Install]);
+
+pub async fn install_cli(cx: &AsyncAppContext) -> Result<()> {
+    let cli_path = cx
+        .run_on_main(|cx| cx.path_for_auxiliary_executable("cli"))?
+        .await?;
+    let link_path = Path::new("/usr/local/bin/zed");
+    let bin_dir_path = link_path.parent().unwrap();
+
+    // Don't re-create symlink if it points to the same CLI binary.
+    if smol::fs::read_link(link_path).await.ok().as_ref() == Some(&cli_path) {
+        return Ok(());
+    }
+
+    // If the symlink is not there or is outdated, first try replacing it
+    // without escalating.
+    smol::fs::remove_file(link_path).await.log_err();
+    if smol::fs::unix::symlink(&cli_path, link_path)
+        .await
+        .log_err()
+        .is_some()
+    {
+        return Ok(());
+    }
+
+    // The symlink could not be created, so use osascript with admin privileges
+    // to create it.
+    let status = smol::process::Command::new("/usr/bin/osascript")
+        .args([
+            "-e",
+            &format!(
+                "do shell script \" \
+                    mkdir -p \'{}\' && \
+                    ln -sf \'{}\' \'{}\' \
+                \" with administrator privileges",
+                bin_dir_path.to_string_lossy(),
+                cli_path.to_string_lossy(),
+                link_path.to_string_lossy(),
+            ),
+        ])
+        .stdout(smol::process::Stdio::inherit())
+        .stderr(smol::process::Stdio::inherit())
+        .output()
+        .await?
+        .status;
+    if status.success() {
+        Ok(())
+    } else {
+        Err(anyhow!("error running osascript"))
+    }
+}