use std::env;
use std::path::Path;
use std::sync::{Arc, Mutex};
use anyhow::{anyhow, Result};
use log::{set_max_level, LevelFilter};
#[cfg(feature = "openssl-vendored")]
use openssl_probe;
use crate::clang_tools::capture_clang_tools_output;
use crate::cli::{get_arg_parser, ClangParams, Cli, FeedbackInput, LinesChangedOnly};
use crate::common_fs::FileFilter;
use crate::logger;
use crate::rest_api::{github::GithubApiClient, RestApiClient};
const VERSION: &str = env!("CARGO_PKG_VERSION");
fn probe_ssl_certs() {
#[cfg(feature = "openssl-vendored")]
openssl_probe::init_ssl_cert_env_vars();
}
pub async fn run_main(args: Vec<String>) -> Result<()> {
probe_ssl_certs();
let arg_parser = get_arg_parser();
let args = arg_parser.get_matches_from(args);
let cli = Cli::from(&args);
if args.subcommand_matches("version").is_some() {
println!("cpp-linter v{}", VERSION);
return Ok(());
}
logger::init().unwrap();
if cli.version == "NO-VERSION" {
log::error!("The `--version` arg is used to specify which version of clang to use.");
log::error!("To get the cpp-linter version, use `cpp-linter version` sub-command.");
return Err(anyhow!("Clang version not specified."));
}
if cli.repo_root != "." {
env::set_current_dir(Path::new(&cli.repo_root))
.unwrap_or_else(|_| panic!("'{}' is inaccessible or does not exist", cli.repo_root));
}
let rest_api_client = GithubApiClient::new()?;
set_max_level(if cli.verbosity || rest_api_client.debug_enabled {
LevelFilter::Debug
} else {
LevelFilter::Info
});
log::info!("Processing event {}", rest_api_client.event_name);
let mut file_filter = FileFilter::new(&cli.ignore, cli.extensions.clone());
file_filter.parse_submodules();
if let Some(files) = &cli.not_ignored {
file_filter.not_ignored.extend(files.clone());
}
if !file_filter.ignored.is_empty() {
log::info!("Ignored:");
for pattern in &file_filter.ignored {
log::info!(" {pattern}");
}
}
if !file_filter.not_ignored.is_empty() {
log::info!("Not Ignored:");
for pattern in &file_filter.not_ignored {
log::info!(" {pattern}");
}
}
rest_api_client.start_log_group(String::from("Get list of specified source files"));
let files = if cli.lines_changed_only != LinesChangedOnly::Off || cli.files_changed_only {
rest_api_client
.get_list_of_changed_files(&file_filter)
.await?
} else {
let mut all_files = file_filter.list_source_files(".")?;
if rest_api_client.event_name == "pull_request" && (cli.tidy_review || cli.format_review) {
let changed_files = rest_api_client
.get_list_of_changed_files(&file_filter)
.await?;
for changed_file in changed_files {
for file in &mut all_files {
if changed_file.name == file.name {
file.diff_chunks = changed_file.diff_chunks.clone();
file.added_lines = changed_file.added_lines.clone();
file.added_ranges = changed_file.added_ranges.clone();
}
}
}
}
all_files
};
let mut arc_files = vec![];
log::info!("Giving attention to the following files:");
for file in files {
log::info!(" ./{}", file.name.to_string_lossy().replace('\\', "/"));
arc_files.push(Arc::new(Mutex::new(file)));
}
rest_api_client.end_log_group();
let mut clang_params = ClangParams::from(&cli);
let user_inputs = FeedbackInput::from(&cli);
let clang_versions = capture_clang_tools_output(
&mut arc_files,
cli.version.as_str(),
&mut clang_params,
&rest_api_client,
)
.await?;
rest_api_client.start_log_group(String::from("Posting feedback"));
let checks_failed = rest_api_client
.post_feedback(&arc_files, user_inputs, clang_versions)
.await?;
rest_api_client.end_log_group();
if env::var("PRE_COMMIT").is_ok_and(|v| v == "1") {
if checks_failed > 1 {
return Err(anyhow!("Some checks did not pass"));
} else {
return Ok(());
}
}
Ok(())
}
#[cfg(test)]
mod test {
use super::run_main;
use std::env;
#[tokio::test]
async fn normal() {
env::remove_var("GITHUB_OUTPUT"); let result = run_main(vec![
"cpp-linter".to_string(),
"-l".to_string(),
"false".to_string(),
"--repo-root".to_string(),
"tests".to_string(),
"demo/demo.cpp".to_string(),
])
.await;
assert!(result.is_ok());
}
#[tokio::test]
async fn version_command() {
env::remove_var("GITHUB_OUTPUT"); let result = run_main(vec!["cpp-linter".to_string(), "version".to_string()]).await;
assert!(result.is_ok());
}
#[tokio::test]
async fn force_debug_output() {
env::remove_var("GITHUB_OUTPUT"); let result = run_main(vec![
"cpp-linter".to_string(),
"-l".to_string(),
"false".to_string(),
"-v".to_string(),
"debug".to_string(),
])
.await;
assert!(result.is_ok());
}
#[tokio::test]
async fn bad_version_input() {
env::remove_var("GITHUB_OUTPUT"); let result = run_main(vec![
"cpp-linter".to_string(),
"-l".to_string(),
"false".to_string(),
"-V".to_string(),
])
.await;
assert!(result.is_err());
}
#[tokio::test]
async fn pre_commit_env() {
env::remove_var("GITHUB_OUTPUT"); env::set_var("PRE_COMMIT", "1");
let result = run_main(vec![
"cpp-linter".to_string(),
"-l".to_string(),
"false".to_string(),
])
.await;
assert!(result.is_err());
}
}