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