Skip to content

Commit

Permalink
new: Enable shims for proto itself. (#725)
Browse files Browse the repository at this point in the history
  • Loading branch information
milesj authored Feb 22, 2025
1 parent bd4cf3d commit ea97f9d
Show file tree
Hide file tree
Showing 13 changed files with 309 additions and 147 deletions.
17 changes: 17 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,23 @@
- [Rust](https://github.com/moonrepo/plugins/blob/master/tools/rust/CHANGELOG.md)
- [Schema (TOML, JSON, YAML)](https://github.com/moonrepo/plugins/blob/master/tools/internal-schema/CHANGELOG.md)

## Unreleased

#### 🚀 Updates

- Added shim support to the internal `proto` tool, allowing the proto version to be pinned in `.prototools`, and the version to dynamically be detected at runtime. This enables a specific proto version to be used per project.
- Updated `proto install` to now install proto if a version has been defined.

#### 🧩 Plugins

- Updated `proto_tool` to v0.5.1.
- Now supports shims.

#### ⚙️ Internal

- Updated Rust to v1.85.
- Updated dependencies.

## 0.46.1

#### 🐞 Fixes
Expand Down
2 changes: 2 additions & 0 deletions crates/cli/src/commands/activate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,8 @@ pub async fn activate(session: ProtoSession, args: ActivateArgs) -> AppResult {
{
info.env
.insert("PROTO_VERSION".into(), Some(version.to_string()));
info.env
.insert("PROTO_PROTO_VERSION".into(), Some(version.to_string()));

info.paths.push(
session
Expand Down
14 changes: 1 addition & 13 deletions crates/cli/src/commands/bin.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
use crate::session::ProtoSession;
use clap::Args;
use proto_core::{Id, PROTO_PLUGIN_KEY, ToolSpec, detect_version_with_spec};
use proto_shim::{get_exe_file_name, locate_proto_exe};
use proto_core::{Id, ToolSpec, detect_version_with_spec};
use starbase::AppResult;

#[derive(Args, Clone, Debug)]
Expand All @@ -21,17 +20,6 @@ pub struct BinArgs {

#[tracing::instrument(skip_all)]
pub async fn bin(session: ProtoSession, args: BinArgs) -> AppResult {
if args.id == PROTO_PLUGIN_KEY {
session.console.out.write_line(
locate_proto_exe("proto")
.unwrap_or(session.env.store.bin_dir.join(get_exe_file_name("proto")))
.display()
.to_string(),
)?;

return Ok(None);
}

let mut tool = session
.load_tool(&args.id, args.spec.clone().and_then(|spec| spec.backend))
.await?;
Expand Down
145 changes: 92 additions & 53 deletions crates/cli/src/commands/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ use crate::error::ProtoCliError;
use crate::session::ProtoSession;
use clap::Args;
use miette::IntoDiagnostic;
use proto_core::{Id, Tool, ToolSpec, detect_version_with_spec};
use proto_core::{Id, PROTO_PLUGIN_KEY, Tool, ToolSpec, detect_version_with_spec};
use proto_pdk_api::{ExecutableConfig, RunHook, RunHookResult};
use proto_shim::exec_command_and_replace;
use proto_shim::{exec_command_and_replace, locate_proto_exe};
use starbase::AppResult;
use starbase_utils::fs;
use std::env;
Expand Down Expand Up @@ -37,8 +37,20 @@ pub struct RunArgs {
passthrough: Vec<String>,
}

fn should_use_global_proto(tool: &Tool) -> miette::Result<bool> {
Ok(tool.id == PROTO_PLUGIN_KEY
&& !tool
.proto
.load_config()?
.versions
.contains_key(PROTO_PLUGIN_KEY))
}

fn is_trying_to_self_upgrade(tool: &Tool, args: &[String]) -> bool {
if tool.metadata.self_upgrade_commands.is_empty() {
if tool.id == PROTO_PLUGIN_KEY
|| tool.metadata.self_upgrade_commands.is_empty()
|| args.is_empty()
{
return false;
}

Expand Down Expand Up @@ -119,13 +131,16 @@ fn create_command<I: IntoIterator<Item = A>, A: AsRef<OsStr>>(
exe_config: &ExecutableConfig,
args: I,
) -> miette::Result<Command> {
let exe_path = exe_config.exe_path.as_ref().unwrap();
let exe_path = exe_config
.exe_path
.as_ref()
.expect("Could not determine executable path.");
let args = args
.into_iter()
.map(|arg| arg.as_ref().to_os_string())
.collect::<Vec<_>>();

let command = if let Some(parent_exe_path) = &exe_config.parent_exe_name {
let mut command = if let Some(parent_exe_path) = &exe_config.parent_exe_name {
let mut exe_args = vec![exe_path.as_os_str().to_os_string()];
exe_args.extend(args);

Expand All @@ -148,6 +163,17 @@ fn create_command<I: IntoIterator<Item = A>, A: AsRef<OsStr>>(
create_process_command(exe_path, args)
};

for (key, value) in tool.proto.load_config()?.get_env_vars(Some(&tool.id))? {
match value {
Some(value) => {
command.env(key, value);
}
None => {
command.env_remove(key);
}
};
}

Ok(command)
}

Expand All @@ -156,6 +182,7 @@ pub async fn run(session: ProtoSession, args: RunArgs) -> AppResult {
let mut tool = session
.load_tool(&args.id, args.spec.clone().and_then(|spec| spec.backend))
.await?;
let mut use_global_proto = should_use_global_proto(&tool)?;

// Avoid running the tool's native self-upgrade as it conflicts with proto
if is_trying_to_self_upgrade(&tool, &args.passthrough) {
Expand All @@ -166,14 +193,61 @@ pub async fn run(session: ProtoSession, args: RunArgs) -> AppResult {
.into());
}

let spec = detect_version_with_spec(&tool, args.spec.clone()).await?;
// Detect a version to run with
let spec = if use_global_proto {
args.spec
.clone()
.unwrap_or_else(|| ToolSpec::parse("*").unwrap())
} else {
detect_version_with_spec(&tool, args.spec.clone()).await?
};

// Check if installed or need to install
if !tool.is_setup_with_spec(&spec).await? {
if tool.is_setup_with_spec(&spec).await? {
if tool.id == PROTO_PLUGIN_KEY {
use_global_proto = false;
}
} else {
let config = tool.proto.load_config()?;
let resolved_version = tool.get_resolved_version();

if !config.settings.auto_install {
// Auto-install the missing tool
if config.settings.auto_install {
session.console.out.write_line(format!(
"Auto-install is enabled, attempting to install {} {}",
tool.get_name(),
resolved_version,
))?;

install_one(
session.clone(),
InstallArgs {
internal: true,
spec: Some(ToolSpec {
backend: spec.backend,
req: resolved_version.to_unresolved_spec(),
res: Some(resolved_version.clone()),
}),
..Default::default()
},
tool.id.clone(),
)
.await?;

session.console.out.write_line(format!(
"{} {} has been installed, continuing execution...",
tool.get_name(),
resolved_version,
))?;
}
// If this is the proto tool running, continue instead of failing
else if use_global_proto {
debug!(
"No proto version detected or located, falling back to the global proto binary!"
);
}
// Otherwise fail with a not installed error
else {
let command = format!("proto install {} {}", tool.id, resolved_version);

if let Ok(source) = env::var(format!("{}_DETECTED_FROM", tool.get_env_var_prefix())) {
Expand All @@ -193,42 +267,18 @@ pub async fn run(session: ProtoSession, args: RunArgs) -> AppResult {
}
.into());
}

// Install the tool
session.console.out.write_line(format!(
"Auto-install is enabled, attempting to install {} {}",
tool.get_name(),
resolved_version,
))?;

install_one(
session.clone(),
InstallArgs {
internal: true,
spec: Some(ToolSpec {
backend: spec.backend,
req: resolved_version.to_unresolved_spec(),
res: Some(resolved_version.clone()),
}),
..Default::default()
},
tool.id.clone(),
)
.await?;

session.console.out.write_line(format!(
"{} {} has been installed, continuing execution...",
tool.get_name(),
resolved_version,
))?;
}

// Determine the binary path to execute
let exe_config = get_executable(&tool, &args).await?;
let exe_path = exe_config
.exe_path
.as_ref()
.expect("Could not determine executable path.");
let exe_config = if use_global_proto {
ExecutableConfig {
exe_path: locate_proto_exe("proto"),
primary: true,
..Default::default()
}
} else {
get_executable(&tool, &args).await?
};

// Run before hook
let hook_result = if tool.plugin.has_func("pre_run").await {
Expand Down Expand Up @@ -258,17 +308,6 @@ pub async fn run(session: ProtoSession, args: RunArgs) -> AppResult {
// Create and run the command
let mut command = create_command(&tool, &exe_config, &args.passthrough)?;

for (key, value) in tool.proto.load_config()?.get_env_vars(Some(&tool.id))? {
match value {
Some(value) => {
command.env(key, value);
}
None => {
command.env_remove(key);
}
};
}

if let Some(hook_args) = hook_result.args {
command.args(hook_args);
}
Expand All @@ -284,7 +323,7 @@ pub async fn run(session: ProtoSession, args: RunArgs) -> AppResult {
)
.env(
format!("{}_BIN", tool.get_env_var_prefix()),
exe_path.to_string_lossy().to_string(),
exe_config.exe_path.as_ref().unwrap(),
);

// Update the last used timestamp
Expand Down
18 changes: 3 additions & 15 deletions crates/cli/src/session.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,9 @@ use crate::utils::progress_instance::ProgressInstance;
use crate::utils::tool_record::ToolRecord;
use async_trait::async_trait;
use miette::IntoDiagnostic;
use proto_core::registry::ProtoRegistry;
use proto_core::{
Backend, ConfigMode, Id, PROTO_PLUGIN_KEY, ProtoConfig, ProtoEnvironment, SCHEMA_PLUGIN_KEY,
Tool, ToolSpec, UnresolvedVersionSpec, load_schema_plugin_with_proto, load_tool,
load_tool_from_locator,
Backend, ConfigMode, Id, ProtoConfig, ProtoEnvironment, SCHEMA_PLUGIN_KEY, ToolSpec,
UnresolvedVersionSpec, load_schema_plugin_with_proto, load_tool, registry::ProtoRegistry,
};
use rustc_hash::FxHashSet;
use semver::Version;
Expand Down Expand Up @@ -162,7 +160,7 @@ impl ProtoSession {
}

// These shouldn't be treated as a "normal plugin"
if id == SCHEMA_PLUGIN_KEY || id == PROTO_PLUGIN_KEY {
if id == SCHEMA_PLUGIN_KEY {
continue;
}

Expand Down Expand Up @@ -218,15 +216,6 @@ impl ProtoSession {
self.load_tools_with_options(options).await
}

pub async fn load_proto_tool(&self) -> miette::Result<Tool> {
load_tool_from_locator(
Id::new(PROTO_PLUGIN_KEY)?,
&self.env,
self.env.load_config()?.builtin_proto_plugin(),
)
.await
}

pub async fn render_progress_loader(&self) -> miette::Result<ProgressInstance> {
use iocraft::prelude::element;

Expand Down Expand Up @@ -270,7 +259,6 @@ impl AppSession for ProtoSession {

async fn analyze(&mut self) -> AppResult {
load_proto_configs(&self.env)?;
download_versioned_proto_tool(self).await?;

Ok(None)
}
Expand Down
37 changes: 1 addition & 36 deletions crates/cli/src/systems.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,6 @@
use crate::app::{App as CLI, Commands};
use crate::helpers::fetch_latest_version;
use crate::session::ProtoSession;
use proto_core::flow::install::InstallOptions;
use proto_core::{
ConfigMode, PROTO_CONFIG_NAME, PROTO_PLUGIN_KEY, ProtoEnvironment, UnresolvedVersionSpec,
is_offline, now,
};
use proto_core::{ConfigMode, ProtoEnvironment, is_offline, now};
use proto_shim::get_exe_file_name;
use semver::Version;
use starbase_styles::color;
Expand Down Expand Up @@ -55,36 +50,6 @@ pub fn load_proto_configs(env: &ProtoEnvironment) -> miette::Result<()> {
Ok(())
}

#[instrument(skip_all)]
pub async fn download_versioned_proto_tool(session: &ProtoSession) -> miette::Result<()> {
let config = session
.env
.load_config_manager()?
.get_merged_config_without_global()?;

if let Some(spec) = config.versions.get(PROTO_PLUGIN_KEY) {
// Only support fully-qualified versions as we need to prepend the
// tool directory into PATH, which doesn't support requirements
if !matches!(spec.req, UnresolvedVersionSpec::Semantic(_)) {
return Ok(());
}

let mut tool = session.load_proto_tool().await?;

if !tool.is_installed() {
debug!(
version = spec.to_string(),
"Downloading a versioned proto because it was configured in {}", PROTO_CONFIG_NAME
);

tool.setup_with_spec(spec, InstallOptions::default())
.await?;
}
}

Ok(())
}

// EXECUTE

#[instrument(skip_all)]
Expand Down
1 change: 1 addition & 0 deletions crates/cli/src/utils/tool_record.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use proto_core::{
use std::collections::BTreeMap;
use std::path::PathBuf;

#[derive(Debug)]
pub struct ToolRecord {
pub tool: Tool,
pub config: ProtoToolConfig,
Expand Down
Loading

0 comments on commit ea97f9d

Please sign in to comment.