use std::cell::RefCell;
use std::collections::{HashMap, HashSet};
use std::env;
use std::ops::ControlFlow;
use std::path::{Path, PathBuf};
use std::rc::Rc;
use clang::{Entity, EntityKind};
use opencv_binding_generator::{
opencv_module_from_path, Class, Constness, EntityExt, EntityWalkerExt, EntityWalkerVisitor, Func, Generator, GeneratorEnv,
Pred, SupportedModule,
};
struct FunctionFinder<'tu> {
pub module: SupportedModule,
pub gen_env: GeneratorEnv<'tu>,
}
impl FunctionFinder<'_> {
pub fn update_used_func(&self, f: &Func) {
let mut matcher = f.matcher();
self.gen_env.settings.arg_override.get(&mut matcher);
self.gen_env.settings.return_override.get(&mut matcher);
self.gen_env.settings.force_infallible.get(&mut matcher);
self.gen_env.settings.func_cfg_attr.get(&mut matcher);
self.gen_env.settings.func_companion_tweak.get(&mut matcher);
self.gen_env.settings.func_replace.get(&mut matcher);
self.gen_env.settings.func_specialize.get(&mut matcher);
self.gen_env.settings.func_unsafe.get(&mut matcher);
}
}
impl<'tu> EntityWalkerVisitor<'tu> for &mut FunctionFinder<'tu> {
fn wants_file(&mut self, path: &Path) -> bool {
opencv_module_from_path(path) == Some(self.module)
}
fn visit_entity(&mut self, entity: Entity<'tu>) -> ControlFlow<()> {
match entity.get_kind() {
EntityKind::ClassDecl
| EntityKind::ClassTemplate
| EntityKind::ClassTemplatePartialSpecialization
| EntityKind::StructDecl => {
let c = Class::new(entity, &self.gen_env);
if !c.template_kind().is_template() {
c.methods(|_| true).into_iter().for_each(|f| self.update_used_func(&f));
c.field_methods(&c.fields(|_| true), None)
.for_each(|f| self.update_used_func(&f));
let _ = entity.walk_methods_while(|child| {
let func = Func::new(child, &self.gen_env);
if func.is_generic() {
self.update_used_func(&func);
}
ControlFlow::Continue(())
});
let _ = entity.walk_classes_while(|child| self.visit_entity(child));
}
}
EntityKind::FunctionDecl => {
let f = Func::new(entity, &self.gen_env);
self.update_used_func(&f);
}
_ => {}
}
ControlFlow::Continue(())
}
}
fn main() {
let mut args = env::args_os().skip(1);
let src_cpp_dir = PathBuf::from(args.next().expect("2nd argument must be dir with custom cpp"));
let opencv_header_dirs = args.map(PathBuf::from);
let global_usage_tracking = Rc::new(RefCell::new(HashMap::<
SupportedModule,
HashMap<&'static str, HashSet<UsageTrackerOwned>>,
>::new()));
for opencv_header_dir in opencv_header_dirs {
println!("Processing header dir: {}", opencv_header_dir.display());
let modules = opencv_header_dir
.join("opencv2")
.read_dir()
.expect("Can't read dir")
.map(|p| p.expect("Bad path").path())
.filter(|p| p.is_file() && p.extension().is_some_and(|e| e == "hpp"))
.filter_map(|mut p| {
p.set_extension("");
p.file_name()
.and_then(|f| f.to_str())
.and_then(SupportedModule::try_from_opencv_name)
});
let gen = Generator::new(&opencv_header_dir, &[], &src_cpp_dir);
for module in modules {
println!(" {}", module.opencv_name());
gen.pre_process(module, false, {
let global_usage_tracking = Rc::clone(&global_usage_tracking);
|root_entity| {
let global_usage_tracking = global_usage_tracking; let mut gen_env = GeneratorEnv::global(module, root_entity);
gen_env.settings.start_usage_tracking();
let mut function_finder = FunctionFinder { module, gen_env };
root_entity.walk_opencv_entities(&mut function_finder);
let usage_tracking = function_finder.gen_env.settings.finish_usage_tracking();
let mut global_usage_tracking = global_usage_tracking.borrow_mut();
let module_usage_tracking = global_usage_tracking.entry(module).or_default();
for (usage_section, new_usage_tracking) in usage_tracking {
let new_usage_tracking: HashSet<UsageTrackerOwned> = new_usage_tracking
.into_iter()
.map(|(name, preds)| (name.to_string(), preds.iter().map(PredOwned::from_pred).collect()))
.collect();
if let Some(prev_usage_tracking) = module_usage_tracking.get_mut(usage_section) {
*prev_usage_tracking = new_usage_tracking.intersection(prev_usage_tracking).cloned().collect();
} else {
module_usage_tracking.insert(usage_section, new_usage_tracking);
}
}
}
});
}
}
let global_usage_tracking = Rc::try_unwrap(global_usage_tracking).expect("Not owned").into_inner();
let mut usage_per_section = HashMap::new();
for (_module, module_usage) in global_usage_tracking {
for (section, preds) in module_usage {
let section_usage = usage_per_section.entry(section).or_insert_with(Vec::new);
for (name, preds) in preds {
section_usage.push((name, preds));
}
}
}
for (section, mut usage_tracking) in usage_per_section {
if usage_tracking.is_empty() {
println!("No unused entries in {section}");
} else {
println!("Unused entries in {section} ({}):", usage_tracking.len());
usage_tracking.sort_unstable();
for (name, mut preds) in usage_tracking {
preds.sort_unstable();
println!(" {name}: {preds:?}");
}
}
}
}
type UsageTrackerOwned = (String, Vec<PredOwned>);
#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
enum PredOwned {
Constness(Constness),
Return(String),
ArgNames(Vec<String>),
ArgTypes(Vec<String>),
}
impl PredOwned {
fn from_pred(pred: &Pred) -> Self {
match pred {
Pred::Constness(c) => Self::Constness(*c),
Pred::Return(r) => Self::Return(r.to_string()),
Pred::ArgNames(a) => Self::ArgNames(a.iter().map(|s| s.to_string()).collect()),
Pred::ArgTypes(a) => Self::ArgTypes(a.iter().map(|s| s.to_string()).collect()),
}
}
}