Skip to main content

windows_bindgen/
cli.rs

1use super::*;
2
3/// Generates bindings using command-line-style arguments.
4///
5/// ```rust,no_run
6/// let args = [
7///     "--out",
8///     "src/bindings.rs",
9///     "--filter",
10///     "GetTickCount",
11/// ];
12///
13/// windows_bindgen::bindgen(args);
14/// ```
15///
16/// Supported arguments:
17///
18/// - `--in`: Metadata files or directories.
19/// - `--out`: Generated Rust file.
20/// - `--filter`: Included or excluded APIs.
21/// - `--rustfmt`: Rust formatter override.
22/// - `--derive`: Extra derived traits.
23/// - `--flat`: Omits namespace modules.
24/// - `--sys`: Generates raw bindings that depend only on `windows-link`.
25/// - `--extern`: Uses extern declarations with `--sys`.
26/// - `--minimal`: Omits class wrappers, inherited forwarders, and handle wrappers.
27/// - `--implement`: Emits implementation traits for selected WinRT interfaces.
28/// - `--compose`: Selects minimal-mode WinRT class composition targets.
29/// - `--dead-code`: Emits `pub(crate)` items for dead-code analysis.
30/// - `--etc`: Reads arguments from command files.
31/// - `--filter-file`: Reads filters from text files.
32///
33/// # `--out`
34///
35/// Exactly one `--out` argument is required.
36///
37/// # `--filter`
38///
39/// Filters select APIs to include. Prefix a filter with `!` to exclude it.
40///
41/// ```text
42/// --filter Windows.Win32.Storage.FileSystem.GetFullPathNameW
43///
44/// --filter Windows.Win32.Storage.FileSystem.GetFullPathNameW
45///          !Windows.Win32.Storage.FileSystem.WIN32_FIND_DATAW
46///
47/// --filter Windows.Win32.Storage.FileSystem
48///
49/// --filter Windows.Win32.Storage.FileSystem
50///          !Windows.Win32.Storage.FileSystem.WIN32_FIND_DATAW
51/// ```
52///
53/// Use a filter file for longer filter lists:
54///
55/// ```text
56/// --filter-file path/to/filter.txt
57/// ```
58///
59/// The in-repo crates use this convention; see the filter `.txt` files in
60/// `crates/tools/bindings/src`.
61///
62/// ## Method-level filtering
63///
64/// Filters can target methods, properties, and events with `Namespace.Type::Member`.
65/// `Property.Name` and `Event.Name` select accessor pairs.
66///
67/// A bare type projects the full type. `Type::{}` emits a name-only shell, while
68/// `Type::Member` and `Type::{a, b}` select members.
69///
70/// ```text
71/// --filter Windows.UI.Xaml.Controls.Button
72///          Windows.UI.Xaml.Controls.TextBlock::put_Text
73///          Windows.UI.Xaml.Controls.TextBlock::Property.FontSize
74///          Windows.UI.Xaml.UIElement::Event.PointerPressed
75/// ```
76///
77#[track_caller]
78pub fn bindgen<I, S>(args: I)
79where
80    I: IntoIterator<Item = S>,
81    S: AsRef<str>,
82{
83    let args = expand_args(args);
84    let mut builder = Bindgen::new();
85    let mut kind = ArgKind::None;
86    let mut has_output = false;
87    let mut implement = None::<Vec<String>>;
88    let mut compose = None::<Vec<String>>;
89
90    for arg in args {
91        if arg.starts_with('-') {
92            kind = ArgKind::None;
93        }
94
95        match kind {
96            ArgKind::None => match arg.as_str() {
97                "--in" => kind = ArgKind::Input,
98                "--out" => kind = ArgKind::Output,
99                "--filter" => kind = ArgKind::Filter,
100                "--filter-file" => kind = ArgKind::FilterFile,
101                "--rustfmt" => kind = ArgKind::Rustfmt,
102                "--derive" => kind = ArgKind::Derive,
103                "--flat" => {
104                    builder.flat();
105                }
106                "--package" => {
107                    builder.package();
108                }
109                "--sys" => {
110                    builder.sys();
111                }
112                "--minimal" => {
113                    builder.minimal();
114                }
115                "--dead-code" => {
116                    builder.dead_code();
117                }
118                "--extern" => {
119                    builder.extern_fns();
120                }
121                "--implement" => {
122                    implement.get_or_insert_with(Vec::new);
123                    kind = ArgKind::Implement;
124                }
125                "--compose" => {
126                    compose.get_or_insert_with(Vec::new);
127                    kind = ArgKind::Compose;
128                }
129                _ => panic!("invalid option `{arg}`"),
130            },
131            ArgKind::Output => {
132                assert!(!has_output, "exactly one `--out` is required");
133                builder.output(arg);
134                has_output = true;
135            }
136            ArgKind::Input => {
137                if arg == "default" {
138                    builder.input_default();
139                } else {
140                    builder.input(arg);
141                }
142            }
143            ArgKind::Filter => {
144                builder.filter(&arg);
145            }
146            ArgKind::FilterFile => {
147                builder.filter_file(&arg);
148            }
149            ArgKind::Derive => {
150                builder.derive(&arg);
151            }
152            ArgKind::Implement => {
153                implement.as_mut().unwrap().push(arg.clone());
154            }
155            ArgKind::Compose => {
156                compose.as_mut().unwrap().push(arg.clone());
157            }
158            ArgKind::Rustfmt => {
159                builder.rustfmt(&arg);
160            }
161        }
162    }
163
164    if let Some(implement) = implement {
165        if implement.is_empty() {
166            builder.implement_all();
167        } else {
168            builder.implements(implement);
169        }
170    }
171    if let Some(compose) = compose {
172        assert!(!compose.is_empty(), "`--compose` requires a class name");
173        builder.composes(compose);
174    }
175
176    builder.write();
177}
178
179enum ArgKind {
180    None,
181    Input,
182    Output,
183    Filter,
184    FilterFile,
185    Rustfmt,
186    Derive,
187    Implement,
188    Compose,
189}
190
191#[track_caller]
192fn expand_args<I, S>(args: I) -> Vec<String>
193where
194    I: IntoIterator<Item = S>,
195    S: AsRef<str>,
196{
197    #[track_caller]
198    fn expand<I, S>(result: &mut Vec<String>, args: I)
199    where
200        I: IntoIterator<Item = S>,
201        S: AsRef<str>,
202    {
203        let mut command_files = false;
204
205        for arg in args.into_iter().map(|arg| arg.as_ref().to_string()) {
206            if arg.starts_with('-') {
207                command_files = false;
208            }
209
210            if command_files {
211                expand(result, read_tokens(arg));
212            } else if arg == "--etc" {
213                command_files = true;
214            } else {
215                result.push(arg);
216            }
217        }
218    }
219
220    let mut result = Vec::new();
221    expand(&mut result, args);
222    result
223}
224
225#[track_caller]
226pub(super) fn read_tokens(input: impl AsRef<Path>) -> Vec<String> {
227    let mut result = Vec::new();
228
229    for line in read_file_lines(input) {
230        if line.trim_start().starts_with("//") {
231            continue;
232        }
233
234        // Split on whitespace but keep `{...}` groups together so that
235        // `Type::{a, b}` is not split across multiple filters.
236        let mut current = String::new();
237        let mut brace_depth = 0u32;
238
239        for ch in line.chars() {
240            if ch == '{' {
241                brace_depth += 1;
242                current.push(ch);
243            } else if ch == '}' {
244                brace_depth = brace_depth.saturating_sub(1);
245                current.push(ch);
246            } else if ch.is_whitespace() && brace_depth == 0 {
247                if !current.is_empty() {
248                    result.push(std::mem::take(&mut current));
249                }
250            } else {
251                current.push(ch);
252            }
253        }
254        if !current.is_empty() {
255            result.push(current);
256        }
257    }
258
259    result
260}