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}