diff --git a/src/bin/rustc_josh_sync.rs b/src/bin/rustc_josh_sync.rs index 1973d69..edb72d2 100644 --- a/src/bin/rustc_josh_sync.rs +++ b/src/bin/rustc_josh_sync.rs @@ -2,7 +2,7 @@ use anyhow::Context; use clap::Parser; use rustc_josh_sync::SyncContext; use rustc_josh_sync::config::{JoshConfig, load_config}; -use rustc_josh_sync::josh::{JoshProxy, try_install_josh}; +use rustc_josh_sync::josh::{JoshProxy, try_install_josh_proxy}; use rustc_josh_sync::sync::{DEFAULT_UPSTREAM_REPO, GitSync, RustcPullError}; use rustc_josh_sync::utils::{get_current_head_sha, prompt}; use std::path::{Path, PathBuf}; @@ -232,12 +232,9 @@ fn get_josh_proxy(proxy_path: Option, verbose: bool) -> anyhow::Result< println!("Using josh-proxy binary from {}", path.display()); Ok(JoshProxy::from_path(path)) } - None => { - println!("Updating/installing josh-proxy binary..."); - match try_install_josh(verbose) { - Some(proxy) => Ok(proxy), - None => Err(anyhow::anyhow!("Could not install josh-proxy")), - } - } + None => match try_install_josh_proxy(verbose) { + Some(proxy) => Ok(proxy), + None => Err(anyhow::anyhow!("Could not install josh-proxy")), + }, } } diff --git a/src/josh.rs b/src/josh.rs index 24d2386..fe58e37 100644 --- a/src/josh.rs +++ b/src/josh.rs @@ -1,5 +1,5 @@ use crate::config::JoshConfig; -use crate::utils::{is_null_sha, run_command, run_command_by_path}; +use crate::utils::{is_inside_ci, is_null_sha, run_command_by_path}; use anyhow::Context; use std::net::{SocketAddr, TcpStream}; use std::path::{Path, PathBuf}; @@ -14,20 +14,11 @@ pub struct JoshProxy { path: PathBuf, } -pub struct JoshFilter { - path: PathBuf, -} - impl JoshProxy { pub fn from_path(path: PathBuf) -> Self { Self { path } } - /// Tries to figure out if `josh-proxy` is installed. - pub fn lookup() -> Option { - which::which("josh-proxy").ok().map(|path| Self { path }) - } - pub fn start(&self, config: &JoshConfig) -> anyhow::Result { // Determine cache directory. let user_dirs = @@ -70,30 +61,13 @@ impl JoshProxy { } } -/// Try to install (or update) josh-proxy, to make sure that we use the correct version. -pub fn try_install_josh(verbose: bool) -> Option { - run_command( - &[ - "cargo", - "+stable", - "install", - "--locked", - "--git", - "https://github.com/josh-project/josh", - "--tag", - JOSH_VERSION, - "josh-proxy", - ], - verbose, - ) - .expect("cannot install josh-proxy"); - JoshProxy::lookup() +pub struct JoshFilter { + path: PathBuf, } impl JoshFilter { - /// Tries to figure out if `josh-filter` is installed. - pub fn lookup() -> Option { - which::which("josh-filter").ok().map(|path| Self { path }) + pub fn from_path(path: PathBuf) -> Self { + Self { path } } pub fn run<'a, Args: AsRef<[&'a str]>>( @@ -113,24 +87,71 @@ impl JoshFilter { } } +fn josh_install_directory() -> PathBuf { + let Some(user_dirs) = directories::ProjectDirs::from("org", "rust-lang", "rustc-josh") else { + eprintln!( + "Cannot determine user directory for Josh installation, falling back to local directory" + ); + return PathBuf::from(".josh-sync").join("cargo"); + }; + let local_dir = user_dirs.data_local_dir(); + local_dir.join("josh-sync").join("cargo") +} + +#[derive(Copy, Clone, Debug)] +enum JoshProgram { + Proxy, + Filter, +} + +pub fn try_install_josh_proxy(verbose: bool) -> Option { + try_install_josh_program(JoshProgram::Proxy, verbose).map(JoshProxy::from_path) +} + pub fn try_install_josh_filter(verbose: bool) -> Option { - // The josh-filter binary is included in the josh-cli crate - run_command( - &[ - "cargo", - "+stable", - "install", - "--locked", - "--git", - "https://github.com/josh-project/josh", - "--tag", - JOSH_VERSION, - "josh-cli", - ], + try_install_josh_program(JoshProgram::Filter, verbose).map(JoshFilter::from_path) +} + +/// Try to install (or update) a josh CLI program in a local installation directory. +/// Ensures that we use the correct version. +fn try_install_josh_program(program: JoshProgram, verbose: bool) -> Option { + let install_dir = josh_install_directory(); + let (krate, binary) = match program { + JoshProgram::Proxy => ("josh-proxy", "josh-proxy"), + JoshProgram::Filter => ("josh-cli", "josh-filter"), + }; + let path = install_dir.join("bin").join(binary); + println!( + "Updating/installing {binary} binary into `{}`...", + path.display() + ); + + let mut args = vec![ + "+stable", + "install", + "--locked", + "--git", + "https://github.com/josh-project/josh", + "--tag", + JOSH_VERSION, + ]; + + // Install binaries globally on CI to ensure better (rust-)cache usage + if !is_inside_ci() { + args.extend(["--root", install_dir.to_str()?]); + } + + args.push(krate); + + run_command_by_path( + &Path::new("cargo"), + &args, + &std::env::current_dir().unwrap(), + false, verbose, ) - .expect("cannot install josh-filter"); - JoshFilter::lookup() + .unwrap_or_else(|e| panic!("cannot install {binary}: {e:?}")); + if path.is_file() { Some(path) } else { None } } /// Create a wrapper that represents a running instance of `josh-proxy` and stops it on drop. diff --git a/src/utils.rs b/src/utils.rs index 9c3a6ef..faf98a1 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,5 +1,5 @@ use anyhow::Context; -use std::path::{Path, PathBuf}; +use std::path::Path; use std::process::Command; /// Run command and return its stdout. @@ -40,7 +40,7 @@ fn run_command_inner<'a, Args: AsRef<[&'a str]>>( } pub fn run_command_by_path<'a, Args: AsRef<[&'a str]>>( - cmd: &PathBuf, + cmd: &Path, args: Args, workdir: &Path, capture: bool, @@ -110,7 +110,7 @@ pub fn get_current_head_sha(verbose: bool) -> anyhow::Result { /// Returns `default_response` on CI. pub fn prompt(prompt: &str, default_response: bool) -> bool { // Do not run interactive prompts on CI - if std::env::var("GITHUB_ACTIONS").as_deref() == Ok("1") { + if is_inside_ci() { return default_response; } @@ -118,6 +118,10 @@ pub fn prompt(prompt: &str, default_response: bool) -> bool { read_line().to_lowercase() == "y" } +pub fn is_inside_ci() -> bool { + std::env::var("GITHUB_ACTIONS").as_deref() == Ok("1") +} + pub fn read_line() -> String { let mut line = String::new(); std::io::stdin()