refactor(gen_features): use Rust instead of Python
Done for /embassy-stm32 only The new generator is in /stm32-gen-features /stm32-metapac could/should be added too A CI check "generated features up to date" could/should be performed
This commit is contained in:
parent
1e1cd0506a
commit
022b809248
7 changed files with 934 additions and 777 deletions
File diff suppressed because it is too large
Load diff
|
@ -1,54 +0,0 @@
|
|||
import os
|
||||
import toml
|
||||
import yaml
|
||||
from glob import glob
|
||||
|
||||
try:
|
||||
from yaml import CSafeLoader as SafeLoader
|
||||
except ImportError:
|
||||
from yaml import SafeLoader
|
||||
|
||||
abspath = os.path.abspath(__file__)
|
||||
dname = os.path.dirname(abspath)
|
||||
os.chdir(dname)
|
||||
|
||||
supported_families = [
|
||||
"STM32F0",
|
||||
'STM32F4',
|
||||
'STM32G0',
|
||||
'STM32L0',
|
||||
'STM32L4',
|
||||
'STM32H7',
|
||||
'STM32WB55',
|
||||
'STM32WL55',
|
||||
]
|
||||
|
||||
# ======= load chip list
|
||||
features = {}
|
||||
for f in sorted(glob('../stm32-data/data/chips/*.yaml')):
|
||||
# Use the filename to get the chip name. Ultra fast, we don't have to read YAML!
|
||||
name = os.path.splitext(os.path.basename(f))[0]
|
||||
if any((family in name for family in supported_families)):
|
||||
name = name.lower()
|
||||
# ======= load chip
|
||||
with open(f, 'r') as f:
|
||||
chip = yaml.load(f, Loader=SafeLoader)
|
||||
|
||||
if len(chip['cores']) > 1:
|
||||
for core in chip['cores']:
|
||||
features[name + "_" + core['name']] = ['stm32-metapac/' + name + '_' + core['name']]
|
||||
else:
|
||||
features[name] = ['stm32-metapac/' + name]
|
||||
|
||||
# ========= Update Cargo features
|
||||
|
||||
SEPARATOR_START = '# BEGIN GENERATED FEATURES\n'
|
||||
SEPARATOR_END = '# END GENERATED FEATURES\n'
|
||||
HELP = '# Generated by gen_features.py. DO NOT EDIT.\n'
|
||||
with open('Cargo.toml', 'r') as f:
|
||||
cargo = f.read()
|
||||
before, cargo = cargo.split(SEPARATOR_START, maxsplit=1)
|
||||
_, after = cargo.split(SEPARATOR_END, maxsplit=1)
|
||||
cargo = before + SEPARATOR_START + HELP + toml.dumps(features) + SEPARATOR_END + after
|
||||
with open('Cargo.toml', 'w') as f:
|
||||
f.write(cargo)
|
3
stm32-gen-features/.cargo/config.toml
Normal file
3
stm32-gen-features/.cargo/config.toml
Normal file
|
@ -0,0 +1,3 @@
|
|||
[profile.dev]
|
||||
opt-level = 3
|
||||
lto = false
|
1
stm32-gen-features/.gitignore
vendored
Normal file
1
stm32-gen-features/.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
/target
|
12
stm32-gen-features/Cargo.toml
Normal file
12
stm32-gen-features/Cargo.toml
Normal file
|
@ -0,0 +1,12 @@
|
|||
[package]
|
||||
name = "gen_features"
|
||||
version = "0.1.0"
|
||||
authors = ["Côme ALLART <come.allart@netc.fr>"]
|
||||
edition = "2018"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
glob = "0.3.0"
|
||||
yaml-rust = "0.4.5"
|
||||
toml = "0.5.8"
|
177
stm32-gen-features/src/lib.rs
Normal file
177
stm32-gen-features/src/lib.rs
Normal file
|
@ -0,0 +1,177 @@
|
|||
use std::{
|
||||
collections::HashMap,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
const SUPPORTED_FAMILIES: [&str; 8] = [
|
||||
"STM32F0",
|
||||
"STM32F4",
|
||||
"STM32G0",
|
||||
"STM32L0",
|
||||
"STM32L4",
|
||||
"STM32H7",
|
||||
"STM32WB55",
|
||||
"STM32WL55",
|
||||
];
|
||||
|
||||
const SEPARATOR_START: &str = "# BEGIN GENERATED FEATURES\n";
|
||||
const SEPARATOR_END: &str = "# END GENERATED FEATURES\n";
|
||||
const HELP: &str = "# Generated by stm32-gen-features. DO NOT EDIT.\n";
|
||||
|
||||
/// True if the chip named `name` is supported else false
|
||||
fn is_supported(name: &str) -> bool {
|
||||
SUPPORTED_FAMILIES
|
||||
.iter()
|
||||
.any(|family| name.starts_with(family))
|
||||
}
|
||||
|
||||
/// Get the yaml file names and the associated chip names for supported chips
|
||||
///
|
||||
/// Print errors to `stderr` when something is returned by the glob but is not in the returned
|
||||
/// [`Vec`]
|
||||
fn supported_chip_yaml_files_with_names() -> Vec<(PathBuf, String)> {
|
||||
glob::glob("../stm32-data/data/chips/*.yaml")
|
||||
.expect("bad glob pattern")
|
||||
.filter_map(|entry| entry.map_err(|e| eprintln!("{:?}", e)).ok())
|
||||
.filter_map(|entry| {
|
||||
if let Some(name) = entry.file_stem().and_then(|stem| stem.to_str()) {
|
||||
if is_supported(name) {
|
||||
let owned_name = name.to_lowercase();
|
||||
Some((entry, owned_name))
|
||||
} else {
|
||||
eprintln!("{} is not supported", name);
|
||||
None
|
||||
}
|
||||
} else {
|
||||
eprintln!("{:?} is not a regural file", entry);
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Get the list of the cores of a chip by its associated file
|
||||
///
|
||||
/// # Panic
|
||||
/// Panics if the file does not exist or if it contains yaml syntax errors
|
||||
///
|
||||
/// # None
|
||||
/// Returns none if "cores" is not an array
|
||||
fn chip_cores(path: &Path) -> Option<Vec<yaml_rust::Yaml>> {
|
||||
let file_contents = std::fs::read_to_string(path).unwrap();
|
||||
let doc = &yaml_rust::YamlLoader::load_from_str(&file_contents).unwrap()[0];
|
||||
doc["cores"].as_vec().cloned()
|
||||
}
|
||||
|
||||
/// Load the list of chips
|
||||
///
|
||||
/// # Panic
|
||||
/// Panics if a file contains yaml syntax errors or if a value does not have a consistent type
|
||||
pub fn load_chip_list() -> HashMap<String, Vec<String>> {
|
||||
let mut result = HashMap::new();
|
||||
for (path, name) in supported_chip_yaml_files_with_names() {
|
||||
let cores = chip_cores(&path).unwrap_or_else(|| panic!("{}[cores] is not an array", name));
|
||||
if cores.len() > 1 {
|
||||
for (i, core) in cores.into_iter().enumerate() {
|
||||
let core_name = core["name"]
|
||||
.as_str()
|
||||
.unwrap_or_else(|| panic!("{}[cores][{}][name] is not a string", name, i));
|
||||
let key = format!("{}_{}", name, core_name);
|
||||
let value = vec![format!("stm32-metapac/{}_{}", name, core_name)];
|
||||
result.insert(key, value);
|
||||
}
|
||||
} else {
|
||||
let value = vec![format!("stm32-metapac/{}", &name)];
|
||||
result.insert(name, value);
|
||||
}
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
/// Get contents before and after generated contents
|
||||
///
|
||||
/// # Panic
|
||||
/// Panics when a separator cound not be not found
|
||||
fn split_cargo_toml_contents(contents: &str) -> (&str, &str) {
|
||||
let (before, remainder) = contents
|
||||
.split_once(SEPARATOR_START)
|
||||
.unwrap_or_else(|| panic!("missing \"{}\" tag", SEPARATOR_START));
|
||||
let (_, after) = remainder
|
||||
.split_once(SEPARATOR_END)
|
||||
.unwrap_or_else(|| panic!("missing \"{}\" tag", SEPARATOR_END));
|
||||
|
||||
(before, after)
|
||||
}
|
||||
|
||||
/// Generates new contents for Cargo.toml
|
||||
///
|
||||
/// # Panic
|
||||
/// Panics when a separator cound not be not found
|
||||
pub fn generate_cargo_toml_file(
|
||||
previous_text: &str,
|
||||
new_contents: &HashMap<String, Vec<String>>,
|
||||
) -> String {
|
||||
let (before, after) = split_cargo_toml_contents(previous_text);
|
||||
let generated_content = toml::to_string(new_contents).unwrap();
|
||||
before.to_owned() + SEPARATOR_START + HELP + &generated_content + SEPARATOR_END + after
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn stm32f407vg_is_supported() {
|
||||
assert!(is_supported("STM32F407VG"))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn abcdef_is_not_supported() {
|
||||
assert!(!is_supported("ABCDEF"))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn stm32f407vg_yaml_file_exists() {
|
||||
assert!(supported_chip_yaml_files_with_names()
|
||||
.into_iter()
|
||||
.any(|(path, name)| {
|
||||
name == "stm32f407vg"
|
||||
&& path.to_str() == Some("../stm32-data/data/chips/STM32F407VG.yaml")
|
||||
}))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn keeps_text_around_separators() {
|
||||
let initial = "\
|
||||
before
|
||||
# BEGIN GENERATED FEATURES
|
||||
# END GENERATED FEATURES
|
||||
after
|
||||
";
|
||||
|
||||
let expected = "\
|
||||
before
|
||||
# BEGIN GENERATED FEATURES
|
||||
# Generated by stm32-gen-features. DO NOT EDIT.
|
||||
a = [\"b\"]
|
||||
# END GENERATED FEATURES
|
||||
after
|
||||
";
|
||||
|
||||
let map = HashMap::from([(String::from("a"), vec![String::from("b")])]);
|
||||
assert_eq!(generate_cargo_toml_file(initial, &map), expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn does_not_generate_if_separators_are_missing() {
|
||||
let initial = "\
|
||||
before
|
||||
# END GENERATED FEATURES
|
||||
after
|
||||
";
|
||||
|
||||
let map = HashMap::from([(String::from("a"), vec![String::from("b")])]);
|
||||
generate_cargo_toml_file(initial, &map);
|
||||
}
|
||||
}
|
18
stm32-gen-features/src/main.rs
Normal file
18
stm32-gen-features/src/main.rs
Normal file
|
@ -0,0 +1,18 @@
|
|||
use std::collections::HashMap;
|
||||
|
||||
use gen_features::{generate_cargo_toml_file, load_chip_list};
|
||||
|
||||
fn main() {
|
||||
let chip_list = load_chip_list();
|
||||
update_cargo_file("../embassy-stm32/Cargo.toml", &chip_list);
|
||||
}
|
||||
|
||||
/// Update a Cargo.toml file
|
||||
///
|
||||
/// Update the content between "# BEGIN GENERATED FEATURES" and "# END GENERATED FEATURES"
|
||||
/// with the given content
|
||||
fn update_cargo_file(path: &str, new_contents: &HashMap<String, Vec<String>>) {
|
||||
let previous_text = std::fs::read_to_string(path).unwrap();
|
||||
let new_text = generate_cargo_toml_file(&previous_text, new_contents);
|
||||
std::fs::write(path, new_text).unwrap();
|
||||
}
|
Loading…
Reference in a new issue