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