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