From a111b959d282d643d022b3c17b9f5ebba5aa8094 Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Mon, 22 Apr 2024 18:01:06 +0200 Subject: [PATCH] cli: Treat first argument as name of release channel to use for the cli (#10856) With this commit, it is now possible to invoke cli with a release channel of bundle as an argument. E.g: `zed stable some_arguments` will find CLI binary of Stable channel installed on your machine and invoke it with `some_arguments` (so the first argument is essentially omitted). Fixes #10851 Release Notes: - CLI now accepts an optional name of release channel as it's first argument. For example, `zed stable` will always use your Stable installation's CLI. Trailing args are passed along. --- Cargo.lock | 1 + crates/cli/Cargo.toml | 1 + crates/cli/src/main.rs | 43 +++++++++++++++++++++++++++++-- crates/release_channel/src/lib.rs | 27 ++++++++++++++----- 4 files changed, 64 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3b243f7d59..e8d019f3ac 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2047,6 +2047,7 @@ dependencies = [ "core-services", "ipc-channel", "plist", + "release_channel", "serde", "util", ] diff --git a/crates/cli/Cargo.toml b/crates/cli/Cargo.toml index d118d9d873..5b982e9bfb 100644 --- a/crates/cli/Cargo.toml +++ b/crates/cli/Cargo.toml @@ -20,6 +20,7 @@ path = "src/main.rs" anyhow.workspace = true clap.workspace = true ipc-channel = "0.18" +release_channel.workspace = true serde.workspace = true util.workspace = true diff --git a/crates/cli/src/main.rs b/crates/cli/src/main.rs index 9ae0ed0465..80c892583f 100644 --- a/crates/cli/src/main.rs +++ b/crates/cli/src/main.rs @@ -7,7 +7,7 @@ use serde::Deserialize; use std::{ env, ffi::OsStr, - fs::{self}, + fs, path::{Path, PathBuf}, }; use util::paths::PathLikeWithPosition; @@ -53,6 +53,16 @@ struct InfoPlist { } fn main() -> Result<()> { + // Intercept version designators + #[cfg(target_os = "macos")] + if let Some(channel) = std::env::args().nth(1) { + // When the first argument is a name of a release channel, we're gonna spawn off a cli of that version, with trailing args passed along. + use std::str::FromStr as _; + + if let Ok(channel) = release_channel::ReleaseChannel::from_str(&channel) { + return mac_os::spawn_channel_cli(channel, std::env::args().skip(2).collect()); + } + } let args = Args::parse(); let bundle = Bundle::detect(args.bundle_path.as_deref()).context("Bundle detection")?; @@ -200,7 +210,7 @@ mod windows { #[cfg(target_os = "macos")] mod mac_os { - use anyhow::Context; + use anyhow::{Context, Result}; use core_foundation::{ array::{CFArray, CFIndex}, string::kCFStringEncodingUTF8, @@ -348,4 +358,33 @@ mod mac_os { ) } } + pub(super) fn spawn_channel_cli( + channel: release_channel::ReleaseChannel, + leftover_args: Vec, + ) -> Result<()> { + use anyhow::bail; + use std::process::Command; + + let app_id_prompt = format!("id of app \"{}\"", channel.display_name()); + let app_id_output = Command::new("osascript") + .arg("-e") + .arg(&app_id_prompt) + .output()?; + if !app_id_output.status.success() { + bail!("Could not determine app id for {}", channel.display_name()); + } + let app_name = String::from_utf8(app_id_output.stdout)?.trim().to_owned(); + let app_path_prompt = format!("kMDItemCFBundleIdentifier == '{app_name}'"); + let app_path_output = Command::new("mdfind").arg(app_path_prompt).output()?; + if !app_path_output.status.success() { + bail!( + "Could not determine app path for {}", + channel.display_name() + ); + } + let app_path = String::from_utf8(app_path_output.stdout)?.trim().to_owned(); + let cli_path = format!("{app_path}/Contents/MacOS/cli"); + Command::new(cli_path).args(leftover_args).spawn()?; + Ok(()) + } } diff --git a/crates/release_channel/src/lib.rs b/crates/release_channel/src/lib.rs index 864df387c0..5418d4f22e 100644 --- a/crates/release_channel/src/lib.rs +++ b/crates/release_channel/src/lib.rs @@ -2,7 +2,7 @@ #![deny(missing_docs)] -use std::env; +use std::{env, str::FromStr}; use gpui::{AppContext, Global, SemanticVersion}; use once_cell::sync::Lazy; @@ -18,11 +18,8 @@ static RELEASE_CHANNEL_NAME: Lazy = if cfg!(debug_assertions) { #[doc(hidden)] pub static RELEASE_CHANNEL: Lazy = - Lazy::new(|| match RELEASE_CHANNEL_NAME.as_str() { - "dev" => ReleaseChannel::Dev, - "nightly" => ReleaseChannel::Nightly, - "preview" => ReleaseChannel::Preview, - "stable" => ReleaseChannel::Stable, + Lazy::new(|| match ReleaseChannel::from_str(&RELEASE_CHANNEL_NAME) { + Ok(channel) => channel, _ => panic!("invalid release channel {}", *RELEASE_CHANNEL_NAME), }); @@ -149,3 +146,21 @@ impl ReleaseChannel { } } } + +/// Error indicating that release channel string does not match any known release channel names. +#[derive(Copy, Clone, Debug, Hash, PartialEq)] +pub struct InvalidReleaseChannel; + +impl FromStr for ReleaseChannel { + type Err = InvalidReleaseChannel; + + fn from_str(channel: &str) -> Result { + Ok(match channel { + "dev" => ReleaseChannel::Dev, + "nightly" => ReleaseChannel::Nightly, + "preview" => ReleaseChannel::Preview, + "stable" => ReleaseChannel::Stable, + _ => return Err(InvalidReleaseChannel), + }) + } +}