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