use crate::core::config::extras::Language;
use crate::core::config::tools::{ToolsConfig, required_tools_for_language};
use super::format::is_tool_available;
pub fn enforce_required_toolchains(languages: &[Language], tools: &ToolsConfig) -> anyhow::Result<()> {
enforce_required_toolchains_with(languages, tools, &is_tool_available)
}
pub(crate) fn enforce_required_toolchains_with(
languages: &[Language],
tools: &ToolsConfig,
is_available: &dyn Fn(&str) -> bool,
) -> anyhow::Result<()> {
for &lang in languages {
for tool in required_tools_for_language(lang, tools) {
if !is_available(&tool) {
anyhow::bail!(
"{tool} not found on PATH; {lang} is enabled for this crate and requires it -- install \
{tool}, or remove {lang} from this crate's languages if it should not be enabled"
);
}
}
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
fn tools() -> ToolsConfig {
ToolsConfig::default()
}
#[test]
fn bails_when_a_required_tool_is_missing_for_rust() {
let error = enforce_required_toolchains_with(&[Language::Rust], &tools(), &|_tool| false)
.expect_err("a missing cargo must fail, not report clean");
assert!(error.to_string().contains("cargo"), "{error}");
}
#[test]
fn bails_when_a_required_tool_is_missing_for_python() {
let error = enforce_required_toolchains_with(&[Language::Python], &tools(), &|_tool| false)
.expect_err("a missing python package manager must fail, not report clean");
assert!(error.to_string().contains("uv"), "{error}");
}
#[test]
fn bails_when_a_required_tool_is_missing_for_ruby() {
let error = enforce_required_toolchains_with(&[Language::Ruby], &tools(), &|_tool| false)
.expect_err("a missing ruby must fail, not report clean");
assert!(error.to_string().contains("ruby"), "{error}");
}
#[test]
fn bails_for_ruby_when_only_the_interpreter_is_present_and_bundler_is_not() {
let error = enforce_required_toolchains_with(&[Language::Ruby], &tools(), &|tool| tool == "ruby")
.expect_err("a present ruby with no bundler must still fail");
assert!(error.to_string().contains("bundle"), "{error}");
}
#[test]
fn bails_on_the_missing_cargo_edit_subcommand_for_rust() {
let error = enforce_required_toolchains_with(&[Language::Rust], &tools(), &|tool| tool == "cargo")
.expect_err("a missing cargo-upgrade (cargo-edit) must fail even when cargo itself is present");
assert!(error.to_string().contains("cargo-upgrade"), "{error}");
}
#[test]
fn does_not_require_a_tool_for_a_language_that_is_not_enabled() {
enforce_required_toolchains_with(&[Language::Python], &tools(), &|_tool| false)
.expect_err("python is enabled, so its own probe must still bail");
enforce_required_toolchains_with(&[], &tools(), &|_tool| false)
.expect("no languages enabled means no toolchain is required");
enforce_required_toolchains_with(&[Language::Python], &tools(), &|tool| tool == "uv")
.expect("a crate that does not enable ruby must not require ruby or bundler");
}
}