blob: 86a31ea6750e43af73ff85162aa9c9ac6e07da96 [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 })
Brian Silvermanf3ec38b2022-07-06 20:43:36 -0700307 .rustfmt_bindings(log::log_enabled!(log::Level::Info))
Brian Silverman4e662aa2022-05-11 23:10:19 -0700308 .size_t_is_usize(true)
309 .enable_cxx_namespaces()
310 .generate_inline_functions(true)
311 .respect_cxx_access_specs(true)
312 .use_specific_virtual_function_receiver(true)
313 .cpp_semantic_attributes(true)
314 .represent_cxx_operators(true)
315 .use_distinct_char16_t(true)
316 .layout_tests(false); // TODO revisit later
317 for item in known_types().get_initial_blocklist() {
318 builder = builder.blocklist_item(item);
319 }
320
321 // 3. Passes allowlist and other options to the bindgen::Builder equivalent
322 // to --output-style=cxx --allowlist=<as passed in>
323 if let Some(allowlist) = self.config.bindgen_allowlist() {
324 for a in allowlist {
325 // TODO - allowlist type/functions/separately
326 builder = builder
327 .allowlist_type(&a)
328 .allowlist_function(&a)
329 .allowlist_var(&a);
330 }
331 }
332
333 log::info!(
334 "Bindgen flags would be: {}",
335 builder
336 .command_line_flags()
337 .into_iter()
338 .map(|f| format!("\"{}\"", f))
339 .join(" ")
340 );
341 builder
342 }
343
344 pub fn get_rs_filename(&self) -> String {
345 self.config.get_rs_filename()
346 }
347
348 /// Generate the Rust bindings. Call `generate` first.
349 pub fn get_rs_output(&self) -> RsOutput {
350 RsOutput {
351 config: &self.config,
352 rs: match &self.state {
353 State::NotGenerated => panic!("Generate first"),
354 State::Generated(gen_results) => gen_results.item_mod.to_token_stream(),
355 State::ParseOnly => TokenStream2::new(),
356 },
357 }
358 }
359
360 /// Returns the name of the mod which this `include_cpp!` will generate.
361 /// Can and should be used to ensure multiple mods in a file don't conflict.
362 pub fn get_mod_name(&self) -> String {
363 self.config.get_mod_name().to_string()
364 }
365
366 fn parse_bindings(&self, bindings: bindgen::Bindings) -> Result<ItemMod> {
367 // This bindings object is actually a TokenStream internally and we're wasting
368 // effort converting to and from string. We could enhance the bindgen API
369 // in future.
370 let bindings = bindings.to_string();
371 // Manually add the mod ffi {} so that we can ask syn to parse
372 // into a single construct.
373 let bindings = format!("mod bindgen {{ {} }}", bindings);
374 info!("Bindings: {}", bindings);
375 syn::parse_str::<ItemMod>(&bindings)
376 .map_err(|e| Error::BindingsParsing(LocatedSynError::new(e, &bindings)))
377 }
378
379 /// Actually examine the headers to find out what needs generating.
380 /// Most errors occur at this stage as we fail to interpret the C++
381 /// headers properly.
382 ///
383 /// See documentation for this type for flow diagrams and more details.
384 pub fn generate(
385 &mut self,
386 inc_dirs: Vec<PathBuf>,
387 extra_clang_args: &[&str],
388 dep_recorder: Option<Box<dyn RebuildDependencyRecorder>>,
389 cpp_codegen_options: &CppCodegenOptions,
390 ) -> Result<()> {
391 // If we are in parse only mode, do nothing. This is used for
392 // doc tests to ensure the parsing is valid, but we can't expect
393 // valid C++ header files or linkers to allow a complete build.
394 match self.state {
395 State::ParseOnly => return Ok(()),
396 State::NotGenerated => {}
397 State::Generated(_) => panic!("Only call generate once"),
398 }
399
400 let mod_name = self.config.get_mod_name();
401 let mut builder = self.make_bindgen_builder(&inc_dirs, extra_clang_args);
402 if let Some(dep_recorder) = dep_recorder {
403 builder = builder.parse_callbacks(Box::new(AutocxxParseCallbacks(dep_recorder)));
404 }
405 let header_contents = self.build_header();
406 self.dump_header_if_so_configured(&header_contents, &inc_dirs, extra_clang_args);
407 let header_and_prelude = format!("{}\n\n{}", known_types().get_prelude(), header_contents);
408 log::info!("Header and prelude for bindgen:\n{}", header_and_prelude);
409 builder = builder.header_contents("example.hpp", &header_and_prelude);
410
411 let bindings = builder.generate().map_err(Error::Bindgen)?;
412 let bindings = self.parse_bindings(bindings)?;
413
414 let converter = BridgeConverter::new(&self.config.inclusions, &self.config);
415
416 let conversion = converter
417 .convert(
418 bindings,
419 self.config.unsafe_policy.clone(),
420 header_contents,
421 cpp_codegen_options,
422 )
423 .map_err(Error::Conversion)?;
424 let mut items = conversion.rs;
425 let mut new_bindings: ItemMod = parse_quote! {
426 #[allow(non_snake_case)]
427 #[allow(dead_code)]
428 #[allow(non_upper_case_globals)]
429 #[allow(non_camel_case_types)]
430 mod #mod_name {
431 }
432 };
433 new_bindings.content.as_mut().unwrap().1.append(&mut items);
434 info!(
435 "New bindings:\n{}",
436 rust_pretty_printer::pretty_print(&new_bindings.to_token_stream())
437 );
438 self.state = State::Generated(Box::new(GenerationResults {
439 item_mod: new_bindings,
440 cpp: conversion.cpp,
441 inc_dirs,
442 cxxgen_header_name: conversion.cxxgen_header_name,
443 }));
444 Ok(())
445 }
446
447 /// Return the include directories used for this include_cpp invocation.
448 #[cfg(any(test, feature = "build"))]
449 fn include_dirs(&self) -> impl Iterator<Item = &PathBuf> {
450 match &self.state {
451 State::Generated(gen_results) => gen_results.inc_dirs.iter(),
452 _ => panic!("Must call generate() before include_dirs()"),
453 }
454 }
455
456 fn dump_header_if_so_configured(
457 &self,
458 header: &str,
459 inc_dirs: &[PathBuf],
460 extra_clang_args: &[&str],
461 ) {
462 if let Ok(output_path) = std::env::var("AUTOCXX_PREPROCESS") {
463 self.make_preprocessed_file(
464 &PathBuf::from(output_path),
465 header,
466 inc_dirs,
467 extra_clang_args,
468 );
469 }
470 #[cfg(feature = "reproduction_case")]
471 if let Ok(output_path) = std::env::var("AUTOCXX_REPRO_CASE") {
472 let tf = NamedTempFile::new().unwrap();
473 self.make_preprocessed_file(
474 &PathBuf::from(tf.path()),
475 header,
476 inc_dirs,
477 extra_clang_args,
478 );
479 let header = std::fs::read(tf.path()).unwrap();
480 let header = String::from_utf8_lossy(&header);
481 let output_path = PathBuf::from(output_path);
482 let config = self.config.to_token_stream().to_string();
483 let json = serde_json::json!({
484 "header": header,
485 "config": config
486 });
487 let f = File::create(&output_path).unwrap();
488 serde_json::to_writer(f, &json).unwrap();
489 }
490 }
491
492 fn make_preprocessed_file(
493 &self,
494 output_path: &Path,
495 header: &str,
496 inc_dirs: &[PathBuf],
497 extra_clang_args: &[&str],
498 ) {
499 // Include a load of system headers at the end of the preprocessed output,
500 // because we would like to be able to generate bindings from the
501 // preprocessed header, and then build those bindings. The C++ parts
502 // of those bindings might need things inside these various headers;
503 // we make sure all these definitions and declarations are inside
504 // this one header file so that the reduction process does not have
505 // to refer to local headers on the reduction machine too.
506 let suffix = ALL_KNOWN_SYSTEM_HEADERS
507 .iter()
508 .map(|hdr| format!("#include <{}>\n", hdr))
509 .join("\n");
510 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",
511 self.config, header, suffix, cxx_gen::HEADER);
512 let mut tf = NamedTempFile::new().unwrap();
513 write!(tf, "{}", input).unwrap();
514 let tp = tf.into_temp_path();
515 preprocess(&tp, &PathBuf::from(output_path), inc_dirs, extra_clang_args).unwrap();
516 }
517}
518
519/// This is a list of all the headers known to be included in generated
520/// C++ by cxx. We only use this when `AUTOCXX_PERPROCESS` is set to true,
521/// in an attempt to make the resulting preprocessed header more hermetic.
522/// We clearly should _not_ use this in any other circumstance; obviously
523/// we'd then want to add an API to cxx_gen such that we could retrieve
524/// that information from source.
525static ALL_KNOWN_SYSTEM_HEADERS: &[&str] = &[
526 "memory",
527 "string",
528 "algorithm",
529 "array",
530 "cassert",
531 "cstddef",
532 "cstdint",
533 "cstring",
534 "exception",
535 "functional",
536 "initializer_list",
537 "iterator",
538 "memory",
539 "new",
540 "stdexcept",
541 "type_traits",
542 "utility",
543 "vector",
544 "sys/types.h",
545];
546
547pub fn do_cxx_cpp_generation(
548 rs: TokenStream2,
549 cpp_codegen_options: &CppCodegenOptions,
550 cxxgen_header_name: String,
551) -> Result<CppFilePair, cxx_gen::Error> {
552 let mut opt = cxx_gen::Opt::default();
553 opt.cxx_impl_annotations = cpp_codegen_options.cxx_impl_annotations.clone();
554 let cxx_generated = cxx_gen::generate_header_and_cc(rs, &opt)?;
555 Ok(CppFilePair {
556 header: strip_system_headers(
557 cxx_generated.header,
558 cpp_codegen_options.suppress_system_headers,
559 ),
560 header_name: cxxgen_header_name,
561 implementation: Some(strip_system_headers(
562 cxx_generated.implementation,
563 cpp_codegen_options.suppress_system_headers,
564 )),
565 })
566}
567
568pub(crate) fn strip_system_headers(input: Vec<u8>, suppress_system_headers: bool) -> Vec<u8> {
569 if suppress_system_headers {
570 std::str::from_utf8(&input)
571 .unwrap()
572 .lines()
573 .filter(|l| !l.starts_with("#include <"))
574 .join("\n")
575 .as_bytes()
576 .to_vec()
577 } else {
578 input
579 }
580}
581
582impl CppBuildable for IncludeCppEngine {
583 /// Generate C++-side bindings for these APIs. Call `generate` first.
584 fn generate_h_and_cxx(
585 &self,
586 cpp_codegen_options: &CppCodegenOptions,
587 ) -> Result<GeneratedCpp, cxx_gen::Error> {
588 let mut files = Vec::new();
589 match &self.state {
590 State::ParseOnly => panic!("Cannot generate C++ in parse-only mode"),
591 State::NotGenerated => panic!("Call generate() first"),
592 State::Generated(gen_results) => {
593 let rs = gen_results.item_mod.to_token_stream();
594 files.push(do_cxx_cpp_generation(
595 rs,
596 cpp_codegen_options,
597 gen_results.cxxgen_header_name.clone(),
598 )?);
599 if let Some(cpp_file_pair) = &gen_results.cpp {
600 files.push(cpp_file_pair.clone());
601 }
602 }
603 };
604 Ok(GeneratedCpp(files))
605 }
606}
607
608/// Get clang args as if we were operating clang the same way as we operate
609/// bindgen.
610pub fn make_clang_args<'a>(
611 incs: &'a [PathBuf],
612 extra_args: &'a [&str],
613) -> impl Iterator<Item = String> + 'a {
614 // AUTOCXX_CLANG_ARGS come first so that any defaults defined there(e.g. for the `-std`
615 // argument) can be overridden by extra_args.
616 AUTOCXX_CLANG_ARGS
617 .iter()
618 .map(|s| s.to_string())
619 .chain(incs.iter().map(|i| format!("-I{}", i.to_str().unwrap())))
620 .chain(extra_args.iter().map(|s| s.to_string()))
621}
622
623/// Preprocess a file using the same options
624/// as is used by autocxx. Input: listing_path, output: preprocess_path.
625pub fn preprocess(
626 listing_path: &Path,
627 preprocess_path: &Path,
628 incs: &[PathBuf],
629 extra_clang_args: &[&str],
630) -> Result<(), std::io::Error> {
631 let mut cmd = Command::new(get_clang_path());
632 cmd.arg("-E");
633 cmd.arg("-C");
634 cmd.args(make_clang_args(incs, extra_clang_args));
635 cmd.arg(listing_path.to_str().unwrap());
636 cmd.stderr(Stdio::inherit());
637 let result = cmd.output().expect("failed to execute clang++");
638 assert!(result.status.success(), "failed to preprocess");
639 let mut file = File::create(preprocess_path)?;
640 file.write_all(&result.stdout)?;
641 Ok(())
642}
643
644/// Get the path to clang which is effective for any preprocessing
645/// operations done by autocxx.
646pub fn get_clang_path() -> String {
647 // `CLANG_PATH` is the environment variable that clang-sys uses to specify
648 // the path to Clang, so in most cases where someone is using a compiler
649 // that's not on the path, things should just work. We also check `CXX`,
650 // since some users may have set that.
651 std::env::var("CLANG_PATH")
652 .or_else(|_| std::env::var("CXX"))
653 .unwrap_or_else(|_| "clang++".to_string())
654}
655
656/// Function to generate the desired name of the header containing autocxx's
657/// extra generated C++.
658/// Newtype wrapper so we can give it a [`Default`].
659pub struct AutocxxgenHeaderNamer<'a>(pub Box<dyn 'a + Fn(String) -> String>);
660
661impl Default for AutocxxgenHeaderNamer<'static> {
662 fn default() -> Self {
663 Self(Box::new(|mod_name| format!("autocxxgen_{}.h", mod_name)))
664 }
665}
666
667impl AutocxxgenHeaderNamer<'_> {
668 fn name_header(&self, mod_name: String) -> String {
669 self.0(mod_name)
670 }
671}
672
673/// Function to generate the desired name of the header containing cxx's
674/// declarations.
675/// Newtype wrapper so we can give it a [`Default`].
676pub struct CxxgenHeaderNamer<'a>(pub Box<dyn 'a + Fn() -> String>);
677
678impl Default for CxxgenHeaderNamer<'static> {
679 fn default() -> Self {
680 Self(Box::new(|| "cxxgen.h".into()))
681 }
682}
683
684impl CxxgenHeaderNamer<'_> {
685 fn name_header(&self) -> String {
686 self.0()
687 }
688}
689
690/// Options for C++ codegen
691#[derive(Default)]
692pub struct CppCodegenOptions<'a> {
693 /// Whether to avoid generating `#include <some-system-header>`.
694 /// You may wish to do this to make a hermetic test case with no
695 /// external dependencies.
696 pub suppress_system_headers: bool,
697 /// Optionally, a prefix to go at `#include "<here>cxx.h". This is a header file from the `cxx`
698 /// crate.
699 pub path_to_cxx_h: Option<String>,
700 /// Optionally, a prefix to go at `#include "<here>cxxgen.h". This is a header file which we
701 /// generate.
702 pub path_to_cxxgen_h: Option<String>,
703 /// Optionally, a function called to determine the name that will be used
704 /// for the autocxxgen.h file.
705 /// The function is passed the name of the module generated by each `include_cpp`,
706 /// configured via `name`. These will be unique.
707 pub autocxxgen_header_namer: AutocxxgenHeaderNamer<'a>,
708 /// A function to generate the name of the cxxgen.h header that should be output.
709 pub cxxgen_header_namer: CxxgenHeaderNamer<'a>,
710 /// An annotation optionally to include on each C++ function.
711 /// For example to export the symbol from a library.
712 pub cxx_impl_annotations: Option<String>,
713}
714
715fn proc_macro_span_to_miette_span(span: &proc_macro2::Span) -> SourceSpan {
716 // A proc_macro2::Span stores its location as a byte offset. But there are
717 // no APIs to get that offset out.
718 // We could use `.start()` and `.end()` to get the line + column numbers, but it appears
719 // they're a little buggy. Hence we do this, to get the offsets directly across into
720 // miette.
721 struct Err;
722 let r: Result<(usize, usize), Err> = (|| {
723 let span_desc = format!("{:?}", span);
724 let re = Regex::new(r"(\d+)..(\d+)").unwrap();
725 let captures = re.captures(&span_desc).ok_or(Err)?;
726 let start = captures.get(1).ok_or(Err)?;
727 let start: usize = start.as_str().parse().map_err(|_| Err)?;
728 let start = start.saturating_sub(1); // proc_macro::Span offsets seem to be off-by-one
729 let end = captures.get(2).ok_or(Err)?;
730 let end: usize = end.as_str().parse().map_err(|_| Err)?;
731 let end = end.saturating_sub(1); // proc_macro::Span offsets seem to be off-by-one
732 Ok((start, end.saturating_sub(start)))
733 })();
734 let (start, end) = r.unwrap_or((0, 0));
735 SourceSpan::new(SourceOffset::from(start), SourceOffset::from(end))
736}