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