Skip to content

Commit ff494ac

Browse files
author
Gopa Kumar
committed
Add new bindgen options to generate wrappers for inline functions
esp-idf-sys has a ton of utility static inlines which one has to re-implement by hand since bindgen cant do anything with static inlines - till recently. Recent bindgen has an option (still under experimental flag) which creates wrappers for static inlines, see rust-lang/rust-bindgen#2405 This PR adds that ability to esp-idf-sys. I started looking into esp stuff for the very first time few days back, so my understanding of the whole ecosystem is very incomplete, comments welcome on how to do things different/better etc.. I have enabled the same bindgen feature on embuild crate also, which is why the embuild in this PR is pointing to my private github repo for now till that is also merged in
1 parent 63b8713 commit ff494ac

File tree

2 files changed

+142
-4
lines changed

2 files changed

+142
-4
lines changed

Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,10 @@ build-time = "0.1" # For esp_app_desc!()
3535
const_format = "0.2" # For esp_app_desc!()
3636

3737
[build-dependencies]
38-
embuild = { version = "0.31.2", features = ["glob", "kconfig"] }
38+
embuild = { git = "https://github.com/gopakumarce/embuild.git", branch = "gopa-inline_fns", features = ["glob", "kconfig"] }
3939
anyhow = "1"
4040
regex = "1.5"
41-
bindgen = "0.63"
41+
bindgen = { version = "0.65.1", features = ["experimental"] }
4242
cargo_metadata = "0.15"
4343
serde = { version = "1.0", features = ["derive"] }
4444
strum = { version = "0.24", features = ["derive"] }

build/build.rs

Lines changed: 140 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,17 @@
22
compile_error!("One of the features `pio` or `native` must be selected.");
33
use std::iter::once;
44

5+
use crate::native::cargo_driver::chip::Chip;
56
use anyhow::*;
67
use bindgen::callbacks::{IntKind, ParseCallbacks};
78
use common::*;
89
use embuild::bindgen::BindgenExt;
910
use embuild::utils::OsStrExt;
1011
use embuild::{bindgen as bindgen_utils, build, cargo, kconfig, path_buf};
12+
use std::fs::File;
13+
use std::io::BufReader;
14+
use std::io::{BufRead, Write};
15+
use std::str::FromStr;
1116

1217
mod common;
1318
mod config;
@@ -48,6 +53,132 @@ impl ParseCallbacks for BindgenCallbacks {
4853
}
4954
}
5055

56+
const STATIC_INLINE: &str = "static_inlines";
57+
58+
fn static_inlines_c() -> String {
59+
format!("{}.c", STATIC_INLINE)
60+
}
61+
62+
fn static_inlines_o() -> String {
63+
format!("{}.o", STATIC_INLINE)
64+
}
65+
66+
fn static_inlines_tmp() -> String {
67+
format!("{}_tmp.c", STATIC_INLINE)
68+
}
69+
70+
fn static_inlines_a() -> String {
71+
format!("lib{}.a", STATIC_INLINE)
72+
}
73+
74+
// TODO: The symbols from the components/esp_rom/<mcu>/ld are hard coded
75+
// addresses resolved during link time, rust linker cant find those symbols
76+
// and hence the inlines that depend on those dont work. Ignore them for now
77+
const IGNORE_STATIC_INLINES: [&str; 3] = [
78+
"_xtos_interrupt_enable__extern",
79+
"_xtos_interrupt_disable__extern",
80+
"esp_cpu_intr_get_handler_arg__extern",
81+
];
82+
83+
fn strip_quotes(args: &str) -> Vec<String> {
84+
let mut out = vec![];
85+
for arg in args.split_whitespace() {
86+
let mut chars = arg.chars();
87+
let first = chars.next();
88+
chars.next_back();
89+
let trim = if first == Some('\"') {
90+
chars.as_str()
91+
} else {
92+
arg
93+
};
94+
out.push(trim.to_string());
95+
}
96+
out
97+
}
98+
99+
fn ignore_api(api: &str) -> bool {
100+
for ignore in IGNORE_STATIC_INLINES.iter() {
101+
if api.contains(ignore) {
102+
return true;
103+
}
104+
}
105+
false
106+
}
107+
108+
fn process_static_inlines(
109+
build_output_args: &str,
110+
clang_args: Vec<String>,
111+
mcu: &str,
112+
headers: Vec<std::path::PathBuf>,
113+
) -> anyhow::Result<()> {
114+
let chip = Chip::from_str(mcu)?;
115+
let gcc = format!("{}-gcc", chip.gcc_toolchain());
116+
let ar = format!("{}-gcc-ar", chip.gcc_toolchain());
117+
118+
let out_dir_path = cargo::out_dir();
119+
let file = File::open(out_dir_path.join(static_inlines_c())).unwrap();
120+
let mut tmp = File::create(out_dir_path.join(static_inlines_tmp())).unwrap();
121+
let lines = BufReader::new(file).lines();
122+
for line in lines {
123+
let line = line.unwrap();
124+
if !ignore_api(&line) {
125+
tmp.write_all(line.as_bytes())?;
126+
writeln!(tmp)?;
127+
}
128+
}
129+
tmp.flush()?;
130+
131+
let mut gcc_cmd = std::process::Command::new(gcc);
132+
let mut gcc_args = gcc_cmd
133+
.arg("-mlongcalls")
134+
.arg("-O")
135+
.arg("-c")
136+
.arg("-o")
137+
.arg(out_dir_path.join(&static_inlines_o()))
138+
.arg(out_dir_path.join(&static_inlines_tmp()));
139+
for hdr in headers.iter() {
140+
gcc_args = gcc_args.arg("-include").arg(hdr);
141+
}
142+
gcc_args = gcc_args.args(strip_quotes(build_output_args));
143+
gcc_args = gcc_args.args(clang_args);
144+
145+
let gcc_output = gcc_args.output().unwrap();
146+
if !gcc_output.status.success() {
147+
panic!(
148+
"Could not compile object file:\n{}",
149+
String::from_utf8_lossy(&gcc_output.stderr)
150+
);
151+
}
152+
153+
#[cfg(not(target_os = "windows"))]
154+
let lib_output = std::process::Command::new(ar)
155+
.arg("rcs")
156+
.arg(out_dir_path.join(static_inlines_a()))
157+
.arg(out_dir_path.join(static_inlines_o()))
158+
.output()
159+
.unwrap();
160+
#[cfg(target_os = "windows")]
161+
let lib_output = std::process::Command::new("lib")
162+
.arg(&out_dir_path.join(static_inlines_o()))
163+
.output()
164+
.unwrap();
165+
166+
if !lib_output.status.success() {
167+
panic!(
168+
"Could not emit library file:\n{}",
169+
String::from_utf8_lossy(&lib_output.stderr)
170+
);
171+
}
172+
173+
println!(
174+
"cargo:rustc-link-search=native={}",
175+
out_dir_path.to_string_lossy()
176+
);
177+
println!("cargo:rustc-link-lib=static={}", STATIC_INLINE);
178+
179+
Ok(())
180+
}
181+
51182
fn main() -> anyhow::Result<()> {
52183
let build_output = build_driver::build()?;
53184

@@ -97,9 +228,13 @@ fn main() -> anyhow::Result<()> {
97228
// Because we have multiple bindgen invocations and we can't clone a bindgen::Builder,
98229
// we have to set the options every time.
99230
let configure_bindgen = |bindgen: bindgen::Builder| {
231+
let mut outdir = cargo::out_dir();
232+
outdir.push(STATIC_INLINE);
100233
Ok(bindgen
101234
.parse_callbacks(Box::new(BindgenCallbacks))
102235
.use_core()
236+
.wrap_static_fns(true)
237+
.wrap_static_fns_path(outdir)
103238
.enable_function_attribute_detection()
104239
.clang_arg("-DESP_PLATFORM")
105240
.blocklist_function("strtold")
@@ -145,7 +280,7 @@ fn main() -> anyhow::Result<()> {
145280
);
146281

147282
configure_bindgen(build_output.bindgen.clone().builder()?)?
148-
.headers(headers)?
283+
.headers(headers.clone())?
149284
.generate()
150285
.with_context(bindgen_err)?
151286
.write_to_file(&bindings_file)
@@ -185,7 +320,7 @@ fn main() -> anyhow::Result<()> {
185320
.into_iter()
186321
.chain(EspIdfVersion::parse(bindings_file)?.cfg_args())
187322
.chain(build_output.components.cfg_args())
188-
.chain(once(mcu))
323+
.chain(once(mcu.clone()))
189324
.collect(),
190325
};
191326
cfg_args.propagate();
@@ -211,5 +346,8 @@ fn main() -> anyhow::Result<()> {
211346
link_args.propagate();
212347
}
213348

349+
let clang_args: Vec<String> = build_output.components.clang_args().collect();
350+
process_static_inlines(&build_output.cincl_args.args, clang_args, &mcu, headers)?;
351+
214352
Ok(())
215353
}

0 commit comments

Comments
 (0)