use anyhow::Result;
use std::fs;
use std::path::Path;
use std::process::Command;
fn main() {
let bindings_path = "src/bindings/bindings.rs";
let mut bindings = Binding::new();
let sfud = Binding::new()
.add_include_dir("-Isrc/driver/mtd/sfud")
.add_headers_from_dir("src/driver/mtd/sfud")
.add_sources_from_dir("src/driver/mtd/sfud")
.compile("sfud")
.expect("Failed to compile SFUD C code")
.clone();
bindings = bindings.concat(sfud);
let littlefs = Binding::new()
.add_include_dir("-Isrc/driver/fs/littlefs")
.add_headers_from_dir("src/driver/fs/littlefs")
.add_sources_from_dir("src/driver/fs/littlefs")
.compile("littlefs")
.expect("Failed to compile LittleFS C code")
.clone();
bindings = bindings.concat(littlefs);
bindings
.generate(bindings_path)
.expect("Failed to generate bindings");
}
#[derive(Debug, Clone)]
struct Binding {
defines: Vec<String>, include_dirs: Vec<String>, headers: Vec<String>, sources: Vec<String>, }
#[allow(unused)]
impl Binding {
pub fn new() -> Self {
Binding {
defines: vec![],
include_dirs: vec![],
headers: vec![],
sources: vec![],
}
}
pub fn concat(&self, other: Binding) -> Binding {
let mut new_binding = Binding::new();
new_binding.defines = {
let mut seen = std::collections::HashSet::new();
let mut result = Vec::new();
for define in self.defines.iter().chain(other.defines.iter()) {
if seen.insert(define) {
result.push(define.clone());
}
}
result
};
new_binding.include_dirs = {
let mut seen = std::collections::HashSet::new();
let mut result = Vec::new();
for include_dir in self.include_dirs.iter().chain(other.include_dirs.iter()) {
if seen.insert(include_dir) {
result.push(include_dir.clone());
}
}
result
};
new_binding.headers = {
let mut seen = std::collections::HashSet::new();
let mut result = Vec::new();
for header in self.headers.iter().chain(other.headers.iter()) {
if seen.insert(header) {
result.push(header.clone());
}
}
result
};
new_binding.sources = {
let mut seen = std::collections::HashSet::new();
let mut result = Vec::new();
for source in self.sources.iter().chain(other.sources.iter()) {
if seen.insert(source) {
result.push(source.clone());
}
}
result
};
new_binding
}
pub fn dump(&self) -> &Self {
println!("RUST_BACKTRACE=1Binding:");
println!("RUST_BACKTRACE=1 Defines:");
for define in &self.defines {
println!("RUST_BACKTRACE=1 {}", define);
}
println!("RUST_BACKTRACE=1 Include Dirs:");
for include_dir in &self.include_dirs {
println!("RUST_BACKTRACE=1 {}", include_dir);
}
println!("RUST_BACKTRACE=1 Headers:");
for header in &self.headers {
println!("RUST_BACKTRACE=1 {}", header);
}
self
}
pub fn from_makefile(makefile_path: &str) -> Result<Self> {
let top_dir = Path::new(makefile_path)
.parent()
.ok_or_else(|| anyhow::anyhow!("Failed to get parent directory of Makefile"))?;
let defines = Self::get_makefile_variable(makefile_path, "C_DEFS")
.map_err(|err| anyhow::anyhow!("Failed to get defines: {}", err))?;
let include_dirs = Self::get_makefile_variable(makefile_path, "C_INCLUDES")
.map_err(|err| anyhow::anyhow!("Failed to get include dirs: {}", err))?;
let include_dirs = include_dirs
.iter()
.map(|dir| {
let path = dir.trim_start_matches("-I");
format!("-I{}", top_dir.join(path).to_str().unwrap())
})
.collect::<Vec<String>>();
Ok(Binding {
defines,
include_dirs,
headers: vec![],
sources: vec![],
})
}
pub fn add_header(&mut self, header: &str) -> &mut Self {
self.headers.push(header.to_string());
self
}
pub fn add_system_header(&mut self, header: &str) -> &mut Self {
let gcc_include_paths = Self::get_gcc_include_paths().unwrap_or_default();
for include_path in &gcc_include_paths {
let full_path = format!("{}/{}", include_path.trim_start_matches("-I"), header);
if Path::new(&full_path).exists() {
self.headers.push(full_path);
return self;
}
}
panic!(
"System header {} not found in GCC include paths: {}",
header,
gcc_include_paths.join("\n")
);
}
pub fn add_headers(&mut self, headers: Vec<&str>) -> &mut Self {
for header in headers {
self.headers.push(header.to_string());
}
self
}
pub fn add_source(&mut self, source: &str) -> &mut Self {
self.sources.push(source.to_string());
self
}
pub fn add_sources(&mut self, sources: Vec<&str>) -> &mut Self {
for source in sources {
self.sources.push(source.to_string());
}
self
}
pub fn add_sources_from_dir(&mut self, dir_path: &str) -> &mut Self {
let entries = fs::read_dir(dir_path)
.map_err(|e| anyhow::anyhow!("Failed to read directory {}: {}", dir_path, e))
.unwrap();
for entry in entries {
let entry = entry
.map_err(|e| anyhow::anyhow!("Failed to read entry in {}: {}", dir_path, e))
.unwrap();
let path = entry.path();
if path.is_file() {
if let Some(ext) = path.extension() {
if ext == "c" {
if let Some(path_str) = path.to_str() {
self.sources.push(path_str.to_string());
}
}
}
}
}
self
}
pub fn add_headers_from_dir(&mut self, dir_path: &str) -> &mut Self {
let entries = fs::read_dir(dir_path)
.map_err(|e| anyhow::anyhow!("Failed to read directory {}: {}", dir_path, e))
.unwrap();
for entry in entries {
let entry = entry
.map_err(|e| anyhow::anyhow!("Failed to read entry in {}: {}", dir_path, e))
.unwrap();
let path = entry.path();
if path.is_file() {
if let Some(ext) = path.extension() {
if ext == "h" {
if let Some(path_str) = path.to_str() {
self.headers.push(path_str.to_string());
}
}
}
}
}
self
}
pub fn add_define(&mut self, define: &str) -> &mut Self {
if define.starts_with("-D") == false {
panic!("Define must start with '-D'");
}
self.defines.push(define.to_string());
self
}
pub fn add_include_dir(&mut self, include_dir: &str) -> &mut Self {
if include_dir.starts_with("-I") == false {
panic!("Include dir must start with '-I'");
}
self.include_dirs.push(include_dir.to_string());
self
}
pub fn add_include_dir_from_makefile(
&mut self,
makefile_path: &str,
var_name: &str,
) -> &mut Self {
let include_dirs = Self::get_makefile_variable(makefile_path, var_name).unwrap_or_default();
for include_dir in include_dirs {
self.include_dirs.push(include_dir);
}
self
}
pub fn generate(&self, output_path: &str) -> Result<&Self> {
println!("cargo:rerun-if-changed={}", output_path);
if let Some(parent) = Path::new(output_path).parent() {
fs::create_dir_all(parent)?;
}
let mut builder = bindgen::Builder::default();
for define in &self.defines {
builder = builder.clang_arg(define);
}
for include_dir in &self.include_dirs {
builder = builder.clang_arg(include_dir);
}
let gcc_include_paths = Self::get_gcc_include_paths()?;
for include_path in gcc_include_paths {
builder = builder.clang_arg(include_path);
}
builder = builder.headers(self.headers.clone());
let header_list_path = output_path.to_string() + ".headers";
fs::write(&header_list_path, self.headers.join("\n"))
.map_err(|e| anyhow::anyhow!("Failed to write header list file: {}", e))?;
let include_dir_list_path = output_path.to_string() + ".includes";
fs::write(&include_dir_list_path, self.include_dirs.join("\n"))
.map_err(|e| anyhow::anyhow!("Failed to write include dir list file: {}", e))?;
let define_list_path = output_path.to_string() + ".defines";
fs::write(&define_list_path, self.defines.join("\n"))
.map_err(|e| anyhow::anyhow!("Failed to write define list file: {}", e))?;
let bindings = builder
.parse_callbacks(Box::new(bindgen::CargoCallbacks::new())) .allowlist_recursively(true) .generate_comments(true) .use_core() .derive_default(true) .derive_debug(true) .size_t_is_usize(true) .raw_line("#![allow(non_upper_case_globals)]") .raw_line("#![allow(non_camel_case_types)]") .raw_line("#![allow(non_snake_case)]") .generate()
.expect("Unable to generate bindings");
bindings
.write_to_file(output_path)
.expect("Couldn't write bindings!");
Ok(self)
}
pub fn compile(&self, output_lib: &str) -> Result<&Self> {
let mut builder = cc::Build::new();
for define in &self.defines {
let def = define.trim_start_matches("-D");
if let Some(pos) = def.find('=') {
builder.define(&def[..pos], Some(&def[pos + 1..]));
} else {
builder.define(def, None);
}
}
for include in &self.include_dirs {
let path = include.trim_start_matches("-I");
builder.include(path);
println!("cargo:rerun-if-changed={}", path);
}
for source in &self.sources {
builder.file(source);
eprintln!(" Compiling: {}", source);
println!("cargo:rerun-if-changed={}", source);
}
builder.compile(output_lib);
Ok(self)
}
fn get_gcc_include_paths() -> Result<Vec<String>> {
let output = Command::new("arm-none-eabi-gcc")
.args(&["-E", "-Wp,-v", "-xc", "/dev/null"])
.output()?;
let stderr = String::from_utf8_lossy(&output.stderr);
let mut paths = Vec::new();
let mut in_include_section = false;
for line in stderr.lines() {
if line.contains("#include <...> search starts here:") {
in_include_section = true;
continue;
}
if line.contains("End of search list.") {
break;
}
if in_include_section {
let path = line.trim();
if !path.is_empty() {
paths.push(format!("-I{}", path));
}
}
}
Ok(paths)
}
fn get_makefile_variable(
makefile_path: &str,
var_name: &str,
) -> Result<Vec<String>, Box<dyn std::error::Error>> {
let content = fs::read_to_string(makefile_path)?;
let lines: Vec<&str> = content.lines().collect();
let mut result = Vec::new();
let mut i = 0;
while i < lines.len() {
let line = lines[i].trim();
if line.starts_with('#') || line.is_empty() {
i += 1;
continue;
}
if let Some((found_var, operator, initial_value)) =
Self::parse_variable_assignment(line)
{
if found_var == var_name {
if !initial_value.is_empty() {
Self::extract_values(&initial_value, &mut result);
}
if line.ends_with('\\') {
i += 1;
while i < lines.len() {
let continuation_line = lines[i].trim();
let content = if continuation_line.ends_with('\\') {
&continuation_line[..continuation_line.len() - 1].trim()
} else {
continuation_line
};
if !content.is_empty() {
Self::extract_values(content, &mut result);
}
if !continuation_line.ends_with('\\') {
break;
}
i += 1;
}
}
if operator == "=" {
break;
}
}
}
i += 1;
}
Ok(result)
}
fn parse_variable_assignment(line: &str) -> Option<(String, String, String)> {
if let Some(eq_pos) = line.find('=') {
let before_eq = &line[..eq_pos].trim();
let after_eq = &line[eq_pos + 1..].trim();
let (var_name, operator) = if before_eq.ends_with('+') {
(&before_eq[..before_eq.len() - 1].trim(), "+=")
} else {
(before_eq, "=")
};
if var_name.chars().all(|c| c.is_alphanumeric() || c == '_') {
let initial_value = if after_eq.ends_with('\\') {
&after_eq[..after_eq.len() - 1].trim()
} else {
after_eq
};
return Some((
var_name.to_string(),
operator.to_string(),
initial_value.to_string(),
));
}
}
None
}
fn extract_values(content: &str, values: &mut Vec<String>) {
let items: Vec<String> = content
.split_whitespace()
.filter(|s| !s.is_empty())
.map(|s| s.to_string())
.collect();
values.extend(items);
}
}