cube2rust/
lib.rs

1//! A tool for generating a rust project from a STM32CubeMX ioc file.
2//!
3//! The tool will run `cargo init` in the same directory as the ioc file.
4//!
5//! It will then add dependencies to `Cargo.toml` and generate a `src/main.rs`, `.cargo/config` and `memory.x`.
6//!
7//! Currently, running this tool will overwrite everything, so use with caution.
8//!
9//! # Installation
10//! ```bash
11//! $ cargo install cube2rust
12//! ```
13//! # Usage
14//! From inside a directory containing an ioc file
15//! ```bash
16//! $ cube2rust
17//! ```
18//!
19//! From anywhere
20//! ```bash
21//! $ cube2rust path/to/project_directory
22//! ```
23//!
24//! # Currently supported
25//! * Only STM32F0
26//! * GPIO, RCC, SPI, USART, I2C
27
28#![warn(rust_2018_idioms)]
29
30#[macro_use]
31extern crate fstrings;
32
33#[macro_use]
34mod utils;
35mod db;
36mod generate;
37mod gpio;
38mod i2c;
39mod rcc;
40mod spi;
41mod usart;
42
43use std::collections::HashMap;
44use std::fs;
45use std::fs::OpenOptions;
46use std::io::Write;
47use std::path::Path;
48use std::process::Command;
49
50use anyhow::{anyhow, bail, ensure, Context};
51
52use crate::gpio::GpioPin;
53use crate::i2c::I2C;
54use crate::rcc::RCC;
55use crate::spi::SPI;
56use crate::usart::USART;
57use crate::utils::*;
58
59type ConfigParams<'a> = HashMap<&'a str, HashMap<&'a str, &'a str>>;
60
61/// A struct containing all the collected information from the ioc file
62#[derive(Debug)]
63pub struct Config {
64    pub version: String,
65    pub mcu_family: MCUFamily,
66    pub mcu_name: String,
67    pub rcc: RCC,
68    pub gpios: Vec<GpioPin>,
69    pub ports: Vec<char>,
70    pub spis: Vec<SPI>,
71    pub usarts: Vec<USART>,
72    pub i2cs: Vec<I2C>,
73}
74
75/// Loads a project configuration from the ioc file content
76pub fn load_ioc(file_content: &str) -> anyhow::Result<Config> {
77    let config_params = parse_ioc(file_content);
78
79    let version = String::from(
80        *config_params
81            .get("File")
82            .ok_or_else(|| anyhow!("Couldn't check ioc version"))?
83            .get("Version")
84            .ok_or_else(|| anyhow!("Couldn't check ioc version"))?,
85    );
86
87    let mcu = config_params
88        .get("Mcu")
89        .ok_or_else(|| anyhow!("Couldn't check MCU information"))?;
90
91    let mcu_family = parse_mandatory_param(mcu, "Family")?;
92
93    let mcu_name = mcu
94        .get("UserName")
95        .ok_or_else(|| anyhow!("Couldn't check MCU name"))?
96        .to_string();
97
98    let rcc = rcc::get_rcc(&config_params).context("Parsing of RCC")?;
99
100    let (ports, gpios) = gpio::get_gpios(&config_params).context("Parsing of GPIOs")?;
101
102    let spis = spi::get_spis(&config_params).context("Parsing of SPIs")?;
103
104    let usarts = usart::get_usarts(&config_params).context("Parsing of USARTs")?;
105
106    let i2cs = i2c::get_i2cs(&config_params).context("Parsing of I2Cs")?;
107
108    Ok(Config {
109        version,
110        mcu_family,
111        mcu_name,
112        rcc,
113        gpios,
114        ports,
115        spis,
116        usarts,
117        i2cs,
118    })
119}
120
121/// Parses the ioc file content into nested HashMaps
122pub fn parse_ioc(file_content: &str) -> ConfigParams<'_> {
123    let mut config_params = HashMap::new();
124
125    for line in file_content.lines() {
126        let name_and_value: Vec<&str> = line.split('=').collect();
127
128        if let [name, value] = name_and_value[..] {
129            let object_and_parameter: Vec<&str> = name.split('.').collect();
130            if let [object_name, parameter_name] = object_and_parameter[..] {
131                config_params
132                    .entry(object_name)
133                    .or_insert_with(HashMap::new)
134                    .insert(parameter_name, value);
135            }
136        }
137    }
138
139    config_params
140}
141
142fn cargo_init(project_dir: &Path) -> anyhow::Result<bool> {
143    let output = if project_dir.eq(Path::new("")) {
144        // empty path as current_dir doesn't work, not sure why
145        Command::new("cargo").arg("init").output()
146    } else {
147        Command::new("cargo")
148            .arg("init")
149            .current_dir(project_dir)
150            .output()
151    }
152    .context("cargo init")?;
153
154    let output = String::from_utf8(output.stderr).unwrap();
155    Ok(output.contains("Created binary (application) package"))
156}
157
158/// Generates a rust project from the given configuration
159pub fn generate(project_dir: &Path, config: Config) -> anyhow::Result<()> {
160    ensure!(
161        config.version == "6",
162        "only File.Version=6 supported in ioc file"
163    );
164
165    // run cargo init
166    let package_created = cargo_init(project_dir)?;
167
168    // append to Cargo.toml
169    // TODO replace this with calls to cargo add, once cargo #5586 is through
170    if package_created {
171        println!("Ran cargo init");
172        let cargo_toml = project_dir.join("Cargo.toml");
173        let mut file = OpenOptions::new().append(true).open(cargo_toml)?;
174
175        let dependencies = generate::generate_dependencies(&config)?;
176        write!(file, "{}", dependencies)?;
177        println!("Added dependencies to Cargo.toml");
178    } else {
179        println!("Detected existing project");
180    }
181
182    // src/main.rs
183    let main_rs = generate::generate_main(&config)?;
184    println!("Generated src/main.rs");
185
186    let path_to_main = project_dir.join("src/main.rs");
187    fs::write(path_to_main, main_rs).context("write to main.rs")?;
188
189    // .cargo/config
190    let cargo_config = generate::generate_cargo_config(&config);
191
192    let path_to_cargo_cofig = project_dir.join(".cargo/config");
193    fs::create_dir_all(path_to_cargo_cofig.parent().unwrap()).unwrap();
194    fs::write(path_to_cargo_cofig, cargo_config).context("write to config")?;
195    println!("Generated .cargo/config");
196
197    // memory.x
198    let memory_config = generate::generate_memory_x(&config)?;
199
200    let path_to_memory_x = project_dir.join("memory.x");
201    fs::write(path_to_memory_x, memory_config).context("write to memory.x")?;
202    println!("Generated memory.x");
203
204    Ok(())
205}