cxx_build/
lib.rs

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//!         .std("c++11")
20//!         .compile("cxxbridge-demo");
21//!
22//!     println!("cargo:rerun-if-changed=src/demo.cc");
23//!     println!("cargo:rerun-if-changed=include/demo.h");
24//! }
25//! ```
26//!
27//! A runnable working setup with this build script is shown in the *demo*
28//! directory of [https://github.com/dtolnay/cxx].
29//!
30//! [https://github.com/dtolnay/cxx]: https://github.com/dtolnay/cxx
31//!
32//! <br>
33//!
34//! # Alternatives
35//!
36//! For use in non-Cargo builds like Bazel or Buck, CXX provides an
37//! alternate way of invoking the C++ code generator as a standalone command
38//! line tool. The tool is packaged as the `cxxbridge-cmd` crate.
39//!
40//! ```bash
41//! $ cargo install cxxbridge-cmd  # or build it from the repo
42//!
43//! $ cxxbridge src/main.rs --header > path/to/mybridge.h
44//! $ cxxbridge src/main.rs > path/to/mybridge.cc
45//! ```
46
47#![doc(html_root_url = "https://docs.rs/cxx-build/1.0.188")]
48#![cfg_attr(not(check_cfg), allow(unexpected_cfgs))]
49#![allow(
50    clippy::cast_sign_loss,
51    clippy::default_trait_access,
52    clippy::doc_markdown,
53    clippy::elidable_lifetime_names,
54    clippy::enum_glob_use,
55    clippy::expl_impl_clone_on_copy, // https://github.com/rust-lang/rust-clippy/issues/15842
56    clippy::explicit_auto_deref,
57    clippy::inherent_to_string,
58    clippy::items_after_statements,
59    clippy::match_bool,
60    clippy::match_like_matches_macro,
61    clippy::match_same_arms,
62    clippy::needless_continue,
63    clippy::needless_doctest_main,
64    clippy::needless_lifetimes,
65    clippy::needless_pass_by_value,
66    clippy::nonminimal_bool,
67    clippy::precedence,
68    clippy::redundant_else,
69    clippy::ref_as_ptr,
70    clippy::ref_option,
71    clippy::similar_names,
72    clippy::single_match_else,
73    clippy::struct_excessive_bools,
74    clippy::struct_field_names,
75    clippy::too_many_arguments,
76    clippy::too_many_lines,
77    clippy::toplevel_ref_arg,
78    clippy::uninlined_format_args,
79    clippy::upper_case_acronyms
80)]
81#![allow(unknown_lints, mismatched_lifetime_syntaxes)]
82
83mod cargo;
84mod cfg;
85mod deps;
86mod error;
87mod gen;
88mod intern;
89mod out;
90mod paths;
91mod syntax;
92mod target;
93mod vec;
94
95use crate::cargo::CargoEnvCfgEvaluator;
96use crate::deps::{Crate, HeaderDir};
97use crate::error::{Error, Result};
98use crate::gen::error::report;
99use crate::gen::Opt;
100use crate::paths::PathExt;
101use crate::syntax::map::{Entry, UnorderedMap};
102use crate::target::TargetDir;
103use cc::Build;
104use std::collections::BTreeSet;
105use std::env;
106use std::ffi::{OsStr, OsString};
107use std::io::{self, Write};
108use std::iter;
109use std::path::{Path, PathBuf};
110use std::process;
111
112pub 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`]: cc::Build::compile
119#[must_use]
120pub 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///     .std("c++11")
132///     .compile("cxxbridge-demo");
133/// ```
134#[must_use]
135pub 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 _ = io::stderr().write_fmt(format_args!("\n\ncxxbridge error: {0}\n\n\n",
        report(err)))writeln!(io::stderr(), "\n\ncxxbridge error: {}\n\n", report(err));
139        process::exit(1);
140    })
141}
142
143struct 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
165impl Project {
166    fn init() -> Result<Self> {
167        let include_prefix = Path::new(CFG.include_prefix);
168        if !include_prefix.is_relative() {
    ::core::panicking::panic("assertion failed: include_prefix.is_relative()")
};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.
213fn 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    { ::std::io::_eprint(format_args!("\nCXX include path:\n")); };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            { ::std::io::_eprint(format_args!("  {0}\n", header_dir.path.display())); };eprintln!("  {}", header_dir.path.display());
232        } else {
233            {
    ::std::io::_eprint(format_args!("  {0} (private)\n",
            header_dir.path.display()));
};eprintln!("  {} (private)", header_dir.path.display());
234        }
235    }
236
237    Ok(build)
238}
239
240fn 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
268fn 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
364fn 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::relative_symlink_dir(manifest_dir, link).is_err() && falsecfg!(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
383fn 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::absolute_symlink_file(original, cxx_h)?;
389        out::absolute_symlink_file(original, shared_cxx_h)?;
390    } else {
391        out::write(shared_cxx_h, gen::include::HEADER.as_bytes())?;
392        out::relative_symlink_file(shared_cxx_h, cxx_h)?;
393    }
394    Ok(include_dir)
395}
396
397fn 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    if !rust_source_file.starts_with(&prj.out_dir) {
405        {
    ::std::io::_print(format_args!("cargo:rerun-if-changed={0}\n",
            rust_source_file.display()));
};println!("cargo:rerun-if-changed={}", rust_source_file.display());
406    }
407    let generated = gen::generate_from_path(rust_source_file, &opt);
408    let ref rel_path = paths::local_relative_path(rust_source_file);
409
410    let cxxbridge = prj.out_dir.join("cxxbridge");
411    let include_dir = cxxbridge.join("include").join(&prj.include_prefix);
412    let sources_dir = cxxbridge.join("sources").join(&prj.include_prefix);
413
414    let ref rel_path_h = rel_path.with_appended_extension(".h");
415    let ref header_path = include_dir.join(rel_path_h);
416    out::write(header_path, &generated.header)?;
417
418    let ref link_path = include_dir.join(rel_path);
419    let _ = out::relative_symlink_file(header_path, link_path);
420
421    let ref rel_path_cc = rel_path.with_appended_extension(".cc");
422    let ref implementation_path = sources_dir.join(rel_path_cc);
423    out::write(implementation_path, &generated.implementation)?;
424    build.file(implementation_path);
425
426    let shared_h = prj.shared_dir.join(&prj.include_prefix).join(rel_path_h);
427    let shared_cc = prj.shared_dir.join(&prj.include_prefix).join(rel_path_cc);
428    let _ = out::relative_symlink_file(header_path, shared_h);
429    let _ = out::relative_symlink_file(implementation_path, shared_cc);
430    Ok(())
431}
432
433fn best_effort_copy_headers(src: &Path, dst: &Path, max_depth: usize) {
434    // Not using crate::gen::fs because we aren't reporting the errors.
435    use std::fs;
436
437    let mut dst_created = false;
438    let Ok(mut entries) = fs::read_dir(src) else {
439        return;
440    };
441
442    while let Some(Ok(entry)) = entries.next() {
443        let file_name = entry.file_name();
444        if file_name.to_string_lossy().starts_with('.') {
445            continue;
446        }
447        match entry.file_type() {
448            Ok(file_type) if file_type.is_dir() && max_depth > 0 => {
449                let src = entry.path();
450                if src.join("Cargo.toml").exists() || src.join("CACHEDIR.TAG").exists() {
451                    continue;
452                }
453                let dst = dst.join(file_name);
454                best_effort_copy_headers(&src, &dst, max_depth - 1);
455            }
456            Ok(file_type) if file_type.is_file() => {
457                let src = entry.path();
458                match src.extension().and_then(OsStr::to_str) {
459                    Some("h" | "hh" | "hpp") => {}
460                    _ => continue,
461                }
462                if !dst_created && fs::create_dir_all(dst).is_err() {
463                    return;
464                }
465                dst_created = true;
466                let dst = dst.join(file_name);
467                let _ = fs::remove_file(&dst);
468                let _ = fs::copy(src, dst);
469            }
470            _ => {}
471        }
472    }
473}
474
475fn env_os(key: impl AsRef<OsStr>) -> Result<OsString> {
476    let key = key.as_ref();
477    env::var_os(key).ok_or_else(|| Error::NoEnv(key.to_owned()))
478}