#![feature(rustc_private)]
extern crate rustc_driver;
extern crate rustc_hir;
extern crate rustc_interface;
extern crate rustc_middle;
extern crate rustc_span;
mod compiler;
mod config;
mod constants;
mod fixes;
mod reporting;
mod rust_syntax;
mod selection;
use std::env;
use std::io;
use std::io::IsTerminal;
use std::process::ExitCode;
use std::time::Duration;
use std::time::Instant;
use anyhow::Result;
use compiler::DRIVER_ENV;
use config::BuildInfoMode;
use config::DiagnosticsConfig;
use config::FixExecution;
use config::OperationMode;
use config::WarningPolicy;
use constants::BUILD_INFO_UNKNOWN;
use fixes::FIX_ALL_MAX_PASSES;
use fixes::MendRunner;
use reporting::BuildOutcome;
use reporting::CARGO_TERM_COLOR_ALWAYS;
use reporting::CARGO_TERM_COLOR_ENV;
use reporting::CARGO_TERM_COLOR_NEVER;
use reporting::CLICOLOR_DISABLED_VALUE;
use reporting::CLICOLOR_ENV;
use reporting::CLICOLOR_FORCE_ENV;
use reporting::ColorMode;
use reporting::CompilerStats;
use reporting::CompilerWarningFacts;
use reporting::DIAGNOSTICS_HELP_NAME_COLUMN_WIDTH;
use reporting::EXIT_CODE_ERROR;
use reporting::EXIT_CODE_WARNING;
use reporting::ExecutionOutcome;
use reporting::MendFailure;
use reporting::OutputFormat;
use selection::DisplayFilter;
use selection::Selection;
fn main() -> ExitCode {
if env::var_os(DRIVER_ENV).is_some() {
return compiler::driver_main();
}
match run() {
Ok(code) => code,
Err(err) => {
eprintln!("mend: {err}");
MendFailure::exit_code()
},
}
}
fn build_diagnostics_help(diagnostics: &DiagnosticsConfig) -> String {
let config_path = config::global_config_path()
.map_or_else(|| "(unavailable)".to_string(), |p| p.display().to_string());
let mut lines = vec![String::new(), "Diagnostics:".to_string()];
for (code, enabled) in diagnostics.entries() {
let name = code.as_str();
let status = enabled.label();
lines.push(format!(
" {name:<DIAGNOSTICS_HELP_NAME_COLUMN_WIDTH$} {status}"
));
}
lines.push(String::new());
lines.push(format!("Config: {config_path}"));
lines.join("\n")
}
fn build_info_text() -> String {
let version = env!("CARGO_PKG_VERSION");
let git_hash = option_env!("MEND_GIT_HASH").unwrap_or(BUILD_INFO_UNKNOWN);
let build_id = option_env!("MEND_BUILD_ID").unwrap_or(BUILD_INFO_UNKNOWN);
let build_sysroot = option_env!("MEND_BUILD_SYSROOT").unwrap_or(BUILD_INFO_UNKNOWN);
format!(
"cargo-mend {version}\n\
git_hash: {git_hash}\n\
build_id: {build_id}\n\
build_sysroot: {build_sysroot}"
)
}
const fn total_fixables(outcome: &ExecutionOutcome) -> usize {
outcome.report.summary.fixable_with_fix
+ outcome.report.summary.fixable_with_fix_pub_use
+ outcome.compiler_fixable
}
fn run() -> Result<ExitCode, MendFailure> {
let global_diagnostics = config::load_global_diagnostics();
let after_help = build_diagnostics_help(&global_diagnostics);
let cli = config::parse(&after_help);
if cli.build_info == BuildInfoMode::Show {
println!("{}", build_info_text());
return Ok(ExitCode::SUCCESS);
}
let selection = selection::resolve_cargo_selection(cli.cargo.explicit_manifest_path())
.map_err(MendFailure::Unexpected)?;
let cargo_plan = selection::build_cargo_check_plan(&selection, &cli.cargo);
let loaded_config = config::load_config(
selection.manifest_dir.as_path(),
selection.workspace_root.as_path(),
cli.manifest.config.as_deref(),
&global_diagnostics,
)
.map_err(MendFailure::Unexpected)?;
let operation_mode = OperationMode::from(&cli.fix);
let color_mode = color_mode();
let output_format = cli.output_format;
let start = Instant::now();
let runner = MendRunner::new(
&selection,
&cargo_plan,
&loaded_config,
color_mode,
output_format,
);
let mut outcome = runner.run(operation_mode.clone())?;
let mut total_compiler_fix_duration = Duration::ZERO;
let mut total_extra_check_duration = Duration::ZERO;
let mut passes = 1;
loop {
let user_asked_for_compiler_fix = cli.fix.runs_compiler_fix();
let pub_use_self_heal = matches!(
cli.fix.execution,
FixExecution::ApplyRequested | FixExecution::ApplyAll
) && outcome.applied_pub_use > 0
&& matches!(
outcome.compiler_warning_facts,
CompilerWarningFacts::UnusedImportWarnings
);
if user_asked_for_compiler_fix || pub_use_self_heal {
total_compiler_fix_duration += compiler::run_cargo_fix(&cargo_plan, color_mode)
.map_err(MendFailure::Unexpected)?;
}
if !matches!(cli.fix.execution, FixExecution::ApplyAll) || passes >= FIX_ALL_MAX_PASSES {
break;
}
let next = runner.run(operation_mode.clone())?;
let next_total = total_fixables(&next);
let prev_total = total_fixables(&outcome);
total_extra_check_duration += next.check_duration;
if next_total == 0 || next_total >= prev_total {
outcome = next;
break;
}
outcome = next;
passes += 1;
}
let total_duration = start.elapsed();
let check_duration =
outcome.check_duration + total_extra_check_duration + total_compiler_fix_duration;
let display_filter = DisplayFilter::from_cli(&cli.cargo, &selection.packages);
display_filter.apply(&mut outcome.report);
render_outcome(
&outcome,
&selection,
output_format,
color_mode,
total_duration,
check_duration,
)?;
if outcome.report.outcome() == BuildOutcome::Failed {
return Ok(ExitCode::from(EXIT_CODE_ERROR));
}
if cli.warning_policy == WarningPolicy::Fail && outcome.report.has_warnings() {
return Ok(ExitCode::from(EXIT_CODE_WARNING));
}
Ok(ExitCode::SUCCESS)
}
fn render_outcome(
outcome: &ExecutionOutcome,
selection: &Selection,
output_format: OutputFormat,
color_mode: ColorMode,
total_duration: Duration,
check_duration: Duration,
) -> Result<(), MendFailure> {
let compiler_stats = CompilerStats {
warnings: outcome.compiler_warnings,
fixable: outcome.compiler_fixable,
};
match output_format {
OutputFormat::Json => {
print!(
"{}",
reporting::render_report(&outcome.report, selection)
.map_err(MendFailure::Unexpected)?
);
},
OutputFormat::Human => {
print!(
"{}",
reporting::render_human_report(&outcome.report, &compiler_stats, color_mode)
);
},
}
if output_format == OutputFormat::Human {
let mend_duration = total_duration.saturating_sub(check_duration);
eprintln!(
"{}",
reporting::render_timing(total_duration, check_duration, mend_duration, color_mode)
);
}
if let Some(notice) = outcome.notice.as_ref() {
eprintln!("{}", notice.render());
}
Ok(())
}
fn color_mode() -> ColorMode {
if let Ok(choice) = env::var(CLICOLOR_FORCE_ENV)
&& choice != CLICOLOR_DISABLED_VALUE
{
return ColorMode::Enabled;
}
if let Ok(choice) = env::var(CARGO_TERM_COLOR_ENV) {
let color_mode = match choice.to_ascii_lowercase().as_str() {
CARGO_TERM_COLOR_NEVER => Some(ColorMode::Disabled),
CARGO_TERM_COLOR_ALWAYS => Some(ColorMode::Enabled),
_ => None,
};
if let Some(color_mode) = color_mode {
return color_mode;
}
}
if let Ok(choice) = env::var(CLICOLOR_ENV)
&& choice == CLICOLOR_DISABLED_VALUE
{
return ColorMode::Disabled;
}
if io::stdout().is_terminal() || io::stderr().is_terminal() {
return ColorMode::Enabled;
}
ColorMode::Disabled
}
#[cfg(test)]
mod tests {
use std::env;
use std::ffi::OsString;
use super::build_info_text;
use super::color_mode;
use crate::reporting::CARGO_TERM_COLOR_ALWAYS;
use crate::reporting::CARGO_TERM_COLOR_ENV;
use crate::reporting::CARGO_TERM_COLOR_NEVER;
use crate::reporting::CLICOLOR_DISABLED_VALUE;
use crate::reporting::CLICOLOR_ENV;
use crate::reporting::CLICOLOR_FORCE_ENV;
use crate::reporting::ColorMode;
struct EnvGuard {
key: &'static str,
previous: Option<OsString>,
}
impl EnvGuard {
fn set(key: &'static str, value: &'static str) -> Self {
let previous = env::var_os(key);
unsafe { env::set_var(key, value) };
Self { key, previous }
}
fn remove(key: &'static str) -> Self {
let previous = env::var_os(key);
unsafe { env::remove_var(key) };
Self { key, previous }
}
}
impl Drop for EnvGuard {
fn drop(&mut self) {
if let Some(previous) = &self.previous {
unsafe { env::set_var(self.key, previous) };
} else {
unsafe { env::remove_var(self.key) };
}
}
}
#[test]
fn cargo_term_color_never_disables_color() {
let _guard = EnvGuard::set(CARGO_TERM_COLOR_ENV, CARGO_TERM_COLOR_NEVER);
assert!(matches!(color_mode(), ColorMode::Disabled));
}
#[test]
fn cargo_term_color_always_enables_color() {
let _guard = EnvGuard::set(CARGO_TERM_COLOR_ENV, CARGO_TERM_COLOR_ALWAYS);
assert!(matches!(color_mode(), ColorMode::Enabled));
}
#[test]
fn clicolor_zero_disables_color() {
let _guard = EnvGuard::set(CLICOLOR_ENV, CLICOLOR_DISABLED_VALUE);
let _cargo_term_color = EnvGuard::remove(CARGO_TERM_COLOR_ENV);
assert!(matches!(color_mode(), ColorMode::Disabled));
}
#[test]
fn term_does_not_enable_color_when_output_is_captured() {
let _cargo_term_color = EnvGuard::remove(CARGO_TERM_COLOR_ENV);
let _clicolor = EnvGuard::remove(CLICOLOR_ENV);
let _clicolor_force = EnvGuard::remove(CLICOLOR_FORCE_ENV);
let _term = EnvGuard::set("TERM", "xterm-256color");
assert!(matches!(color_mode(), ColorMode::Disabled));
}
#[test]
fn build_info_contains_expected_fields() {
let build_info = build_info_text();
assert!(build_info.starts_with(&format!("cargo-mend {}", env!("CARGO_PKG_VERSION"))));
assert!(build_info.contains("\ngit_hash: "));
assert!(build_info.contains("\nbuild_id: "));
assert!(build_info.contains("\nbuild_sysroot: "));
}
}