1#![deny(missing_docs)]
11#![deny(unused_extern_crates)]
12#![deny(clippy::disallowed_methods)]
13#![allow(non_upper_case_globals)]
16#![recursion_limit = "128"]
18
19#[macro_use]
20extern crate bitflags;
21#[macro_use]
22extern crate quote;
23
24#[cfg(feature = "logging")]
25#[macro_use]
26extern crate log;
27
28#[cfg(not(feature = "logging"))]
29#[macro_use]
30mod log_stubs;
31
32#[macro_use]
33mod extra_assertions;
34
35mod codegen;
36mod deps;
37mod options;
38mod time;
39
40pub mod callbacks;
41
42mod clang;
43#[cfg(feature = "experimental")]
44mod diagnostics;
45mod features;
46mod ir;
47mod parse;
48mod regex_set;
49
50pub use codegen::{
51 AliasVariation, EnumVariation, MacroTypeVariation, NonCopyUnionStyle,
52};
53pub use features::{RustEdition, RustTarget, LATEST_STABLE_RUST};
54pub use ir::annotations::FieldVisibilityKind;
55pub use ir::function::Abi;
56#[cfg(feature = "__cli")]
57pub use options::cli::builder_from_flags;
58
59use codegen::CodegenError;
60use features::RustFeatures;
61use ir::comment;
62use ir::context::{BindgenContext, ItemId};
63use ir::item::Item;
64use options::BindgenOptions;
65use parse::ParseError;
66
67use std::borrow::Cow;
68use std::collections::hash_map::Entry;
69use std::env;
70use std::ffi::OsStr;
71use std::fs::{File, OpenOptions};
72use std::io::{self, Write};
73use std::mem::size_of;
74use std::path::{Path, PathBuf};
75use std::process::{Command, Stdio};
76use std::rc::Rc;
77use std::str::FromStr;
78
79type HashMap<K, V> = rustc_hash::FxHashMap<K, V>;
81type HashSet<K> = rustc_hash::FxHashSet<K>;
82
83pub const DEFAULT_ANON_FIELDS_PREFIX: &str = "__bindgen_anon_";
85
86const DEFAULT_NON_EXTERN_FNS_SUFFIX: &str = "__extern";
87
88fn file_is_cpp(name_file: &str) -> bool {
89 Path::new(name_file).extension().is_some_and(|ext| {
90 ext.eq_ignore_ascii_case("hpp")
91 || ext.eq_ignore_ascii_case("hxx")
92 || ext.eq_ignore_ascii_case("hh")
93 || ext.eq_ignore_ascii_case("h++")
94 })
95}
96
97fn args_are_cpp(clang_args: &[Box<str>]) -> bool {
98 for w in clang_args.windows(2) {
99 if w[0].as_ref() == "-xc++" || w[1].as_ref() == "-xc++" {
100 return true;
101 }
102 if w[0].as_ref() == "-x" && w[1].as_ref() == "c++" {
103 return true;
104 }
105 if w[0].as_ref() == "-include" && file_is_cpp(w[1].as_ref()) {
106 return true;
107 }
108 }
109 false
110}
111
112bitflags! {
113 #[derive(Copy, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
115 pub struct CodegenConfig: u32 {
116 const FUNCTIONS = 1 << 0;
118 const TYPES = 1 << 1;
120 const VARS = 1 << 2;
122 const METHODS = 1 << 3;
124 const CONSTRUCTORS = 1 << 4;
126 const DESTRUCTORS = 1 << 5;
128 }
129}
130
131impl CodegenConfig {
132 pub fn functions(self) -> bool {
134 self.contains(CodegenConfig::FUNCTIONS)
135 }
136
137 pub fn types(self) -> bool {
139 self.contains(CodegenConfig::TYPES)
140 }
141
142 pub fn vars(self) -> bool {
144 self.contains(CodegenConfig::VARS)
145 }
146
147 pub fn methods(self) -> bool {
149 self.contains(CodegenConfig::METHODS)
150 }
151
152 pub fn constructors(self) -> bool {
154 self.contains(CodegenConfig::CONSTRUCTORS)
155 }
156
157 pub fn destructors(self) -> bool {
159 self.contains(CodegenConfig::DESTRUCTORS)
160 }
161}
162
163impl Default for CodegenConfig {
164 fn default() -> Self {
165 CodegenConfig::all()
166 }
167}
168
169#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
171#[non_exhaustive]
172pub enum Formatter {
173 None,
175 #[default]
177 Rustfmt,
178 #[cfg(feature = "prettyplease")]
179 Prettyplease,
181}
182
183impl FromStr for Formatter {
184 type Err = String;
185
186 fn from_str(s: &str) -> Result<Self, Self::Err> {
187 match s {
188 "none" => Ok(Self::None),
189 "rustfmt" => Ok(Self::Rustfmt),
190 #[cfg(feature = "prettyplease")]
191 "prettyplease" => Ok(Self::Prettyplease),
192 _ => Err(format!("`{s}` is not a valid formatter")),
193 }
194 }
195}
196
197impl std::fmt::Display for Formatter {
198 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
199 let s = match self {
200 Self::None => "none",
201 Self::Rustfmt => "rustfmt",
202 #[cfg(feature = "prettyplease")]
203 Self::Prettyplease => "prettyplease",
204 };
205
206 std::fmt::Display::fmt(&s, f)
207 }
208}
209
210#[derive(Debug, Default, Clone)]
286pub struct Builder {
287 options: BindgenOptions,
288}
289
290pub fn builder() -> Builder {
292 Default::default()
293}
294
295fn get_extra_clang_args(
296 parse_callbacks: &[Rc<dyn callbacks::ParseCallbacks>],
297) -> Vec<String> {
298 let Some(extra_clang_args) = get_target_dependent_env_var(
300 parse_callbacks,
301 "BINDGEN_EXTRA_CLANG_ARGS",
302 ) else {
303 return vec![];
304 };
305
306 if let Some(strings) = shlex::split(&extra_clang_args) {
308 return strings;
309 }
310 vec![extra_clang_args]
311}
312
313impl Builder {
314 pub fn generate(mut self) -> Result<Bindings, BindgenError> {
316 self.options.rust_features = match self.options.rust_edition {
318 Some(edition) => {
319 if !edition.is_available(self.options.rust_target) {
320 return Err(BindgenError::UnsupportedEdition(
321 edition,
322 self.options.rust_target,
323 ));
324 }
325 RustFeatures::new(self.options.rust_target, edition)
326 }
327 None => {
328 RustFeatures::new_with_latest_edition(self.options.rust_target)
329 }
330 };
331
332 self.options.clang_args.extend(
334 get_extra_clang_args(&self.options.parse_callbacks)
335 .into_iter()
336 .map(String::into_boxed_str),
337 );
338
339 for header in &self.options.input_headers {
340 self.options
341 .for_each_callback(|cb| cb.header_file(header.as_ref()));
342 }
343
344 self.options.fallback_clang_args = self
346 .options
347 .clang_args
348 .iter()
349 .filter(|arg| {
350 !arg.starts_with("-MMD")
351 && !arg.starts_with("-MD")
352 && !arg.starts_with("--write-user-dependencies")
353 && !arg.starts_with("--user-dependencies")
354 })
355 .cloned()
356 .collect::<Vec<_>>();
357 self.options.clang_args.extend(
358 self.options.input_headers
359 [..self.options.input_headers.len().saturating_sub(1)]
360 .iter()
361 .flat_map(|header| ["-include".into(), header.clone()]),
362 );
363
364 let input_unsaved_files =
365 std::mem::take(&mut self.options.input_header_contents)
366 .into_iter()
367 .map(|(name, contents)| {
368 clang::UnsavedFile::new(name.as_ref(), contents.as_ref())
369 })
370 .collect::<Vec<_>>();
371
372 Bindings::generate(self.options, &input_unsaved_files)
373 }
374
375 pub fn dump_preprocessed_input(&self) -> io::Result<()> {
381 let clang = ext_php_rs_clang_sys::support::Clang::find(None, &[])
382 .ok_or_else(|| {
383 io::Error::new(
384 io::ErrorKind::Other,
385 "Cannot find clang executable",
386 )
387 })?;
388
389 let mut wrapper_contents = String::new();
392
393 let mut is_cpp = args_are_cpp(&self.options.clang_args);
395
396 for header in &self.options.input_headers {
398 is_cpp |= file_is_cpp(header);
399
400 wrapper_contents.push_str("#include \"");
401 wrapper_contents.push_str(header);
402 wrapper_contents.push_str("\"\n");
403 }
404
405 for (name, contents) in &self.options.input_header_contents {
408 is_cpp |= file_is_cpp(name);
409
410 wrapper_contents.push_str("#line 0 \"");
411 wrapper_contents.push_str(name);
412 wrapper_contents.push_str("\"\n");
413 wrapper_contents.push_str(contents);
414 }
415
416 let wrapper_path = PathBuf::from(if is_cpp {
417 "__bindgen.cpp"
418 } else {
419 "__bindgen.c"
420 });
421
422 {
423 let mut wrapper_file = File::create(&wrapper_path)?;
424 wrapper_file.write_all(wrapper_contents.as_bytes())?;
425 }
426
427 let mut cmd = Command::new(clang.path);
428 cmd.arg("-save-temps")
429 .arg("-E")
430 .arg("-C")
431 .arg("-c")
432 .arg(&wrapper_path)
433 .stdout(Stdio::piped());
434
435 for a in &self.options.clang_args {
436 cmd.arg(a.as_ref());
437 }
438
439 for a in get_extra_clang_args(&self.options.parse_callbacks) {
440 cmd.arg(a);
441 }
442
443 let mut child = cmd.spawn()?;
444
445 let mut preprocessed = child.stdout.take().unwrap();
446 let mut file = File::create(if is_cpp {
447 "__bindgen.ii"
448 } else {
449 "__bindgen.i"
450 })?;
451 io::copy(&mut preprocessed, &mut file)?;
452
453 if child.wait()?.success() {
454 Ok(())
455 } else {
456 Err(io::Error::new(
457 io::ErrorKind::Other,
458 "clang exited with non-zero status",
459 ))
460 }
461 }
462}
463
464impl BindgenOptions {
465 fn build(&mut self) {
466 const REGEX_SETS_LEN: usize = 29;
467
468 let regex_sets: [_; REGEX_SETS_LEN] = [
469 &mut self.blocklisted_types,
470 &mut self.blocklisted_functions,
471 &mut self.blocklisted_items,
472 &mut self.blocklisted_files,
473 &mut self.blocklisted_vars,
474 &mut self.opaque_types,
475 &mut self.allowlisted_vars,
476 &mut self.allowlisted_types,
477 &mut self.allowlisted_functions,
478 &mut self.allowlisted_files,
479 &mut self.allowlisted_items,
480 &mut self.bitfield_enums,
481 &mut self.constified_enums,
482 &mut self.constified_enum_modules,
483 &mut self.newtype_enums,
484 &mut self.newtype_global_enums,
485 &mut self.rustified_enums,
486 &mut self.rustified_non_exhaustive_enums,
487 &mut self.type_alias,
488 &mut self.new_type_alias,
489 &mut self.new_type_alias_deref,
490 &mut self.bindgen_wrapper_union,
491 &mut self.manually_drop_union,
492 &mut self.no_partialeq_types,
493 &mut self.no_copy_types,
494 &mut self.no_debug_types,
495 &mut self.no_default_types,
496 &mut self.no_hash_types,
497 &mut self.must_use_types,
498 ];
499
500 let record_matches = self.record_matches;
501 #[cfg(feature = "experimental")]
502 {
503 let sets_len = REGEX_SETS_LEN + self.abi_overrides.len();
504 let names = if self.emit_diagnostics {
505 <[&str; REGEX_SETS_LEN]>::into_iter([
506 "--blocklist-type",
507 "--blocklist-function",
508 "--blocklist-item",
509 "--blocklist-file",
510 "--blocklist-var",
511 "--opaque-type",
512 "--allowlist-type",
513 "--allowlist-function",
514 "--allowlist-var",
515 "--allowlist-file",
516 "--allowlist-item",
517 "--bitfield-enum",
518 "--newtype-enum",
519 "--newtype-global-enum",
520 "--rustified-enum",
521 "--rustified-enum-non-exhaustive",
522 "--constified-enum-module",
523 "--constified-enum",
524 "--type-alias",
525 "--new-type-alias",
526 "--new-type-alias-deref",
527 "--bindgen-wrapper-union",
528 "--manually-drop-union",
529 "--no-partialeq",
530 "--no-copy",
531 "--no-debug",
532 "--no-default",
533 "--no-hash",
534 "--must-use",
535 ])
536 .chain((0..self.abi_overrides.len()).map(|_| "--override-abi"))
537 .map(Some)
538 .collect()
539 } else {
540 vec![None; sets_len]
541 };
542
543 for (regex_set, name) in
544 self.abi_overrides.values_mut().chain(regex_sets).zip(names)
545 {
546 regex_set.build_with_diagnostics(record_matches, name);
547 }
548 }
549 #[cfg(not(feature = "experimental"))]
550 for regex_set in self.abi_overrides.values_mut().chain(regex_sets) {
551 regex_set.build(record_matches);
552 }
553 }
554
555 pub fn set_rust_target(&mut self, rust_target: RustTarget) {
557 self.rust_target = rust_target;
558 }
559
560 pub fn rust_features(&self) -> RustFeatures {
562 self.rust_features
563 }
564
565 fn last_callback<T>(
566 &self,
567 f: impl Fn(&dyn callbacks::ParseCallbacks) -> Option<T>,
568 ) -> Option<T> {
569 self.parse_callbacks
570 .iter()
571 .filter_map(|cb| f(cb.as_ref()))
572 .next_back()
573 }
574
575 fn all_callbacks<T>(
576 &self,
577 f: impl Fn(&dyn callbacks::ParseCallbacks) -> Vec<T>,
578 ) -> Vec<T> {
579 self.parse_callbacks
580 .iter()
581 .flat_map(|cb| f(cb.as_ref()))
582 .collect()
583 }
584
585 fn for_each_callback(&self, f: impl Fn(&dyn callbacks::ParseCallbacks)) {
586 self.parse_callbacks.iter().for_each(|cb| f(cb.as_ref()));
587 }
588
589 fn process_comment(&self, comment: &str) -> String {
590 let comment = comment::preprocess(comment);
591 self.last_callback(|cb| cb.process_comment(&comment))
592 .unwrap_or(comment)
593 }
594}
595
596#[cfg(feature = "runtime")]
597fn ensure_libclang_is_loaded() {
598 use std::sync::{Arc, OnceLock};
599
600 if ext_php_rs_clang_sys::is_loaded() {
601 return;
602 }
603
604 static LIBCLANG: OnceLock<Arc<ext_php_rs_clang_sys::SharedLibrary>> =
609 OnceLock::new();
610 let libclang = LIBCLANG.get_or_init(|| {
611 ext_php_rs_clang_sys::load().expect("Unable to find libclang");
612 ext_php_rs_clang_sys::get_library()
613 .expect("We just loaded libclang and it had better still be here!")
614 });
615
616 ext_php_rs_clang_sys::set_library(Some(libclang.clone()));
617}
618
619#[cfg(not(feature = "runtime"))]
620fn ensure_libclang_is_loaded() {}
621
622#[derive(Debug, Clone, PartialEq, Eq, Hash)]
624#[non_exhaustive]
625pub enum BindgenError {
626 FolderAsHeader(PathBuf),
628 InsufficientPermissions(PathBuf),
630 NotExist(PathBuf),
632 ClangDiagnostic(String),
634 Codegen(CodegenError),
636 UnsupportedEdition(RustEdition, RustTarget),
638}
639
640impl std::fmt::Display for BindgenError {
641 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
642 match self {
643 BindgenError::FolderAsHeader(h) => {
644 write!(f, "'{}' is a folder", h.display())
645 }
646 BindgenError::InsufficientPermissions(h) => {
647 write!(f, "insufficient permissions to read '{}'", h.display())
648 }
649 BindgenError::NotExist(h) => {
650 write!(f, "header '{}' does not exist.", h.display())
651 }
652 BindgenError::ClangDiagnostic(message) => {
653 write!(f, "clang diagnosed error: {message}")
654 }
655 BindgenError::Codegen(err) => {
656 write!(f, "codegen error: {err}")
657 }
658 BindgenError::UnsupportedEdition(edition, target) => {
659 write!(f, "edition {edition} is not available on Rust {target}")
660 }
661 }
662 }
663}
664
665impl std::error::Error for BindgenError {}
666
667#[derive(Debug)]
669pub struct Bindings {
670 options: BindgenOptions,
671 module: proc_macro2::TokenStream,
672}
673
674pub(crate) const HOST_TARGET: &str =
675 include_str!(concat!(env!("OUT_DIR"), "/host-target.txt"));
676
677fn rust_to_clang_target(rust_target: &str) -> Box<str> {
680 const TRIPLE_HYPHENS_MESSAGE: &str = "Target triple should contain hyphens";
681
682 let mut triple: Vec<&str> = rust_target.split_terminator('-').collect();
683
684 assert!(!triple.is_empty(), "{TRIPLE_HYPHENS_MESSAGE}");
685 triple.resize(4, "");
686
687 if triple[0].starts_with("riscv32") {
689 triple[0] = "riscv32";
690 } else if triple[0].starts_with("riscv64") {
691 triple[0] = "riscv64";
692 }
693
694 if triple[1] == "apple" {
696 if triple[0] == "aarch64" {
697 triple[0] = "arm64";
698 }
699 if triple[3] == "sim" {
700 triple[3] = "simulator";
701 }
702 }
703
704 if triple[2] == "espidf" {
706 triple[2] = "elf";
707 }
708
709 triple
710 .iter()
711 .skip(1)
712 .fold(triple[0].to_string(), |triple, part| {
713 if part.is_empty() {
714 triple
715 } else {
716 triple + "-" + part
717 }
718 })
719 .into()
720}
721
722fn find_effective_target(clang_args: &[Box<str>]) -> (Box<str>, bool) {
725 let mut args = clang_args.iter();
726 while let Some(opt) = args.next() {
727 if opt.starts_with("--target=") {
728 let mut split = opt.split('=');
729 split.next();
730 return (split.next().unwrap().into(), true);
731 }
732
733 if opt.as_ref() == "-target" {
734 if let Some(target) = args.next() {
735 return (target.clone(), true);
736 }
737 }
738 }
739
740 if let Ok(t) = env::var("TARGET") {
742 return (rust_to_clang_target(&t), false);
743 }
744
745 (rust_to_clang_target(HOST_TARGET), false)
746}
747
748impl Bindings {
749 pub(crate) fn generate(
751 mut options: BindgenOptions,
752 input_unsaved_files: &[clang::UnsavedFile],
753 ) -> Result<Bindings, BindgenError> {
754 ensure_libclang_is_loaded();
755
756 #[cfg(feature = "runtime")]
757 match ext_php_rs_clang_sys::get_library().unwrap().version() {
758 None => {
759 warn!("Could not detect a Clang version, make sure you are using libclang 9 or newer");
760 }
761 Some(version) => {
762 if version < ext_php_rs_clang_sys::Version::V9_0 {
763 warn!("Detected Clang version {version:?} which is unsupported and can cause invalid code generation, use libclang 9 or newer");
764 }
765 }
766 }
767
768 #[cfg(feature = "runtime")]
769 debug!(
770 "Generating bindings, libclang at {}",
771 ext_php_rs_clang_sys::get_library()
772 .unwrap()
773 .path()
774 .display()
775 );
776 #[cfg(not(feature = "runtime"))]
777 debug!("Generating bindings, libclang linked");
778
779 options.build();
780
781 let (effective_target, explicit_target) =
782 find_effective_target(&options.clang_args);
783
784 let is_host_build =
785 rust_to_clang_target(HOST_TARGET) == effective_target;
786
787 if !explicit_target && !is_host_build {
793 options.clang_args.insert(
794 0,
795 format!("--target={effective_target}").into_boxed_str(),
796 );
797 }
798
799 fn detect_include_paths(options: &mut BindgenOptions) {
800 if !options.detect_include_paths {
801 return;
802 }
803
804 let clang_args_for_clang_sys = {
807 let mut last_was_include_prefix = false;
808 options
809 .clang_args
810 .iter()
811 .filter(|arg| {
812 if last_was_include_prefix {
813 last_was_include_prefix = false;
814 return false;
815 }
816
817 let arg = arg.as_ref();
818
819 if arg == "-I" || arg == "--include-directory" {
822 last_was_include_prefix = true;
823 return false;
824 }
825
826 if arg.starts_with("-I")
827 || arg.starts_with("--include-directory=")
828 {
829 return false;
830 }
831
832 true
833 })
834 .map(|arg| arg.clone().into())
835 .collect::<Vec<_>>()
836 };
837
838 debug!(
839 "Trying to find clang with flags: {clang_args_for_clang_sys:?}"
840 );
841
842 let Some(clang) = ext_php_rs_clang_sys::support::Clang::find(
843 None,
844 &clang_args_for_clang_sys,
845 ) else {
846 return;
847 };
848
849 debug!("Found clang: {clang:?}");
850
851 let is_cpp = args_are_cpp(&options.clang_args)
853 || options.input_headers.iter().any(|h| file_is_cpp(h));
854
855 let search_paths = if is_cpp {
856 clang.cpp_search_paths
857 } else {
858 clang.c_search_paths
859 };
860
861 if let Some(search_paths) = search_paths {
862 for path in search_paths {
863 if let Ok(path) = path.into_os_string().into_string() {
864 options.clang_args.push("-isystem".into());
865 options.clang_args.push(path.into_boxed_str());
866 }
867 }
868 }
869 }
870
871 detect_include_paths(&mut options);
872
873 #[cfg(unix)]
874 fn can_read(perms: &std::fs::Permissions) -> bool {
875 use std::os::unix::fs::PermissionsExt;
876 perms.mode() & 0o444 > 0
877 }
878
879 #[cfg(not(unix))]
880 fn can_read(_: &std::fs::Permissions) -> bool {
881 true
882 }
883
884 if let Some(h) = options.input_headers.last() {
885 let path = Path::new(h.as_ref());
886 if let Ok(md) = std::fs::metadata(path) {
887 if md.is_dir() {
888 return Err(BindgenError::FolderAsHeader(path.into()));
889 }
890 if !can_read(&md.permissions()) {
891 return Err(BindgenError::InsufficientPermissions(
892 path.into(),
893 ));
894 }
895 options.clang_args.push(h.clone());
896 } else {
897 return Err(BindgenError::NotExist(path.into()));
898 }
899 }
900
901 for (idx, f) in input_unsaved_files.iter().enumerate() {
902 if idx != 0 || !options.input_headers.is_empty() {
903 options.clang_args.push("-include".into());
904 }
905 options.clang_args.push(f.name.to_str().unwrap().into());
906 }
907
908 debug!("Fixed-up options: {options:?}");
909
910 let time_phases = options.time_phases;
911 let mut context = BindgenContext::new(options, input_unsaved_files);
912
913 if is_host_build {
914 debug_assert_eq!(
915 context.target_pointer_size(),
916 size_of::<*mut ()>(),
917 "{effective_target:?} {HOST_TARGET:?}"
918 );
919 }
920
921 {
922 let _t = time::Timer::new("parse").with_output(time_phases);
923 parse(&mut context)?;
924 }
925
926 let (module, options) =
927 codegen::codegen(context).map_err(BindgenError::Codegen)?;
928
929 Ok(Bindings { options, module })
930 }
931
932 pub fn write_to_file<P: AsRef<Path>>(&self, path: P) -> io::Result<()> {
934 let file = OpenOptions::new()
935 .write(true)
936 .truncate(true)
937 .create(true)
938 .open(path.as_ref())?;
939 self.write(file)?;
940 Ok(())
941 }
942
943 pub fn write(&self, mut writer: impl Write) -> io::Result<()> {
945 const NL: &str = if cfg!(windows) { "\r\n" } else { "\n" };
946
947 if !self.options.disable_header_comment {
948 let version =
949 option_env!("CARGO_PKG_VERSION").unwrap_or("(unknown version)");
950 write!(
951 writer,
952 "/* automatically generated by rust-bindgen {version} */{NL}{NL}",
953 )?;
954 }
955
956 for line in &self.options.raw_lines {
957 writer.write_all(line.as_bytes())?;
958 writer.write_all(NL.as_bytes())?;
959 }
960
961 if !self.options.raw_lines.is_empty() {
962 writer.write_all(NL.as_bytes())?;
963 }
964
965 match self.format_tokens(&self.module) {
966 Ok(formatted_bindings) => {
967 writer.write_all(formatted_bindings.as_bytes())?;
968 }
969 Err(err) => {
970 eprintln!(
971 "Failed to run rustfmt: {err} (non-fatal, continuing)"
972 );
973 writer.write_all(self.module.to_string().as_bytes())?;
974 }
975 }
976 Ok(())
977 }
978
979 fn rustfmt_path(&self) -> Cow<'_, Path> {
981 debug_assert!(matches!(self.options.formatter, Formatter::Rustfmt));
982 if let Some(ref p) = self.options.rustfmt_path {
983 Cow::Borrowed(p)
984 } else if let Ok(rustfmt) = env::var("RUSTFMT") {
985 Cow::Owned(rustfmt.into())
986 } else {
987 Cow::Borrowed(Path::new("rustfmt"))
990 }
991 }
992
993 fn format_tokens(
995 &self,
996 tokens: &proc_macro2::TokenStream,
997 ) -> io::Result<String> {
998 let _t = time::Timer::new("rustfmt_generated_string")
999 .with_output(self.options.time_phases);
1000
1001 match self.options.formatter {
1002 Formatter::None => return Ok(tokens.to_string()),
1003 #[cfg(feature = "prettyplease")]
1004 Formatter::Prettyplease => {
1005 return Ok(prettyplease::unparse(&syn::parse_quote!(#tokens)));
1006 }
1007 Formatter::Rustfmt => (),
1008 }
1009
1010 let rustfmt = self.rustfmt_path();
1011 let mut cmd = Command::new(&*rustfmt);
1012
1013 cmd.stdin(Stdio::piped()).stdout(Stdio::piped());
1014
1015 if let Some(path) = self
1016 .options
1017 .rustfmt_configuration_file
1018 .as_ref()
1019 .and_then(|f| f.to_str())
1020 {
1021 cmd.args(["--config-path", path]);
1022 }
1023
1024 let edition = self
1025 .options
1026 .rust_edition
1027 .unwrap_or_else(|| self.options.rust_target.latest_edition());
1028 cmd.args(["--edition", &format!("{edition}")]);
1029
1030 let mut child = cmd.spawn()?;
1031 let mut child_stdin = child.stdin.take().unwrap();
1032 let mut child_stdout = child.stdout.take().unwrap();
1033
1034 let source = tokens.to_string();
1035
1036 let stdin_handle = ::std::thread::spawn(move || {
1040 let _ = child_stdin.write_all(source.as_bytes());
1041 source
1042 });
1043
1044 let mut output = vec![];
1045 io::copy(&mut child_stdout, &mut output)?;
1046
1047 let status = child.wait()?;
1048 let source = stdin_handle.join().expect(
1049 "The thread writing to rustfmt's stdin doesn't do \
1050 anything that could panic",
1051 );
1052
1053 match String::from_utf8(output) {
1054 Ok(bindings) => match status.code() {
1055 Some(0) => Ok(bindings),
1056 Some(2) => Err(io::Error::new(
1057 io::ErrorKind::Other,
1058 "Rustfmt parsing errors.".to_string(),
1059 )),
1060 Some(3) => {
1061 rustfmt_non_fatal_error_diagnostic(
1062 "Rustfmt could not format some lines",
1063 &self.options,
1064 );
1065 Ok(bindings)
1066 }
1067 _ => Err(io::Error::new(
1068 io::ErrorKind::Other,
1069 "Internal rustfmt error".to_string(),
1070 )),
1071 },
1072 _ => Ok(source),
1073 }
1074 }
1075}
1076
1077fn rustfmt_non_fatal_error_diagnostic(msg: &str, _options: &BindgenOptions) {
1078 warn!("{msg}");
1079
1080 #[cfg(feature = "experimental")]
1081 if _options.emit_diagnostics {
1082 use crate::diagnostics::{Diagnostic, Level};
1083
1084 Diagnostic::default()
1085 .with_title(msg, Level::Warning)
1086 .add_annotation(
1087 "The bindings will be generated but not formatted.",
1088 Level::Note,
1089 )
1090 .display();
1091 }
1092}
1093
1094impl std::fmt::Display for Bindings {
1095 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1096 let mut bytes = vec![];
1097 self.write(&mut bytes)
1098 .expect("writing to a vec cannot fail");
1099 f.write_str(
1100 std::str::from_utf8(&bytes)
1101 .expect("we should only write bindings that are valid utf-8"),
1102 )
1103 }
1104}
1105
1106fn filter_builtins(ctx: &BindgenContext, cursor: &clang::Cursor) -> bool {
1109 ctx.options().builtins || !cursor.is_builtin()
1110}
1111
1112fn parse_one(
1114 ctx: &mut BindgenContext,
1115 cursor: clang::Cursor,
1116 parent: Option<ItemId>,
1117) {
1118 if !filter_builtins(ctx, &cursor) {
1119 return;
1120 }
1121
1122 match Item::parse(cursor, parent, ctx) {
1123 Ok(..) => {}
1124 Err(ParseError::Continue) => {}
1125 Err(ParseError::Recurse) => {
1126 cursor
1127 .visit_sorted(ctx, |ctx, child| parse_one(ctx, child, parent));
1128 }
1129 }
1130}
1131
1132fn parse(context: &mut BindgenContext) -> Result<(), BindgenError> {
1134 use ext_php_rs_clang_sys::*;
1135
1136 let mut error = None;
1137 for d in &context.translation_unit().diags() {
1138 let msg = d.format();
1139 let is_err = d.severity() >= CXDiagnostic_Error;
1140 if is_err {
1141 let error = error.get_or_insert_with(String::new);
1142 error.push_str(&msg);
1143 error.push('\n');
1144 } else {
1145 eprintln!("clang diag: {msg}");
1146 }
1147 }
1148
1149 if let Some(message) = error {
1150 return Err(BindgenError::ClangDiagnostic(message));
1151 }
1152
1153 let cursor = context.translation_unit().cursor();
1154
1155 if context.options().emit_ast {
1156 fn dump_if_not_builtin(cur: &clang::Cursor) -> CXChildVisitResult {
1157 if cur.is_builtin() {
1158 CXChildVisit_Continue
1159 } else {
1160 clang::ast_dump(cur, 0)
1161 }
1162 }
1163 cursor.visit(|cur| dump_if_not_builtin(&cur));
1164 }
1165
1166 let root = context.root_module();
1167 context.with_module(root, |ctx| {
1168 cursor.visit_sorted(ctx, |ctx, child| parse_one(ctx, child, None));
1169 });
1170
1171 assert_eq!(
1172 context.current_module(),
1173 context.root_module(),
1174 "How did this happen?"
1175 );
1176 Ok(())
1177}
1178
1179#[derive(Debug)]
1181pub struct ClangVersion {
1182 pub parsed: Option<(u32, u32)>,
1184 pub full: String,
1186}
1187
1188pub fn clang_version() -> ClangVersion {
1190 ensure_libclang_is_loaded();
1191
1192 let raw_v: String = clang::extract_clang_version();
1194 let split_v: Option<Vec<&str>> = raw_v
1195 .split_whitespace()
1196 .find(|t| t.chars().next().is_some_and(|v| v.is_ascii_digit()))
1197 .map(|v| v.split('.').collect());
1198 if let Some(v) = split_v {
1199 if v.len() >= 2 {
1200 let maybe_major = v[0].parse::<u32>();
1201 let maybe_minor = v[1].parse::<u32>();
1202 if let (Ok(major), Ok(minor)) = (maybe_major, maybe_minor) {
1203 return ClangVersion {
1204 parsed: Some((major, minor)),
1205 full: raw_v.clone(),
1206 };
1207 }
1208 }
1209 }
1210 ClangVersion {
1211 parsed: None,
1212 full: raw_v.clone(),
1213 }
1214}
1215
1216fn env_var<K: AsRef<str> + AsRef<OsStr>>(
1217 parse_callbacks: &[Rc<dyn callbacks::ParseCallbacks>],
1218 key: K,
1219) -> Result<String, env::VarError> {
1220 for callback in parse_callbacks {
1221 callback.read_env_var(key.as_ref());
1222 }
1223 env::var(key)
1224}
1225
1226fn get_target_dependent_env_var(
1228 parse_callbacks: &[Rc<dyn callbacks::ParseCallbacks>],
1229 var: &str,
1230) -> Option<String> {
1231 if let Ok(target) = env_var(parse_callbacks, "TARGET") {
1232 if let Ok(v) = env_var(parse_callbacks, format!("{var}_{target}")) {
1233 return Some(v);
1234 }
1235 if let Ok(v) = env_var(
1236 parse_callbacks,
1237 format!("{var}_{}", target.replace('-', "_")),
1238 ) {
1239 return Some(v);
1240 }
1241 }
1242
1243 env_var(parse_callbacks, var).ok()
1244}
1245
1246#[derive(Debug)]
1259pub struct CargoCallbacks {
1260 rerun_on_header_files: bool,
1261}
1262
1263#[deprecated = "Use `CargoCallbacks::new()` instead. Please, check the documentation for further information."]
1268pub const CargoCallbacks: CargoCallbacks = CargoCallbacks {
1269 rerun_on_header_files: false,
1270};
1271
1272impl CargoCallbacks {
1273 pub fn new() -> Self {
1275 Self {
1276 rerun_on_header_files: true,
1277 }
1278 }
1279
1280 pub fn rerun_on_header_files(mut self, doit: bool) -> Self {
1285 self.rerun_on_header_files = doit;
1286 self
1287 }
1288}
1289
1290impl Default for CargoCallbacks {
1291 fn default() -> Self {
1292 Self::new()
1293 }
1294}
1295
1296impl callbacks::ParseCallbacks for CargoCallbacks {
1297 fn header_file(&self, filename: &str) {
1298 if self.rerun_on_header_files {
1299 println!("cargo:rerun-if-changed={filename}");
1300 }
1301 }
1302
1303 fn include_file(&self, filename: &str) {
1304 println!("cargo:rerun-if-changed={filename}");
1305 }
1306
1307 fn read_env_var(&self, key: &str) {
1308 println!("cargo:rerun-if-env-changed={key}");
1309 }
1310}
1311
1312#[test]
1314fn commandline_flag_unit_test_function() {
1315 let bindings = builder();
1317 let command_line_flags = bindings.command_line_flags();
1318
1319 let test_cases = [
1320 "--rust-target",
1321 "--no-derive-default",
1322 "--generate",
1323 "functions,types,vars,methods,constructors,destructors",
1324 ]
1325 .iter()
1326 .map(|&x| x.into())
1327 .collect::<Vec<String>>();
1328
1329 assert!(test_cases.iter().all(|x| command_line_flags.contains(x)));
1330
1331 let bindings = builder()
1333 .header("input_header")
1334 .allowlist_type("Distinct_Type")
1335 .allowlist_function("safe_function");
1336
1337 let command_line_flags = bindings.command_line_flags();
1338 let test_cases = [
1339 "--rust-target",
1340 "input_header",
1341 "--no-derive-default",
1342 "--generate",
1343 "functions,types,vars,methods,constructors,destructors",
1344 "--allowlist-type",
1345 "Distinct_Type",
1346 "--allowlist-function",
1347 "safe_function",
1348 ]
1349 .iter()
1350 .map(|&x| x.into())
1351 .collect::<Vec<String>>();
1352 println!("{command_line_flags:?}");
1353
1354 assert!(test_cases.iter().all(|x| command_line_flags.contains(x)));
1355}
1356
1357#[test]
1358fn test_rust_to_clang_target() {
1359 assert_eq!(
1360 rust_to_clang_target("aarch64-apple-ios").as_ref(),
1361 "arm64-apple-ios"
1362 );
1363}
1364
1365#[test]
1366fn test_rust_to_clang_target_riscv() {
1367 assert_eq!(
1368 rust_to_clang_target("riscv64gc-unknown-linux-gnu").as_ref(),
1369 "riscv64-unknown-linux-gnu"
1370 );
1371 assert_eq!(
1372 rust_to_clang_target("riscv64imac-unknown-none-elf").as_ref(),
1373 "riscv64-unknown-none-elf"
1374 );
1375 assert_eq!(
1376 rust_to_clang_target("riscv32imc-unknown-none-elf").as_ref(),
1377 "riscv32-unknown-none-elf"
1378 );
1379 assert_eq!(
1380 rust_to_clang_target("riscv32imac-unknown-none-elf").as_ref(),
1381 "riscv32-unknown-none-elf"
1382 );
1383 assert_eq!(
1384 rust_to_clang_target("riscv32imafc-unknown-none-elf").as_ref(),
1385 "riscv32-unknown-none-elf"
1386 );
1387 assert_eq!(
1388 rust_to_clang_target("riscv32i-unknown-none-elf").as_ref(),
1389 "riscv32-unknown-none-elf"
1390 );
1391}
1392
1393#[test]
1394fn test_rust_to_clang_target_espidf() {
1395 assert_eq!(
1396 rust_to_clang_target("riscv32imc-esp-espidf").as_ref(),
1397 "riscv32-esp-elf"
1398 );
1399 assert_eq!(
1400 rust_to_clang_target("xtensa-esp32-espidf").as_ref(),
1401 "xtensa-esp32-elf"
1402 );
1403}
1404
1405#[test]
1406fn test_rust_to_clang_target_simulator() {
1407 assert_eq!(
1408 rust_to_clang_target("aarch64-apple-ios-sim").as_ref(),
1409 "arm64-apple-ios-simulator"
1410 );
1411 assert_eq!(
1412 rust_to_clang_target("aarch64-apple-tvos-sim").as_ref(),
1413 "arm64-apple-tvos-simulator"
1414 );
1415 assert_eq!(
1416 rust_to_clang_target("aarch64-apple-watchos-sim").as_ref(),
1417 "arm64-apple-watchos-simulator"
1418 );
1419}