windows_bindgen/
lib.rs

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