autocxx_engine/
builder.rs1use autocxx_parser::file_locations::FileLocationStrategy;
10use miette::Diagnostic;
11use thiserror::Error;
12
13use crate::{generate_rs_single, CodegenOptions};
14use crate::{get_cxx_header_bytes, CppCodegenOptions, ParseError, RebuildDependencyRecorder};
15use std::ffi::OsStr;
16use std::ffi::OsString;
17use std::fs::File;
18use std::io::Write;
19use std::marker::PhantomData;
20use std::path::{Path, PathBuf};
21
22#[derive(Error, Diagnostic, Debug)]
25#[cfg_attr(feature = "nightly", doc(cfg(feature = "build")))]
26pub enum BuilderError {
27 #[error("cxx couldn't handle our generated bindings - could be a bug in autocxx: {0}")]
28 InvalidCxx(cxx_gen::Error),
29 #[error(transparent)]
30 #[diagnostic(transparent)]
31 ParseError(ParseError),
32 #[error("we couldn't write the generated code to disk at {1}: {0}")]
33 FileWriteFail(std::io::Error, PathBuf),
34 #[error("no include_cpp! macro was found")]
35 NoIncludeCxxMacrosFound,
36 #[error("could not create a directory {1}: {0}")]
37 UnableToCreateDirectory(std::io::Error, PathBuf),
38}
39
40#[cfg_attr(feature = "nightly", doc(cfg(feature = "build")))]
41pub type BuilderBuild = cc::Build;
42
43#[cfg_attr(feature = "nightly", doc(cfg(feature = "build")))]
46pub struct BuilderSuccess(pub BuilderBuild, pub Vec<PathBuf>, pub Vec<PathBuf>);
47
48#[cfg_attr(feature = "nightly", doc(cfg(feature = "build")))]
50pub type BuilderResult = Result<BuilderSuccess, BuilderError>;
51
52#[cfg_attr(feature = "nightly", doc(cfg(feature = "build")))]
55pub trait BuilderContext {
56 fn setup() {}
59
60 fn get_dependency_recorder() -> Option<Box<dyn RebuildDependencyRecorder>>;
62}
63
64#[cfg_attr(feature = "nightly", doc(cfg(feature = "build")))]
77pub struct Builder<'a, BuilderContext> {
78 rs_file: PathBuf,
79 autocxx_incs: Vec<OsString>,
80 extra_clang_args: Vec<String>,
81 dependency_recorder: Option<Box<dyn RebuildDependencyRecorder>>,
82 custom_gendir: Option<PathBuf>,
83 auto_allowlist: bool,
84 codegen_options: CodegenOptions<'a>,
85 ctx: PhantomData<BuilderContext>,
93}
94
95impl<CTX: BuilderContext> Builder<'_, CTX> {
96 pub fn new(
105 rs_file: impl AsRef<Path>,
106 autocxx_incs: impl IntoIterator<Item = impl AsRef<OsStr>>,
107 ) -> Self {
108 CTX::setup();
109 Self {
110 rs_file: rs_file.as_ref().to_path_buf(),
111 autocxx_incs: autocxx_incs
112 .into_iter()
113 .map(|s| s.as_ref().to_os_string())
114 .collect(),
115 extra_clang_args: Vec::new(),
116 dependency_recorder: CTX::get_dependency_recorder(),
117 custom_gendir: None,
118 auto_allowlist: false,
119 codegen_options: CodegenOptions::default(),
120 ctx: PhantomData,
121 }
122 }
123
124 pub fn extra_clang_args(mut self, extra_clang_args: &[&str]) -> Self {
128 self.extra_clang_args = extra_clang_args.iter().map(|s| s.to_string()).collect();
129 self
130 }
131
132 pub fn custom_gendir(mut self, custom_gendir: PathBuf) -> Self {
134 self.custom_gendir = Some(custom_gendir);
135 self
136 }
137
138 pub fn cpp_codegen_options<F>(mut self, modifier: F) -> Self
140 where
141 F: FnOnce(&mut CppCodegenOptions),
142 {
143 modifier(&mut self.codegen_options.cpp_codegen_options);
144 self
145 }
146
147 pub fn auto_allowlist(mut self, do_it: bool) -> Self {
162 self.auto_allowlist = do_it;
163 self
164 }
165
166 #[doc(hidden)]
167 pub fn force_wrapper_generation(mut self, do_it: bool) -> Self {
171 self.codegen_options.force_wrapper_gen = do_it;
172 self
173 }
174
175 pub fn suppress_system_headers(mut self, do_it: bool) -> Self {
181 self.codegen_options
182 .cpp_codegen_options
183 .suppress_system_headers = do_it;
184 self
185 }
186
187 pub fn cxx_impl_annotations(mut self, cxx_impl_annotations: Option<String>) -> Self {
190 self.codegen_options
191 .cpp_codegen_options
192 .cxx_impl_annotations = cxx_impl_annotations;
193 self
194 }
195
196 pub fn build(self) -> Result<BuilderBuild, BuilderError> {
215 self.build_listing_files().map(|r| r.0)
216 }
217
218 pub fn build_listing_files(self) -> Result<BuilderSuccess, BuilderError> {
222 let clang_args = &self
223 .extra_clang_args
224 .iter()
225 .map(|s| &s[..])
226 .collect::<Vec<_>>();
227 rust_version_check();
228 let gen_location_strategy = match self.custom_gendir {
229 None => FileLocationStrategy::new(),
230 Some(custom_dir) => FileLocationStrategy::Custom(custom_dir),
231 };
232 let incdir = gen_location_strategy.get_include_dir();
233 ensure_created(&incdir)?;
234 let cxxdir = gen_location_strategy.get_cxx_dir();
235 ensure_created(&cxxdir)?;
236 let rsdir = gen_location_strategy.get_rs_dir();
237 ensure_created(&rsdir)?;
238 write_to_file(
244 &incdir,
245 "cxx.h",
246 &get_cxx_header_bytes(
247 self.codegen_options
248 .cpp_codegen_options
249 .suppress_system_headers,
250 ),
251 )?;
252
253 let autocxx_inc = build_autocxx_inc(self.autocxx_incs, &incdir);
254 gen_location_strategy.set_cargo_env_vars_for_build();
255
256 let mut parsed_file = crate::parse_file(self.rs_file, self.auto_allowlist)
257 .map_err(BuilderError::ParseError)?;
258 parsed_file
259 .resolve_all(
260 autocxx_inc,
261 clang_args,
262 self.dependency_recorder,
263 &self.codegen_options,
264 )
265 .map_err(BuilderError::ParseError)?;
266 let mut counter = 0;
267 let mut builder = cc::Build::new();
268 builder.cpp(true);
269 if std::env::var_os("AUTOCXX_ASAN").is_some() {
270 builder.flag_if_supported("-fsanitize=address");
271 }
272 let mut generated_rs = Vec::new();
273 let mut generated_cpp = Vec::new();
274 builder.includes(parsed_file.include_dirs());
275 for include_cpp in parsed_file.get_cpp_buildables() {
276 let generated_code = include_cpp
277 .generate_h_and_cxx(&self.codegen_options.cpp_codegen_options)
278 .map_err(BuilderError::InvalidCxx)?;
279 for filepair in generated_code.0 {
280 let fname = format!("gen{counter}.cxx");
281 counter += 1;
282 if let Some(implementation) = &filepair.implementation {
283 let gen_cxx_path = write_to_file(&cxxdir, &fname, implementation)?;
284 builder.file(&gen_cxx_path);
285 generated_cpp.push(gen_cxx_path);
286 }
287 write_to_file(&incdir, &filepair.header_name, &filepair.header)?;
288 generated_cpp.push(incdir.join(filepair.header_name));
289 }
290 }
291
292 for rs_output in parsed_file.get_rs_outputs() {
293 let rs = generate_rs_single(rs_output);
294 generated_rs.push(write_to_file(&rsdir, &rs.filename, rs.code.as_bytes())?);
295 }
296 if counter == 0 {
297 Err(BuilderError::NoIncludeCxxMacrosFound)
298 } else {
299 Ok(BuilderSuccess(builder, generated_rs, generated_cpp))
300 }
301 }
302}
303
304fn ensure_created(dir: &Path) -> Result<(), BuilderError> {
305 std::fs::create_dir_all(dir)
306 .map_err(|e| BuilderError::UnableToCreateDirectory(e, dir.to_path_buf()))
307}
308
309fn build_autocxx_inc<I, T>(paths: I, extra_path: &Path) -> Vec<PathBuf>
310where
311 I: IntoIterator<Item = T>,
312 T: AsRef<OsStr>,
313{
314 paths
315 .into_iter()
316 .map(|p| PathBuf::from(p.as_ref()))
317 .chain(std::iter::once(extra_path.to_path_buf()))
318 .collect()
319}
320
321fn write_to_file(dir: &Path, filename: &str, content: &[u8]) -> Result<PathBuf, BuilderError> {
322 let path = dir.join(filename);
323 if let Ok(existing_contents) = std::fs::read(&path) {
324 if existing_contents == content {
327 return Ok(path);
328 }
329 }
330 try_write_to_file(&path, content).map_err(|e| BuilderError::FileWriteFail(e, path.clone()))?;
331 Ok(path)
332}
333
334fn try_write_to_file(path: &Path, content: &[u8]) -> std::io::Result<()> {
335 let mut f = File::create(path)?;
336 f.write_all(content)
337}
338
339fn rust_version_check() {
340 if !version_check::is_min_version("1.54.0").unwrap_or(false) {
341 panic!("Rust 1.54 or later is required.")
342 }
343}