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