1 | #![doc = include_str!("../readme.md" )] |
2 | #![allow ( |
3 | non_upper_case_globals, |
4 | clippy::enum_variant_names, |
5 | clippy::upper_case_acronyms, |
6 | clippy::needless_doctest_main |
7 | )] |
8 | |
9 | mod derive; |
10 | mod derive_writer; |
11 | mod filter; |
12 | mod guid; |
13 | mod io; |
14 | mod libraries; |
15 | mod references; |
16 | mod signature; |
17 | mod tables; |
18 | mod tokens; |
19 | mod type_map; |
20 | mod type_name; |
21 | mod type_tree; |
22 | mod types; |
23 | mod value; |
24 | mod winmd; |
25 | mod writer; |
26 | |
27 | use derive::*; |
28 | use derive_writer::*; |
29 | use filter::*; |
30 | use guid::*; |
31 | use io::*; |
32 | pub use libraries::*; |
33 | use references::*; |
34 | use signature::*; |
35 | use std::cmp::Ordering; |
36 | use std::collections::*; |
37 | use std::fmt::Write; |
38 | use tables::*; |
39 | use tokens::*; |
40 | use type_map::*; |
41 | use type_name::*; |
42 | use type_tree::*; |
43 | use types::*; |
44 | use value::*; |
45 | use winmd::*; |
46 | use writer::*; |
47 | mod method_names; |
48 | use method_names::*; |
49 | |
50 | struct Config { |
51 | pub types: TypeMap, |
52 | pub references: References, |
53 | pub output: String, |
54 | pub flat: bool, |
55 | pub no_allow: bool, |
56 | pub no_comment: bool, |
57 | pub no_core: bool, |
58 | pub no_toml: bool, |
59 | pub package: bool, |
60 | pub rustfmt: String, |
61 | pub sys: bool, |
62 | pub implement: bool, |
63 | pub derive: Derive, |
64 | } |
65 | |
66 | /// The Windows code generator. |
67 | #[track_caller ] |
68 | pub fn bindgen<I, S>(args: I) |
69 | where |
70 | I: IntoIterator<Item = S>, |
71 | S: AsRef<str>, |
72 | { |
73 | let args = expand_args(args); |
74 | let mut kind = ArgKind::None; |
75 | let mut input = Vec::new(); |
76 | let mut include = Vec::new(); |
77 | let mut exclude = Vec::new(); |
78 | let mut references = Vec::new(); |
79 | let mut derive = Vec::new(); |
80 | |
81 | let mut flat = false; |
82 | let mut no_allow = false; |
83 | let mut no_comment = false; |
84 | let mut no_core = false; |
85 | let mut no_toml = false; |
86 | let mut package = false; |
87 | let mut implement = false; |
88 | let mut rustfmt = String::new(); |
89 | let mut output = String::new(); |
90 | let mut sys = false; |
91 | |
92 | for arg in &args { |
93 | if arg.starts_with('-' ) { |
94 | kind = ArgKind::None; |
95 | } |
96 | |
97 | match kind { |
98 | ArgKind::None => match arg.as_str() { |
99 | "--in" => kind = ArgKind::Input, |
100 | "--out" => kind = ArgKind::Output, |
101 | "--filter" => kind = ArgKind::Filter, |
102 | "--rustfmt" => kind = ArgKind::Rustfmt, |
103 | "--reference" => kind = ArgKind::Reference, |
104 | "--derive" => kind = ArgKind::Derive, |
105 | "--flat" => flat = true, |
106 | "--no-allow" => no_allow = true, |
107 | "--no-comment" => no_comment = true, |
108 | "--no-core" => no_core = true, |
109 | "--no-toml" => no_toml = true, |
110 | "--package" => package = true, |
111 | "--sys" => sys = true, |
112 | "--implement" => implement = true, |
113 | _ => panic!("invalid option ` {arg}`" ), |
114 | }, |
115 | ArgKind::Output => { |
116 | if output.is_empty() { |
117 | output = arg.to_string(); |
118 | } else { |
119 | panic!("exactly one `--out` is required" ); |
120 | } |
121 | } |
122 | ArgKind::Input => input.push(arg.as_str()), |
123 | ArgKind::Filter => { |
124 | if let Some(rest) = arg.strip_prefix('!' ) { |
125 | exclude.push(rest); |
126 | } else { |
127 | include.push(arg.as_str()); |
128 | } |
129 | } |
130 | ArgKind::Reference => { |
131 | references.push(ReferenceStage::parse(arg)); |
132 | } |
133 | ArgKind::Derive => { |
134 | derive.push(arg.as_str()); |
135 | } |
136 | ArgKind::Rustfmt => rustfmt = arg.to_string(), |
137 | } |
138 | } |
139 | |
140 | if !sys && no_core { |
141 | panic!("`--no-core` requires `--sys`" ); |
142 | } |
143 | |
144 | if package && flat { |
145 | panic!("cannot combine `--package` and `--flat`" ); |
146 | } |
147 | |
148 | if input.is_empty() { |
149 | input.push("default" ); |
150 | }; |
151 | |
152 | if output.is_empty() { |
153 | panic!("exactly one `--out` is required" ); |
154 | }; |
155 | |
156 | // This isn't strictly necessary but avoids a common newbie pitfall where all metadata |
157 | // would be generated when building a component for a specific API. |
158 | if include.is_empty() { |
159 | panic!("at least one `--filter` required" ); |
160 | } |
161 | |
162 | let reader = Reader::new(expand_input(&input)); |
163 | let filter = Filter::new(reader, &include, &exclude); |
164 | let references = References::new(reader, references); |
165 | let types = TypeMap::filter(reader, &filter, &references); |
166 | let derive = Derive::new(reader, &types, &derive); |
167 | |
168 | let config = Box::leak(Box::new(Config { |
169 | types, |
170 | flat, |
171 | references, |
172 | derive, |
173 | no_allow, |
174 | no_comment, |
175 | no_core, |
176 | no_toml, |
177 | package, |
178 | rustfmt, |
179 | output, |
180 | sys, |
181 | implement, |
182 | })); |
183 | |
184 | let tree = TypeTree::new(&config.types); |
185 | |
186 | let writer = Writer { |
187 | config, |
188 | namespace: "" , |
189 | }; |
190 | |
191 | writer.write(tree) |
192 | } |
193 | |
194 | enum ArgKind { |
195 | None, |
196 | Input, |
197 | Output, |
198 | Filter, |
199 | Rustfmt, |
200 | Reference, |
201 | Derive, |
202 | } |
203 | |
204 | #[track_caller ] |
205 | fn expand_args<I, S>(args: I) -> Vec<String> |
206 | where |
207 | I: IntoIterator<Item = S>, |
208 | S: AsRef<str>, |
209 | { |
210 | // This function is needed to avoid a recursion limit in the Rust compiler. |
211 | #[track_caller ] |
212 | fn from_string(result: &mut Vec<String>, value: &str) { |
213 | expand_args(result, value.split_whitespace().map(|arg| arg.to_string())) |
214 | } |
215 | |
216 | #[track_caller ] |
217 | fn expand_args<I, S>(result: &mut Vec<String>, args: I) |
218 | where |
219 | I: IntoIterator<Item = S>, |
220 | S: AsRef<str>, |
221 | { |
222 | let mut expand = false; |
223 | |
224 | for arg in args.into_iter().map(|arg| arg.as_ref().to_string()) { |
225 | if arg.starts_with('-' ) { |
226 | expand = false; |
227 | } |
228 | if expand { |
229 | for args in io::read_file_lines(&arg) { |
230 | if !args.starts_with("//" ) { |
231 | from_string(result, &args); |
232 | } |
233 | } |
234 | } else if arg == "--etc" { |
235 | expand = true; |
236 | } else { |
237 | result.push(arg); |
238 | } |
239 | } |
240 | } |
241 | |
242 | let mut result = vec![]; |
243 | expand_args(&mut result, args); |
244 | result |
245 | } |
246 | |
247 | #[track_caller ] |
248 | fn expand_input(input: &[&str]) -> Vec<File> { |
249 | #[track_caller ] |
250 | fn expand_input(result: &mut Vec<String>, input: &str) { |
251 | let path = std::path::Path::new(input); |
252 | |
253 | if path.is_dir() { |
254 | let prev_len = result.len(); |
255 | |
256 | for path in path |
257 | .read_dir() |
258 | .unwrap_or_else(|_| panic!("failed to read directory ` {input}`" )) |
259 | .flatten() |
260 | .map(|entry| entry.path()) |
261 | { |
262 | if path.is_file() |
263 | && path |
264 | .extension() |
265 | .is_some_and(|extension| extension.eq_ignore_ascii_case("winmd" )) |
266 | { |
267 | result.push(path.to_string_lossy().to_string()); |
268 | } |
269 | } |
270 | |
271 | if result.len() == prev_len { |
272 | panic!("failed to find .winmd files in directory ` {input}`" ); |
273 | } |
274 | } else { |
275 | result.push(input.to_string()); |
276 | } |
277 | } |
278 | |
279 | let mut paths = vec![]; |
280 | let mut use_default = false; |
281 | |
282 | for input in input { |
283 | if *input == "default" { |
284 | use_default = true; |
285 | } else { |
286 | expand_input(&mut paths, input); |
287 | } |
288 | } |
289 | |
290 | let mut input = vec![]; |
291 | |
292 | if use_default { |
293 | input = [ |
294 | std::include_bytes!("../default/Windows.winmd" ).to_vec(), |
295 | std::include_bytes!("../default/Windows.Win32.winmd" ).to_vec(), |
296 | std::include_bytes!("../default/Windows.Wdk.winmd" ).to_vec(), |
297 | ] |
298 | .into_iter() |
299 | .map(|bytes| File::new(bytes).unwrap()) |
300 | .collect(); |
301 | } |
302 | |
303 | for path in &paths { |
304 | let Ok(bytes) = std::fs::read(path) else { |
305 | panic!("failed to read binary file ` {path}`" ); |
306 | }; |
307 | |
308 | let Some(file) = File::new(bytes) else { |
309 | panic!("failed to read .winmd format ` {path}`" ); |
310 | }; |
311 | |
312 | input.push(file); |
313 | } |
314 | |
315 | input |
316 | } |
317 | |
318 | fn namespace_starts_with(namespace: &str, starts_with: &str) -> bool { |
319 | namespace.starts_with(starts_with) |
320 | && (namespace.len() == starts_with.len() |
321 | || namespace.as_bytes().get(index:starts_with.len()) == Some(&b'.' )) |
322 | } |
323 | |
324 | #[cfg (test)] |
325 | mod tests { |
326 | use super::*; |
327 | |
328 | #[test ] |
329 | fn test_starts_with() { |
330 | assert!(namespace_starts_with( |
331 | "Windows.Win32.Graphics.Direct3D11on12" , |
332 | "Windows.Win32.Graphics.Direct3D11on12" |
333 | )); |
334 | assert!(namespace_starts_with( |
335 | "Windows.Win32.Graphics.Direct3D11on12" , |
336 | "Windows.Win32.Graphics" |
337 | )); |
338 | assert!(!namespace_starts_with( |
339 | "Windows.Win32.Graphics.Direct3D11on12" , |
340 | "Windows.Win32.Graphics.Direct3D11" |
341 | )); |
342 | assert!(!namespace_starts_with( |
343 | "Windows.Win32.Graphics.Direct3D" , |
344 | "Windows.Win32.Graphics.Direct3D11" |
345 | )); |
346 | } |
347 | } |
348 | |