1 //! The CXX code generator for constructing and compiling C++ code.
2 //!
3 //! This is intended to be used from Cargo build scripts to execute CXX's
4 //! C++ code generator, set up any additional compiler flags depending on
5 //! the use case, and make the C++ compiler invocation.
6 //!
7 //! <br>
8 //!
9 //! # Example
10 //!
11 //! Example of a canonical Cargo build script that builds a CXX bridge:
12 //!
13 //! ```no_run
14 //! // build.rs
15 //!
16 //! fn main() {
17 //! cxx_build::bridge("src/main.rs")
18 //! .file("src/demo.cc")
19 //! .flag_if_supported("-std=c++11")
20 //! .compile("cxxbridge-demo");
21 //!
22 //! println!("cargo:rerun-if-changed=src/main.rs");
23 //! println!("cargo:rerun-if-changed=src/demo.cc");
24 //! println!("cargo:rerun-if-changed=include/demo.h");
25 //! }
26 //! ```
27 //!
28 //! A runnable working setup with this build script is shown in the *demo*
29 //! directory of [https://github.com/dtolnay/cxx].
30 //!
31 //! [https://github.com/dtolnay/cxx]: https://github.com/dtolnay/cxx
32 //!
33 //! <br>
34 //!
35 //! # Alternatives
36 //!
37 //! For use in non-Cargo builds like Bazel or Buck, CXX provides an
38 //! alternate way of invoking the C++ code generator as a standalone command
39 //! line tool. The tool is packaged as the `cxxbridge-cmd` crate.
40 //!
41 //! ```bash
42 //! $ cargo install cxxbridge-cmd # or build it from the repo
43 //!
44 //! $ cxxbridge src/main.rs --header > path/to/mybridge.h
45 //! $ cxxbridge src/main.rs > path/to/mybridge.cc
46 //! ```
47
48 #![allow(
49 clippy::cast_sign_loss,
50 clippy::default_trait_access,
51 clippy::derive_partial_eq_without_eq,
52 clippy::doc_markdown,
53 clippy::drop_copy,
54 clippy::enum_glob_use,
55 clippy::explicit_auto_deref,
56 clippy::if_same_then_else,
57 clippy::inherent_to_string,
58 clippy::items_after_statements,
59 clippy::match_bool,
60 clippy::match_on_vec_items,
61 clippy::match_same_arms,
62 clippy::module_name_repetitions,
63 clippy::needless_doctest_main,
64 clippy::needless_pass_by_value,
65 clippy::new_without_default,
66 clippy::nonminimal_bool,
67 clippy::option_if_let_else,
68 clippy::or_fun_call,
69 clippy::redundant_else,
70 clippy::shadow_unrelated,
71 clippy::significant_drop_in_scrutinee,
72 clippy::similar_names,
73 clippy::single_match_else,
74 clippy::struct_excessive_bools,
75 clippy::too_many_arguments,
76 clippy::too_many_lines,
77 clippy::toplevel_ref_arg,
78 clippy::upper_case_acronyms,
79 // clippy bug: https://github.com/rust-lang/rust-clippy/issues/6983
80 clippy::wrong_self_convention
81 )]
82
83 mod cargo;
84 mod cfg;
85 mod deps;
86 mod error;
87 mod gen;
88 mod intern;
89 mod out;
90 mod paths;
91 mod syntax;
92 mod target;
93 mod vec;
94
95 use crate::cargo::CargoEnvCfgEvaluator;
96 use crate::deps::{Crate, HeaderDir};
97 use crate::error::{Error, Result};
98 use crate::gen::error::report;
99 use crate::gen::Opt;
100 use crate::paths::PathExt;
101 use crate::syntax::map::{Entry, UnorderedMap};
102 use crate::target::TargetDir;
103 use cc::Build;
104 use std::collections::BTreeSet;
105 use std::env;
106 use std::ffi::{OsStr, OsString};
107 use std::io::{self, Write};
108 use std::iter;
109 use std::path::{Path, PathBuf};
110 use std::process;
111
112 pub use crate::cfg::{Cfg, CFG};
113
114 /// This returns a [`cc::Build`] on which you should continue to set up any
115 /// additional source files or compiler flags, and lastly call its [`compile`]
116 /// method to execute the C++ build.
117 ///
118 /// [`compile`]: https://docs.rs/cc/1.0.49/cc/struct.Build.html#method.compile
119 #[must_use]
bridge(rust_source_file: impl AsRef<Path>) -> Build120 pub fn bridge(rust_source_file: impl AsRef<Path>) -> Build {
121 bridges(iter::once(rust_source_file))
122 }
123
124 /// `cxx_build::bridge` but for when more than one file contains a
125 /// #\[cxx::bridge\] module.
126 ///
127 /// ```no_run
128 /// let source_files = vec!["src/main.rs", "src/path/to/other.rs"];
129 /// cxx_build::bridges(source_files)
130 /// .file("src/demo.cc")
131 /// .flag_if_supported("-std=c++11")
132 /// .compile("cxxbridge-demo");
133 /// ```
134 #[must_use]
bridges(rust_source_files: impl IntoIterator<Item = impl AsRef<Path>>) -> Build135 pub fn bridges(rust_source_files: impl IntoIterator<Item = impl AsRef<Path>>) -> Build {
136 let ref mut rust_source_files = rust_source_files.into_iter();
137 build(rust_source_files).unwrap_or_else(|err| {
138 let _ = writeln!(io::stderr(), "\n\ncxxbridge error: {}\n\n", report(err));
139 process::exit(1);
140 })
141 }
142
143 struct Project {
144 include_prefix: PathBuf,
145 manifest_dir: PathBuf,
146 // The `links = "..."` value from Cargo.toml.
147 links_attribute: Option<OsString>,
148 // Output directory as received from Cargo.
149 out_dir: PathBuf,
150 // Directory into which to symlink all generated code.
151 //
152 // This is *not* used for an #include path, only as a debugging convenience.
153 // Normally available at target/cxxbridge/ if we are able to know where the
154 // target dir is, otherwise under a common scratch dir.
155 //
156 // The reason this isn't the #include dir is that we do not want builds to
157 // have access to headers from arbitrary other parts of the dependency
158 // graph. Using a global directory for all builds would be both a race
159 // condition depending on what order Cargo randomly executes the build
160 // scripts, as well as semantically undesirable for builds not to have to
161 // declare their real dependencies.
162 shared_dir: PathBuf,
163 }
164
165 impl Project {
init() -> Result<Self>166 fn init() -> Result<Self> {
167 let include_prefix = Path::new(CFG.include_prefix);
168 assert!(include_prefix.is_relative());
169 let include_prefix = include_prefix.components().collect();
170
171 let links_attribute = env::var_os("CARGO_MANIFEST_LINKS");
172
173 let manifest_dir = paths::manifest_dir()?;
174 let out_dir = paths::out_dir()?;
175
176 let shared_dir = match target::find_target_dir(&out_dir) {
177 TargetDir::Path(target_dir) => target_dir.join("cxxbridge"),
178 TargetDir::Unknown => scratch::path("cxxbridge"),
179 };
180
181 Ok(Project {
182 include_prefix,
183 manifest_dir,
184 links_attribute,
185 out_dir,
186 shared_dir,
187 })
188 }
189 }
190
191 // We lay out the OUT_DIR as follows. Everything is namespaced under a cxxbridge
192 // subdirectory to avoid stomping on other things that the caller's build script
193 // might be doing inside OUT_DIR.
194 //
195 // $OUT_DIR/
196 // cxxbridge/
197 // crate/
198 // $CARGO_PKG_NAME -> $CARGO_MANIFEST_DIR
199 // include/
200 // rust/
201 // cxx.h
202 // $CARGO_PKG_NAME/
203 // .../
204 // lib.rs.h
205 // sources/
206 // $CARGO_PKG_NAME/
207 // .../
208 // lib.rs.cc
209 //
210 // The crate/ and include/ directories are placed on the #include path for the
211 // current build as well as for downstream builds that have a direct dependency
212 // on the current crate.
build(rust_source_files: &mut dyn Iterator<Item = impl AsRef<Path>>) -> Result<Build>213 fn build(rust_source_files: &mut dyn Iterator<Item = impl AsRef<Path>>) -> Result<Build> {
214 let ref prj = Project::init()?;
215 validate_cfg(prj)?;
216 let this_crate = make_this_crate(prj)?;
217
218 let mut build = Build::new();
219 build.cpp(true);
220 build.cpp_link_stdlib(None); // linked via link-cplusplus crate
221
222 for path in rust_source_files {
223 generate_bridge(prj, &mut build, path.as_ref())?;
224 }
225
226 this_crate.print_to_cargo();
227 eprintln!("\nCXX include path:");
228 for header_dir in this_crate.header_dirs {
229 build.include(&header_dir.path);
230 if header_dir.exported {
231 eprintln!(" {}", header_dir.path.display());
232 } else {
233 eprintln!(" {} (private)", header_dir.path.display());
234 }
235 }
236
237 Ok(build)
238 }
239
validate_cfg(prj: &Project) -> Result<()>240 fn validate_cfg(prj: &Project) -> Result<()> {
241 for exported_dir in &CFG.exported_header_dirs {
242 if !exported_dir.is_absolute() {
243 return Err(Error::ExportedDirNotAbsolute(exported_dir));
244 }
245 }
246
247 for prefix in &CFG.exported_header_prefixes {
248 if prefix.is_empty() {
249 return Err(Error::ExportedEmptyPrefix);
250 }
251 }
252
253 if prj.links_attribute.is_none() {
254 if !CFG.exported_header_dirs.is_empty() {
255 return Err(Error::ExportedDirsWithoutLinks);
256 }
257 if !CFG.exported_header_prefixes.is_empty() {
258 return Err(Error::ExportedPrefixesWithoutLinks);
259 }
260 if !CFG.exported_header_links.is_empty() {
261 return Err(Error::ExportedLinksWithoutLinks);
262 }
263 }
264
265 Ok(())
266 }
267
make_this_crate(prj: &Project) -> Result<Crate>268 fn make_this_crate(prj: &Project) -> Result<Crate> {
269 let crate_dir = make_crate_dir(prj);
270 let include_dir = make_include_dir(prj)?;
271
272 let mut this_crate = Crate {
273 include_prefix: Some(prj.include_prefix.clone()),
274 links: prj.links_attribute.clone(),
275 header_dirs: Vec::new(),
276 };
277
278 // The generated code directory (include_dir) is placed in front of
279 // crate_dir on the include line so that `#include "path/to/file.rs"` from
280 // C++ "magically" works and refers to the API generated from that Rust
281 // source file.
282 this_crate.header_dirs.push(HeaderDir {
283 exported: true,
284 path: include_dir,
285 });
286
287 this_crate.header_dirs.push(HeaderDir {
288 exported: true,
289 path: crate_dir,
290 });
291
292 for exported_dir in &CFG.exported_header_dirs {
293 this_crate.header_dirs.push(HeaderDir {
294 exported: true,
295 path: PathBuf::from(exported_dir),
296 });
297 }
298
299 let mut header_dirs_index = UnorderedMap::new();
300 let mut used_header_links = BTreeSet::new();
301 let mut used_header_prefixes = BTreeSet::new();
302 for krate in deps::direct_dependencies() {
303 let mut is_link_exported = || match &krate.links {
304 None => false,
305 Some(links_attribute) => CFG.exported_header_links.iter().any(|&exported| {
306 let matches = links_attribute == exported;
307 if matches {
308 used_header_links.insert(exported);
309 }
310 matches
311 }),
312 };
313
314 let mut is_prefix_exported = || match &krate.include_prefix {
315 None => false,
316 Some(include_prefix) => CFG.exported_header_prefixes.iter().any(|&exported| {
317 let matches = include_prefix.starts_with(exported);
318 if matches {
319 used_header_prefixes.insert(exported);
320 }
321 matches
322 }),
323 };
324
325 let exported = is_link_exported() || is_prefix_exported();
326
327 for dir in krate.header_dirs {
328 // Deduplicate dirs reachable via multiple transitive dependencies.
329 match header_dirs_index.entry(dir.path.clone()) {
330 Entry::Vacant(entry) => {
331 entry.insert(this_crate.header_dirs.len());
332 this_crate.header_dirs.push(HeaderDir {
333 exported,
334 path: dir.path,
335 });
336 }
337 Entry::Occupied(entry) => {
338 let index = *entry.get();
339 this_crate.header_dirs[index].exported |= exported;
340 }
341 }
342 }
343 }
344
345 if let Some(unused) = CFG
346 .exported_header_links
347 .iter()
348 .find(|&exported| !used_header_links.contains(exported))
349 {
350 return Err(Error::UnusedExportedLinks(unused));
351 }
352
353 if let Some(unused) = CFG
354 .exported_header_prefixes
355 .iter()
356 .find(|&exported| !used_header_prefixes.contains(exported))
357 {
358 return Err(Error::UnusedExportedPrefix(unused));
359 }
360
361 Ok(this_crate)
362 }
363
make_crate_dir(prj: &Project) -> PathBuf364 fn make_crate_dir(prj: &Project) -> PathBuf {
365 if prj.include_prefix.as_os_str().is_empty() {
366 return prj.manifest_dir.clone();
367 }
368 let crate_dir = prj.out_dir.join("cxxbridge").join("crate");
369 let ref link = crate_dir.join(&prj.include_prefix);
370 let ref manifest_dir = prj.manifest_dir;
371 if out::symlink_dir(manifest_dir, link).is_err() && cfg!(not(unix)) {
372 let cachedir_tag = "\
373 Signature: 8a477f597d28d172789f06886806bc55\n\
374 # This file is a cache directory tag created by cxx.\n\
375 # For information about cache directory tags see https://bford.info/cachedir/\n";
376 let _ = out::write(crate_dir.join("CACHEDIR.TAG"), cachedir_tag.as_bytes());
377 let max_depth = 6;
378 best_effort_copy_headers(manifest_dir, link, max_depth);
379 }
380 crate_dir
381 }
382
make_include_dir(prj: &Project) -> Result<PathBuf>383 fn make_include_dir(prj: &Project) -> Result<PathBuf> {
384 let include_dir = prj.out_dir.join("cxxbridge").join("include");
385 let cxx_h = include_dir.join("rust").join("cxx.h");
386 let ref shared_cxx_h = prj.shared_dir.join("rust").join("cxx.h");
387 if let Some(ref original) = env::var_os("DEP_CXXBRIDGE1_HEADER") {
388 out::symlink_file(original, cxx_h)?;
389 out::symlink_file(original, shared_cxx_h)?;
390 } else {
391 out::write(shared_cxx_h, gen::include::HEADER.as_bytes())?;
392 out::symlink_file(shared_cxx_h, cxx_h)?;
393 }
394 Ok(include_dir)
395 }
396
generate_bridge(prj: &Project, build: &mut Build, rust_source_file: &Path) -> Result<()>397 fn generate_bridge(prj: &Project, build: &mut Build, rust_source_file: &Path) -> Result<()> {
398 let opt = Opt {
399 allow_dot_includes: false,
400 cfg_evaluator: Box::new(CargoEnvCfgEvaluator),
401 doxygen: CFG.doxygen,
402 ..Opt::default()
403 };
404 let generated = gen::generate_from_path(rust_source_file, &opt);
405 let ref rel_path = paths::local_relative_path(rust_source_file);
406
407 let cxxbridge = prj.out_dir.join("cxxbridge");
408 let include_dir = cxxbridge.join("include").join(&prj.include_prefix);
409 let sources_dir = cxxbridge.join("sources").join(&prj.include_prefix);
410
411 let ref rel_path_h = rel_path.with_appended_extension(".h");
412 let ref header_path = include_dir.join(rel_path_h);
413 out::write(header_path, &generated.header)?;
414
415 let ref link_path = include_dir.join(rel_path);
416 let _ = out::symlink_file(header_path, link_path);
417
418 let ref rel_path_cc = rel_path.with_appended_extension(".cc");
419 let ref implementation_path = sources_dir.join(rel_path_cc);
420 out::write(implementation_path, &generated.implementation)?;
421 build.file(implementation_path);
422
423 let shared_h = prj.shared_dir.join(&prj.include_prefix).join(rel_path_h);
424 let shared_cc = prj.shared_dir.join(&prj.include_prefix).join(rel_path_cc);
425 let _ = out::symlink_file(header_path, shared_h);
426 let _ = out::symlink_file(implementation_path, shared_cc);
427 Ok(())
428 }
429
best_effort_copy_headers(src: &Path, dst: &Path, max_depth: usize)430 fn best_effort_copy_headers(src: &Path, dst: &Path, max_depth: usize) {
431 // Not using crate::gen::fs because we aren't reporting the errors.
432 use std::fs;
433
434 let mut dst_created = false;
435 let mut entries = match fs::read_dir(src) {
436 Ok(entries) => entries,
437 Err(_) => return,
438 };
439
440 while let Some(Ok(entry)) = entries.next() {
441 let file_name = entry.file_name();
442 if file_name.to_string_lossy().starts_with('.') {
443 continue;
444 }
445 match entry.file_type() {
446 Ok(file_type) if file_type.is_dir() && max_depth > 0 => {
447 let src = entry.path();
448 if src.join("Cargo.toml").exists() || src.join("CACHEDIR.TAG").exists() {
449 continue;
450 }
451 let dst = dst.join(file_name);
452 best_effort_copy_headers(&src, &dst, max_depth - 1);
453 }
454 Ok(file_type) if file_type.is_file() => {
455 let src = entry.path();
456 match src.extension().and_then(OsStr::to_str) {
457 Some("h") | Some("hh") | Some("hpp") => {}
458 _ => continue,
459 }
460 if !dst_created && fs::create_dir_all(dst).is_err() {
461 return;
462 }
463 dst_created = true;
464 let dst = dst.join(file_name);
465 let _ = fs::remove_file(&dst);
466 let _ = fs::copy(src, dst);
467 }
468 _ => {}
469 }
470 }
471 }
472
env_os(key: impl AsRef<OsStr>) -> Result<OsString>473 fn env_os(key: impl AsRef<OsStr>) -> Result<OsString> {
474 let key = key.as_ref();
475 env::var_os(key).ok_or_else(|| Error::NoEnv(key.to_owned()))
476 }
477