use crate::dependency_binaries::{
DependencyBinaryInstaller, RepositoryDependencyBinaryInstaller, host_target,
};
use crate::dirs::{BaseDirs, SystemBaseDirs};
use crate::error::{InstallerError, Result};
use std::io;
use std::io::Write;
use std::path::Path;
use std::process::{Command, Output};
mod install;
use install::*;
pub trait CommandExecutor {
fn run(&self, cmd: &str, args: &[&str]) -> Result<Output>;
}
#[derive(Debug, Clone, Copy, Default)]
pub struct SystemCommandExecutor;
impl CommandExecutor for SystemCommandExecutor {
fn run(&self, cmd: &str, args: &[&str]) -> Result<Output> {
Command::new(cmd)
.args(args)
.output()
.map_err(InstallerError::from)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct DependencyTool {
package: &'static str,
command: &'static str,
args: &'static [&'static str],
}
const CARGO_DYLINT_TOOL: DependencyTool = DependencyTool {
package: "cargo-dylint",
command: "cargo",
args: &["dylint", "--version"],
};
const DYLINT_LINK_TOOL: DependencyTool = DependencyTool {
package: "dylint-link",
command: "dylint-link",
args: &["--version"],
};
const DEPENDENCY_TOOLS: [DependencyTool; 2] = [CARGO_DYLINT_TOOL, DYLINT_LINK_TOOL];
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct DylintToolStatus {
pub cargo_dylint: bool,
pub dylint_link: bool,
}
impl DylintToolStatus {
#[must_use]
pub fn all_installed(&self) -> bool {
self.cargo_dylint && self.dylint_link
}
}
#[cfg(any(test, feature = "test-support"))]
pub struct DependencyInstallOptions<'a> {
pub dirs: &'a dyn BaseDirs,
pub repository_installer: &'a dyn DependencyBinaryInstaller,
pub target: Option<crate::installer_packaging::TargetTriple>,
pub quiet: bool,
}
#[must_use]
pub fn check_dylint_tools(executor: &dyn CommandExecutor) -> DylintToolStatus {
DylintToolStatus {
cargo_dylint: is_tool_installed(executor, &CARGO_DYLINT_TOOL),
dylint_link: is_tool_installed(executor, &DYLINT_LINK_TOOL),
}
}
pub fn install_dylint_tools(
executor: &dyn CommandExecutor,
status: &DylintToolStatus,
) -> Result<()> {
let mut sink = io::sink();
install_dylint_tools_with_output(executor, status, true, &mut sink)
}
pub fn install_dylint_tools_with_output(
executor: &dyn CommandExecutor,
status: &DylintToolStatus,
quiet: bool,
stderr: &mut dyn Write,
) -> Result<()> {
let repository_installer = RepositoryDependencyBinaryInstaller;
let system_dirs = SystemBaseDirs::new();
let target = host_target();
let dirs = system_dirs.as_ref().map(|dirs| dirs as &dyn BaseDirs);
let cargo_fallback_mode = cargo_fallback_mode(executor);
install_missing_tools(
executor,
status,
stderr,
&InstallContext {
repo: repository_install_context(
dirs,
Some(&repository_installer as &dyn DependencyBinaryInstaller),
target.as_ref(),
),
cargo_fallback_mode,
quiet,
},
)
}
#[cfg(any(test, feature = "test-support"))]
#[doc(hidden)]
pub fn install_dylint_tools_with_options(
executor: &dyn CommandExecutor,
status: &DylintToolStatus,
stderr: &mut dyn Write,
options: DependencyInstallOptions<'_>,
) -> Result<()> {
let cargo_fallback_mode = cargo_fallback_mode(executor);
install_missing_tools(
executor,
status,
stderr,
&InstallContext {
repo: repository_install_context(
Some(options.dirs),
Some(options.repository_installer),
options.target.as_ref(),
),
cargo_fallback_mode,
quiet: options.quiet,
},
)
}
fn is_tool_installed(executor: &dyn CommandExecutor, tool: &DependencyTool) -> bool {
if tool == &DYLINT_LINK_TOOL {
return match find_binary_on_path(tool.command) {
Some(binary_path) => dylint_link_probe_succeeds(&binary_path),
None => false,
};
}
command_succeeds(executor, tool.command, tool.args)
}
fn is_binstall_available(executor: &dyn CommandExecutor) -> bool {
command_succeeds(executor, "cargo", &["binstall", "--version"])
}
#[cfg(test)]
fn is_binary_on_path(binary_name: &str) -> bool {
find_binary_on_path(binary_name).is_some()
}
fn find_binary_on_path(binary_name: &str) -> Option<std::path::PathBuf> {
let path_var = std::env::var_os("PATH")?;
std::env::split_paths(&path_var)
.find_map(|directory| find_binary_in_directory(&directory, binary_name))
}
fn find_binary_in_directory(directory: &Path, binary_name: &str) -> Option<std::path::PathBuf> {
binary_candidates(directory, binary_name)
.into_iter()
.find(|candidate| is_executable_file(candidate))
}
fn dylint_link_probe_succeeds(binary_path: &Path) -> bool {
let mut command = Command::new(binary_path);
command.arg("--help");
if let Some(toolchain) = dylint_link_probe_toolchain() {
command.env("RUSTUP_TOOLCHAIN", toolchain);
}
command.output().is_ok_and(|output| output.status.success())
}
fn dylint_link_probe_toolchain() -> Option<String> {
std::env::var("RUSTUP_TOOLCHAIN")
.ok()
.filter(|toolchain| !toolchain.trim().is_empty())
.or_else(|| {
host_target().map(|target| {
format!("stable-{}", target.as_str())
})
})
}
fn binary_candidates(directory: &Path, binary_name: &str) -> Vec<std::path::PathBuf> {
#[cfg(windows)]
let mut candidates = Vec::new();
#[cfg(not(windows))]
let candidates = vec![directory.join(binary_name)];
#[cfg(windows)]
{
if Path::new(binary_name).extension().is_some() {
candidates.push(directory.join(binary_name));
} else {
let lowercase_name = binary_name.to_ascii_lowercase();
candidates.extend(
windows_path_extensions()
.into_iter()
.filter(|extension| !lowercase_name.ends_with(&extension.to_ascii_lowercase()))
.map(|extension| directory.join(format!("{binary_name}{extension}"))),
);
}
}
candidates
}
#[cfg(windows)]
fn windows_path_extensions() -> Vec<String> {
let path_ext = std::env::var_os("PATHEXT")
.filter(|value| !value.is_empty())
.unwrap_or_else(|| std::ffi::OsString::from(".COM;.EXE;.BAT;.CMD"));
path_ext
.to_string_lossy()
.split(';')
.filter_map(|extension| {
let trimmed = extension.trim();
if trimmed.is_empty() {
None
} else if trimmed.starts_with('.') {
Some(trimmed.to_owned())
} else {
Some(format!(".{trimmed}"))
}
})
.collect()
}
#[cfg(unix)]
fn is_executable_file(path: &Path) -> bool {
use std::os::unix::fs::PermissionsExt;
std::fs::metadata(path)
.map(|metadata| metadata.is_file() && metadata.permissions().mode() & 0o111 != 0)
.unwrap_or(false)
}
#[cfg(not(unix))]
fn is_executable_file(path: &Path) -> bool {
path.is_file()
}
#[cfg(test)]
mod path_tests;
#[cfg(test)]
mod tests;