Skip to content

Commit

Permalink
fix: Find absolute bins before executing commands. (#298)
Browse files Browse the repository at this point in the history
  • Loading branch information
milesj authored Nov 20, 2023
1 parent 80f0930 commit a74f357
Show file tree
Hide file tree
Showing 10 changed files with 132 additions and 79 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
#### 🐞 Fixes

- Fixed an issue where checksum verification would fail if the `.sha256` file prefixed the file name with `*`.
- Fixed an issue where installing a global would fail to find a proto shim on Windows.

## 0.23.1

Expand Down
3 changes: 2 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion crates/cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@ miette = { workspace = true }
reqwest = { workspace = true, features = ["rustls-tls-native-roots", "stream"] }
semver = { workspace = true }
serde = { workspace = true }
shell-words = "1.1.0"
starbase = { workspace = true }
starbase_archive = { workspace = true }
starbase_styles = { workspace = true }
Expand Down
51 changes: 17 additions & 34 deletions crates/cli/src/commands/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@ use proto_core::{detect_version, load_tool, Id, ProtoError, Tool, UnresolvedVers
use proto_pdk_api::{ExecutableConfig, RunHook};
use starbase::system;
use std::env;
use std::ffi::OsStr;
use std::process::exit;
use system_env::is_command_on_path;
use system_env::create_process_command;
use tokio::process::Command;
use tracing::debug;

Expand Down Expand Up @@ -117,41 +118,23 @@ fn get_executable(tool: &Tool, args: &RunArgs) -> miette::Result<ExecutableConfi
Ok(config)
}

fn create_command(exe_config: &ExecutableConfig, args: &[String]) -> Command {
fn create_command<I: IntoIterator<Item = A>, A: AsRef<OsStr>>(
exe_config: &ExecutableConfig,
args: I,
) -> Command {
let exe_path = exe_config.exe_path.as_ref().unwrap();

match exe_path.extension().map(|e| e.to_str().unwrap()) {
Some("ps1" | "cmd" | "bat") => {
let mut cmd = Command::new(if is_command_on_path("pwsh") {
"pwsh"
} else {
"powershell"
});
cmd.arg("-Command");
cmd.arg(
format!(
"{} {} {}",
exe_config.parent_exe_name.clone().unwrap_or_default(),
exe_path.display(),
shell_words::join(args)
)
.trim(),
);
cmd
}
_ => {
if let Some(parent_exe) = &exe_config.parent_exe_name {
let mut cmd = Command::new(parent_exe);
cmd.arg(exe_path);
cmd.args(args);
cmd
} else {
let mut cmd = Command::new(exe_path);
cmd.args(args);
cmd
}
}
}
let command = if let Some(parent_exe) = &exe_config.parent_exe_name {
let mut exe_args = vec![exe_path.as_os_str().to_os_string()];
exe_args.extend(args.into_iter().map(|arg| arg.as_ref().to_os_string()));

create_process_command(parent_exe, exe_args)
} else {
create_process_command(exe_path, args)
};

// Convert std to tokio
Command::from(command)
}

#[system]
Expand Down
1 change: 1 addition & 0 deletions crates/core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ repository = "https://github.com/moonrepo/proto"

[dependencies]
proto_pdk_api = { version = "0.10.4", path = "../pdk-api" }
system_env = { version = "0.1.4", path = "../system-env" }
version_spec = { version = "0.1.5", path = "../version-spec" }
warpgate = { version = "0.5.14", path = "../warpgate" }
cached = { workspace = true }
Expand Down
5 changes: 2 additions & 3 deletions crates/core/src/host_funcs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ use proto_pdk_api::{ExecCommandInput, ExecCommandOutput, HostLogInput, HostLogTa
use starbase_utils::fs;
use std::env;
use std::path::PathBuf;
use std::process::Command;
use std::sync::Arc;
use system_env::create_process_command;
use tracing::trace;
use warpgate::Id;

Expand Down Expand Up @@ -129,8 +129,7 @@ fn exec_command(
// let data = user_data.any().unwrap();
// let data = data.downcast_ref::<HostData>().unwrap();

let mut command = Command::new(&input.command);
command.args(&input.args);
let mut command = create_process_command(&input.command, &input.args);
command.envs(&input.env_vars);
// command.current_dir(&data.proto.cwd);

Expand Down
1 change: 1 addition & 0 deletions crates/system-env/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ repository = "https://github.com/moonrepo/proto"
schematic = { workspace = true, optional = true }
serde = { workspace = true }
serde_json = { workspace = true }
shell-words = "1.1.0"
thiserror = { workspace = true }

[features]
Expand Down
41 changes: 1 addition & 40 deletions crates/system-env/src/env.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::env::{self, consts};
use std::env::consts;
use std::fmt;

/// Architecture of the host environment.
Expand Down Expand Up @@ -167,42 +167,3 @@ impl fmt::Display for SystemOS {
write!(f, "{}", format!("{:?}", self).to_lowercase())
}
}

/// Return true if the provided program (without extension) is available
/// on `PATH`. Will use `PATHEXT` to cycle through known extensions.
#[cfg(windows)]
pub fn is_command_on_path(name: &str) -> bool {
let Ok(system_path) = env::var("PATH") else {
return false;
};
let Ok(path_ext) = env::var("PATHEXT") else {
return false;
};
let exts = path_ext.split(';').collect::<Vec<_>>();

for path_dir in env::split_paths(&system_path) {
for ext in &exts {
if path_dir.join(format!("{name}{ext}")).exists() {
return true;
}
}
}

false
}

/// Return true if the provided command is available on `PATH`.
#[cfg(not(windows))]
pub fn is_command_on_path(name: &str) -> bool {
let Ok(system_path) = env::var("PATH") else {
return false;
};

for path_dir in env::split_paths(&system_path) {
if path_dir.join(name).exists() {
return true;
}
}

false
}
105 changes: 105 additions & 0 deletions crates/system-env/src/helpers.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
use std::env;
use std::ffi::OsStr;
use std::path::PathBuf;
use std::process::Command;

/// Return an absolute path to the provided program (without extension)
/// by checking `PATH` and cycling through `PATHEXT` extensions.
#[cfg(windows)]
pub fn find_command_on_path<T: AsRef<OsStr>>(name: T) -> Option<PathBuf> {
let Ok(system_path) = env::var("PATH") else {
return None;
};
let Ok(path_ext) = env::var("PATHEXT") else {
return None;
};

let exts = path_ext.split(';').collect::<Vec<_>>();
let name = name.as_ref();

for path_dir in env::split_paths(&system_path) {
for ext in &exts {
let mut file_name = name.to_os_string();
file_name.push(ext);

let path = path_dir.join(file_name);

if path.exists() {
return Some(path);
}
}
}

None
}

/// Return an absolute path to the provided command by checking `PATH`.
#[cfg(not(windows))]
pub fn find_command_on_path<T: AsRef<OsStr>>(name: T) -> Option<PathBuf> {
let Ok(system_path) = env::var("PATH") else {
return None;
};

let name = name.as_ref();

for path_dir in env::split_paths(&system_path) {
let path = path_dir.join(name);

if path.exists() {
return Some(path);
}
}

None
}

/// Return true if the provided command/program (without extension)
/// is available on `PATH`.
pub fn is_command_on_path<T: AsRef<OsStr>>(name: T) -> bool {
find_command_on_path(name.as_ref()).is_some()
}

/// Create a new process [`Command`] and append the provided arguments. If the provided binary
/// name is not an absolute path, we'll attempt to find it on `PATH` using [`find_command_on_path`].
///
/// Furthermore, if the binary path is a Windows script (`.ps1`, `.cmd`, `.bat``), we'll wrap
/// the binary in a PowerShell command, and pass the original command via `-Command`.
pub fn create_process_command<T: AsRef<OsStr>, I: IntoIterator<Item = A>, A: AsRef<OsStr>>(
bin: T,
args: I,
) -> Command {
let bin = bin.as_ref();

// If an absolute path, use as-is, otherwise find the command
let bin_path = if bin
.as_encoded_bytes()
.iter()
.any(|b| b.eq_ignore_ascii_case(&b'/') || b.eq_ignore_ascii_case(&b'\\'))
{
PathBuf::from(bin)
} else {
find_command_on_path(bin).unwrap_or_else(|| bin.into())
};

// If a Windows script, we must execute the command through powershell
match bin_path.extension().map(|e| e.to_str().unwrap()) {
Some("ps1" | "cmd" | "bat") => {
// This conversion is unfortunate...
let args = args
.into_iter()
.map(|a| String::from_utf8_lossy(a.as_ref().as_encoded_bytes()).to_string())
.collect::<Vec<_>>();

let mut cmd =
Command::new(find_command_on_path("pwsh").unwrap_or_else(|| "powershell".into()));
cmd.arg("-Command");
cmd.arg(format!("{} {}", bin_path.display(), shell_words::join(args)).trim());
cmd
}
_ => {
let mut cmd = Command::new(bin_path);
cmd.args(args);
cmd
}
}
}
2 changes: 2 additions & 0 deletions crates/system-env/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
mod deps;
mod env;
mod error;
mod helpers;
mod pm;
mod pm_vendor;
mod system;

pub use deps::*;
pub use env::*;
pub use error::*;
pub use helpers::*;
pub use pm::*;
pub use pm_vendor::*;
pub use system::*;

0 comments on commit a74f357

Please sign in to comment.