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
9mod derive;
10mod derive_writer;
11mod filter;
12mod guid;
13mod io;
14mod libraries;
15mod references;
16mod signature;
17mod tables;
18mod tokens;
19mod type_map;
20mod type_name;
21mod type_tree;
22mod types;
23mod value;
24mod winmd;
25mod writer;
26
27use derive::*;
28use derive_writer::*;
29use filter::*;
30use guid::*;
31use io::*;
32pub use libraries::*;
33use references::*;
34use signature::*;
35use std::cmp::Ordering;
36use std::collections::*;
37use std::fmt::Write;
38use tables::*;
39use tokens::*;
40use type_map::*;
41use type_name::*;
42use type_tree::*;
43use types::*;
44use value::*;
45use winmd::*;
46use writer::*;
47mod method_names;
48use method_names::*;
49
50struct 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]
68pub fn bindgen<I, S>(args: I)
69where
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
194enum ArgKind {
195 None,
196 Input,
197 Output,
198 Filter,
199 Rustfmt,
200 Reference,
201 Derive,
202}
203
204#[track_caller]
205fn expand_args<I, S>(args: I) -> Vec<String>
206where
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]
248fn 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
318fn 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)]
325mod 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