blob: 9dd620ca5bdd6bb37709744be537872197589d8f [file] [log] [blame]
Brian Silverman4e662aa2022-05-11 23:10:19 -07001// Copyright 2020 Google LLC
2//
3// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
4// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
5// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
6// option. This file may not be copied, modified, or distributed
7// except according to those terms.
8
9use crate::ast_discoverer::{Discoveries, DiscoveryErr};
10use crate::output_generators::RsOutput;
11use crate::{
12 cxxbridge::CxxBridge, Error as EngineError, GeneratedCpp, IncludeCppEngine,
13 RebuildDependencyRecorder,
14};
Austin Schuh6ea9bfa2023-08-06 19:05:10 -070015use crate::{proc_macro_span_to_miette_span, CodegenOptions, CppCodegenOptions, LocatedSynError};
Brian Silverman4e662aa2022-05-11 23:10:19 -070016use autocxx_parser::directive_names::SUBCLASS;
17use autocxx_parser::{AllowlistEntry, RustPath, Subclass, SubclassAttrs};
18use indexmap::set::IndexSet as HashSet;
Austin Schuh6ea9bfa2023-08-06 19:05:10 -070019use miette::{Diagnostic, SourceSpan};
Brian Silverman4e662aa2022-05-11 23:10:19 -070020use quote::ToTokens;
21use std::{io::Read, path::PathBuf};
22use std::{panic::UnwindSafe, path::Path, rc::Rc};
Austin Schuh6ea9bfa2023-08-06 19:05:10 -070023use syn::spanned::Spanned;
James Kuszmaul850a4752024-02-14 20:27:12 -080024use syn::Item;
Brian Silverman4e662aa2022-05-11 23:10:19 -070025use thiserror::Error;
26
27/// Errors which may occur when parsing a Rust source file to discover
28/// and interpret include_cxx macros.
29#[derive(Error, Diagnostic, Debug)]
30pub enum ParseError {
Austin Schuh6ea9bfa2023-08-06 19:05:10 -070031 #[error("unable to open the source file containing your autocxx bindings. (This filename is usually specified within your build.rs file.): {0}")]
Brian Silverman4e662aa2022-05-11 23:10:19 -070032 FileOpen(std::io::Error),
33 #[error("the .rs file couldn't be read: {0}")]
34 FileRead(std::io::Error),
35 #[error("syntax error interpreting Rust code: {0}")]
36 #[diagnostic(transparent)]
37 Syntax(LocatedSynError),
38 #[error("generate!/generate_ns! was used at the same time as generate_all!")]
39 ConflictingAllowlist,
40 #[error("the subclass attribute couldn't be parsed: {0}")]
41 #[diagnostic(transparent)]
42 SubclassSyntax(LocatedSynError),
Austin Schuh6ea9bfa2023-08-06 19:05:10 -070043 #[error("the subclass attribute macro with a superclass attribute requires the Builder::auto_allowlist option to be specified (probably in your build script). This is not recommended - instead you can specify subclass! within your include_cpp!.")]
44 SubclassSuperclassWithoutAutoAllowlist(#[source_code] String, #[label("here")] SourceSpan),
Brian Silverman4e662aa2022-05-11 23:10:19 -070045 /// The include CPP macro could not be expanded into
46 /// Rust bindings to C++, because of some problem during the conversion
47 /// process. This could be anything from a C++ parsing error to some
48 /// C++ feature that autocxx can't yet handle and isn't able to skip
49 /// over. It could also cover errors in your syntax of the `include_cpp`
50 /// macro or the directives inside.
51 #[error("the include_cpp! macro couldn't be expanded into Rust bindings to C++: {0}")]
52 #[diagnostic(transparent)]
53 AutocxxCodegenError(EngineError),
54 /// There are two or more `include_cpp` macros with the same
55 /// mod name.
56 #[error("there are two or more include_cpp! mods with the same mod name")]
57 ConflictingModNames,
Brian Silverman4e662aa2022-05-11 23:10:19 -070058 #[error("dynamic discovery was enabled but multiple mods were found")]
59 MultipleModsForDynamicDiscovery,
60 #[error("a problem occurred while discovering C++ APIs used within the Rust: {0}")]
61 Discovery(DiscoveryErr),
62}
63
64/// Parse a Rust file, and spot any include_cpp macros within it.
65pub fn parse_file<P1: AsRef<Path>>(
66 rs_file: P1,
67 auto_allowlist: bool,
68) -> Result<ParsedFile, ParseError> {
69 let mut source_code = String::new();
70 let mut file = std::fs::File::open(rs_file).map_err(ParseError::FileOpen)?;
71 file.read_to_string(&mut source_code)
72 .map_err(ParseError::FileRead)?;
73 proc_macro2::fallback::force();
74 let source = syn::parse_file(&source_code)
75 .map_err(|e| ParseError::Syntax(LocatedSynError::new(e, &source_code)))?;
76 parse_file_contents(source, auto_allowlist, &source_code)
77}
78
79fn parse_file_contents(
80 source: syn::File,
81 auto_allowlist: bool,
82 file_contents: &str,
83) -> Result<ParsedFile, ParseError> {
84 #[derive(Default)]
85 struct State {
86 auto_allowlist: bool,
87 results: Vec<Segment>,
88 extra_superclasses: Vec<Subclass>,
89 discoveries: Discoveries,
90 }
Austin Schuh6ea9bfa2023-08-06 19:05:10 -070091 let file_contents = Rc::new(file_contents.to_string());
Brian Silverman4e662aa2022-05-11 23:10:19 -070092 impl State {
93 fn parse_item(
94 &mut self,
95 item: Item,
96 mod_path: Option<RustPath>,
Austin Schuh6ea9bfa2023-08-06 19:05:10 -070097 file_contents: Rc<String>,
Brian Silverman4e662aa2022-05-11 23:10:19 -070098 ) -> Result<(), ParseError> {
99 let result = match item {
100 Item::Macro(mac)
101 if mac
102 .mac
103 .path
104 .segments
105 .last()
106 .map(|s| s.ident == "include_cpp")
107 .unwrap_or(false) =>
108 {
109 Segment::Autocxx(
110 crate::IncludeCppEngine::new_from_syn(mac.mac, file_contents)
111 .map_err(ParseError::AutocxxCodegenError)?,
112 )
113 }
114 Item::Mod(itm)
Austin Schuh6ea9bfa2023-08-06 19:05:10 -0700115 if itm.attrs.iter().any(|attr| {
116 attr.path().to_token_stream().to_string() == "cxx :: bridge"
117 }) =>
Brian Silverman4e662aa2022-05-11 23:10:19 -0700118 {
119 Segment::Cxx(CxxBridge::from(itm))
120 }
121 Item::Mod(itm) => {
James Kuszmaul850a4752024-02-14 20:27:12 -0800122 if let Some((_, items)) = itm.content {
Brian Silverman4e662aa2022-05-11 23:10:19 -0700123 let mut mod_state = State {
124 auto_allowlist: self.auto_allowlist,
125 ..Default::default()
126 };
127 let mod_path = match &mod_path {
128 None => RustPath::new_from_ident(itm.ident.clone()),
129 Some(mod_path) => mod_path.append(itm.ident.clone()),
130 };
131 for item in items {
Austin Schuh6ea9bfa2023-08-06 19:05:10 -0700132 mod_state.parse_item(
133 item,
134 Some(mod_path.clone()),
135 file_contents.clone(),
136 )?
Brian Silverman4e662aa2022-05-11 23:10:19 -0700137 }
138 self.extra_superclasses.extend(mod_state.extra_superclasses);
139 self.discoveries.extend(mod_state.discoveries);
James Kuszmaul850a4752024-02-14 20:27:12 -0800140 Segment::Mod(mod_state.results)
Brian Silverman4e662aa2022-05-11 23:10:19 -0700141 } else {
James Kuszmaul850a4752024-02-14 20:27:12 -0800142 Segment::Other
Brian Silverman4e662aa2022-05-11 23:10:19 -0700143 }
144 }
Austin Schuh6ea9bfa2023-08-06 19:05:10 -0700145 Item::Struct(ref its) => {
Brian Silverman4e662aa2022-05-11 23:10:19 -0700146 let attrs = &its.attrs;
147 let is_superclass_attr = attrs.iter().find(|attr| {
Austin Schuh6ea9bfa2023-08-06 19:05:10 -0700148 attr.path()
Brian Silverman4e662aa2022-05-11 23:10:19 -0700149 .segments
150 .last()
151 .map(|seg| seg.ident == "is_subclass" || seg.ident == SUBCLASS)
152 .unwrap_or(false)
153 });
154 if let Some(is_superclass_attr) = is_superclass_attr {
Austin Schuh6ea9bfa2023-08-06 19:05:10 -0700155 if is_superclass_attr.meta.require_path_only().is_err() {
Brian Silverman4e662aa2022-05-11 23:10:19 -0700156 let subclass = its.ident.clone();
157 let args: SubclassAttrs =
158 is_superclass_attr.parse_args().map_err(|e| {
159 ParseError::SubclassSyntax(LocatedSynError::new(
160 e,
Austin Schuh6ea9bfa2023-08-06 19:05:10 -0700161 &file_contents,
Brian Silverman4e662aa2022-05-11 23:10:19 -0700162 ))
163 })?;
164 if let Some(superclass) = args.superclass {
Austin Schuh6ea9bfa2023-08-06 19:05:10 -0700165 if !self.auto_allowlist {
166 return Err(
167 ParseError::SubclassSuperclassWithoutAutoAllowlist(
168 file_contents.to_string(),
169 proc_macro_span_to_miette_span(&its.span()),
170 ),
171 );
172 }
Brian Silverman4e662aa2022-05-11 23:10:19 -0700173 self.extra_superclasses.push(Subclass {
174 superclass,
175 subclass,
176 })
177 }
178 }
179 }
180 self.discoveries
181 .search_item(&item, mod_path)
182 .map_err(ParseError::Discovery)?;
James Kuszmaul850a4752024-02-14 20:27:12 -0800183 Segment::Other
Brian Silverman4e662aa2022-05-11 23:10:19 -0700184 }
185 _ => {
186 self.discoveries
187 .search_item(&item, mod_path)
188 .map_err(ParseError::Discovery)?;
James Kuszmaul850a4752024-02-14 20:27:12 -0800189 Segment::Other
Brian Silverman4e662aa2022-05-11 23:10:19 -0700190 }
191 };
192 self.results.push(result);
193 Ok(())
194 }
195 }
196 let mut state = State {
197 auto_allowlist,
198 ..Default::default()
199 };
200 for item in source.items {
Austin Schuh6ea9bfa2023-08-06 19:05:10 -0700201 state.parse_item(item, None, file_contents.clone())?
Brian Silverman4e662aa2022-05-11 23:10:19 -0700202 }
203 let State {
204 auto_allowlist,
205 mut results,
206 mut extra_superclasses,
207 mut discoveries,
208 } = state;
209
210 let must_handle_discovered_things = discoveries.found_rust()
211 || !extra_superclasses.is_empty()
212 || (auto_allowlist && discoveries.found_allowlist());
213
214 // We do not want to enter this 'if' block unless the above conditions are true,
215 // since we may emit errors.
216 if must_handle_discovered_things {
Austin Schuh6ea9bfa2023-08-06 19:05:10 -0700217 // If we have to handle discovered things but there was no include_cpp! macro,
218 // fake one.
219 if !results.iter().any(|seg| matches!(seg, Segment::Autocxx(_))) {
220 results.push(Segment::Autocxx(IncludeCppEngine::new_for_autodiscover()));
221 }
Brian Silverman4e662aa2022-05-11 23:10:19 -0700222 let mut autocxx_seg_iterator = results.iter_mut().filter_map(|seg| match seg {
223 Segment::Autocxx(engine) => Some(engine),
224 _ => None,
225 });
226 let our_seg = autocxx_seg_iterator.next();
227 match our_seg {
Austin Schuh6ea9bfa2023-08-06 19:05:10 -0700228 None => panic!("We should have just added a fake mod but apparently didn't"),
Brian Silverman4e662aa2022-05-11 23:10:19 -0700229 Some(engine) => {
230 engine
231 .config_mut()
232 .subclasses
233 .append(&mut extra_superclasses);
234 if auto_allowlist {
235 for cpp in discoveries.cpp_list {
236 engine
237 .config_mut()
238 .allowlist
239 .push(AllowlistEntry::Item(cpp))
240 .map_err(|_| ParseError::ConflictingAllowlist)?;
241 }
242 }
243 engine
244 .config_mut()
245 .extern_rust_funs
246 .append(&mut discoveries.extern_rust_funs);
247 engine
248 .config_mut()
249 .rust_types
250 .append(&mut discoveries.extern_rust_types);
251 }
252 }
253 if autocxx_seg_iterator.next().is_some() {
254 return Err(ParseError::MultipleModsForDynamicDiscovery);
255 }
256 }
257 let autocxx_seg_iterator = results.iter_mut().filter_map(|seg| match seg {
258 Segment::Autocxx(engine) => Some(engine),
259 _ => None,
260 });
261 for seg in autocxx_seg_iterator {
262 seg.config.confirm_complete();
263 }
264 Ok(ParsedFile(results))
265}
266
267/// A Rust file parsed by autocxx. May contain zero or more autocxx 'engines',
268/// i.e. the `IncludeCpp` class, corresponding to zero or more include_cpp
269/// macros within this file. Also contains `syn::Item` structures for all
270/// the rest of the Rust code, such that it can be reconstituted if necessary.
271pub struct ParsedFile(Vec<Segment>);
272
273#[allow(clippy::large_enum_variant)]
274enum Segment {
275 Autocxx(IncludeCppEngine),
276 Cxx(CxxBridge),
James Kuszmaul850a4752024-02-14 20:27:12 -0800277 Mod(Vec<Segment>),
278 Other,
Brian Silverman4e662aa2022-05-11 23:10:19 -0700279}
280
281pub trait CppBuildable {
282 fn generate_h_and_cxx(
283 &self,
284 cpp_codegen_options: &CppCodegenOptions,
285 ) -> Result<GeneratedCpp, cxx_gen::Error>;
286}
287
288impl ParsedFile {
289 /// Get all the autocxx `include_cpp` macros found in this file.
290 pub fn get_autocxxes(&self) -> impl Iterator<Item = &IncludeCppEngine> {
291 fn do_get_autocxxes(segments: &[Segment]) -> impl Iterator<Item = &IncludeCppEngine> {
292 segments
293 .iter()
294 .flat_map(|s| -> Box<dyn Iterator<Item = &IncludeCppEngine>> {
295 match s {
296 Segment::Autocxx(includecpp) => Box::new(std::iter::once(includecpp)),
James Kuszmaul850a4752024-02-14 20:27:12 -0800297 Segment::Mod(segments) => Box::new(do_get_autocxxes(segments)),
Brian Silverman4e662aa2022-05-11 23:10:19 -0700298 _ => Box::new(std::iter::empty()),
299 }
300 })
301 }
302
303 do_get_autocxxes(&self.0)
304 }
305
306 /// Get all the areas of Rust code which need to be built for these bindings.
307 /// A shortcut for `get_autocxxes()` then calling `get_rs_output` on each.
308 pub fn get_rs_outputs(&self) -> impl Iterator<Item = RsOutput> {
309 self.get_autocxxes().map(|autocxx| autocxx.get_rs_output())
310 }
311
312 /// Get all items which can result in C++ code
313 pub fn get_cpp_buildables(&self) -> impl Iterator<Item = &dyn CppBuildable> {
314 fn do_get_cpp_buildables(segments: &[Segment]) -> impl Iterator<Item = &dyn CppBuildable> {
315 segments
316 .iter()
317 .flat_map(|s| -> Box<dyn Iterator<Item = &dyn CppBuildable>> {
318 match s {
319 Segment::Autocxx(includecpp) => {
320 Box::new(std::iter::once(includecpp as &dyn CppBuildable))
321 }
322 Segment::Cxx(cxxbridge) => {
323 Box::new(std::iter::once(cxxbridge as &dyn CppBuildable))
324 }
James Kuszmaul850a4752024-02-14 20:27:12 -0800325 Segment::Mod(segments) => Box::new(do_get_cpp_buildables(segments)),
Brian Silverman4e662aa2022-05-11 23:10:19 -0700326 _ => Box::new(std::iter::empty()),
327 }
328 })
329 }
330
331 do_get_cpp_buildables(&self.0)
332 }
333
334 fn get_autocxxes_mut(&mut self) -> impl Iterator<Item = &mut IncludeCppEngine> {
335 fn do_get_autocxxes_mut(
336 segments: &mut [Segment],
337 ) -> impl Iterator<Item = &mut IncludeCppEngine> {
338 segments
339 .iter_mut()
340 .flat_map(|s| -> Box<dyn Iterator<Item = &mut IncludeCppEngine>> {
341 match s {
342 Segment::Autocxx(includecpp) => Box::new(std::iter::once(includecpp)),
James Kuszmaul850a4752024-02-14 20:27:12 -0800343 Segment::Mod(segments) => Box::new(do_get_autocxxes_mut(segments)),
Brian Silverman4e662aa2022-05-11 23:10:19 -0700344 _ => Box::new(std::iter::empty()),
345 }
346 })
347 }
348
349 do_get_autocxxes_mut(&mut self.0)
350 }
351
352 /// Determines the include dirs that were set for each include_cpp, so they can be
353 /// used as input to a `cc::Build`.
354 #[cfg(any(test, feature = "build"))]
355 pub(crate) fn include_dirs(&self) -> impl Iterator<Item = &PathBuf> {
356 fn do_get_include_dirs(segments: &[Segment]) -> impl Iterator<Item = &PathBuf> {
357 segments
358 .iter()
359 .flat_map(|s| -> Box<dyn Iterator<Item = &PathBuf>> {
360 match s {
361 Segment::Autocxx(includecpp) => Box::new(includecpp.include_dirs()),
James Kuszmaul850a4752024-02-14 20:27:12 -0800362 Segment::Mod(segments) => Box::new(do_get_include_dirs(segments)),
Brian Silverman4e662aa2022-05-11 23:10:19 -0700363 _ => Box::new(std::iter::empty()),
364 }
365 })
366 }
367
368 do_get_include_dirs(&self.0)
369 }
370
371 pub fn resolve_all(
372 &mut self,
373 autocxx_inc: Vec<PathBuf>,
374 extra_clang_args: &[&str],
375 dep_recorder: Option<Box<dyn RebuildDependencyRecorder>>,
Austin Schuh6ea9bfa2023-08-06 19:05:10 -0700376 codegen_options: &CodegenOptions,
Brian Silverman4e662aa2022-05-11 23:10:19 -0700377 ) -> Result<(), ParseError> {
378 let mut mods_found = HashSet::new();
379 let inner_dep_recorder: Option<Rc<dyn RebuildDependencyRecorder>> =
380 dep_recorder.map(Rc::from);
381 for include_cpp in self.get_autocxxes_mut() {
382 #[allow(clippy::manual_map)] // because of dyn shenanigans
383 let dep_recorder: Option<Box<dyn RebuildDependencyRecorder>> = match &inner_dep_recorder
384 {
385 None => None,
386 Some(inner_dep_recorder) => Some(Box::new(CompositeDepRecorder::new(
387 inner_dep_recorder.clone(),
388 ))),
389 };
390 if !mods_found.insert(include_cpp.get_mod_name()) {
391 return Err(ParseError::ConflictingModNames);
392 }
393 include_cpp
394 .generate(
395 autocxx_inc.clone(),
396 extra_clang_args,
397 dep_recorder,
Austin Schuh6ea9bfa2023-08-06 19:05:10 -0700398 codegen_options,
Brian Silverman4e662aa2022-05-11 23:10:19 -0700399 )
400 .map_err(ParseError::AutocxxCodegenError)?
401 }
402 Ok(())
403 }
404}
405
406/// Shenanigans required to share the same RebuildDependencyRecorder
407/// with all of the include_cpp instances in this one file.
408#[derive(Debug, Clone)]
409struct CompositeDepRecorder(Rc<dyn RebuildDependencyRecorder>);
410
411impl CompositeDepRecorder {
412 fn new(inner: Rc<dyn RebuildDependencyRecorder>) -> Self {
413 CompositeDepRecorder(inner)
414 }
415}
416
417impl UnwindSafe for CompositeDepRecorder {}
418
419impl RebuildDependencyRecorder for CompositeDepRecorder {
420 fn record_header_file_dependency(&self, filename: &str) {
421 self.0.record_header_file_dependency(filename);
422 }
423}