1#![doc(html_root_url = "https://docs.rs/cxx-build/1.0.135")]
49#![cfg_attr(not(check_cfg), allow(unexpected_cfgs))]
50#![allow(
51 clippy::cast_sign_loss,
52 clippy::default_trait_access,
53 clippy::derive_partial_eq_without_eq,
54 clippy::doc_markdown,
55 clippy::enum_glob_use,
56 clippy::explicit_auto_deref,
57 clippy::if_same_then_else,
58 clippy::inherent_to_string,
59 clippy::into_iter_without_iter,
60 clippy::items_after_statements,
61 clippy::match_bool,
62 clippy::match_on_vec_items,
63 clippy::match_same_arms,
64 clippy::module_name_repetitions,
65 clippy::needless_doctest_main,
66 clippy::needless_lifetimes,
67 clippy::needless_pass_by_value,
68 clippy::new_without_default,
69 clippy::nonminimal_bool,
70 clippy::or_fun_call,
71 clippy::redundant_else,
72 clippy::ref_option,
73 clippy::shadow_unrelated,
74 clippy::significant_drop_in_scrutinee,
75 clippy::similar_names,
76 clippy::single_match_else,
77 clippy::struct_excessive_bools,
78 clippy::struct_field_names,
79 clippy::too_many_arguments,
80 clippy::too_many_lines,
81 clippy::toplevel_ref_arg,
82 clippy::unconditional_recursion, clippy::uninlined_format_args,
84 clippy::upper_case_acronyms,
85)]
86
87mod cargo;
88mod cfg;
89mod deps;
90mod error;
91mod gen;
92mod intern;
93mod out;
94mod paths;
95mod syntax;
96mod target;
97mod vec;
98
99use crate::cargo::CargoEnvCfgEvaluator;
100use crate::deps::{Crate, HeaderDir};
101use crate::error::{Error, Result};
102use crate::gen::error::report;
103use crate::gen::Opt;
104use crate::paths::PathExt;
105use crate::syntax::map::{Entry, UnorderedMap};
106use crate::target::TargetDir;
107use cc::Build;
108use std::collections::BTreeSet;
109use std::env;
110use std::ffi::{OsStr, OsString};
111use std::io::{self, Write};
112use std::iter;
113use std::path::{Path, PathBuf};
114use std::process;
115
116pub use crate::cfg::{Cfg, CFG};
117
118#[must_use]
124pub fn bridge(rust_source_file: impl AsRef<Path>) -> Build {
125 bridges(iter::once(rust_source_file))
126}
127
128#[must_use]
139pub fn bridges(rust_source_files: impl IntoIterator<Item = impl AsRef<Path>>) -> Build {
140 let ref mut rust_source_files = rust_source_files.into_iter();
141 build(rust_source_files).unwrap_or_else(|err| {
142 let _ = writeln!(io::stderr(), "\n\ncxxbridge error: {}\n\n", report(err));
143 process::exit(1);
144 })
145}
146
147struct Project {
148 include_prefix: PathBuf,
149 manifest_dir: PathBuf,
150 links_attribute: Option<OsString>,
152 out_dir: PathBuf,
154 shared_dir: PathBuf,
167}
168
169impl Project {
170 fn init() -> Result<Self> {
171 let include_prefix = Path::new(CFG.include_prefix);
172 assert!(include_prefix.is_relative());
173 let include_prefix = include_prefix.components().collect();
174
175 let links_attribute = env::var_os("CARGO_MANIFEST_LINKS");
176
177 let manifest_dir = paths::manifest_dir()?;
178 let out_dir = paths::out_dir()?;
179
180 let shared_dir = match target::find_target_dir(&out_dir) {
181 TargetDir::Path(target_dir) => target_dir.join("cxxbridge"),
182 TargetDir::Unknown => scratch::path("cxxbridge"),
183 };
184
185 Ok(Project {
186 include_prefix,
187 manifest_dir,
188 links_attribute,
189 out_dir,
190 shared_dir,
191 })
192 }
193}
194
195fn build(rust_source_files: &mut dyn Iterator<Item = impl AsRef<Path>>) -> Result<Build> {
218 let ref prj = Project::init()?;
219 validate_cfg(prj)?;
220 let this_crate = make_this_crate(prj)?;
221
222 let mut build = Build::new();
223 build.cpp(true);
224 build.cpp_link_stdlib(None); for path in rust_source_files {
227 generate_bridge(prj, &mut build, path.as_ref())?;
228 }
229
230 this_crate.print_to_cargo();
231 eprintln!("\nCXX include path:");
232 for header_dir in this_crate.header_dirs {
233 build.include(&header_dir.path);
234 if header_dir.exported {
235 eprintln!(" {}", header_dir.path.display());
236 } else {
237 eprintln!(" {} (private)", header_dir.path.display());
238 }
239 }
240
241 Ok(build)
242}
243
244fn validate_cfg(prj: &Project) -> Result<()> {
245 for exported_dir in &CFG.exported_header_dirs {
246 if !exported_dir.is_absolute() {
247 return Err(Error::ExportedDirNotAbsolute(exported_dir));
248 }
249 }
250
251 for prefix in &CFG.exported_header_prefixes {
252 if prefix.is_empty() {
253 return Err(Error::ExportedEmptyPrefix);
254 }
255 }
256
257 if prj.links_attribute.is_none() {
258 if !CFG.exported_header_dirs.is_empty() {
259 return Err(Error::ExportedDirsWithoutLinks);
260 }
261 if !CFG.exported_header_prefixes.is_empty() {
262 return Err(Error::ExportedPrefixesWithoutLinks);
263 }
264 if !CFG.exported_header_links.is_empty() {
265 return Err(Error::ExportedLinksWithoutLinks);
266 }
267 }
268
269 Ok(())
270}
271
272fn make_this_crate(prj: &Project) -> Result<Crate> {
273 let crate_dir = make_crate_dir(prj);
274 let include_dir = make_include_dir(prj)?;
275
276 let mut this_crate = Crate {
277 include_prefix: Some(prj.include_prefix.clone()),
278 links: prj.links_attribute.clone(),
279 header_dirs: Vec::new(),
280 };
281
282 this_crate.header_dirs.push(HeaderDir {
287 exported: true,
288 path: include_dir,
289 });
290
291 this_crate.header_dirs.push(HeaderDir {
292 exported: true,
293 path: crate_dir,
294 });
295
296 for exported_dir in &CFG.exported_header_dirs {
297 this_crate.header_dirs.push(HeaderDir {
298 exported: true,
299 path: PathBuf::from(exported_dir),
300 });
301 }
302
303 let mut header_dirs_index = UnorderedMap::new();
304 let mut used_header_links = BTreeSet::new();
305 let mut used_header_prefixes = BTreeSet::new();
306 for krate in deps::direct_dependencies() {
307 let mut is_link_exported = || match &krate.links {
308 None => false,
309 Some(links_attribute) => CFG.exported_header_links.iter().any(|&exported| {
310 let matches = links_attribute == exported;
311 if matches {
312 used_header_links.insert(exported);
313 }
314 matches
315 }),
316 };
317
318 let mut is_prefix_exported = || match &krate.include_prefix {
319 None => false,
320 Some(include_prefix) => CFG.exported_header_prefixes.iter().any(|&exported| {
321 let matches = include_prefix.starts_with(exported);
322 if matches {
323 used_header_prefixes.insert(exported);
324 }
325 matches
326 }),
327 };
328
329 let exported = is_link_exported() || is_prefix_exported();
330
331 for dir in krate.header_dirs {
332 match header_dirs_index.entry(dir.path.clone()) {
334 Entry::Vacant(entry) => {
335 entry.insert(this_crate.header_dirs.len());
336 this_crate.header_dirs.push(HeaderDir {
337 exported,
338 path: dir.path,
339 });
340 }
341 Entry::Occupied(entry) => {
342 let index = *entry.get();
343 this_crate.header_dirs[index].exported |= exported;
344 }
345 }
346 }
347 }
348
349 if let Some(unused) = CFG
350 .exported_header_links
351 .iter()
352 .find(|&exported| !used_header_links.contains(exported))
353 {
354 return Err(Error::UnusedExportedLinks(unused));
355 }
356
357 if let Some(unused) = CFG
358 .exported_header_prefixes
359 .iter()
360 .find(|&exported| !used_header_prefixes.contains(exported))
361 {
362 return Err(Error::UnusedExportedPrefix(unused));
363 }
364
365 Ok(this_crate)
366}
367
368fn make_crate_dir(prj: &Project) -> PathBuf {
369 if prj.include_prefix.as_os_str().is_empty() {
370 return prj.manifest_dir.clone();
371 }
372 let crate_dir = prj.out_dir.join("cxxbridge").join("crate");
373 let ref link = crate_dir.join(&prj.include_prefix);
374 let ref manifest_dir = prj.manifest_dir;
375 if out::relative_symlink_dir(manifest_dir, link).is_err() && cfg!(not(unix)) {
376 let cachedir_tag = "\
377 Signature: 8a477f597d28d172789f06886806bc55\n\
378 # This file is a cache directory tag created by cxx.\n\
379 # For information about cache directory tags see https://bford.info/cachedir/\n";
380 let _ = out::write(crate_dir.join("CACHEDIR.TAG"), cachedir_tag.as_bytes());
381 let max_depth = 6;
382 best_effort_copy_headers(manifest_dir, link, max_depth);
383 }
384 crate_dir
385}
386
387fn make_include_dir(prj: &Project) -> Result<PathBuf> {
388 let include_dir = prj.out_dir.join("cxxbridge").join("include");
389 let cxx_h = include_dir.join("rust").join("cxx.h");
390 let ref shared_cxx_h = prj.shared_dir.join("rust").join("cxx.h");
391 if let Some(ref original) = env::var_os("DEP_CXXBRIDGE1_HEADER") {
392 out::absolute_symlink_file(original, cxx_h)?;
393 out::absolute_symlink_file(original, shared_cxx_h)?;
394 } else {
395 out::write(shared_cxx_h, gen::include::HEADER.as_bytes())?;
396 out::relative_symlink_file(shared_cxx_h, cxx_h)?;
397 }
398 Ok(include_dir)
399}
400
401fn generate_bridge(prj: &Project, build: &mut Build, rust_source_file: &Path) -> Result<()> {
402 let opt = Opt {
403 allow_dot_includes: false,
404 cfg_evaluator: Box::new(CargoEnvCfgEvaluator),
405 doxygen: CFG.doxygen,
406 ..Opt::default()
407 };
408 let generated = gen::generate_from_path(rust_source_file, &opt);
409 let ref rel_path = paths::local_relative_path(rust_source_file);
410
411 let cxxbridge = prj.out_dir.join("cxxbridge");
412 let include_dir = cxxbridge.join("include").join(&prj.include_prefix);
413 let sources_dir = cxxbridge.join("sources").join(&prj.include_prefix);
414
415 let ref rel_path_h = rel_path.with_appended_extension(".h");
416 let ref header_path = include_dir.join(rel_path_h);
417 out::write(header_path, &generated.header)?;
418
419 let ref link_path = include_dir.join(rel_path);
420 let _ = out::relative_symlink_file(header_path, link_path);
421
422 let ref rel_path_cc = rel_path.with_appended_extension(".cc");
423 let ref implementation_path = sources_dir.join(rel_path_cc);
424 out::write(implementation_path, &generated.implementation)?;
425 build.file(implementation_path);
426
427 let shared_h = prj.shared_dir.join(&prj.include_prefix).join(rel_path_h);
428 let shared_cc = prj.shared_dir.join(&prj.include_prefix).join(rel_path_cc);
429 let _ = out::relative_symlink_file(header_path, shared_h);
430 let _ = out::relative_symlink_file(implementation_path, shared_cc);
431 Ok(())
432}
433
434fn best_effort_copy_headers(src: &Path, dst: &Path, max_depth: usize) {
435 use std::fs;
437
438 let mut dst_created = false;
439 let Ok(mut entries) = fs::read_dir(src) else {
440 return;
441 };
442
443 while let Some(Ok(entry)) = entries.next() {
444 let file_name = entry.file_name();
445 if file_name.to_string_lossy().starts_with('.') {
446 continue;
447 }
448 match entry.file_type() {
449 Ok(file_type) if file_type.is_dir() && max_depth > 0 => {
450 let src = entry.path();
451 if src.join("Cargo.toml").exists() || src.join("CACHEDIR.TAG").exists() {
452 continue;
453 }
454 let dst = dst.join(file_name);
455 best_effort_copy_headers(&src, &dst, max_depth - 1);
456 }
457 Ok(file_type) if file_type.is_file() => {
458 let src = entry.path();
459 match src.extension().and_then(OsStr::to_str) {
460 Some("h" | "hh" | "hpp") => {}
461 _ => continue,
462 }
463 if !dst_created && fs::create_dir_all(dst).is_err() {
464 return;
465 }
466 dst_created = true;
467 let dst = dst.join(file_name);
468 let _ = fs::remove_file(&dst);
469 let _ = fs::copy(src, dst);
470 }
471 _ => {}
472 }
473 }
474}
475
476fn env_os(key: impl AsRef<OsStr>) -> Result<OsString> {
477 let key = key.as_ref();
478 env::var_os(key).ok_or_else(|| Error::NoEnv(key.to_owned()))
479}