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 84mod cargo; 85mod cfg; 86mod deps; 87mod error; 88mod gen; 89mod intern; 90mod out; 91mod paths; 92mod syntax; 93mod target; 94mod vec; 95 96use crate::cargo::CargoEnvCfgEvaluator; 97use crate::deps::{Crate, HeaderDir}; 98use crate::error::{Error, Result}; 99use crate::gen::error::report; 100use crate::gen::Opt; 101use crate::paths::PathExt; 102use crate::syntax::map::{Entry, UnorderedMap}; 103use crate::target::TargetDir; 104use cc::Build; 105use std::collections::BTreeSet; 106use std::env; 107use std::ffi::{OsStr, OsString}; 108use std::io::{self, Write}; 109use std::iter; 110use std::path::{Path, PathBuf}; 111use std::process; 112 113pub 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] 121pub 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] 136pub 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 144struct 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 166impl Project { 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. 214fn 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 241fn 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 269fn 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 365fn 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 384fn 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 398fn 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 431fn 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 474fn 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