Skip to content

Commit c96e697

Browse files
committed
Add support for using on-the-fly generated definitions
1 parent 88f59d9 commit c96e697

File tree

14 files changed

+324
-107
lines changed

14 files changed

+324
-107
lines changed

binding-generator/src/bin/settings-cleanup.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,8 @@ fn main() {
138138
let gen = Generator::new(None, &opencv_header_dir, &src_cpp_dir, clang);
139139
for module in modules {
140140
println!(" {}", module);
141-
gen.process_module(&module, false, |root_entity| {
141+
gen.process_module(&module, false, |root_tu| {
142+
let root_entity = root_tu.get_entity();
142143
let gen_env = GeneratorEnv::new(root_entity, &module);
143144
let walker = EntityWalker::new(root_entity);
144145
walker.walk_opencv_entities(FunctionFinder {

binding-generator/src/generator.rs

Lines changed: 158 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,32 @@
11
use std::{
22
borrow::Cow,
3-
fmt,
3+
collections::{HashMap, HashSet},
4+
fmt::{self, Write},
45
fs::File,
56
io::BufReader,
67
path::{Path, PathBuf},
78
};
89

910
use clang::{
1011
Clang,
12+
diagnostic::Diagnostic,
1113
diagnostic::Severity,
1214
Entity,
1315
EntityKind,
16+
EntityVisitResult,
1417
Index,
1518
TranslationUnit,
1619
Type,
20+
Unsaved,
1721
};
1822
use dunce::canonicalize;
23+
use maplit::hashmap;
24+
use once_cell::sync::Lazy;
1925

2026
use crate::{
2127
AbstractRefWrapper,
2228
Class,
29+
CompiledInterpolation,
2330
Const,
2431
Element,
2532
EntityExt,
@@ -31,9 +38,11 @@ use crate::{
3138
GeneratorEnv,
3239
get_definition_text,
3340
line_reader,
41+
opencv_module_from_path,
3442
return_type_wrapper::ReturnTypeWrapper,
3543
settings,
3644
smart_ptr::SmartPtr,
45+
StrExt,
3746
type_ref::Kind as TypeRefKind,
3847
Typedef,
3948
vector::Vector,
@@ -59,6 +68,114 @@ pub trait GeneratorVisitor<'tu> {
5968
fn visit_typedef(&mut self, typedef: Typedef) {}
6069
fn visit_class(&mut self, class: Class) {}
6170
fn visit_dependent_type(&mut self, typ: Self::D) {}
71+
72+
fn visit_ephemeral_header(&mut self, contents: &str) {}
73+
}
74+
75+
struct EphemeralGenerator<'m> {
76+
module: &'m str,
77+
used_in_smart_ptr: HashSet<String>,
78+
descendants: HashMap<String, HashSet<String>>,
79+
}
80+
81+
impl<'m> EphemeralGenerator<'m> {
82+
pub fn new(module: &'m str) -> Self {
83+
Self {
84+
module,
85+
used_in_smart_ptr: HashSet::with_capacity(32),
86+
descendants: HashMap::with_capacity(16),
87+
}
88+
}
89+
90+
fn add_used_in_smart_ptr(&mut self, func: Entity) {
91+
for arg in func.get_arguments().into_iter().flatten() {
92+
if let Some(arg_type) = arg.get_type() {
93+
if arg_type.get_declaration().map_or(false, |ent| ent.cpp_fullname().starts_with("cv::Ptr")) {
94+
let inner_type_ent = arg_type.get_template_argument_types().into_iter()
95+
.flatten()
96+
.flatten()
97+
.next()
98+
.and_then(|t| t.get_declaration());
99+
if let Some(inner_type_ent) = inner_type_ent {
100+
self.used_in_smart_ptr.insert(inner_type_ent.cpp_fullname().into_owned());
101+
}
102+
}
103+
}
104+
}
105+
}
106+
107+
pub fn make_generated_header(&self) -> String {
108+
static TPL: Lazy<CompiledInterpolation> = Lazy::new(
109+
|| include_str!("../tpl/ephemeral/ephemeral.tpl.hpp").compile_interpolation()
110+
);
111+
112+
let mut includes = String::with_capacity(128);
113+
let mut resolve_types = String::with_capacity(1024);
114+
let mut generate_types: Vec<Cow<_>> = Vec::with_capacity(32);
115+
116+
let mut resolve_types_idx = 0usize;
117+
let global_tweaks = settings::GENERATOR_MODULE_TWEAKS.get("*");
118+
let module_tweaks = settings::GENERATOR_MODULE_TWEAKS.get(self.module);
119+
for tweak in global_tweaks.iter().chain(module_tweaks.iter()) {
120+
for &include in &tweak.includes {
121+
writeln!(&mut includes, "#include <opencv2/{}>", include).expect("Can't fail");
122+
}
123+
for &res_type in &tweak.resolve_types {
124+
writeln!(&mut resolve_types, "typedef {} ephem{};", res_type, resolve_types_idx).expect("Can't fail");
125+
resolve_types_idx += 1;
126+
}
127+
for &gen_type in &tweak.generate_types {
128+
generate_types.push(gen_type.into());
129+
}
130+
}
131+
for used_cppfull in &self.used_in_smart_ptr {
132+
for desc_cppfull in self.descendants.get(used_cppfull).into_iter().flatten() {
133+
if !self.used_in_smart_ptr.contains(desc_cppfull) {
134+
generate_types.push(format!("cv::Ptr<{}>", desc_cppfull).into());
135+
}
136+
}
137+
}
138+
139+
TPL.interpolate(&hashmap! {
140+
"includes" => includes,
141+
"resolve_types" => resolve_types,
142+
"generate_types" => generate_types.join(",\n"),
143+
})
144+
}
145+
}
146+
147+
impl EntityWalkerVisitor<'_> for &mut EphemeralGenerator<'_> {
148+
fn wants_file(&mut self, path: &Path) -> bool {
149+
opencv_module_from_path(path).map_or(false, |m| m == self.module)
150+
}
151+
152+
fn visit_entity(&mut self, entity: Entity<'_>) -> bool {
153+
match entity.get_kind() {
154+
EntityKind::ClassDecl | EntityKind::StructDecl => {
155+
entity.visit_children(|c, _| {
156+
match c.get_kind() {
157+
EntityKind::BaseSpecifier => {
158+
let c_decl = c.get_definition().expect("Can't get base class definition");
159+
self.descendants.entry(c_decl.cpp_fullname().into_owned())
160+
.or_insert_with(|| HashSet::with_capacity(4))
161+
.insert(entity.cpp_fullname().into_owned());
162+
}
163+
EntityKind::Constructor | EntityKind::Method | EntityKind::FunctionTemplate
164+
| EntityKind::ConversionFunction => {
165+
self.add_used_in_smart_ptr(c)
166+
}
167+
_ => {}
168+
}
169+
EntityVisitResult::Continue
170+
});
171+
}
172+
EntityKind::FunctionDecl => {
173+
self.add_used_in_smart_ptr(entity);
174+
}
175+
_ => {}
176+
}
177+
true
178+
}
62179
}
63180

64181
#[derive(Debug)]
@@ -349,6 +466,26 @@ impl Generator {
349466
}
350467
}
351468

469+
fn make_ephemeral_header(&self, contents: &str) -> Unsaved {
470+
Unsaved::new(self.src_cpp_dir.join("ocvrs_ephemeral.hpp"), contents)
471+
}
472+
473+
fn handle_diags(diags: &[Diagnostic], panic_on_error: bool) {
474+
if !diags.is_empty() {
475+
let mut has_error = false;
476+
eprintln!("=== WARNING: {} diagnostic messages", diags.len());
477+
for diag in diags {
478+
if !has_error && matches!(diag.get_severity(), Severity::Error | Severity::Fatal) {
479+
has_error = true;
480+
}
481+
eprintln!("=== {}", diag);
482+
}
483+
if has_error && panic_on_error {
484+
panic!("=== Errors during header parsing");
485+
}
486+
}
487+
}
488+
352489
pub fn build_clang_command_line_args(&self) -> Vec<Cow<'static, str>> {
353490
let mut args = self.clang_include_dirs.iter()
354491
.map(|d| format!("-isystem{}", d.to_str().expect("Incorrect system include path")).into())
@@ -357,42 +494,39 @@ impl Generator {
357494
)
358495
.collect::<Vec<_>>();
359496
args.push("-DOCVRS_PARSING_HEADERS".into());
360-
args.push("-includeocvrs_resolve_types.hpp".into());
497+
args.push("-includeocvrs_ephemeral.hpp".into());
361498
// need to have c++14 here because VS headers contain features that require it
362499
args.push("-std=c++14".into());
363500
args
364501
}
365502

366-
pub fn process_module(&self, module: &str, panic_on_error: bool, entity_processor: impl FnOnce(Entity)) {
503+
pub fn process_module(&self, module: &str, panic_on_error: bool, entity_processor: impl FnOnce(TranslationUnit)) {
367504
let index = Index::new(&self.clang, true, false);
368505
let mut module_file = self.src_cpp_dir.join(format!("{}.hpp", module));
369506
if !module_file.exists() {
370507
module_file = self.opencv_include_dir.join(format!("opencv2/{}.hpp", module));
371508
}
372509
let root_tu: TranslationUnit = index.parser(module_file)
510+
.unsaved(&[self.make_ephemeral_header("")])
373511
.arguments(&self.build_clang_command_line_args())
374512
.detailed_preprocessing_record(true)
375513
.skip_function_bodies(true)
376514
.parse().expect("Cannot parse");
377-
let diags = root_tu.get_diagnostics();
378-
if !diags.is_empty() {
379-
let mut has_error = false;
380-
eprintln!("=== WARNING: {} diagnostic messages", diags.len());
381-
for diag in diags {
382-
if !has_error && matches!(diag.get_severity(), Severity::Error | Severity::Fatal) {
383-
has_error = true;
384-
}
385-
eprintln!("=== {}", diag);
386-
}
387-
if has_error && panic_on_error {
388-
panic!("=== Errors during header parsing");
389-
}
390-
}
391-
entity_processor(root_tu.get_entity());
515+
Self::handle_diags(&root_tu.get_diagnostics(), panic_on_error);
516+
entity_processor(root_tu);
392517
}
393518

394-
pub fn process_opencv_module(&self, module: &str, visitor: impl for<'gtu> GeneratorVisitor<'gtu>) {
395-
self.process_module(module, true, |root_entity| {
519+
pub fn process_opencv_module(&self, module: &str, mut visitor: impl for<'gtu> GeneratorVisitor<'gtu>) {
520+
self.process_module(module, true, |mut root_tu| {
521+
let mut root_entity = root_tu.get_entity();
522+
let walker = EntityWalker::new(root_entity);
523+
let mut ephem_gen = EphemeralGenerator::new(module);
524+
walker.walk_opencv_entities(&mut ephem_gen);
525+
let hdr = ephem_gen.make_generated_header();
526+
visitor.visit_ephemeral_header(&hdr);
527+
root_tu = root_tu.reparse(&[self.make_ephemeral_header(&hdr)]).expect("Can't reparse file");
528+
Self::handle_diags(&root_tu.get_diagnostics(), true);
529+
root_entity = root_tu.get_entity();
396530
let gen_env = GeneratorEnv::new(root_entity, module);
397531
let opencv_walker = OpenCVWalker::new(
398532
&self.opencv_include_dir,
@@ -405,6 +539,10 @@ impl Generator {
405539
}
406540
}
407541

542+
pub fn is_ephemeral_header(path: &Path) -> bool {
543+
path.ends_with("ocvrs_ephemeral.hpp")
544+
}
545+
408546
#[allow(unused)]
409547
pub fn dbg_clang_type(type_ref: Type) {
410548
struct TypeWrapper<'tu>(Type<'tu>);

binding-generator/src/generator_env.rs

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ use crate::{
1616
Element,
1717
EntityWalker,
1818
EntityWalkerVisitor,
19+
is_ephemeral_header,
1920
is_opencv_path,
2021
memo_map,
2122
MemoizeMap,
@@ -72,7 +73,7 @@ impl<'tu> DBPopulator<'_, 'tu> {
7273
impl<'tu> EntityWalkerVisitor<'tu> for DBPopulator<'_, 'tu> {
7374
fn wants_file(&mut self, path: &Path) -> bool {
7475
is_opencv_path(path)
75-
|| path.ends_with("ocvrs_resolve_types.hpp")
76+
|| is_ephemeral_header(path)
7677
|| opencv_module_from_path(path).map_or(false, |m| m == self.gen_env.module)
7778
}
7879

@@ -146,18 +147,23 @@ impl<'tu> GeneratorEnv<'tu> {
146147
fn key(entity: Entity) -> ExportIdx {
147148
let (loc, offset) = if entity.get_kind() == EntityKind::MacroExpansion {
148149
// sometimes CV_EXPORT macros are located on a separate line so for those we compensate the offset
149-
let l = entity.get_range().expect("Can't get export macro range").get_end().get_spelling_location();
150-
let mut f = File::open(l.file.expect("Can't get export macro file").get_path()).expect("Can't open export macro file");
151-
f.seek(SeekFrom::Start(l.offset as u64)).expect("Can't seek export macro file");
152-
let mut buf = [0; 8];
153-
let read = f.read(&mut buf).expect("Can't read file");
154-
let line_offset = buf[0..read].iter()
155-
.take_while(|&&c| c == b'\n')
156-
.count();
157-
if line_offset > 1 {
158-
panic!("Line offset more than 1 is not supported, modify fuzzy_key in get_export_config() to support higher values");
150+
let l = entity.get_range().expect("Can't get exported macro range").get_end().get_spelling_location();
151+
let path = l.file.expect("Can't get exported macro file").get_path();
152+
if is_ephemeral_header(&path) {
153+
(l, 0)
154+
} else {
155+
let mut f = File::open(path).expect("Can't open export macro file");
156+
f.seek(SeekFrom::Start(l.offset as u64)).expect("Can't seek export macro file");
157+
let mut buf = [0; 8];
158+
let read = f.read(&mut buf).expect("Can't read file");
159+
let line_offset = buf[0..read].iter()
160+
.take_while(|&&c| c == b'\n')
161+
.count();
162+
if line_offset > 1 {
163+
panic!("Line offset more than 1 is not supported, modify fuzzy_key in get_export_config() to support higher values");
164+
}
165+
(l, line_offset as u32)
159166
}
160-
(l, line_offset as u32)
161167
} else {
162168
let loc = if let Some(range) = entity.get_range() {
163169
range.get_start().get_spelling_location()

binding-generator/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ pub use func::{Func, FunctionTypeHint};
4242
use function::Function;
4343
#[allow(unused)]
4444
use generator::{dbg_clang_entity, dbg_clang_type};
45-
pub use generator::{DependentType, Generator, GeneratorVisitor};
45+
pub use generator::{DependentType, Generator, GeneratorVisitor, is_ephemeral_header};
4646
pub use generator_env::{ExportConfig, GeneratorEnv};
4747
pub use iterator_ext::IteratorExt;
4848
#[allow(unused)]

0 commit comments

Comments
 (0)