blob: 4edc4a461ff6922cc7e641c5d5d1b5b9fc511563 [file] [log] [blame]
Brian Silverman4e662aa2022-05-11 23:10:19 -07001//! The core of the `autocxx` engine, used by both the
2//! `autocxx_macro` and also code generators (e.g. `autocxx_build`).
3//! See [IncludeCppEngine] for general description of how this engine works.
4
5// Copyright 2020 Google LLC
6//
7// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
8// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
9// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
10// option. This file may not be copied, modified, or distributed
11// except according to those terms.
12
13// This feature=nightly could be set by build.rs, but since we only care
14// about it for docs, we ask docs.rs to set it in the Cargo.toml.
15#![cfg_attr(feature = "nightly", feature(doc_cfg))]
16#![forbid(unsafe_code)]
17
18mod ast_discoverer;
19mod conversion;
20mod cxxbridge;
21mod known_types;
22mod output_generators;
23mod parse_callbacks;
24mod parse_file;
25mod rust_pretty_printer;
26mod types;
27
28#[cfg(any(test, feature = "build"))]
29mod builder;
30
31use autocxx_parser::{IncludeCppConfig, UnsafePolicy};
32use conversion::BridgeConverter;
33use miette::{SourceOffset, SourceSpan};
34use parse_callbacks::AutocxxParseCallbacks;
35use parse_file::CppBuildable;
36use proc_macro2::TokenStream as TokenStream2;
37use regex::Regex;
38use std::path::PathBuf;
39use std::{
40 fs::File,
41 io::prelude::*,
42 path::Path,
43 process::{Command, Stdio},
44};
45use tempfile::NamedTempFile;
46
47use quote::ToTokens;
48use syn::Result as ParseResult;
49use syn::{
50 parse::{Parse, ParseStream},
51 parse_quote, ItemMod, Macro,
52};
53use thiserror::Error;
54
55use itertools::{join, Itertools};
56use known_types::known_types;
57use log::info;
58use miette::Diagnostic;
59
60/// We use a forked version of bindgen - for now.
61/// We hope to unfork.
62use autocxx_bindgen as bindgen;
63
64#[cfg(any(test, feature = "build"))]
65pub use builder::{
66 Builder, BuilderBuild, BuilderContext, BuilderError, BuilderResult, BuilderSuccess,
67};
68pub use output_generators::{generate_rs_archive, generate_rs_single, RsOutput};
69pub use parse_file::{parse_file, ParseError, ParsedFile};
70
71pub use cxx_gen::HEADER;
72
73#[derive(Clone)]
74/// Some C++ content which should be written to disk and built.
75pub struct CppFilePair {
76 /// Declarations to go into a header file.
77 pub header: Vec<u8>,
78 /// Implementations to go into a .cpp file.
79 pub implementation: Option<Vec<u8>>,
80 /// The name which should be used for the header file
81 /// (important as it may be `#include`d elsewhere)
82 pub header_name: String,
83}
84
85/// All generated C++ content which should be written to disk.
86pub struct GeneratedCpp(pub Vec<CppFilePair>);
87
88/// A [`syn::Error`] which also implements [`miette::Diagnostic`] so can be pretty-printed
89/// to show the affected span of code.
90#[derive(Error, Debug, Diagnostic)]
91#[error("{err}")]
92pub struct LocatedSynError {
93 err: syn::Error,
94 #[source_code]
95 file: String,
96 #[label("error here")]
97 span: SourceSpan,
98}
99
100impl LocatedSynError {
101 fn new(err: syn::Error, file: &str) -> Self {
102 let span = proc_macro_span_to_miette_span(&err.span());
103 Self {
104 err,
105 file: file.to_string(),
106 span,
107 }
108 }
109}
110
111/// Errors which may occur in generating bindings for these C++
112/// functions.
113#[derive(Debug, Error, Diagnostic)]
114pub enum Error {
115 #[error("Bindgen was unable to generate the initial .rs bindings for this file. This may indicate a parsing problem with the C++ headers.")]
116 Bindgen(()),
117 #[error(transparent)]
118 #[diagnostic(transparent)]
119 MacroParsing(LocatedSynError),
120 #[error(transparent)]
121 #[diagnostic(transparent)]
122 BindingsParsing(LocatedSynError),
123 #[error("no C++ include directory was provided.")]
124 NoAutoCxxInc,
125 #[error(transparent)]
126 Conversion(conversion::ConvertError),
127}
128
129/// Result type.
130pub type Result<T, E = Error> = std::result::Result<T, E>;
131
132struct GenerationResults {
133 item_mod: ItemMod,
134 cpp: Option<CppFilePair>,
135 #[allow(dead_code)]
136 inc_dirs: Vec<PathBuf>,
137 cxxgen_header_name: String,
138}
139enum State {
140 NotGenerated,
141 ParseOnly,
142 Generated(Box<GenerationResults>),
143}
144
145const AUTOCXX_CLANG_ARGS: &[&str; 4] = &["-x", "c++", "-std=c++14", "-DBINDGEN"];
146
147/// Implement to learn of header files which get included
148/// by this build process, such that your build system can choose
149/// to rerun the build process if any such file changes in future.
150pub trait RebuildDependencyRecorder: std::fmt::Debug {
151 /// Records that this autocxx build depends on the given
152 /// header file. Full paths will be provided.
153 fn record_header_file_dependency(&self, filename: &str);
154}
155
156#[cfg_attr(doc, aquamarine::aquamarine)]
157/// Core of the autocxx engine.
158///
159/// The basic idea is this. We will run `bindgen` which will spit
160/// out a ton of Rust code corresponding to all the types and functions
161/// defined in C++. We'll then post-process that bindgen output
162/// into a form suitable for ingestion by `cxx`.
163/// (It's the `BridgeConverter` mod which does that.)
164/// Along the way, the `bridge_converter` might tell us of additional
165/// C++ code which we should generate, e.g. wrappers to move things
166/// into and out of `UniquePtr`s.
167///
168/// ```mermaid
169/// flowchart TB
170/// s[(C++ headers)]
171/// s --> lc
172/// rss[(.rs input)]
173/// rss --> parser
174/// parser --> include_cpp_conf
175/// cpp_output[(C++ output)]
176/// rs_output[(.rs output)]
177/// subgraph autocxx[autocxx_engine]
178/// parser[File parser]
179/// subgraph bindgen[autocxx_bindgen]
180/// lc[libclang parse]
181/// bir(bindgen IR)
182/// lc --> bir
183/// end
184/// bgo(bindgen generated bindings)
185/// bir --> bgo
186/// include_cpp_conf(Config from include_cpp)
187/// syn[Parse with syn]
188/// bgo --> syn
189/// conv[['conversion' mod: see below]]
190/// syn --> conv
191/// rsgen(Generated .rs TokenStream)
192/// conv --> rsgen
193/// subgraph cxx_gen
194/// cxx_codegen[cxx_gen C++ codegen]
195/// end
196/// rsgen --> cxx_codegen
197/// end
198/// conv -- autocxx C++ codegen --> cpp_output
199/// rsgen -- autocxx .rs codegen --> rs_output
200/// cxx_codegen -- cxx C++ codegen --> cpp_output
201/// subgraph rustc [rustc build]
202/// subgraph autocxx_macro
203/// include_cpp[autocxx include_cpp macro]
204/// end
205/// subgraph cxx
206/// cxxm[cxx procedural macro]
207/// end
208/// comprs(Fully expanded Rust code)
209/// end
210/// rs_output-. included .->include_cpp
211/// include_cpp --> cxxm
212/// cxxm --> comprs
213/// rss --> rustc
214/// include_cpp_conf -. used to configure .-> bindgen
215/// include_cpp_conf --> conv
216/// link[linker]
217/// cpp_output --> link
218/// comprs --> link
219/// ```
220///
221/// Here's a zoomed-in view of the "conversion" part:
222///
223/// ```mermaid
224/// flowchart TB
225/// syn[(syn parse)]
226/// apis(Unanalyzed APIs)
227/// subgraph parse
228/// syn ==> parse_bindgen
229/// end
230/// parse_bindgen ==> apis
231/// subgraph analysis
232/// typedef[typedef analysis]
233/// pod[POD analysis]
234/// apis ==> typedef
235/// typedef ==> pod
236/// podapis(APIs with POD analysis)
237/// pod ==> podapis
238/// fun[Function materialization analysis]
239/// podapis ==> fun
240/// funapis(APIs with function analysis)
241/// fun ==> funapis
242/// gc[Garbage collection]
243/// funapis ==> gc
244/// ctypes[C int analysis]
245/// gc ==> ctypes
246/// ctypes ==> finalapis
247/// end
248/// finalapis(Analyzed APIs)
249/// codegenrs(.rs codegen)
250/// codegencpp(.cpp codegen)
251/// finalapis ==> codegenrs
252/// finalapis ==> codegencpp
253/// ```
254pub struct IncludeCppEngine {
255 config: IncludeCppConfig,
256 state: State,
257}
258
259impl Parse for IncludeCppEngine {
260 fn parse(input: ParseStream) -> ParseResult<Self> {
261 let config = input.parse::<IncludeCppConfig>()?;
262 let state = if config.parse_only {
263 State::ParseOnly
264 } else {
265 State::NotGenerated
266 };
267 Ok(Self { config, state })
268 }
269}
270
271impl IncludeCppEngine {
272 pub fn new_from_syn(mac: Macro, file_contents: &str) -> Result<Self> {
273 mac.parse_body::<IncludeCppEngine>()
274 .map_err(|e| Error::MacroParsing(LocatedSynError::new(e, file_contents)))
275 }
276
277 pub fn config_mut(&mut self) -> &mut IncludeCppConfig {
278 assert!(
279 matches!(self.state, State::NotGenerated),
280 "Can't alter config after generation commenced"
281 );
282 &mut self.config
283 }
284
285 fn build_header(&self) -> String {
286 join(
287 self.config
288 .inclusions
289 .iter()
290 .map(|path| format!("#include \"{}\"\n", path)),
291 "",
292 )
293 }
294
295 fn make_bindgen_builder(
296 &self,
297 inc_dirs: &[PathBuf],
298 extra_clang_args: &[&str],
299 ) -> bindgen::Builder {
300 let mut builder = bindgen::builder()
301 .clang_args(make_clang_args(inc_dirs, extra_clang_args))
302 .derive_copy(false)
303 .derive_debug(false)
304 .default_enum_style(bindgen::EnumVariation::Rust {
305 non_exhaustive: false,
306 })
307 .size_t_is_usize(true)
308 .enable_cxx_namespaces()
309 .generate_inline_functions(true)
310 .respect_cxx_access_specs(true)
311 .use_specific_virtual_function_receiver(true)
312 .cpp_semantic_attributes(true)
313 .represent_cxx_operators(true)
314 .use_distinct_char16_t(true)
315 .layout_tests(false); // TODO revisit later
316 for item in known_types().get_initial_blocklist() {
317 builder = builder.blocklist_item(item);
318 }
319
320 // 3. Passes allowlist and other options to the bindgen::Builder equivalent
321 // to --output-style=cxx --allowlist=<as passed in>
322 if let Some(allowlist) = self.config.bindgen_allowlist() {
323 for a in allowlist {
324 // TODO - allowlist type/functions/separately
325 builder = builder
326 .allowlist_type(&a)
327 .allowlist_function(&a)
328 .allowlist_var(&a);
329 }
330 }
331
332 log::info!(
333 "Bindgen flags would be: {}",
334 builder
335 .command_line_flags()
336 .into_iter()
337 .map(|f| format!("\"{}\"", f))
338 .join(" ")
339 );
340 builder
341 }
342
343 pub fn get_rs_filename(&self) -> String {
344 self.config.get_rs_filename()
345 }
346
347 /// Generate the Rust bindings. Call `generate` first.
348 pub fn get_rs_output(&self) -> RsOutput {
349 RsOutput {
350 config: &self.config,
351 rs: match &self.state {
352 State::NotGenerated => panic!("Generate first"),
353 State::Generated(gen_results) => gen_results.item_mod.to_token_stream(),
354 State::ParseOnly => TokenStream2::new(),
355 },
356 }
357 }
358
359 /// Returns the name of the mod which this `include_cpp!` will generate.
360 /// Can and should be used to ensure multiple mods in a file don't conflict.
361 pub fn get_mod_name(&self) -> String {
362 self.config.get_mod_name().to_string()
363 }
364
365 fn parse_bindings(&self, bindings: bindgen::Bindings) -> Result<ItemMod> {
366 // This bindings object is actually a TokenStream internally and we're wasting
367 // effort converting to and from string. We could enhance the bindgen API
368 // in future.
369 let bindings = bindings.to_string();
370 // Manually add the mod ffi {} so that we can ask syn to parse
371 // into a single construct.
372 let bindings = format!("mod bindgen {{ {} }}", bindings);
373 info!("Bindings: {}", bindings);
374 syn::parse_str::<ItemMod>(&bindings)
375 .map_err(|e| Error::BindingsParsing(LocatedSynError::new(e, &bindings)))
376 }
377
378 /// Actually examine the headers to find out what needs generating.
379 /// Most errors occur at this stage as we fail to interpret the C++
380 /// headers properly.
381 ///
382 /// See documentation for this type for flow diagrams and more details.
383 pub fn generate(
384 &mut self,
385 inc_dirs: Vec<PathBuf>,
386 extra_clang_args: &[&str],
387 dep_recorder: Option<Box<dyn RebuildDependencyRecorder>>,
388 cpp_codegen_options: &CppCodegenOptions,
389 ) -> Result<()> {
390 // If we are in parse only mode, do nothing. This is used for
391 // doc tests to ensure the parsing is valid, but we can't expect
392 // valid C++ header files or linkers to allow a complete build.
393 match self.state {
394 State::ParseOnly => return Ok(()),
395 State::NotGenerated => {}
396 State::Generated(_) => panic!("Only call generate once"),
397 }
398
399 let mod_name = self.config.get_mod_name();
400 let mut builder = self.make_bindgen_builder(&inc_dirs, extra_clang_args);
401 if let Some(dep_recorder) = dep_recorder {
402 builder = builder.parse_callbacks(Box::new(AutocxxParseCallbacks(dep_recorder)));
403 }
404 let header_contents = self.build_header();
405 self.dump_header_if_so_configured(&header_contents, &inc_dirs, extra_clang_args);
406 let header_and_prelude = format!("{}\n\n{}", known_types().get_prelude(), header_contents);
407 log::info!("Header and prelude for bindgen:\n{}", header_and_prelude);
408 builder = builder.header_contents("example.hpp", &header_and_prelude);
409
410 let bindings = builder.generate().map_err(Error::Bindgen)?;
411 let bindings = self.parse_bindings(bindings)?;
412
413 let converter = BridgeConverter::new(&self.config.inclusions, &self.config);
414
415 let conversion = converter
416 .convert(
417 bindings,
418 self.config.unsafe_policy.clone(),
419 header_contents,
420 cpp_codegen_options,
421 )
422 .map_err(Error::Conversion)?;
423 let mut items = conversion.rs;
424 let mut new_bindings: ItemMod = parse_quote! {
425 #[allow(non_snake_case)]
426 #[allow(dead_code)]
427 #[allow(non_upper_case_globals)]
428 #[allow(non_camel_case_types)]
429 mod #mod_name {
430 }
431 };
432 new_bindings.content.as_mut().unwrap().1.append(&mut items);
433 info!(
434 "New bindings:\n{}",
435 rust_pretty_printer::pretty_print(&new_bindings.to_token_stream())
436 );
437 self.state = State::Generated(Box::new(GenerationResults {
438 item_mod: new_bindings,
439 cpp: conversion.cpp,
440 inc_dirs,
441 cxxgen_header_name: conversion.cxxgen_header_name,
442 }));
443 Ok(())
444 }
445
446 /// Return the include directories used for this include_cpp invocation.
447 #[cfg(any(test, feature = "build"))]
448 fn include_dirs(&self) -> impl Iterator<Item = &PathBuf> {
449 match &self.state {
450 State::Generated(gen_results) => gen_results.inc_dirs.iter(),
451 _ => panic!("Must call generate() before include_dirs()"),
452 }
453 }
454
455 fn dump_header_if_so_configured(
456 &self,
457 header: &str,
458 inc_dirs: &[PathBuf],
459 extra_clang_args: &[&str],
460 ) {
461 if let Ok(output_path) = std::env::var("AUTOCXX_PREPROCESS") {
462 self.make_preprocessed_file(
463 &PathBuf::from(output_path),
464 header,
465 inc_dirs,
466 extra_clang_args,
467 );
468 }
469 #[cfg(feature = "reproduction_case")]
470 if let Ok(output_path) = std::env::var("AUTOCXX_REPRO_CASE") {
471 let tf = NamedTempFile::new().unwrap();
472 self.make_preprocessed_file(
473 &PathBuf::from(tf.path()),
474 header,
475 inc_dirs,
476 extra_clang_args,
477 );
478 let header = std::fs::read(tf.path()).unwrap();
479 let header = String::from_utf8_lossy(&header);
480 let output_path = PathBuf::from(output_path);
481 let config = self.config.to_token_stream().to_string();
482 let json = serde_json::json!({
483 "header": header,
484 "config": config
485 });
486 let f = File::create(&output_path).unwrap();
487 serde_json::to_writer(f, &json).unwrap();
488 }
489 }
490
491 fn make_preprocessed_file(
492 &self,
493 output_path: &Path,
494 header: &str,
495 inc_dirs: &[PathBuf],
496 extra_clang_args: &[&str],
497 ) {
498 // Include a load of system headers at the end of the preprocessed output,
499 // because we would like to be able to generate bindings from the
500 // preprocessed header, and then build those bindings. The C++ parts
501 // of those bindings might need things inside these various headers;
502 // we make sure all these definitions and declarations are inside
503 // this one header file so that the reduction process does not have
504 // to refer to local headers on the reduction machine too.
505 let suffix = ALL_KNOWN_SYSTEM_HEADERS
506 .iter()
507 .map(|hdr| format!("#include <{}>\n", hdr))
508 .join("\n");
509 let input = format!("/*\nautocxx config:\n\n{:?}\n\nend autocxx config.\nautocxx preprocessed input:\n*/\n\n{}\n\n/* autocxx: extra headers added below for completeness. */\n\n{}\n{}\n",
510 self.config, header, suffix, cxx_gen::HEADER);
511 let mut tf = NamedTempFile::new().unwrap();
512 write!(tf, "{}", input).unwrap();
513 let tp = tf.into_temp_path();
514 preprocess(&tp, &PathBuf::from(output_path), inc_dirs, extra_clang_args).unwrap();
515 }
516}
517
518/// This is a list of all the headers known to be included in generated
519/// C++ by cxx. We only use this when `AUTOCXX_PERPROCESS` is set to true,
520/// in an attempt to make the resulting preprocessed header more hermetic.
521/// We clearly should _not_ use this in any other circumstance; obviously
522/// we'd then want to add an API to cxx_gen such that we could retrieve
523/// that information from source.
524static ALL_KNOWN_SYSTEM_HEADERS: &[&str] = &[
525 "memory",
526 "string",
527 "algorithm",
528 "array",
529 "cassert",
530 "cstddef",
531 "cstdint",
532 "cstring",
533 "exception",
534 "functional",
535 "initializer_list",
536 "iterator",
537 "memory",
538 "new",
539 "stdexcept",
540 "type_traits",
541 "utility",
542 "vector",
543 "sys/types.h",
544];
545
546pub fn do_cxx_cpp_generation(
547 rs: TokenStream2,
548 cpp_codegen_options: &CppCodegenOptions,
549 cxxgen_header_name: String,
550) -> Result<CppFilePair, cxx_gen::Error> {
551 let mut opt = cxx_gen::Opt::default();
552 opt.cxx_impl_annotations = cpp_codegen_options.cxx_impl_annotations.clone();
553 let cxx_generated = cxx_gen::generate_header_and_cc(rs, &opt)?;
554 Ok(CppFilePair {
555 header: strip_system_headers(
556 cxx_generated.header,
557 cpp_codegen_options.suppress_system_headers,
558 ),
559 header_name: cxxgen_header_name,
560 implementation: Some(strip_system_headers(
561 cxx_generated.implementation,
562 cpp_codegen_options.suppress_system_headers,
563 )),
564 })
565}
566
567pub(crate) fn strip_system_headers(input: Vec<u8>, suppress_system_headers: bool) -> Vec<u8> {
568 if suppress_system_headers {
569 std::str::from_utf8(&input)
570 .unwrap()
571 .lines()
572 .filter(|l| !l.starts_with("#include <"))
573 .join("\n")
574 .as_bytes()
575 .to_vec()
576 } else {
577 input
578 }
579}
580
581impl CppBuildable for IncludeCppEngine {
582 /// Generate C++-side bindings for these APIs. Call `generate` first.
583 fn generate_h_and_cxx(
584 &self,
585 cpp_codegen_options: &CppCodegenOptions,
586 ) -> Result<GeneratedCpp, cxx_gen::Error> {
587 let mut files = Vec::new();
588 match &self.state {
589 State::ParseOnly => panic!("Cannot generate C++ in parse-only mode"),
590 State::NotGenerated => panic!("Call generate() first"),
591 State::Generated(gen_results) => {
592 let rs = gen_results.item_mod.to_token_stream();
593 files.push(do_cxx_cpp_generation(
594 rs,
595 cpp_codegen_options,
596 gen_results.cxxgen_header_name.clone(),
597 )?);
598 if let Some(cpp_file_pair) = &gen_results.cpp {
599 files.push(cpp_file_pair.clone());
600 }
601 }
602 };
603 Ok(GeneratedCpp(files))
604 }
605}
606
607/// Get clang args as if we were operating clang the same way as we operate
608/// bindgen.
609pub fn make_clang_args<'a>(
610 incs: &'a [PathBuf],
611 extra_args: &'a [&str],
612) -> impl Iterator<Item = String> + 'a {
613 // AUTOCXX_CLANG_ARGS come first so that any defaults defined there(e.g. for the `-std`
614 // argument) can be overridden by extra_args.
615 AUTOCXX_CLANG_ARGS
616 .iter()
617 .map(|s| s.to_string())
618 .chain(incs.iter().map(|i| format!("-I{}", i.to_str().unwrap())))
619 .chain(extra_args.iter().map(|s| s.to_string()))
620}
621
622/// Preprocess a file using the same options
623/// as is used by autocxx. Input: listing_path, output: preprocess_path.
624pub fn preprocess(
625 listing_path: &Path,
626 preprocess_path: &Path,
627 incs: &[PathBuf],
628 extra_clang_args: &[&str],
629) -> Result<(), std::io::Error> {
630 let mut cmd = Command::new(get_clang_path());
631 cmd.arg("-E");
632 cmd.arg("-C");
633 cmd.args(make_clang_args(incs, extra_clang_args));
634 cmd.arg(listing_path.to_str().unwrap());
635 cmd.stderr(Stdio::inherit());
636 let result = cmd.output().expect("failed to execute clang++");
637 assert!(result.status.success(), "failed to preprocess");
638 let mut file = File::create(preprocess_path)?;
639 file.write_all(&result.stdout)?;
640 Ok(())
641}
642
643/// Get the path to clang which is effective for any preprocessing
644/// operations done by autocxx.
645pub fn get_clang_path() -> String {
646 // `CLANG_PATH` is the environment variable that clang-sys uses to specify
647 // the path to Clang, so in most cases where someone is using a compiler
648 // that's not on the path, things should just work. We also check `CXX`,
649 // since some users may have set that.
650 std::env::var("CLANG_PATH")
651 .or_else(|_| std::env::var("CXX"))
652 .unwrap_or_else(|_| "clang++".to_string())
653}
654
655/// Function to generate the desired name of the header containing autocxx's
656/// extra generated C++.
657/// Newtype wrapper so we can give it a [`Default`].
658pub struct AutocxxgenHeaderNamer<'a>(pub Box<dyn 'a + Fn(String) -> String>);
659
660impl Default for AutocxxgenHeaderNamer<'static> {
661 fn default() -> Self {
662 Self(Box::new(|mod_name| format!("autocxxgen_{}.h", mod_name)))
663 }
664}
665
666impl AutocxxgenHeaderNamer<'_> {
667 fn name_header(&self, mod_name: String) -> String {
668 self.0(mod_name)
669 }
670}
671
672/// Function to generate the desired name of the header containing cxx's
673/// declarations.
674/// Newtype wrapper so we can give it a [`Default`].
675pub struct CxxgenHeaderNamer<'a>(pub Box<dyn 'a + Fn() -> String>);
676
677impl Default for CxxgenHeaderNamer<'static> {
678 fn default() -> Self {
679 Self(Box::new(|| "cxxgen.h".into()))
680 }
681}
682
683impl CxxgenHeaderNamer<'_> {
684 fn name_header(&self) -> String {
685 self.0()
686 }
687}
688
689/// Options for C++ codegen
690#[derive(Default)]
691pub struct CppCodegenOptions<'a> {
692 /// Whether to avoid generating `#include <some-system-header>`.
693 /// You may wish to do this to make a hermetic test case with no
694 /// external dependencies.
695 pub suppress_system_headers: bool,
696 /// Optionally, a prefix to go at `#include "<here>cxx.h". This is a header file from the `cxx`
697 /// crate.
698 pub path_to_cxx_h: Option<String>,
699 /// Optionally, a prefix to go at `#include "<here>cxxgen.h". This is a header file which we
700 /// generate.
701 pub path_to_cxxgen_h: Option<String>,
702 /// Optionally, a function called to determine the name that will be used
703 /// for the autocxxgen.h file.
704 /// The function is passed the name of the module generated by each `include_cpp`,
705 /// configured via `name`. These will be unique.
706 pub autocxxgen_header_namer: AutocxxgenHeaderNamer<'a>,
707 /// A function to generate the name of the cxxgen.h header that should be output.
708 pub cxxgen_header_namer: CxxgenHeaderNamer<'a>,
709 /// An annotation optionally to include on each C++ function.
710 /// For example to export the symbol from a library.
711 pub cxx_impl_annotations: Option<String>,
712}
713
714fn proc_macro_span_to_miette_span(span: &proc_macro2::Span) -> SourceSpan {
715 // A proc_macro2::Span stores its location as a byte offset. But there are
716 // no APIs to get that offset out.
717 // We could use `.start()` and `.end()` to get the line + column numbers, but it appears
718 // they're a little buggy. Hence we do this, to get the offsets directly across into
719 // miette.
720 struct Err;
721 let r: Result<(usize, usize), Err> = (|| {
722 let span_desc = format!("{:?}", span);
723 let re = Regex::new(r"(\d+)..(\d+)").unwrap();
724 let captures = re.captures(&span_desc).ok_or(Err)?;
725 let start = captures.get(1).ok_or(Err)?;
726 let start: usize = start.as_str().parse().map_err(|_| Err)?;
727 let start = start.saturating_sub(1); // proc_macro::Span offsets seem to be off-by-one
728 let end = captures.get(2).ok_or(Err)?;
729 let end: usize = end.as_str().parse().map_err(|_| Err)?;
730 let end = end.saturating_sub(1); // proc_macro::Span offsets seem to be off-by-one
731 Ok((start, end.saturating_sub(start)))
732 })();
733 let (start, end) = r.unwrap_or((0, 0));
734 SourceSpan::new(SourceOffset::from(start), SourceOffset::from(end))
735}