1use std::env;
7use std::path::Path;
8use std::sync::{Arc, Mutex};
9
10use anyhow::{anyhow, Result};
12use log::{set_max_level, LevelFilter};
13#[cfg(feature = "openssl-vendored")]
14use openssl_probe;
15
16use crate::clang_tools::capture_clang_tools_output;
18use crate::cli::{get_arg_parser, ClangParams, Cli, FeedbackInput, LinesChangedOnly};
19use crate::common_fs::FileFilter;
20use crate::logger;
21use crate::rest_api::{github::GithubApiClient, RestApiClient};
22
23const VERSION: &str = env!("CARGO_PKG_VERSION");
24
25fn probe_ssl_certs() {
26 #[cfg(feature = "openssl-vendored")]
27 openssl_probe::init_ssl_cert_env_vars();
28}
29
30pub async fn run_main(args: Vec<String>) -> Result<()> {
50 probe_ssl_certs();
51
52 let arg_parser = get_arg_parser();
53 let args = arg_parser.get_matches_from(args);
54 let cli = Cli::from(&args);
55
56 if args.subcommand_matches("version").is_some() {
57 println!("cpp-linter v{}", VERSION);
58 return Ok(());
59 }
60
61 logger::init().unwrap();
62
63 if cli.version == "NO-VERSION" {
64 log::error!("The `--version` arg is used to specify which version of clang to use.");
65 log::error!("To get the cpp-linter version, use `cpp-linter version` sub-command.");
66 return Err(anyhow!("Clang version not specified."));
67 }
68
69 if cli.repo_root != "." {
70 env::set_current_dir(Path::new(&cli.repo_root))
71 .unwrap_or_else(|_| panic!("'{}' is inaccessible or does not exist", cli.repo_root));
72 }
73
74 let rest_api_client = GithubApiClient::new()?;
75 set_max_level(if cli.verbosity || rest_api_client.debug_enabled {
76 LevelFilter::Debug
77 } else {
78 LevelFilter::Info
79 });
80 log::info!("Processing event {}", rest_api_client.event_name);
81
82 let mut file_filter = FileFilter::new(&cli.ignore, cli.extensions.clone());
83 file_filter.parse_submodules();
84 if let Some(files) = &cli.not_ignored {
85 file_filter.not_ignored.extend(files.clone());
86 }
87
88 if !file_filter.ignored.is_empty() {
89 log::info!("Ignored:");
90 for pattern in &file_filter.ignored {
91 log::info!(" {pattern}");
92 }
93 }
94 if !file_filter.not_ignored.is_empty() {
95 log::info!("Not Ignored:");
96 for pattern in &file_filter.not_ignored {
97 log::info!(" {pattern}");
98 }
99 }
100
101 rest_api_client.start_log_group(String::from("Get list of specified source files"));
102 let files = if cli.lines_changed_only != LinesChangedOnly::Off || cli.files_changed_only {
103 rest_api_client
105 .get_list_of_changed_files(&file_filter)
106 .await?
107 } else {
108 let mut all_files = file_filter.list_source_files(".")?;
110 if rest_api_client.event_name == "pull_request" && (cli.tidy_review || cli.format_review) {
111 let changed_files = rest_api_client
112 .get_list_of_changed_files(&file_filter)
113 .await?;
114 for changed_file in changed_files {
115 for file in &mut all_files {
116 if changed_file.name == file.name {
117 file.diff_chunks = changed_file.diff_chunks.clone();
118 file.added_lines = changed_file.added_lines.clone();
119 file.added_ranges = changed_file.added_ranges.clone();
120 }
121 }
122 }
123 }
124 all_files
125 };
126 let mut arc_files = vec![];
127 log::info!("Giving attention to the following files:");
128 for file in files {
129 log::info!(" ./{}", file.name.to_string_lossy().replace('\\', "/"));
130 arc_files.push(Arc::new(Mutex::new(file)));
131 }
132 rest_api_client.end_log_group();
133
134 let mut clang_params = ClangParams::from(&cli);
135 let user_inputs = FeedbackInput::from(&cli);
136 let clang_versions = capture_clang_tools_output(
137 &mut arc_files,
138 cli.version.as_str(),
139 &mut clang_params,
140 &rest_api_client,
141 )
142 .await?;
143 rest_api_client.start_log_group(String::from("Posting feedback"));
144 let checks_failed = rest_api_client
145 .post_feedback(&arc_files, user_inputs, clang_versions)
146 .await?;
147 rest_api_client.end_log_group();
148 if env::var("PRE_COMMIT").is_ok_and(|v| v == "1") {
149 if checks_failed > 1 {
150 return Err(anyhow!("Some checks did not pass"));
151 } else {
152 return Ok(());
153 }
154 }
155 Ok(())
156}
157
158#[cfg(test)]
159mod test {
160 use super::run_main;
161 use std::env;
162
163 #[tokio::test]
164 async fn normal() {
165 env::remove_var("GITHUB_OUTPUT"); let result = run_main(vec![
167 "cpp-linter".to_string(),
168 "-l".to_string(),
169 "false".to_string(),
170 "--repo-root".to_string(),
171 "tests".to_string(),
172 "demo/demo.cpp".to_string(),
173 ])
174 .await;
175 assert!(result.is_ok());
176 }
177
178 #[tokio::test]
179 async fn version_command() {
180 env::remove_var("GITHUB_OUTPUT"); let result = run_main(vec!["cpp-linter".to_string(), "version".to_string()]).await;
182 assert!(result.is_ok());
183 }
184
185 #[tokio::test]
186 async fn force_debug_output() {
187 env::remove_var("GITHUB_OUTPUT"); let result = run_main(vec![
189 "cpp-linter".to_string(),
190 "-l".to_string(),
191 "false".to_string(),
192 "-v".to_string(),
193 "debug".to_string(),
194 ])
195 .await;
196 assert!(result.is_ok());
197 }
198
199 #[tokio::test]
200 async fn bad_version_input() {
201 env::remove_var("GITHUB_OUTPUT"); let result = run_main(vec![
203 "cpp-linter".to_string(),
204 "-l".to_string(),
205 "false".to_string(),
206 "-V".to_string(),
207 ])
208 .await;
209 assert!(result.is_err());
210 }
211
212 #[tokio::test]
213 async fn pre_commit_env() {
214 env::remove_var("GITHUB_OUTPUT"); env::set_var("PRE_COMMIT", "1");
216 let result = run_main(vec![
217 "cpp-linter".to_string(),
218 "-l".to_string(),
219 "false".to_string(),
220 ])
221 .await;
222 assert!(result.is_err());
223 }
224
225 #[tokio::test]
228 async fn no_analysis() {
229 env::remove_var("GITHUB_OUTPUT"); let result = run_main(vec![
231 "cpp-linter".to_string(),
232 "-l".to_string(),
233 "false".to_string(),
234 "--style".to_string(),
235 String::new(),
236 "--tidy-checks=-*".to_string(),
237 ])
238 .await;
239 assert!(result.is_ok());
240 }
241}