1#![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, 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#[must_use]
119pub fn bridge(rust_source_file: impl AsRef<Path>) -> Build {
120 bridges(iter::once(rust_source_file))
121}
122
123#[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 links_attribute: Option<OsString>,
147 out_dir: PathBuf,
149 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
190fn 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); 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 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 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 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}