1pub use crate::{
2 config::{Config, ConfigMap},
3 sdk::{SdkPath, SdkPathError},
4};
5
6#[derive(Debug)]
7pub struct Builder {
8 framework: String,
9 sdk: SdkPath,
10 target: Option<String>,
11 config: Config,
12}
13
14impl Builder {
15 pub fn new(
16 framework: &str,
17 sdk: impl TryInto<SdkPath, Error = SdkPathError>,
18 config: Config,
19 ) -> Result<Self, SdkPathError> {
20 Ok(Self {
21 framework: framework.to_owned(),
22 sdk: sdk.try_into()?,
23 target: None,
24 config,
25 })
26 }
27
28 pub fn with_builtin_config(
29 framework: &str,
30 sdk: impl TryInto<SdkPath, Error = SdkPathError>,
31 ) -> Result<Self, SdkPathError> {
32 Self::new(
33 framework,
34 sdk,
35 ConfigMap::with_builtin_config().framework_config(framework),
36 )
37 }
38
39 pub fn target(mut self, target: impl AsRef<str>) -> Self {
40 assert!(self.target.is_none());
41 self.target = Some(target.as_ref().to_owned());
42 self
43 }
44
45 pub fn bindgen_builder(&self) -> bindgen::Builder {
46 let mut builder = bindgen::Builder::default();
48
49 let mut clang_args = vec!["-x", "objective-c", "-fblocks", "-fmodules"];
50 let target_arg;
51 if let Some(target) = self.target.as_ref() {
52 target_arg = format!("--target={}", target);
53 clang_args.push(&target_arg);
54 }
55
56 clang_args.extend(&[
57 "-isysroot",
58 self.sdk
59 .path()
60 .to_str()
61 .expect("sdk path is not utf-8 representable"),
62 ]);
63
64 builder = builder
65 .clang_args(&clang_args)
66 .layout_tests(self.config.layout_tests)
67 .formatter(bindgen::Formatter::Prettyplease);
68
69 for opaque_type in &self.config.opaque_types {
70 builder = builder.opaque_type(opaque_type);
71 }
72 for blocklist_item in &self.config.blocklist_items {
73 builder = builder.blocklist_item(blocklist_item);
74 }
75
76 builder = builder.header_contents(
77 &format!("{}.h", self.framework),
78 &format!("@import {};", self.framework),
79 );
80
81 builder = builder
87 .allowlist_file(".*\\.framework/.*")
88 .allowlist_file(".*/usr/include/objc/.*")
89 .allowlist_file(".*/usr/include/os/.*")
90 .allowlist_file(".*/usr/include/MacTypes\\.h");
91
92 builder
93 }
94
95 pub fn generate(&self) -> Result<String, bindgen::BindgenError> {
96 let bindgen_builder = self.bindgen_builder();
97
98 let bindings = bindgen_builder.generate()?;
100
101 let mut out = bindings.to_string();
103
104 out = out.replace("pub type id = *mut objc::runtime::Object", "PUB-TYPE-ID");
106 let re = regex::Regex::new("pub type id = .*;").unwrap();
107 out = re.replace_all(&mut out, "").into_owned();
108 out = out.replace("PUB-TYPE-ID", "pub type id = *mut objc::runtime::Object");
109
110 for replacement in &self.config.replacements {
112 let (old, new) = replacement
113 .split_once(" #=># ")
114 .expect("Bindgen.toml is malformed");
115 out = out.replace(old, new);
116 }
117
118 out = fix_msg_send_type_collisions(&out);
122
123 for ty in &self.config.impl_debugs {
125 if out.contains(ty) {
126 out.push_str(&format!(
127 r#"
128impl ::std::fmt::Debug for {ty} {{
129 fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {{
130 f.debug_struct(stringify!({ty}))
131 .finish()
132 }}
133}}
134 "#
135 ));
136 }
137 }
138
139 Ok(out)
140 }
141}
142
143fn fix_msg_send_type_collisions(source: &str) -> String {
151 use std::collections::HashSet;
152
153 let mut shadow_names: HashSet<&str> = HashSet::new();
154 for line in source.lines() {
155 let trimmed = line.trim();
156 let rest = trimmed
157 .strip_prefix("pub struct ")
158 .or_else(|| trimmed.strip_prefix("pub const "));
159 if let Some(rest) = rest {
160 if let Some(name) = rest
161 .split(|c: char| !c.is_alphanumeric() && c != '_')
162 .next()
163 {
164 if !name.is_empty() {
165 shadow_names.insert(name);
166 }
167 }
168 }
169 }
170 if shadow_names.is_empty() {
171 return source.to_string();
172 }
173
174 let msg_arg_re = regex::Regex::new(r" : (\w+)").unwrap();
175 let comma_param_re = regex::Regex::new(r", (\w+): ").unwrap();
176 let paren_param_re = regex::Regex::new(r"\((\w+): ").unwrap();
177
178 let mut in_trait = false;
179 let mut trait_brace_depth: i32 = 0;
180 let mut in_msg_send = false;
181 let mut msg_send_depth: i32 = 0;
182
183 let mut result = String::with_capacity(source.len());
184 for line in source.lines() {
185 let trimmed = line.trim();
186
187 if !in_trait && trimmed.starts_with("pub trait ") {
189 in_trait = true;
190 trait_brace_depth = 0;
191 }
192 if in_trait {
193 for c in line.chars() {
194 match c {
195 '{' => trait_brace_depth += 1,
196 '}' => trait_brace_depth -= 1,
197 _ => {}
198 }
199 }
200 if trait_brace_depth <= 0 {
201 in_trait = false;
202 }
203 }
204
205 let is_in_msg_send = in_msg_send;
207 if !in_msg_send && trimmed.contains("msg_send") {
208 in_msg_send = true;
209 msg_send_depth = 0;
210 }
211 if in_msg_send {
212 for c in line.chars() {
213 match c {
214 '(' => msg_send_depth += 1,
215 ')' => msg_send_depth -= 1,
216 _ => {}
217 }
218 }
219 if msg_send_depth <= 0 {
220 in_msg_send = false;
221 }
222 }
223
224 let mut fixed = line.to_string();
225 let mut did_fix = false;
226
227 if fixed.contains("msg_send") || is_in_msg_send {
230 let new_fixed = msg_arg_re.replace_all(&fixed, |caps: ®ex::Captures| {
231 let name = caps.get(1).unwrap().as_str();
232 if shadow_names.contains(name) {
233 format!(" : {}_", name)
234 } else {
235 caps[0].to_string()
236 }
237 });
238 if new_fixed != fixed {
239 fixed = new_fixed.into_owned();
240 did_fix = true;
241 }
242 }
243
244 if in_trait {
246 if !trimmed.starts_with("unsafe fn ") && fixed.starts_with(" ") {
248 let after_indent = &fixed[8..];
249 if let Some(colon_pos) = after_indent.find(": ") {
250 let candidate = &after_indent[..colon_pos];
251 if !candidate.is_empty()
252 && candidate.chars().all(|c| c.is_alphanumeric() || c == '_')
253 && shadow_names.contains(candidate)
254 {
255 fixed = fixed.replacen(
256 &format!(" {}: ", candidate),
257 &format!(" {}_: ", candidate),
258 1,
259 );
260 did_fix = true;
261 }
262 }
263 }
264
265 {
267 let orig = fixed.clone();
268 let new_fixed = comma_param_re.replace_all(&orig, |caps: ®ex::Captures| {
269 let name = caps.get(1).unwrap().as_str();
270 if shadow_names.contains(name) {
271 format!(", {}_: ", name)
272 } else {
273 caps[0].to_string()
274 }
275 });
276 if new_fixed != orig {
277 fixed = new_fixed.into_owned();
278 did_fix = true;
279 }
280 }
281
282 {
284 let orig = fixed.clone();
285 let new_fixed = paren_param_re.replace_all(&orig, |caps: ®ex::Captures| {
286 let name = caps.get(1).unwrap().as_str();
287 if shadow_names.contains(name) {
288 format!("({}_: ", name)
289 } else {
290 caps[0].to_string()
291 }
292 });
293 if new_fixed != orig {
294 fixed = new_fixed.into_owned();
295 did_fix = true;
296 }
297 }
298 }
299
300 if did_fix {
301 result.push_str(&fixed);
302 } else {
303 result.push_str(line);
304 }
305 result.push('\n');
306 }
307 result
308}