From 7d4af3e7f812319e814b17176fcdf3370b04d711 Mon Sep 17 00:00:00 2001 From: Julien Peeters Date: Mon, 9 Dec 2024 09:57:59 +0000 Subject: [PATCH 1/7] zephyr-sys: use expect instead of unwrap This commit uses `expect` instead of `unwrap` on Result<_, _> in order to provide better error information to developer. --- zephyr-sys/build.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/zephyr-sys/build.rs b/zephyr-sys/build.rs index 5d43b42d..796c4556 100644 --- a/zephyr-sys/build.rs +++ b/zephyr-sys/build.rs @@ -50,8 +50,8 @@ fn main() -> Result<()> { // println!("includes: {:?}", env::var("INCLUDE_DIRS")); // println!("defines: {:?}", env::var("INCLUDE_DEFINES")); - let out_path = PathBuf::from(env::var("OUT_DIR").unwrap()); - let wrapper_path = PathBuf::from(env::var("WRAPPER_FILE").unwrap()); + let out_path = PathBuf::from(env::var("OUT_DIR").expect("missing output directory")); + let wrapper_path = PathBuf::from(env::var("WRAPPER_FILE").expect("missing wrapper file")); // Bindgen everything. let bindings = Builder::default() @@ -91,7 +91,7 @@ fn main() -> Result<()> { } fn define_args(bindings: Builder, prefix: &str, var_name: &str) -> Builder { - let text = env::var(var_name).unwrap(); + let text = env::var(var_name).expect("missing environment variable"); let mut bindings = bindings; for entry in text.split(" ") { if entry.is_empty() { From efd9538a439a7aeaff69b68430f1c1daeb7fa572 Mon Sep 17 00:00:00 2001 From: Julien Peeters Date: Mon, 9 Dec 2024 10:08:29 +0000 Subject: [PATCH 2/7] zephyr-build: separate kconfig extraction from cargo build config This commit separates the implementation of Kconfig extraction from the generation of cargo configuration at build time. With this commit, the result of Kconfig extraction can be reused anywhere else, providing a set of enabled Kconfig options. --- zephyr-build/Cargo.toml | 1 + zephyr-build/src/lib.rs | 37 +++++++++++++++++++++++++++---------- zephyr/build.rs | 2 +- 3 files changed, 29 insertions(+), 11 deletions(-) diff --git a/zephyr-build/Cargo.toml b/zephyr-build/Cargo.toml index a31eaa8e..22498955 100644 --- a/zephyr-build/Cargo.toml +++ b/zephyr-build/Cargo.toml @@ -14,4 +14,5 @@ Provides utilities for accessing Kconfig and devicetree information. # Whether these need to be vendored is an open question. They are not # used by the core Zephyr tree, but are needed by zephyr applications. [dependencies] +anyhow = "1.0.93" regex = "1.10.3" diff --git a/zephyr-build/src/lib.rs b/zephyr-build/src/lib.rs index bec71347..50ffa5fa 100644 --- a/zephyr-build/src/lib.rs +++ b/zephyr-build/src/lib.rs @@ -12,30 +12,47 @@ // output configuration settings that affect the compilation. use std::io::{BufRead, BufReader, Write}; +use std::collections::HashSet; use std::env; use std::fs::File; use std::path::Path; use regex::Regex; +/// Extract boolean Kconfig entries. This must happen in any crate that wishes to access the +/// configuration settings. +pub fn extract_kconfig_bool_options(path: &str) -> anyhow::Result> { + let config_y = Regex::new(r"^(CONFIG_.*)=y$").expect("hardcoded regex is always valid"); + + let input = File::open(path)?; + let flags: HashSet = + BufReader::new(input) + .lines() + .fold(HashSet::new(), |mut set, line| { + if let Ok(line) = &line { + if let Some(caps) = config_y.captures(line) { + set.insert(caps[1].into()); + } + } + set + }); + + Ok(flags) +} + /// Export boolean Kconfig entries. This must happen in any crate that wishes to access the /// configuration settings. -pub fn export_bool_kconfig() { +pub fn export_kconfig_bool_options() { let dotconfig = env::var("DOTCONFIG").expect("DOTCONFIG must be set by wrapper"); // Ensure the build script is rerun when the dotconfig changes. println!("cargo:rerun-if-env-changed=DOTCONFIG"); println!("cargo-rerun-if-changed={}", dotconfig); - let config_y = Regex::new(r"^(CONFIG_.*)=y$").unwrap(); - - let file = File::open(&dotconfig).expect("Unable to open dotconfig"); - for line in BufReader::new(file).lines() { - let line = line.expect("reading line from dotconfig"); - if let Some(caps) = config_y.captures(&line) { - println!("cargo:rustc-cfg={}", &caps[1]); - } - } + extract_kconfig_bool_options(&dotconfig) + .expect("failed to extract flags from .config") + .iter() + .for_each(|flag| println!("cargo:rustc-cfg={flag}")); } /// Capture bool, numeric and string kconfig values in a 'kconfig' module. diff --git a/zephyr/build.rs b/zephyr/build.rs index f4345e95..34ee38b5 100644 --- a/zephyr/build.rs +++ b/zephyr/build.rs @@ -12,6 +12,6 @@ // output configuration settings that affect the compilation. fn main() { - zephyr_build::export_bool_kconfig(); + zephyr_build::export_kconfig_bool_options(); zephyr_build::build_kconfig_mod(); } From 554bca118cd3c2d7b560dc7650f025707afaa06d Mon Sep 17 00:00:00 2001 From: Julien Peeters Date: Mon, 9 Dec 2024 10:14:29 +0000 Subject: [PATCH 3/7] zephyr-sys: provide selective binding exports based on kconfig This commit provides a selective generation of bindings based on Kconfig options. Then, only enabled features has exported bindings to Rust. As a result, one developer can see early during the Rust application build if the correct Kconfig options has been enabled. Before this commit, most problems appeared during C and Rust linking time. --- zephyr-sys/build.rs | 103 +++++++++++++++++++++++++++++++++---------- zephyr-sys/wrapper.h | 5 ++- 2 files changed, 84 insertions(+), 24 deletions(-) diff --git a/zephyr-sys/build.rs b/zephyr-sys/build.rs index 796c4556..a3b4a7d5 100644 --- a/zephyr-sys/build.rs +++ b/zephyr-sys/build.rs @@ -11,14 +11,12 @@ // This builds a program that is run on the compilation host before the code is compiled. It can // output configuration settings that affect the compilation. -use anyhow::Result; - -use bindgen::Builder; - use std::env; use std::path::{Path, PathBuf}; -fn main() -> Result<()> { +use bindgen::Builder; + +fn main() -> anyhow::Result<()> { // Determine which version of Clang we linked with. let version = bindgen::clang_version(); println!("Clang version: {:?}", version); @@ -55,31 +53,54 @@ fn main() -> Result<()> { // Bindgen everything. let bindings = Builder::default() - .header(Path::new("wrapper.h").canonicalize().unwrap().to_str().unwrap()) - .use_core() - .clang_arg(&target_arg); + .clang_arg("-DRUST_BINDGEN") + .clang_arg(format!("-I{}/lib/libc/minimal/include", zephyr_base)) + .clang_arg(&target_arg) + .header( + Path::new("wrapper.h") + .canonicalize() + .unwrap() + .to_str() + .unwrap(), + ) + .use_core(); + let bindings = define_args(bindings, "-I", "INCLUDE_DIRS"); let bindings = define_args(bindings, "-D", "INCLUDE_DEFINES"); + let bindings = bindings .wrap_static_fns(true) - .wrap_static_fns_path(wrapper_path) - // seems to come from somewhere mysterious in Zephyr. For us, just pull in the - // one from the minimal libc. - .clang_arg("-DRUST_BINDGEN") - .clang_arg(format!("-I{}/lib/libc/minimal/include", zephyr_base)) - .derive_copy(false) - .allowlist_function("k_.*") - .allowlist_function("gpio_.*") - .allowlist_function("sys_.*") - .allowlist_function("z_log.*") - .allowlist_function("bt_.*") + .wrap_static_fns_path(wrapper_path); + + let bindings = bindings.derive_copy(false); + + let bindings = bindings + // Deprecated + .blocklist_function("sys_clock_timeout_end_calc") + .parse_callbacks(Box::new(bindgen::CargoCallbacks::new())); + + let dotconfig = env::var("DOTCONFIG").expect("missing DOTCONFIG path"); + let options = zephyr_build::extract_kconfig_bool_options(&dotconfig) + .expect("failed to extract kconfig boolean options"); + + let bindings = bindings + // Kernel .allowlist_item("E.*") .allowlist_item("K_.*") + .allowlist_item("Z_.*") .allowlist_item("ZR_.*") - .allowlist_item("LOG_LEVEL_.*") - // Deprecated - .blocklist_function("sys_clock_timeout_end_calc") - .parse_callbacks(Box::new(bindgen::CargoCallbacks::new())) + .allowlist_function("k_.*") + .allowlist_function("z_log.*") + // Bluetooth + .allowlist_item_if("CONFIG_BT_.*", || options.contains("CONFIG_BT")) + .allowlist_function_if("bt_.*", || options.contains("CONFIG_BT")) + // GPIO + .allowlist_item_if("CONFIG_GPIO_.*", || options.contains("CONFIG_GPIO")) + .allowlist_function_if("gpio_.*", || options.contains("CONFIG_GPIO")) + // UART + .allowlist_item_if("CONFIG_UART_.*", || options.contains("CONFIG_SERIAL")) + .allowlist_function_if("uart_.*", || options.contains("CONFIG_SERIAL")) + // Generate .generate() .expect("Unable to generate bindings"); @@ -90,6 +111,42 @@ fn main() -> Result<()> { Ok(()) } +trait BuilderExt { + type B; + + fn allowlist_function_if

(self, pattern: &str, pred: P) -> Self::B + where + P: FnOnce() -> bool; + + fn allowlist_item_if

(self, pattern: &str, pred: P) -> Self::B + where + P: FnOnce() -> bool; +} + +impl BuilderExt for Builder { + type B = Builder; + + fn allowlist_function_if

(self, pattern: &str, pred: P) -> Self::B + where + P: FnOnce() -> bool, + { + if pred() { + return self.allowlist_function(pattern); + } + self + } + + fn allowlist_item_if

(self, pattern: &str, pred: P) -> Self::B + where + P: FnOnce() -> bool, + { + if pred() { + return self.allowlist_item(pattern); + } + self + } +} + fn define_args(bindings: Builder, prefix: &str, var_name: &str) -> Builder { let text = env::var(var_name).expect("missing environment variable"); let mut bindings = bindings; diff --git a/zephyr-sys/wrapper.h b/zephyr-sys/wrapper.h index 8c3ca158..fb04c5d2 100644 --- a/zephyr-sys/wrapper.h +++ b/zephyr-sys/wrapper.h @@ -32,9 +32,12 @@ extern int errno; #include #include + #include -#include +#include + #include +#include /* * bindgen will output #defined constant that resolve to simple numbers. There are some symbols From 6d1ab8a23e4beccedcddb7340a0b439d87ce72dc Mon Sep 17 00:00:00 2001 From: Julien Peeters Date: Mon, 9 Dec 2024 10:36:01 +0000 Subject: [PATCH 4/7] zephyr-sys: add missing zyphyr-build crate as dependency --- zephyr-sys/Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/zephyr-sys/Cargo.toml b/zephyr-sys/Cargo.toml index 385dcd5f..81f4a8f7 100644 --- a/zephyr-sys/Cargo.toml +++ b/zephyr-sys/Cargo.toml @@ -16,3 +16,4 @@ Zephyr low-level API bindings. anyhow = "1.0" bindgen = { version = "0.69.4", features = ["experimental"] } # zephyr-build = { version = "3.7.0", path = "../zephyr-build" } +zephyr-build = { version = "3.7.0", path = "../zephyr-build" } From 7a539a437ac40a7b29484da0762a38045f8bcfbb Mon Sep 17 00:00:00 2001 From: Julien Peeters Date: Mon, 9 Dec 2024 10:40:03 +0000 Subject: [PATCH 5/7] zephyr-sys: fix missing export bindings for log items --- zephyr-sys/build.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/zephyr-sys/build.rs b/zephyr-sys/build.rs index a3b4a7d5..89745bfc 100644 --- a/zephyr-sys/build.rs +++ b/zephyr-sys/build.rs @@ -87,6 +87,7 @@ fn main() -> anyhow::Result<()> { // Kernel .allowlist_item("E.*") .allowlist_item("K_.*") + .allowlist_item("LOG_.*") .allowlist_item("Z_.*") .allowlist_item("ZR_.*") .allowlist_function("k_.*") From 7a51798abaeec970e511cf7a60e3a69c680c7f1e Mon Sep 17 00:00:00 2001 From: Julien Peeters Date: Fri, 20 Dec 2024 13:31:56 +0000 Subject: [PATCH 6/7] zephyr-build: refactor iteration on .config lines --- zephyr-build/src/lib.rs | 33 +++++++++++++++------------------ 1 file changed, 15 insertions(+), 18 deletions(-) diff --git a/zephyr-build/src/lib.rs b/zephyr-build/src/lib.rs index 50ffa5fa..582ad68f 100644 --- a/zephyr-build/src/lib.rs +++ b/zephyr-build/src/lib.rs @@ -11,10 +11,10 @@ // This builds a program that is run on the compilation host before the code is compiled. It can // output configuration settings that affect the compilation. -use std::io::{BufRead, BufReader, Write}; use std::collections::HashSet; use std::env; use std::fs::File; +use std::io::{BufRead, BufReader, Write}; use std::path::Path; use regex::Regex; @@ -25,17 +25,12 @@ pub fn extract_kconfig_bool_options(path: &str) -> anyhow::Result = - BufReader::new(input) - .lines() - .fold(HashSet::new(), |mut set, line| { - if let Ok(line) = &line { - if let Some(caps) = config_y.captures(line) { - set.insert(caps[1].into()); - } - } - set - }); + let flags: HashSet = BufReader::new(input) + .lines() + // If the line is an Err(_), just ignore it. + .map(|x| x.unwrap_or_default()) + .filter(|line| config_y.is_match(line)) + .collect(); Ok(flags) } @@ -77,16 +72,18 @@ pub fn build_kconfig_mod() { let line = line.expect("reading line from dotconfig"); if let Some(caps) = config_hex.captures(&line) { writeln!(&mut f, "#[allow(dead_code)]").unwrap(); - writeln!(&mut f, "pub const {}: usize = {};", - &caps[1], &caps[2]).unwrap(); + writeln!(&mut f, "pub const {}: usize = {};", &caps[1], &caps[2]).unwrap(); } else if let Some(caps) = config_int.captures(&line) { writeln!(&mut f, "#[allow(dead_code)]").unwrap(); - writeln!(&mut f, "pub const {}: isize = {};", - &caps[1], &caps[2]).unwrap(); + writeln!(&mut f, "pub const {}: isize = {};", &caps[1], &caps[2]).unwrap(); } else if let Some(caps) = config_str.captures(&line) { writeln!(&mut f, "#[allow(dead_code)]").unwrap(); - writeln!(&mut f, "pub const {}: &'static str = {};", - &caps[1], &caps[2]).unwrap(); + writeln!( + &mut f, + "pub const {}: &'static str = {};", + &caps[1], &caps[2] + ) + .unwrap(); } } } From 9d5d375ff2c7faa3a9227aed12489b78d18152dc Mon Sep 17 00:00:00 2001 From: Julien Peeters Date: Fri, 20 Dec 2024 13:32:25 +0000 Subject: [PATCH 7/7] samples: fix use of renamed function from zephyr-build --- samples/philosophers/build.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/philosophers/build.rs b/samples/philosophers/build.rs index 22233f15..0e4ea67c 100644 --- a/samples/philosophers/build.rs +++ b/samples/philosophers/build.rs @@ -5,5 +5,5 @@ // zephyr-build must be a build dependency. fn main() { - zephyr_build::export_bool_kconfig(); + zephyr_build::export_kconfig_bool_options(); }