use clap::{ArgGroup, Parser};
use log::{error, info};
use std::collections::HashSet;
use std::fs;
use std::io;
use std::path::PathBuf;
use nom_kconfig::{KconfigFile, KconfigInput};
use kconfirm_lib::AnalysisArgs;
use kconfirm_lib::Check;
use kconfirm_lib::check_kconfig;
use kconfirm_lib::output::{Finding, print_findings};
use kconfirm_lib::parse_check;
use kconfirm_linux::collect_kconfig_root_files;
#[derive(Parser, Debug)]
#[command(
author,
version,
about,
long_about = None,
group(
ArgGroup::new("source")
.args(["linux_dir_path", "coreboot_dir_path", "other_kconfig_path"]) // NOTE: make sure to rename these strings when renaming the args
.required(true)
)
)]
struct Args {
#[arg(long)]
linux_dir_path: Option<PathBuf>,
#[arg(long)]
coreboot_dir_path: Option<PathBuf>,
#[arg(long)]
other_kconfig_path: Option<PathBuf>,
#[arg(long, value_delimiter = ',', num_args = 1..)]
enable_check: Vec<String>,
#[arg(long, value_delimiter = ',', num_args = 1..)]
disable_check: Vec<String>,
}
fn main() -> io::Result<()> {
env_logger::init();
let cli_args = Args::parse();
let mut enabled_checks: HashSet<Check> = [
Check::SelectVisible,
Check::DuplicateDependency,
Check::DuplicateRange,
Check::DeadRange,
Check::DuplicateSelect,
Check::DeadDefault,
Check::ConstantCondition,
Check::DuplicateDefault,
Check::DuplicateImply,
Check::ReverseRange,
]
.into_iter()
.collect();
for name in &cli_args.enable_check {
if let Some(c) = parse_check(name) {
enabled_checks.insert(c);
}
}
for name in &cli_args.disable_check {
if let Some(c) = parse_check(name) {
enabled_checks.remove(&c);
}
}
let analysis_args = AnalysisArgs { enabled_checks };
let findings: Vec<Finding>;
match (
cli_args.linux_dir_path,
cli_args.coreboot_dir_path,
cli_args.other_kconfig_path,
) {
(Some(linux_path), None, None) => {
let kconfig_files = collect_kconfig_root_files(linux_path)?;
let kconfig_inputs = kconfig_files
.iter()
.map(|kconfig| {
let kconfig_input = KconfigInput::new_extra(
&kconfig.file_contents,
kconfig.kconfig_file.clone(),
);
(kconfig.arch_config_option.clone(), kconfig_input)
})
.collect();
findings = check_kconfig(analysis_args, kconfig_inputs);
}
(None, Some(coreboot_path), None) => {
let root_kconfig_path = PathBuf::from("src/Kconfig");
let root_kconfig_file = KconfigFile::new(coreboot_path.clone(), root_kconfig_path);
let file_contents = root_kconfig_file.read_to_string()?;
let kconfig_input = KconfigInput::new_extra(&file_contents, root_kconfig_file);
let kconfig_inputs = vec![(None, kconfig_input)];
let site_local_dir = coreboot_path.join("site-local");
let site_local_kconfig = site_local_dir.join("Kconfig");
let mut created_dir = false;
let mut temp_kconfig = None;
if !site_local_kconfig.exists() {
info!(
"coreboot/site-local/Kconfig was missing. Attempting to create it temporarily..."
);
if !site_local_dir.exists() {
fs::create_dir(&site_local_dir)?;
info!("coreboot/site-local/ was created temporarily.");
created_dir = true;
}
temp_kconfig = Some(fs::File::create_new(&site_local_kconfig)?);
info!("coreboot/site-local/Kconfig was created temporarily.");
}
findings = check_kconfig(analysis_args, kconfig_inputs);
if temp_kconfig.is_some() {
fs::remove_file(site_local_kconfig)?;
info!("Cleaned up coreboot/site-local/Kconfig.");
}
if created_dir {
fs::remove_dir(site_local_dir)?;
info!("Cleaned up coreboot/site-local/.");
}
}
(None, None, Some(other_kconfig_path)) => {
if !other_kconfig_path.is_file() {
error!(
"A directory was passed to '--other_kconfig_path'. Instead, please pass the kconfig entry file (probably 'Kconfig' or 'Config.in'."
);
panic!(); }
let root_dir = other_kconfig_path
.parent()
.expect("kconfig file is in a directory")
.to_path_buf();
let relative_path = other_kconfig_path
.strip_prefix(&root_dir)
.unwrap_or(&other_kconfig_path)
.to_path_buf();
let root_kconfig_file = KconfigFile::new(root_dir, relative_path);
let file_contents = fs::read_to_string(&other_kconfig_path)?;
let kconfig_input = KconfigInput::new_extra(&file_contents, root_kconfig_file);
let kconfig_inputs = vec![(None, kconfig_input)];
findings = check_kconfig(analysis_args, kconfig_inputs);
}
_ => unreachable!("clap ensures that these arguments are mutually-exclusive"),
}
print_findings(findings);
Ok(())
}