1 // Functionality that is shared between the cxx_build::bridge entry point and
2 // the cxxbridge CLI command.
3
4 mod block;
5 mod builtin;
6 mod cfg;
7 mod check;
8 pub(super) mod error;
9 mod file;
10 pub(super) mod fs;
11 mod ifndef;
12 pub(super) mod include;
13 mod names;
14 mod namespace;
15 mod nested;
16 pub(super) mod out;
17 mod write;
18
19 use self::cfg::UnsupportedCfgEvaluator;
20 use self::error::{format_err, Result};
21 use self::file::File;
22 use self::include::Include;
23 use crate::syntax::cfg::CfgExpr;
24 use crate::syntax::report::Errors;
25 use crate::syntax::{self, attrs, Types};
26 use std::collections::BTreeSet as Set;
27 use std::path::Path;
28
29 pub(super) use self::error::Error;
30
31 /// Options for C++ code generation.
32 ///
33 /// We expect options to be added over time, so this is a non-exhaustive struct.
34 /// To instantiate one you need to crate a default value and mutate those fields
35 /// that you want to modify.
36 ///
37 /// ```
38 /// # use cxx_gen::Opt;
39 /// #
40 /// let impl_annotations = r#"__attribute__((visibility("default")))"#.to_owned();
41 ///
42 /// let mut opt = Opt::default();
43 /// opt.cxx_impl_annotations = Some(impl_annotations);
44 /// ```
45 #[non_exhaustive]
46 pub struct Opt {
47 /// Any additional headers to #include. The cxxbridge tool does not parse or
48 /// even require the given paths to exist; they simply go into the generated
49 /// C++ code as #include lines.
50 pub include: Vec<Include>,
51 /// Optional annotation for implementations of C++ function wrappers that
52 /// may be exposed to Rust. You may for example need to provide
53 /// `__declspec(dllexport)` or `__attribute__((visibility("default")))` if
54 /// Rust code from one shared object or executable depends on these C++
55 /// functions in another.
56 pub cxx_impl_annotations: Option<String>,
57 /// Impl for handling conditional compilation attributes.
58 pub cfg_evaluator: Box<dyn CfgEvaluator>,
59
60 pub(super) gen_header: bool,
61 pub(super) gen_implementation: bool,
62 pub(super) allow_dot_includes: bool,
63 pub(super) doxygen: bool,
64 }
65
66 /// Logic to decide whether a conditional compilation attribute is enabled or
67 /// disabled.
68 pub trait CfgEvaluator {
69 /// A name-only attribute such as `cfg(ident)` is passed with a `value` of
70 /// None, while `cfg(key = "value")` is passed with the "value" in `value`.
eval(&self, name: &str, value: Option<&str>) -> CfgResult71 fn eval(&self, name: &str, value: Option<&str>) -> CfgResult;
72 }
73
74 /// Result of a [`CfgEvaluator`] evaluation.
75 pub enum CfgResult {
76 /// Cfg option is enabled.
77 True,
78 /// Cfg option is disabled.
79 False,
80 /// Cfg option is neither enabled nor disabled.
81 Undetermined {
82 /// Message explaining why the cfg option is undetermined.
83 msg: String,
84 },
85 }
86
87 /// Results of code generation.
88 #[derive(Default)]
89 pub struct GeneratedCode {
90 /// The bytes of a C++ header file.
91 pub header: Vec<u8>,
92 /// The bytes of a C++ implementation file (e.g. .cc, cpp etc.)
93 pub implementation: Vec<u8>,
94 }
95
96 impl Default for Opt {
default() -> Self97 fn default() -> Self {
98 Opt {
99 include: Vec::new(),
100 cxx_impl_annotations: None,
101 gen_header: true,
102 gen_implementation: true,
103 allow_dot_includes: true,
104 cfg_evaluator: Box::new(UnsupportedCfgEvaluator),
105 doxygen: false,
106 }
107 }
108 }
109
generate_from_path(path: &Path, opt: &Opt) -> GeneratedCode110 pub(super) fn generate_from_path(path: &Path, opt: &Opt) -> GeneratedCode {
111 let source = match read_to_string(path) {
112 Ok(source) => source,
113 Err(err) => format_err(path, "", err),
114 };
115 match generate_from_string(&source, opt) {
116 Ok(out) => out,
117 Err(err) => format_err(path, &source, err),
118 }
119 }
120
read_to_string(path: &Path) -> Result<String>121 fn read_to_string(path: &Path) -> Result<String> {
122 let bytes = if path == Path::new("-") {
123 fs::read_stdin()
124 } else {
125 fs::read(path)
126 }?;
127 match String::from_utf8(bytes) {
128 Ok(string) => Ok(string),
129 Err(err) => Err(Error::Utf8(path.to_owned(), err.utf8_error())),
130 }
131 }
132
generate_from_string(source: &str, opt: &Opt) -> Result<GeneratedCode>133 fn generate_from_string(source: &str, opt: &Opt) -> Result<GeneratedCode> {
134 let mut source = source;
135 if source.starts_with("#!") && !source.starts_with("#![") {
136 let shebang_end = source.find('\n').unwrap_or(source.len());
137 source = &source[shebang_end..];
138 }
139 let syntax: File = syn::parse_str(source)?;
140 generate(syntax, opt)
141 }
142
generate(syntax: File, opt: &Opt) -> Result<GeneratedCode>143 pub(super) fn generate(syntax: File, opt: &Opt) -> Result<GeneratedCode> {
144 if syntax.modules.is_empty() {
145 return Err(Error::NoBridgeMod);
146 }
147
148 let ref mut apis = Vec::new();
149 let ref mut errors = Errors::new();
150 let ref mut cfg_errors = Set::new();
151 for bridge in syntax.modules {
152 let mut cfg = CfgExpr::Unconditional;
153 attrs::parse(
154 errors,
155 bridge.attrs,
156 attrs::Parser {
157 cfg: Some(&mut cfg),
158 ignore_unrecognized: true,
159 ..Default::default()
160 },
161 );
162 if cfg::eval(errors, cfg_errors, opt.cfg_evaluator.as_ref(), &cfg) {
163 let ref namespace = bridge.namespace;
164 let trusted = bridge.unsafety.is_some();
165 apis.extend(syntax::parse_items(
166 errors,
167 bridge.content,
168 trusted,
169 namespace,
170 ));
171 }
172 }
173
174 cfg::strip(errors, cfg_errors, opt.cfg_evaluator.as_ref(), apis);
175 errors.propagate()?;
176
177 let ref types = Types::collect(errors, apis);
178 check::precheck(errors, apis, opt);
179 errors.propagate()?;
180
181 let generator = check::Generator::Build;
182 check::typecheck(errors, apis, types, generator);
183 errors.propagate()?;
184
185 // Some callers may wish to generate both header and implementation from the
186 // same token stream to avoid parsing twice. Others only need to generate
187 // one or the other.
188 let (mut header, mut implementation) = Default::default();
189 if opt.gen_header {
190 header = write::gen(apis, types, opt, true);
191 }
192 if opt.gen_implementation {
193 implementation = write::gen(apis, types, opt, false);
194 }
195 Ok(GeneratedCode {
196 header,
197 implementation,
198 })
199 }
200