Skip to content

Commit db3254a

Browse files
committed
Generate bindings for extra components
There are two ways the bindings are generated. The first is when `bindings_module` is not set. In that case the header of the extra component will just be added to the normal esp-idf bindings generation. All additional bindings will also be in the root of `esp-idf-sys`. The second: when `bindings_module` is set. For every unique `bindings_module` bindgen will be invoked to generate the bindings, which will then be appended as a new module to the `bindings.rs` file. Note though that since this is a new bindgen invocation duplicate symbols of the normal esp-idf bindings may be generated.
1 parent 5f73553 commit db3254a

File tree

7 files changed

+172
-26
lines changed

7 files changed

+172
-26
lines changed

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ embuild = { version = "0.30", features = ["glob", "kconfig"] }
3636
anyhow = "1"
3737
regex = "1.5"
3838
bindgen = "0.60"
39-
cargo_metadata = { version = "0.15" }
39+
cargo_metadata = "0.15"
4040
serde = { version = "1.0", features = ["derive"] }
4141
strum = { version = "0.24", features = ["derive"] }
4242
envy = "0.4.2"

build/build.rs

Lines changed: 70 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
11
#[cfg(not(any(feature = "pio", feature = "native")))]
22
compile_error!("One of the features `pio` or `native` must be selected.");
33

4+
use std::fs;
5+
use std::io::{BufWriter, Write};
46
use std::iter::once;
57

6-
use ::bindgen::callbacks::{IntKind, ParseCallbacks};
78
use anyhow::*;
9+
use bindgen::callbacks::{IntKind, ParseCallbacks};
810
use common::*;
11+
use embuild::bindgen::BindgenExt;
912
use embuild::utils::OsStrExt;
10-
use embuild::{bindgen, build, cargo, kconfig, path_buf};
13+
use embuild::{bindgen as bindgen_utils, build, cargo, kconfig, path_buf};
1114

1215
mod common;
1316
mod config;
@@ -94,13 +97,12 @@ fn main() -> anyhow::Result<()> {
9497

9598
cargo::track_file(&header_file);
9699

97-
let bindings_file = bindgen::run(
98-
build_output
99-
.bindgen
100-
.builder()?
100+
// Because we have multiple bindgen invocations and we can't clone a bindgen::Builder,
101+
// we have to set the options every time.
102+
let configure_bindgen = |bindgen: bindgen::Builder| {
103+
Ok(bindgen
101104
.parse_callbacks(Box::new(BindgenCallbacks))
102105
.ctypes_prefix("c_types")
103-
.header(header_file.try_to_str()?)
104106
.blocklist_function("strtold")
105107
.blocklist_function("_strtold_r")
106108
.blocklist_function("v.*printf")
@@ -118,8 +120,62 @@ fn main() -> anyhow::Result<()> {
118120
// We don't really have a similar issue with Xtensa, but we pass it explicitly as well just in case
119121
"xtensa"
120122
},
121-
]),
122-
)?;
123+
]))
124+
};
125+
126+
let bindings_file = bindgen_utils::default_bindings_file()?;
127+
let bindgen_err = || {
128+
anyhow!(
129+
"failed to generate bindings in file '{}'",
130+
bindings_file.display()
131+
)
132+
};
133+
let mut headers = vec![header_file];
134+
135+
#[cfg(feature = "native")]
136+
// Add additional headers from extra components.
137+
headers.extend(
138+
build_output
139+
.config
140+
.native
141+
.combined_bindings_headers()?
142+
.into_iter()
143+
.inspect(|h| cargo::track_file(h)),
144+
);
145+
146+
configure_bindgen(build_output.bindgen.clone().builder()?)?
147+
.headers(headers)?
148+
.generate()
149+
.with_context(bindgen_err)?
150+
.write_to_file(&bindings_file)
151+
.with_context(bindgen_err)?;
152+
153+
// Generate bindings separately for each unique module name.
154+
#[cfg(feature = "native")]
155+
(|| {
156+
let mut output_file =
157+
BufWriter::new(fs::File::options().append(true).open(&bindings_file)?);
158+
159+
for (module_name, headers) in build_output.config.native.module_bindings_headers()? {
160+
let bindings = configure_bindgen(build_output.bindgen.clone().builder()?)?
161+
.headers(headers.into_iter().inspect(|h| cargo::track_file(h)))?
162+
.generate()?;
163+
164+
writeln!(
165+
&mut output_file,
166+
"pub mod {} {{\
167+
use crate::c_types;\
168+
{}\
169+
}}",
170+
module_name, bindings
171+
)?;
172+
}
173+
Ok(())
174+
})()
175+
.with_context(bindgen_err)?;
176+
177+
// Cargo fmt generated bindings.
178+
bindgen_utils::cargo_fmt_file(&bindings_file);
123179

124180
let cfg_args = build::CfgArgs {
125181
args: cfg_args
@@ -130,7 +186,6 @@ fn main() -> anyhow::Result<()> {
130186
.chain(once(mcu))
131187
.collect(),
132188
};
133-
134189
cfg_args.propagate();
135190
cfg_args.output();
136191

@@ -139,13 +194,14 @@ fn main() -> anyhow::Result<()> {
139194

140195
// In case other crates need to have access to the ESP-IDF toolchains
141196
if let Some(env_path) = build_output.env_path {
142-
// TODO: Replace with embuild::build::VAR_ENV_PATH once we have a new embuild release
143-
cargo::set_metadata("EMBUILD_ENV_PATH", env_path);
197+
cargo::set_metadata(embuild::build::ENV_PATH_VAR, env_path);
144198
}
145199

146200
// In case other crates need to the ESP-IDF SDK
147-
// TODO: Replace with embuild::espidf::XXX paths once we have a new embuild release
148-
cargo::set_metadata("EMBUILD_ESP_IDF_PATH", build_output.esp_idf.try_to_str()?);
201+
cargo::set_metadata(
202+
embuild::build::ESP_IDF_PATH_VAR,
203+
build_output.esp_idf.try_to_str()?,
204+
);
149205

150206
build_output.cincl_args.propagate();
151207

build/common.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ use embuild::utils::{OsStrExt, PathExt};
1111
use embuild::{bindgen, build, kconfig};
1212
use strum::{Display, EnumDiscriminants, EnumString};
1313

14+
use crate::config::BuildConfig;
15+
1416
#[allow(dead_code)]
1517
pub const V_4_3_2_PATCHES: &[&str] = &[
1618
"patches/missing_riscv_atomics_fix.diff",
@@ -28,6 +30,7 @@ pub struct EspIdfBuildOutput {
2830
pub bindgen: bindgen::Factory,
2931
pub env_path: Option<String>,
3032
pub esp_idf: PathBuf,
33+
pub config: BuildConfig,
3134
}
3235

3336
pub struct EspIdfComponents(Vec<String>);

build/native/cargo_driver.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -392,6 +392,7 @@ pub fn build() -> Result<EspIdfBuildOutput> {
392392
),
393393
env_path: Some(idf.exported_path.try_to_str()?.to_owned()),
394394
esp_idf: build_info.esp_idf_dir,
395+
config,
395396
};
396397

397398
Ok(build_output)

build/native/cargo_driver/config.rs

Lines changed: 94 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use std::collections::HashMap;
12
use std::path::PathBuf;
23

34
use anyhow::{anyhow, bail, Context, Result};
@@ -110,6 +111,63 @@ impl NativeConfig {
110111
})
111112
}
112113

114+
/// Get all bindings C headers of extra components where the bindings will be
115+
/// generated combined with the normal `esp-idf` bindings
116+
/// (all extra components where [`ExtraComponent::bindings_module`] is [`None`]).
117+
///
118+
/// This method will validate that all returned C header files exist.
119+
pub fn combined_bindings_headers(&self) -> Result<Vec<PathBuf>> {
120+
let mut results = Vec::new();
121+
for comp in &self.extra_components {
122+
// Skip all extra components with separate bindings.
123+
if comp.bindings_module.is_some() {
124+
continue;
125+
}
126+
127+
if let Some(header) = &comp.bindings_header {
128+
let header_path = header.abspath_relative_to(&comp.manifest_dir);
129+
130+
if !header_path.exists() {
131+
bail!(
132+
"extra components C header file '{}' specified by crate '{}' does not exists",
133+
header_path.display(), comp.manifest_dir.display()
134+
);
135+
}
136+
results.push(header_path);
137+
}
138+
}
139+
Ok(results)
140+
}
141+
142+
/// Get all bindings C headers grouped by the [`ExtraComponent::bindings_module`] name.
143+
///
144+
/// This method will validate that all returned C header files exist and also that the
145+
/// module name only contains ACII alphanumeric and `_` characters.
146+
pub fn module_bindings_headers(&self) -> Result<HashMap<&str, Vec<PathBuf>>> {
147+
let headers = self.extra_components.iter().filter_map(|comp| {
148+
match (&comp.bindings_header, &comp.bindings_module) {
149+
(Some(header), Some(module)) => {
150+
Some((header.abspath_relative_to(&comp.manifest_dir), module, comp))
151+
}
152+
_ => None,
153+
}
154+
});
155+
let mut map = HashMap::<&str, Vec<PathBuf>>::new();
156+
157+
for (header_path, module_name, comp) in headers {
158+
if !header_path.exists() {
159+
bail!(
160+
"extra components C header file '{}' specified by crate '{}' does not exists",
161+
header_path.display(),
162+
comp.manifest_dir.display()
163+
);
164+
}
165+
validate_module_name(module_name, comp)?;
166+
map.entry(&*module_name).or_default().push(header_path);
167+
}
168+
Ok(map)
169+
}
170+
113171
pub fn with_cargo_metadata(&mut self, root: &Package, metadata: &Metadata) -> Result<()> {
114172
let EspIdfSys {
115173
v:
@@ -228,27 +286,28 @@ pub struct ExtraComponent {
228286
/// Otherwise, if absent, the component bindings will be added to the existing
229287
/// `esp-idf` bindings (which are available in the crate root).
230288
///
231-
/// To put the bindings into its own module, a separate bindgen instanace will generate
232-
/// the bindings. Note that this will result in duplicate `esp-idf` bindings if the same
233-
/// `esp-idf` headers are included by the component(s) that were already processed for
234-
/// the `esp-idf` bindings.
289+
/// To put the bindings into its own module, a separate bindgen instance will generate
290+
/// the bindings. Note that this will result in duplicate `esp-idf` bindings if the
291+
/// same `esp-idf` headers that were already processed for the `esp-idf` bindings are
292+
/// included by the component(s).
235293
///
236294
/// Optional
237295
#[serde(default)]
238296
pub bindings_module: Option<String>,
239297

298+
/// Internal field; the path of the directory containing the manifest (`Cargo.toml`)
299+
/// that defined this [`ExtraComponent`].
240300
#[serde(skip)]
241301
pub manifest_dir: PathBuf,
242302
}
243303

244304
mod parse {
245305
use std::str::FromStr;
246306

247-
use embuild::{cmake, git};
248-
use serde::{Deserialize, Deserializer};
307+
use serde::Deserializer;
249308
use strum::IntoEnumIterator;
250309

251-
use super::DEFAULT_CMAKE_GENERATOR;
310+
use super::*;
252311
use crate::config::utils::ValueOrVec;
253312

254313
/// Parse a cmake generator, either `default` or one of [`cmake::Generator`].
@@ -294,3 +353,31 @@ mod parse {
294353
})
295354
}
296355
}
356+
357+
/// A extra component module name can only contain ASCII alphanumeric and `_` characters.
358+
/// Additionally it must not start with a digit.
359+
pub fn validate_module_name(module_name: &str, comp: &ExtraComponent) -> Result<()> {
360+
if module_name.is_empty() {
361+
bail!(
362+
"extra component module name '{}' specified by crate '{}' cannot be empty",
363+
module_name,
364+
comp.manifest_dir.display()
365+
);
366+
}
367+
368+
let mut chars = module_name.chars();
369+
let first_char = chars.next().unwrap();
370+
371+
let first_char_valid = first_char.is_ascii_alphabetic() || first_char == '_';
372+
let other_valid = chars.all(|c| c.is_ascii_alphanumeric() || c == '_');
373+
374+
if !first_char_valid || !other_valid {
375+
bail!(
376+
"extra component module name '{}' specified by crate '{}' can only contain \
377+
ASCII alphanumeric or `_` characters and must be a valid Rust module name",
378+
module_name,
379+
comp.manifest_dir.display()
380+
);
381+
}
382+
Ok(())
383+
}

build/native/cmake_driver.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ pub fn build() -> Result<EspIdfBuildOutput> {
5858
),
5959
env_path: None,
6060
esp_idf: PathBuf::from(env::var(CARGO_CMAKE_BUILD_ESP_IDF_VAR)?),
61+
config: Default::default(),
6162
};
6263

6364
Ok(build_output)

build/pio.rs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,7 @@ pub fn build() -> Result<EspIdfBuildOutput> {
2525
(pio_scons_vars, None)
2626
} else {
2727
let config = BuildConfig::try_from_env().map(|mut config| {
28-
config
29-
.with_cargo_metadata()
30-
.context("failed to read configuration from manifest metadata")
31-
.into_warning();
28+
config.with_cargo_metadata().into_warning();
3229
config
3330
})?;
3431
config.print();
@@ -174,6 +171,7 @@ pub fn build() -> Result<EspIdfBuildOutput> {
174171
}),
175172
),
176173
esp_idf,
174+
config: Default::default(),
177175
};
178176

179177
Ok(build_output)

0 commit comments

Comments
 (0)