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