-
Notifications
You must be signed in to change notification settings - Fork 43
/
Copy pathbindgen.rs
307 lines (265 loc) · 10.1 KB
/
bindgen.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
//! Bindgen utilities for generating bindings to C/C++ code.
use std::ffi::OsStr;
use std::path::{Path, PathBuf};
use std::{env, fs};
use anyhow::{anyhow, bail, Context, Error, Result};
use crate::cargo::out_dir;
use crate::utils::OsStrExt;
use crate::{cargo, cmd};
/// Re-export the bindgen crate so that downstream crates can use it without
/// having to add it as a dependency.
pub mod types {
pub use ::bindgen::*;
}
/// The environment variable name containing the file path of the file that contains the
/// generated bindings.
pub const VAR_BINDINGS_FILE: &str = "EMBUILD_GENERATED_BINDINGS_FILE";
/// A builder for creating a [`bindgen::Builder`].
#[derive(Clone, Default, Debug)]
#[must_use]
pub struct Factory {
pub clang_args: Vec<String>,
pub linker: Option<PathBuf>,
pub mcu: Option<String>,
pub force_cpp: bool,
pub sysroot: Option<PathBuf>,
}
impl Factory {
/// Create a new factory populating the clang args, linker and mcu from the
/// Scons variables of a platformio project.
#[cfg(feature = "pio")]
pub fn from_scons_vars(scons_vars: &crate::pio::project::SconsVariables) -> Result<Self> {
use crate::cli;
let clang_args = cli::NativeCommandArgs::new(&scons_vars.incflags)
.chain(cli::NativeCommandArgs::new(
scons_vars.clangargs.as_deref().unwrap_or_default(),
))
.collect();
Ok(Self {
clang_args,
linker: Some(scons_vars.full_path(scons_vars.link.clone())?),
mcu: scons_vars.mcu.clone(),
force_cpp: false,
sysroot: None,
})
}
/// Create a new factory populating the clang args, force cpp, and sysroot from the
/// cmake file-api compile group.
#[cfg(feature = "cmake")]
pub fn from_cmake(
compile_group: &crate::cmake::file_api::codemodel::target::CompileGroup,
) -> Result<Self> {
use crate::cmake::file_api::codemodel::Language;
assert!(
compile_group.language == Language::C || compile_group.language == Language::Cpp,
"Generating bindings for languages other than C/C++ is not supported"
);
let clang_args = compile_group
.defines
.iter()
.map(|d| format!("-D{}", d.define))
.chain(
compile_group
.includes
.iter()
.map(|i| format!("-I{}", &i.path)),
)
.collect();
Ok(Self {
clang_args,
linker: None,
force_cpp: compile_group.language == Language::Cpp,
mcu: None,
sysroot: compile_group.sysroot.as_ref().map(|s| s.path.clone()),
})
}
pub fn new() -> Self {
Default::default()
}
/// Set the clang args that need to be passed down to the Bindgen instance.
pub fn with_clang_args<S>(mut self, clang_args: impl IntoIterator<Item = S>) -> Self
where
S: Into<String>,
{
self.clang_args
.extend(clang_args.into_iter().map(Into::into));
self
}
/// Set the sysroot to be used for generating bindings.
pub fn with_sysroot(mut self, sysroot: impl Into<PathBuf>) -> Self {
self.sysroot = Some(sysroot.into());
self
}
/// Set the linker used to determine the sysroot to be used for generating bindings, if the sysroot is not explicitly passed.
pub fn with_linker(mut self, linker: impl Into<PathBuf>) -> Self {
self.linker = Some(linker.into());
self
}
/// Create a [`bindgen::Builder`] with these settings.
pub fn builder(self) -> Result<bindgen::Builder> {
self.create_builder(false)
}
/// Create a [`bindgen::Builder`] creating C++ bindings with these settings.
pub fn cpp_builder(self) -> Result<bindgen::Builder> {
self.create_builder(true)
}
fn create_builder(self, cpp: bool) -> Result<bindgen::Builder> {
let cpp = self.force_cpp || cpp;
let sysroot = self
.sysroot
.clone()
.map_or_else(|| try_get_sysroot(&self.linker), Ok)?;
let sysroot_args = [
format!("--sysroot={}", sysroot.try_to_str()?),
format!("-I{}", sysroot.join("include").try_to_str()?),
];
let cpp_args = if cpp {
get_cpp_includes(&sysroot)?
} else {
vec![]
};
let builder = bindgen::Builder::default()
.use_core()
.layout_tests(false)
.formatter(bindgen::Formatter::None)
.derive_default(true)
.clang_arg("-D__bindgen")
// Include directories provided by the build system
// should be first on the search path (before sysroot includes),
// or else libc's <dirent.h> does not correctly override sysroot's <dirent.h>
.clang_args(&self.clang_args)
.clang_args(sysroot_args)
.clang_args(&["-x", if cpp { "c++" } else { "c" }])
.clang_args(cpp_args);
log::debug!(
"Bindgen builder factory flags: {:?}",
builder.command_line_flags()
);
Ok(builder)
}
}
/// Get the default filename for bindings and set the environment variable named
/// [`VAR_BINDINGS_FILE`] that is available during crate compilation to that path.
pub fn default_bindings_file() -> Result<PathBuf> {
let bindings_file = out_dir().join("bindings.rs");
cargo::set_rustc_env(VAR_BINDINGS_FILE, bindings_file.try_to_str()?);
Ok(bindings_file)
}
/// Create rust bindings in [`default_bindings_file`] using [`run_for_file`].
pub fn run(builder: bindgen::Builder) -> Result<PathBuf> {
let output_file = default_bindings_file()?;
run_for_file(builder, &output_file)?;
Ok(output_file)
}
/// Try to `cargo fmt` `file` using any of the current, `stable` and `nightly` toolchains.
/// If all of them fail print a warning ([`cargo::print_warning`]).
pub fn cargo_fmt_file(file: impl AsRef<Path>) {
let file = file.as_ref();
// Run rustfmt on the generated bindings separately, because custom toolchains often do not have rustfmt
// We try multiple rustfmt instances:
// - The one from the currently active toolchain
// - The one from stable
// - The one from nightly
if cmd!("rustfmt", file).run().is_err()
&& cmd!("rustup", "run", "stable", "rustfmt", file)
.run()
.is_err()
&& cmd!("rustup", "run", "nightly", "rustfmt", file)
.run()
.is_err()
{
cargo::print_warning(
"rustfmt not found in the current toolchain, nor in stable or nightly. \
The generated bindings will not be properly formatted.",
);
}
}
/// Create rust bindings in `output_file` and run `cargo fmt` over that file.
pub fn run_for_file(builder: bindgen::Builder, output_file: impl AsRef<Path>) -> Result<()> {
let output_file = output_file.as_ref();
eprintln!("Output: {output_file:?}");
eprintln!("Bindgen builder flags: {:?}", builder.command_line_flags());
let bindings = builder
.generate()
.map_err(|_| Error::msg("Failed to generate bindings"))?;
bindings.write_to_file(output_file)?;
cargo_fmt_file(output_file);
Ok(())
}
/// Extension trait for [`bindgen::Builder`].
/// Extension trait for [`bindgen::Builder`].
pub trait BindgenExt: Sized {
/// Add all input C/C++ headers using repeated [`bindgen::Builder::header`].
fn path_headers(self, headers: impl IntoIterator<Item = impl AsRef<Path>>) -> Result<Self>;
}
impl BindgenExt for bindgen::Builder {
fn path_headers(mut self, headers: impl IntoIterator<Item = impl AsRef<Path>>) -> Result<Self> {
for header in headers {
self = self.header(header.as_ref().try_to_str()?)
}
Ok(self)
}
}
fn try_get_sysroot(linker: &Option<impl AsRef<Path>>) -> Result<PathBuf> {
let linker = if let Some(ref linker) = linker {
linker.as_ref().to_owned()
} else if let Some(linker) = env::var_os("RUSTC_LINKER") {
PathBuf::from(linker)
} else {
bail!("Could not determine linker: No explicit linker and `RUSTC_LINKER` not set");
};
let gcc_file_stem = linker
.file_stem()
.and_then(OsStr::to_str)
.filter(|&s| s == "gcc" || s.ends_with("-gcc"));
// For whatever reason, --print-sysroot does not work with GCC
// Change it to LD
let linker = if let Some(stem) = gcc_file_stem {
let mut ld_linker =
linker.with_file_name(format!("{}{}", stem.strip_suffix("gcc").unwrap(), "ld"));
if let Some(ext) = linker.extension() {
ld_linker.set_extension(ext);
}
ld_linker
} else {
linker
};
cmd!(&linker, "--print-sysroot")
.stdout()
.with_context(|| {
anyhow!(
"Could not determine sysroot from linker '{}'",
linker.display()
)
})
.map(PathBuf::from)
}
fn get_cpp_includes(sysroot: impl AsRef<Path>) -> Result<Vec<String>> {
let sysroot = sysroot.as_ref();
let cpp_includes_root = sysroot.join("include").join("c++");
let cpp_version = fs::read_dir(cpp_includes_root)?
.map(|dir_entry_r| dir_entry_r.map(|dir_entry| dir_entry.path()))
.fold(None, |ao: Option<PathBuf>, sr: Result<PathBuf, _>| {
if let Some(a) = ao.as_ref() {
sr.ok()
.map_or(ao.clone(), |s| if a >= &s { ao.clone() } else { Some(s) })
} else {
sr.ok()
}
});
if let Some(cpp_version) = cpp_version {
let mut cpp_include_paths = vec![
format!("-I{}", cpp_version.try_to_str()?),
format!("-I{}", cpp_version.join("backward").try_to_str()?),
];
if let Some(sysroot_last_segment) = fs::canonicalize(sysroot)?.file_name() {
cpp_include_paths.push(format!(
"-I{}",
cpp_version.join(sysroot_last_segment).try_to_str()?
));
}
Ok(cpp_include_paths)
} else {
Ok(Vec::new())
}
}