zephyr_build/
lib.rs

1// Copyright (c) 2024 Linaro LTD
2// SPDX-License-Identifier: Apache-2.0
3
4// Pre-build code for zephyr module.
5
6// This module makes the values from the generated .config available as conditional compilation.
7// Note that this only applies to the zephyr module, and the user's application will not be able to
8// see these definitions.  To make that work, this will need to be moved into a support crate which
9// can be invoked by the user's build.rs.
10
11// This builds a program that is run on the compilation host before the code is compiled.  It can
12// output configuration settings that affect the compilation.
13
14use std::env;
15use std::fs::File;
16use std::io::{BufRead, BufReader, Write};
17use std::path::Path;
18use std::process::{Command, Stdio};
19
20use proc_macro2::TokenStream;
21use regex::Regex;
22
23use devicetree::{Augment, DeviceTree};
24
25mod devicetree;
26
27/// Export boolean Kconfig entries.  This must happen in any crate that wishes to access the
28/// configuration settings.
29pub fn export_bool_kconfig() {
30    let dotconfig = env::var("DOTCONFIG").expect("DOTCONFIG must be set by wrapper");
31
32    // Ensure the build script is rerun when the dotconfig changes.
33    println!("cargo:rerun-if-env-changed=DOTCONFIG");
34    println!("cargo-rerun-if-changed={}", dotconfig);
35
36    let config_y = Regex::new(r"^(CONFIG_.*)=y$").unwrap();
37
38    let file = File::open(&dotconfig).expect("Unable to open dotconfig");
39    for line in BufReader::new(file).lines() {
40        let line = line.expect("reading line from dotconfig");
41        if let Some(caps) = config_y.captures(&line) {
42            println!("cargo:rustc-cfg={}", &caps[1]);
43        }
44    }
45}
46
47/// Capture bool, numeric and string kconfig values in a 'kconfig' module.
48/// This is a little simplistic, and will make the entries numeric if they look like numbers.
49/// Ideally, this would be built on the types of the values, but that will require more
50/// introspection.
51pub fn build_kconfig_mod() {
52    let dotconfig = env::var("DOTCONFIG").expect("DOTCONFIG must be set by wrapper");
53    let outdir = env::var("OUT_DIR").expect("OUT_DIR must be set");
54
55    // The assumption is that hex values are unsigned, and decimal are signed.
56    let config_hex = Regex::new(r"^(CONFIG_.*)=(0x[0-9a-fA-F]+)$").unwrap();
57    let config_int = Regex::new(r"^(CONFIG_.*)=(-?[1-9][0-9]*)$").unwrap();
58    // It is unclear what quoting might be used in the .config.
59    let config_str = Regex::new(r#"^(CONFIG_.*)=(".*")$"#).unwrap();
60    let gen_path = Path::new(&outdir).join("kconfig.rs");
61
62    let mut f = File::create(&gen_path).unwrap();
63
64    let file = File::open(&dotconfig).expect("Unable to open dotconfig");
65    for line in BufReader::new(file).lines() {
66        let line = line.expect("reading line from dotconfig");
67        if let Some(caps) = config_hex.captures(&line) {
68            writeln!(&mut f, "#[allow(dead_code)]").unwrap();
69            writeln!(&mut f, "pub const {}: usize = {};", &caps[1], &caps[2]).unwrap();
70        } else if let Some(caps) = config_int.captures(&line) {
71            writeln!(&mut f, "#[allow(dead_code)]").unwrap();
72            writeln!(&mut f, "pub const {}: isize = {};", &caps[1], &caps[2]).unwrap();
73        } else if let Some(caps) = config_str.captures(&line) {
74            writeln!(&mut f, "#[allow(dead_code)]").unwrap();
75            writeln!(&mut f, "pub const {}: &str = {};", &caps[1], &caps[2]).unwrap();
76        }
77    }
78}
79
80/// Parse the finalized DTS file, generating the Rust devicetree file.
81fn import_dt() -> DeviceTree {
82    let zephyr_dts = env::var("ZEPHYR_DTS").expect("ZEPHYR_DTS must be set");
83    let gen_include =
84        env::var("BINARY_DIR_INCLUDE_GENERATED").expect("BINARY_DIR_INCLUDE_GENERATED must be set");
85
86    let generated = format!("{}/devicetree_generated.h", gen_include);
87    DeviceTree::new(&zephyr_dts, generated)
88}
89
90pub fn build_dts() {
91    let dt = import_dt();
92
93    let outdir = env::var("OUT_DIR").expect("OUT_DIR must be set");
94    let out_path = Path::new(&outdir).join("devicetree.rs");
95    let mut out = File::create(&out_path).expect("Unable to create devicetree.rs");
96
97    let augments = env::var("DT_AUGMENTS").expect("DT_AUGMENTS must be set");
98    let augments: Vec<String> = augments.split_whitespace().map(String::from).collect();
99
100    // Make sure that cargo knows to run if this changes, or any file mentioned changes.
101    println!("cargo:rerun-if-env-changed=DT_AUGMENTS");
102    for name in &augments {
103        println!("cargo:rerun-if-changed={}", name);
104    }
105
106    let mut augs = Vec::new();
107    for aug in &augments {
108        // println!("Load augment: {:?}", aug);
109        let mut aug = devicetree::load_augments(aug).expect("Loading augment file");
110        augs.append(&mut aug);
111    }
112    // For now, just print it out.
113    // println!("augments: {:#?}", augs);
114    let augs: Vec<_> = augs
115        .into_iter()
116        .map(|aug| Box::new(aug) as Box<dyn Augment>)
117        .collect();
118
119    let tokens = dt.to_tokens(&augs);
120    if has_rustfmt() {
121        write_formatted(out, tokens);
122    } else {
123        writeln!(out, "{}", tokens).unwrap();
124    };
125}
126
127/// Generate cfg directives for each of the nodes in the generated device tree.
128///
129/// This assumes that build_dts was already run by the `zephyr` crate, which should happen if this
130/// is called from a user application.
131pub fn dt_cfgs() {
132    let dt = import_dt();
133    dt.output_node_paths(&mut std::io::stdout()).unwrap();
134}
135
136/// Determine if `rustfmt` is in the path, and can be excecuted. Returns false on any kind of error.
137pub fn has_rustfmt() -> bool {
138    matches!(Command::new("rustfmt").arg("--version").status(), Ok(st) if st.success())
139}
140
141/// Attempt to write the contents to a file, using rustfmt. If there is an error running rustfmt,
142/// print a warning, and then just directly write the file.
143fn write_formatted(file: File, tokens: TokenStream) {
144    let mut rustfmt = Command::new("rustfmt")
145        .args(["--emit", "stdout"])
146        .stdin(Stdio::piped())
147        .stdout(file)
148        .stderr(Stdio::inherit())
149        .spawn()
150        .expect("Failed to run rustfmt");
151    // TODO: Handle the above failing.
152
153    let mut stdin = rustfmt
154        .stdin
155        .as_ref()
156        .expect("Stdin should have been opened by spawn");
157    writeln!(stdin, "{}", tokens).expect("Writing to rustfmt");
158
159    match rustfmt.wait() {
160        Ok(st) if st.success() => (),
161        _ => panic!("Failure running rustfmt"),
162    }
163}