blob: 16ea6250803cb5a424ca2022cdd3724846655c77 [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;
Brian Silverman4e662aa2022-05-11 23:10:19 -070024use syn::{token::Brace, Item, ItemMod};
25use 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) => {
122 if let Some((brace, items)) = itm.content {
123 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);
140 Segment::Mod(
141 mod_state.results,
142 (
143 brace,
144 ItemMod {
145 content: None,
146 ..itm
147 },
148 ),
149 )
150 } else {
151 Segment::Other(Item::Mod(itm))
152 }
153 }
Austin Schuh6ea9bfa2023-08-06 19:05:10 -0700154 Item::Struct(ref its) => {
Brian Silverman4e662aa2022-05-11 23:10:19 -0700155 let attrs = &its.attrs;
156 let is_superclass_attr = attrs.iter().find(|attr| {
Austin Schuh6ea9bfa2023-08-06 19:05:10 -0700157 attr.path()
Brian Silverman4e662aa2022-05-11 23:10:19 -0700158 .segments
159 .last()
160 .map(|seg| seg.ident == "is_subclass" || seg.ident == SUBCLASS)
161 .unwrap_or(false)
162 });
163 if let Some(is_superclass_attr) = is_superclass_attr {
Austin Schuh6ea9bfa2023-08-06 19:05:10 -0700164 if is_superclass_attr.meta.require_path_only().is_err() {
Brian Silverman4e662aa2022-05-11 23:10:19 -0700165 let subclass = its.ident.clone();
166 let args: SubclassAttrs =
167 is_superclass_attr.parse_args().map_err(|e| {
168 ParseError::SubclassSyntax(LocatedSynError::new(
169 e,
Austin Schuh6ea9bfa2023-08-06 19:05:10 -0700170 &file_contents,
Brian Silverman4e662aa2022-05-11 23:10:19 -0700171 ))
172 })?;
173 if let Some(superclass) = args.superclass {
Austin Schuh6ea9bfa2023-08-06 19:05:10 -0700174 if !self.auto_allowlist {
175 return Err(
176 ParseError::SubclassSuperclassWithoutAutoAllowlist(
177 file_contents.to_string(),
178 proc_macro_span_to_miette_span(&its.span()),
179 ),
180 );
181 }
Brian Silverman4e662aa2022-05-11 23:10:19 -0700182 self.extra_superclasses.push(Subclass {
183 superclass,
184 subclass,
185 })
186 }
187 }
188 }
189 self.discoveries
190 .search_item(&item, mod_path)
191 .map_err(ParseError::Discovery)?;
192 Segment::Other(item)
193 }
194 _ => {
195 self.discoveries
196 .search_item(&item, mod_path)
197 .map_err(ParseError::Discovery)?;
198 Segment::Other(item)
199 }
200 };
201 self.results.push(result);
202 Ok(())
203 }
204 }
205 let mut state = State {
206 auto_allowlist,
207 ..Default::default()
208 };
209 for item in source.items {
Austin Schuh6ea9bfa2023-08-06 19:05:10 -0700210 state.parse_item(item, None, file_contents.clone())?
Brian Silverman4e662aa2022-05-11 23:10:19 -0700211 }
212 let State {
213 auto_allowlist,
214 mut results,
215 mut extra_superclasses,
216 mut discoveries,
217 } = state;
218
219 let must_handle_discovered_things = discoveries.found_rust()
220 || !extra_superclasses.is_empty()
221 || (auto_allowlist && discoveries.found_allowlist());
222
223 // We do not want to enter this 'if' block unless the above conditions are true,
224 // since we may emit errors.
225 if must_handle_discovered_things {
Austin Schuh6ea9bfa2023-08-06 19:05:10 -0700226 // If we have to handle discovered things but there was no include_cpp! macro,
227 // fake one.
228 if !results.iter().any(|seg| matches!(seg, Segment::Autocxx(_))) {
229 results.push(Segment::Autocxx(IncludeCppEngine::new_for_autodiscover()));
230 }
Brian Silverman4e662aa2022-05-11 23:10:19 -0700231 let mut autocxx_seg_iterator = results.iter_mut().filter_map(|seg| match seg {
232 Segment::Autocxx(engine) => Some(engine),
233 _ => None,
234 });
235 let our_seg = autocxx_seg_iterator.next();
236 match our_seg {
Austin Schuh6ea9bfa2023-08-06 19:05:10 -0700237 None => panic!("We should have just added a fake mod but apparently didn't"),
Brian Silverman4e662aa2022-05-11 23:10:19 -0700238 Some(engine) => {
239 engine
240 .config_mut()
241 .subclasses
242 .append(&mut extra_superclasses);
243 if auto_allowlist {
244 for cpp in discoveries.cpp_list {
245 engine
246 .config_mut()
247 .allowlist
248 .push(AllowlistEntry::Item(cpp))
249 .map_err(|_| ParseError::ConflictingAllowlist)?;
250 }
251 }
252 engine
253 .config_mut()
254 .extern_rust_funs
255 .append(&mut discoveries.extern_rust_funs);
256 engine
257 .config_mut()
258 .rust_types
259 .append(&mut discoveries.extern_rust_types);
260 }
261 }
262 if autocxx_seg_iterator.next().is_some() {
263 return Err(ParseError::MultipleModsForDynamicDiscovery);
264 }
265 }
266 let autocxx_seg_iterator = results.iter_mut().filter_map(|seg| match seg {
267 Segment::Autocxx(engine) => Some(engine),
268 _ => None,
269 });
270 for seg in autocxx_seg_iterator {
271 seg.config.confirm_complete();
272 }
273 Ok(ParsedFile(results))
274}
275
276/// A Rust file parsed by autocxx. May contain zero or more autocxx 'engines',
277/// i.e. the `IncludeCpp` class, corresponding to zero or more include_cpp
278/// macros within this file. Also contains `syn::Item` structures for all
279/// the rest of the Rust code, such that it can be reconstituted if necessary.
280pub struct ParsedFile(Vec<Segment>);
281
282#[allow(clippy::large_enum_variant)]
283enum Segment {
284 Autocxx(IncludeCppEngine),
285 Cxx(CxxBridge),
286 Mod(Vec<Segment>, (Brace, ItemMod)),
287 Other(Item),
288}
289
290pub trait CppBuildable {
291 fn generate_h_and_cxx(
292 &self,
293 cpp_codegen_options: &CppCodegenOptions,
294 ) -> Result<GeneratedCpp, cxx_gen::Error>;
295}
296
297impl ParsedFile {
298 /// Get all the autocxx `include_cpp` macros found in this file.
299 pub fn get_autocxxes(&self) -> impl Iterator<Item = &IncludeCppEngine> {
300 fn do_get_autocxxes(segments: &[Segment]) -> impl Iterator<Item = &IncludeCppEngine> {
301 segments
302 .iter()
303 .flat_map(|s| -> Box<dyn Iterator<Item = &IncludeCppEngine>> {
304 match s {
305 Segment::Autocxx(includecpp) => Box::new(std::iter::once(includecpp)),
306 Segment::Mod(segments, _) => Box::new(do_get_autocxxes(segments)),
307 _ => Box::new(std::iter::empty()),
308 }
309 })
310 }
311
312 do_get_autocxxes(&self.0)
313 }
314
315 /// Get all the areas of Rust code which need to be built for these bindings.
316 /// A shortcut for `get_autocxxes()` then calling `get_rs_output` on each.
317 pub fn get_rs_outputs(&self) -> impl Iterator<Item = RsOutput> {
318 self.get_autocxxes().map(|autocxx| autocxx.get_rs_output())
319 }
320
321 /// Get all items which can result in C++ code
322 pub fn get_cpp_buildables(&self) -> impl Iterator<Item = &dyn CppBuildable> {
323 fn do_get_cpp_buildables(segments: &[Segment]) -> impl Iterator<Item = &dyn CppBuildable> {
324 segments
325 .iter()
326 .flat_map(|s| -> Box<dyn Iterator<Item = &dyn CppBuildable>> {
327 match s {
328 Segment::Autocxx(includecpp) => {
329 Box::new(std::iter::once(includecpp as &dyn CppBuildable))
330 }
331 Segment::Cxx(cxxbridge) => {
332 Box::new(std::iter::once(cxxbridge as &dyn CppBuildable))
333 }
334 Segment::Mod(segments, _) => Box::new(do_get_cpp_buildables(segments)),
335 _ => Box::new(std::iter::empty()),
336 }
337 })
338 }
339
340 do_get_cpp_buildables(&self.0)
341 }
342
343 fn get_autocxxes_mut(&mut self) -> impl Iterator<Item = &mut IncludeCppEngine> {
344 fn do_get_autocxxes_mut(
345 segments: &mut [Segment],
346 ) -> impl Iterator<Item = &mut IncludeCppEngine> {
347 segments
348 .iter_mut()
349 .flat_map(|s| -> Box<dyn Iterator<Item = &mut IncludeCppEngine>> {
350 match s {
351 Segment::Autocxx(includecpp) => Box::new(std::iter::once(includecpp)),
352 Segment::Mod(segments, _) => Box::new(do_get_autocxxes_mut(segments)),
353 _ => Box::new(std::iter::empty()),
354 }
355 })
356 }
357
358 do_get_autocxxes_mut(&mut self.0)
359 }
360
361 /// Determines the include dirs that were set for each include_cpp, so they can be
362 /// used as input to a `cc::Build`.
363 #[cfg(any(test, feature = "build"))]
364 pub(crate) fn include_dirs(&self) -> impl Iterator<Item = &PathBuf> {
365 fn do_get_include_dirs(segments: &[Segment]) -> impl Iterator<Item = &PathBuf> {
366 segments
367 .iter()
368 .flat_map(|s| -> Box<dyn Iterator<Item = &PathBuf>> {
369 match s {
370 Segment::Autocxx(includecpp) => Box::new(includecpp.include_dirs()),
371 Segment::Mod(segments, _) => Box::new(do_get_include_dirs(segments)),
372 _ => Box::new(std::iter::empty()),
373 }
374 })
375 }
376
377 do_get_include_dirs(&self.0)
378 }
379
380 pub fn resolve_all(
381 &mut self,
382 autocxx_inc: Vec<PathBuf>,
383 extra_clang_args: &[&str],
384 dep_recorder: Option<Box<dyn RebuildDependencyRecorder>>,
Austin Schuh6ea9bfa2023-08-06 19:05:10 -0700385 codegen_options: &CodegenOptions,
Brian Silverman4e662aa2022-05-11 23:10:19 -0700386 ) -> Result<(), ParseError> {
387 let mut mods_found = HashSet::new();
388 let inner_dep_recorder: Option<Rc<dyn RebuildDependencyRecorder>> =
389 dep_recorder.map(Rc::from);
390 for include_cpp in self.get_autocxxes_mut() {
391 #[allow(clippy::manual_map)] // because of dyn shenanigans
392 let dep_recorder: Option<Box<dyn RebuildDependencyRecorder>> = match &inner_dep_recorder
393 {
394 None => None,
395 Some(inner_dep_recorder) => Some(Box::new(CompositeDepRecorder::new(
396 inner_dep_recorder.clone(),
397 ))),
398 };
399 if !mods_found.insert(include_cpp.get_mod_name()) {
400 return Err(ParseError::ConflictingModNames);
401 }
402 include_cpp
403 .generate(
404 autocxx_inc.clone(),
405 extra_clang_args,
406 dep_recorder,
Austin Schuh6ea9bfa2023-08-06 19:05:10 -0700407 codegen_options,
Brian Silverman4e662aa2022-05-11 23:10:19 -0700408 )
409 .map_err(ParseError::AutocxxCodegenError)?
410 }
411 Ok(())
412 }
413}
414
415/// Shenanigans required to share the same RebuildDependencyRecorder
416/// with all of the include_cpp instances in this one file.
417#[derive(Debug, Clone)]
418struct CompositeDepRecorder(Rc<dyn RebuildDependencyRecorder>);
419
420impl CompositeDepRecorder {
421 fn new(inner: Rc<dyn RebuildDependencyRecorder>) -> Self {
422 CompositeDepRecorder(inner)
423 }
424}
425
426impl UnwindSafe for CompositeDepRecorder {}
427
428impl RebuildDependencyRecorder for CompositeDepRecorder {
429 fn record_header_file_dependency(&self, filename: &str) {
430 self.0.record_header_file_dependency(filename);
431 }
432}