use anyhow::{Context, Result, ensure};
use clap::Parser;
use probe_rs::probe::{DebugProbeSelector, WireProtocol};
use probe_rs_target::ChipFamily;
use std::{
env::current_dir,
fs::create_dir,
num::ParseIntError,
path::{Path, PathBuf},
};
use tracing_subscriber::{EnvFilter, filter::LevelFilter};
use target_gen::{
commands::{
elf::{cmd_elf, serialize_to_yaml_string},
test::cmd_test,
},
generate,
};
#[derive(clap::Parser)]
enum TargetGen {
Pack {
#[clap(
value_name = "INPUT",
value_parser,
help = "A Pack file or the unziped Pack directory."
)]
input: PathBuf,
#[clap(
value_name = "OUTPUT",
value_parser,
help = "An output directory where all the generated .yaml files are put in."
)]
output_dir: PathBuf,
},
Arm {
#[clap(
long = "list",
short = 'l',
help = "Optionally, list the names of all pack files available in <https://www.keil.com/pack/Keil.pidx>"
)]
list: bool,
#[clap(
long = "filter",
short = 'f',
help = "Optionally, filter the pack files that start with the specified name,\ne.g. `STM32H7xx` or `LPC55S69_DFP`.\nSee `target-gen arm --list` for a list of available Pack files"
)]
pack_filter: Option<String>,
#[clap(
name = "OUTPUT",
value_parser,
help = "An output directory where all the generated .yaml files are put in."
)]
output_dir: Option<PathBuf>,
},
Elf {
#[clap(value_parser)]
elf: PathBuf,
#[clap(long)]
fixed_load_address: bool,
#[clap(long = "name", short = 'n')]
name: Option<String>,
#[clap(long = "update", short = 'u', requires = "output")]
update: bool,
#[clap(value_parser)]
output: Option<PathBuf>,
},
Test {
template_path: PathBuf,
definition_export_path: PathBuf,
target_artifact: PathBuf,
#[clap(long = "test-address", value_parser = parse_u64)]
test_start_sector_address: Option<u64>,
#[clap(long = "chip")]
chip: Option<String>,
#[clap(long = "name", short = 'n')]
name: Option<String>,
#[arg(long)]
probe: Option<DebugProbeSelector>,
#[clap(long = "speed", default_value = None)]
speed: Option<u32>,
#[clap(long = "protocol", default_value = None)]
protocol: Option<WireProtocol>,
},
Reformat {
yaml_path: PathBuf,
},
}
pub fn parse_u64(input: &str) -> Result<u64, ParseIntError> {
parse_int::parse(input)
}
#[tokio::main]
async fn main() -> Result<()> {
tracing_subscriber::fmt()
.compact()
.with_env_filter(
EnvFilter::builder()
.with_default_directive(LevelFilter::WARN.into())
.from_env_lossy(),
)
.init();
let options = TargetGen::parse();
let t = std::time::Instant::now();
match options {
TargetGen::Pack { input, output_dir } => cmd_pack(&input, &output_dir)?,
TargetGen::Elf {
elf,
output,
update,
name,
fixed_load_address,
} => cmd_elf(
elf.as_path(),
fixed_load_address,
output.as_deref(),
update,
name,
)?,
TargetGen::Arm {
output_dir,
pack_filter: chip_family,
list,
} => cmd_arm(output_dir, chip_family, list).await?,
TargetGen::Test {
target_artifact,
template_path,
definition_export_path,
test_start_sector_address,
chip,
name,
speed,
protocol,
probe,
} => cmd_test(
target_artifact.as_path(),
template_path.as_path(),
definition_export_path.as_path(),
test_start_sector_address,
chip,
name,
probe,
speed,
protocol,
)?,
TargetGen::Reformat { yaml_path } => {
if yaml_path.is_dir() {
let entries = std::fs::read_dir(&yaml_path).context(format!(
"Failed to read directory '{}'.",
yaml_path.display()
))?;
for entry in entries {
let entry = entry.context("Failed to read directory entry.")?;
let path = entry.path();
if path.extension().is_some_and(|ext| ext == "yaml") {
refresh_yaml(&path)?;
}
}
} else {
refresh_yaml(&yaml_path)?;
}
}
}
println!("Finished in {:?}", t.elapsed());
Ok(())
}
fn cmd_pack(input: &Path, out_dir: &Path) -> Result<()> {
ensure!(
input.exists(),
"No such file or directory: {}",
input.display()
);
if !out_dir.exists() {
create_dir(out_dir).context(format!(
"Failed to create output directory '{}'.",
out_dir.display()
))?;
}
let mut families = Vec::<ChipFamily>::new();
if input.is_file() {
generate::visit_file(input, &mut families)
.context(format!("Failed to process file {}.", input.display()))?;
} else {
generate::visit_dirs(input, &mut families)
.context("Failed to generate target configuration.")?;
ensure!(
!families.is_empty(),
"Unable to find any .pdsc files in the provided input directory."
);
}
save_files(out_dir, &families)?;
Ok(())
}
async fn cmd_arm(out_dir: Option<PathBuf>, chip_family: Option<String>, list: bool) -> Result<()> {
if list {
let mut packs = target_gen::fetch::get_vidx().await?;
println!("Available ARM CMSIS Pack files:");
packs.pdsc_index.sort_by(|a, b| a.name.cmp(&b.name));
for pack in packs.pdsc_index.iter() {
println!("\t{}", pack.name);
}
return Ok(());
}
let out_dir = if let Some(target_dir) = out_dir {
target_dir.as_path().to_owned()
} else {
log::info!("No output directory specified. Using current directory.");
current_dir()?
};
if !out_dir.exists() {
create_dir(&out_dir).context(format!(
"Failed to create output directory '{}'.",
out_dir.display()
))?;
}
let mut families = Vec::<ChipFamily>::new();
generate::visit_arm_files(&mut families, chip_family).await?;
save_files(&out_dir, &families)?;
Ok(())
}
fn refresh_yaml(yaml_path: &Path) -> Result<()> {
let yaml = std::fs::read_to_string(yaml_path)
.context(format!("Failed to read file '{}'.", yaml_path.display()))?;
let family = serde_yaml::from_str::<ChipFamily>(&yaml)
.context(format!("Failed to parse file '{}'.", yaml_path.display()))?;
let yaml = serialize_to_yaml_string(&family)?;
std::fs::write(yaml_path, yaml)
.context(format!("Failed to write file '{}'.", yaml_path.display()))?;
Ok(())
}
fn save_files(out_dir: &Path, families: &[ChipFamily]) -> Result<()> {
let mut generated_files = Vec::with_capacity(families.len());
for family in families {
let path = out_dir.join(family.name.clone().replace(' ', "_") + ".yaml");
let yaml = serialize_to_yaml_string(family)?;
std::fs::write(&path, yaml)
.context(format!("Failed to create file '{}'.", path.display()))?;
generated_files.push(path);
}
println!("Generated {} target definition(s):", generated_files.len());
for file in generated_files {
println!("\t{}", file.display());
}
Ok(())
}