
26 changed files with 558 additions and 0 deletions
@ -0,0 +1,24 @@ |
|||
[package] |
|||
name = "probe-rs-cli-util" |
|||
version = "0.8.0" |
|||
authors = ["Noah Hüsser <yatekii@yatekii.ch", "Dominik Boehi <dominik.boehi@gmail.com>"] |
|||
edition = "2018" |
|||
description = "Helper library for CLI applications based on probe-rs." |
|||
documentation = "https://docs.rs/probe-rs-cli-util/" |
|||
homepage = "https://github.com/probe-rs/probe-rs" |
|||
repository = "https://github.com/probe-rs/probe-rs" |
|||
readme = "../README.md" |
|||
categories = ["embedded", "hardware-support", "development-tools::debugging"] |
|||
keywords = ["embedded"] |
|||
license = "MIT OR Apache-2.0" |
|||
|
|||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html |
|||
|
|||
[dependencies] |
|||
structopt = "0.3.16" |
|||
anyhow = "1.0" |
|||
log = "0.4.0" |
|||
probe-rs = { version = "0.8.0", path = "../probe-rs" } |
|||
cargo_toml = "0.8.1" |
|||
serde = { version = "1.0.115", features = [ "derive" ] } |
|||
cargo_metadata = "0.11.1" |
@ -0,0 +1,120 @@ |
|||
use anyhow::{anyhow, Context, Result}; |
|||
use cargo_toml::Manifest; |
|||
use serde::Deserialize; |
|||
|
|||
use cargo_metadata::Message; |
|||
use std::{ |
|||
path::{Path, PathBuf}, |
|||
process::{Command, Stdio}, |
|||
}; |
|||
|
|||
pub struct Metadata { |
|||
pub chip: Option<String>, |
|||
} |
|||
#[derive(Clone, Debug, PartialEq, Deserialize)] |
|||
pub struct Meta { |
|||
pub chip: Option<String>, |
|||
} |
|||
|
|||
pub fn read_metadata(work_dir: &Path) -> Result<Metadata> { |
|||
let cargo_toml = work_dir.join("Cargo.toml"); |
|||
|
|||
let cargo_toml_content = std::fs::read(&cargo_toml).context(format!( |
|||
"Unable to read configuration file '{}'", |
|||
cargo_toml.display(), |
|||
))?; |
|||
|
|||
let meta = match Manifest::<Meta>::from_slice_with_metadata(&cargo_toml_content) { |
|||
Ok(m) => m.package.map(|p| p.metadata).flatten(), |
|||
Err(_e) => None, |
|||
}; |
|||
|
|||
Ok(Metadata { |
|||
chip: meta.and_then(|m| m.chip), |
|||
}) |
|||
} |
|||
|
|||
/// Run `cargo build` and return the path to the generated binary artifact.
|
|||
///
|
|||
/// `args` will be passed to cargo build, and `--message-format json` will be
|
|||
/// added to the list of arguments.
|
|||
///
|
|||
/// The output of `cargo build` is parsed to detect the path to the generated binary artifact.
|
|||
/// If either no artifact, or more than a single artifact are created, an error is returned.
|
|||
pub fn build_artifact(work_dir: &Path, args: &[String]) -> Result<PathBuf> { |
|||
let work_dir = work_dir.canonicalize()?; |
|||
|
|||
let cargo_executable = std::env::var("CARGO").unwrap_or_else(|_| "cargo".to_owned()); |
|||
|
|||
log::debug!( |
|||
"Running '{}' in directory {}", |
|||
cargo_executable, |
|||
work_dir.display() |
|||
); |
|||
|
|||
// Build the project.
|
|||
let cargo_command = Command::new(cargo_executable) |
|||
.current_dir(work_dir) |
|||
.arg("build") |
|||
.args(args) |
|||
.args(&["--message-format", "json"]) |
|||
.stdout(Stdio::piped()) |
|||
.spawn()?; |
|||
|
|||
let output = cargo_command.wait_with_output()?; |
|||
|
|||
// Parse build output.
|
|||
let messages = Message::parse_stream(&output.stdout[..]); |
|||
|
|||
// Find artifacts.
|
|||
let mut target_artifact = None; |
|||
|
|||
for message in messages { |
|||
match message? { |
|||
Message::CompilerArtifact(artifact) => { |
|||
if artifact.executable.is_some() { |
|||
if target_artifact.is_some() { |
|||
// We found multiple binary artifacts,
|
|||
// so we don't know which one to use.
|
|||
return Err(anyhow!( |
|||
"Multiple binary artifacts found. \ |
|||
Use '--bin' to specify which binary to flash." |
|||
)); |
|||
} else { |
|||
target_artifact = Some(artifact); |
|||
} |
|||
} |
|||
} |
|||
Message::CompilerMessage(message) => { |
|||
if let Some(rendered) = message.message.rendered { |
|||
print!("{}", rendered); |
|||
} |
|||
} |
|||
// Ignore other messages.
|
|||
_ => (), |
|||
} |
|||
} |
|||
|
|||
// Check if the command succeeded, otherwise return an error.
|
|||
// Any error messages occuring during the build are shown above,
|
|||
// when the compiler messages are rendered.
|
|||
if !output.status.success() { |
|||
// Show error output
|
|||
return Err(anyhow!( |
|||
"Failed to run cargo build: exit code = {:?}", |
|||
output.status.code() |
|||
)); |
|||
} |
|||
|
|||
if let Some(artifact) = target_artifact { |
|||
// Unwrap is safe, we only store artifacts with an executable.
|
|||
Ok(artifact.executable.unwrap()) |
|||
} else { |
|||
// We did not find a binary, so we should return an error.
|
|||
Err(anyhow!( |
|||
"Unable to find any binary artifacts. \ |
|||
Use '--example' to specify an example to flash, \ |
|||
or '--package' to specify which package to flash in a workspace." |
|||
)) |
|||
} |
|||
} |
@ -0,0 +1,236 @@ |
|||
use std::path::PathBuf; |
|||
|
|||
// Test reading metadata from
|
|||
// the [package.metadata] section of
|
|||
// Cargo.toml
|
|||
#[test] |
|||
fn read_chip_metadata() { |
|||
let work_dir = test_project_dir("binary_project"); |
|||
|
|||
let metadata = probe_rs_cli_util::read_metadata(&work_dir).expect("Failed to read metadata."); |
|||
|
|||
assert_eq!(metadata.chip, Some("nrf51822".to_owned())); |
|||
} |
|||
|
|||
#[test] |
|||
fn get_binary_artifact() { |
|||
let work_dir = test_project_dir("binary_project"); |
|||
let mut expected_path = work_dir.join("target"); |
|||
expected_path.push("debug"); |
|||
expected_path.push(host_binary_name("binary_project")); |
|||
|
|||
let args = []; |
|||
|
|||
let binary_path = |
|||
probe_rs_cli_util::build_artifact(&work_dir, &args).expect("Failed to read artifact path."); |
|||
|
|||
assert_eq!(binary_path, expected_path); |
|||
} |
|||
|
|||
#[test] |
|||
fn get_binary_artifact_with_cargo_config() { |
|||
let work_dir = test_project_dir("binary_cargo_config"); |
|||
|
|||
let mut expected_path = work_dir.join("target"); |
|||
expected_path.push("thumbv7m-none-eabi"); |
|||
expected_path.push("debug"); |
|||
expected_path.push("binary_cargo_config"); |
|||
|
|||
let args = []; |
|||
|
|||
let binary_path = |
|||
probe_rs_cli_util::build_artifact(&work_dir, &args).expect("Failed to read artifact path."); |
|||
|
|||
assert_eq!( |
|||
binary_path, |
|||
expected_path |
|||
.canonicalize() |
|||
.expect("Failed to canonicalize path") |
|||
); |
|||
} |
|||
|
|||
#[test] |
|||
fn get_binary_artifact_with_cargo_config_toml() { |
|||
let work_dir = test_project_dir("binary_cargo_config_toml"); |
|||
let mut expected_path = work_dir.join("target"); |
|||
expected_path.push("thumbv7m-none-eabi"); |
|||
expected_path.push("debug"); |
|||
expected_path.push("binary_cargo_config_toml"); |
|||
|
|||
let args = []; |
|||
|
|||
let binary_path = |
|||
probe_rs_cli_util::build_artifact(&work_dir, &args).expect("Failed to read artifact path."); |
|||
|
|||
assert_eq!( |
|||
binary_path, |
|||
expected_path |
|||
.canonicalize() |
|||
.expect("Failed to canonicalize path") |
|||
); |
|||
} |
|||
|
|||
#[test] |
|||
fn get_library_artifact_fails() { |
|||
let work_dir = test_project_dir("library_project"); |
|||
|
|||
let args = ["--release".to_owned()]; |
|||
|
|||
let binary_path = probe_rs_cli_util::build_artifact(&work_dir, &args); |
|||
|
|||
assert!( |
|||
binary_path.is_err(), |
|||
"Library project should not return a path to a binary, but got {}", |
|||
binary_path.unwrap().display() |
|||
); |
|||
} |
|||
|
|||
#[test] |
|||
fn workspace_root() { |
|||
// In a workspace with a single binary crate,
|
|||
// we should be able to find the binary for that crate.
|
|||
|
|||
let work_dir = test_project_dir("workspace_project"); |
|||
|
|||
let mut expected_path = work_dir.join("target"); |
|||
expected_path.push("release"); |
|||
expected_path.push(host_binary_name("workspace_bin")); |
|||
|
|||
let args = owned_args(&["--release"]); |
|||
|
|||
let binary_path = |
|||
probe_rs_cli_util::build_artifact(&work_dir, &args).expect("Failed to read artifact path."); |
|||
|
|||
assert_eq!(binary_path, expected_path); |
|||
} |
|||
|
|||
#[test] |
|||
fn workspace_binary_package() { |
|||
// In a binary crate which is a member of a workspace,
|
|||
// we should be able to find the binary for that crate.
|
|||
|
|||
let workspace_dir = test_project_dir("workspace_project"); |
|||
let work_dir = workspace_dir.join("workspace_bin"); |
|||
|
|||
let mut expected_path = workspace_dir.join("target"); |
|||
expected_path.push("release"); |
|||
expected_path.push(host_binary_name("workspace_bin")); |
|||
|
|||
let args = ["--release".to_owned()]; |
|||
|
|||
let binary_path = |
|||
probe_rs_cli_util::build_artifact(&work_dir, &args).expect("Failed to read artifact path."); |
|||
|
|||
assert_eq!(binary_path, expected_path); |
|||
} |
|||
|
|||
#[test] |
|||
fn workspace_library_package() { |
|||
// In a library crate which is a member of a workspace,
|
|||
// we should show an error message.
|
|||
|
|||
let work_dir = test_project_dir("workspace_project/workspace_lib"); |
|||
|
|||
let args = ["--release".to_owned()]; |
|||
|
|||
let binary_path = probe_rs_cli_util::build_artifact(&work_dir, &args); |
|||
|
|||
assert!( |
|||
binary_path.is_err(), |
|||
"Library project should not return a path to a binary, but got {}", |
|||
binary_path.unwrap().display() |
|||
); |
|||
} |
|||
|
|||
#[test] |
|||
fn multiple_binaries_in_crate() { |
|||
// With multiple binaries in a crate,
|
|||
// we should show an error message if no binary is specified
|
|||
let work_dir = test_project_dir("multiple_binary_project"); |
|||
|
|||
let args = []; |
|||
|
|||
let binary_path = probe_rs_cli_util::build_artifact(&work_dir, &args); |
|||
|
|||
assert!( |
|||
binary_path.is_err(), |
|||
"With multiple binaries, an error message should be shown. Got path '{}' instead.", |
|||
binary_path.unwrap().display() |
|||
); |
|||
} |
|||
|
|||
#[test] |
|||
fn multiple_binaries_in_crate_select_binary() { |
|||
// With multiple binaries in a crate,
|
|||
// we should show an error message if no binary is specified
|
|||
let work_dir = test_project_dir("multiple_binary_project"); |
|||
let mut expected_path = work_dir.join("target"); |
|||
expected_path.push("debug"); |
|||
expected_path.push(host_binary_name("bin_a")); |
|||
|
|||
let args = ["--bin".to_owned(), "bin_a".to_owned()]; |
|||
|
|||
let binary_path = |
|||
probe_rs_cli_util::build_artifact(&work_dir, &args).expect("Failed to get artifact path."); |
|||
|
|||
assert_eq!(binary_path, expected_path); |
|||
} |
|||
|
|||
#[test] |
|||
fn library_with_example() { |
|||
// In a library with no binary target, but with an example,
|
|||
// we should return an error. (Same behaviour as cargo run)
|
|||
let work_dir = test_project_dir("library_with_example_project"); |
|||
|
|||
let args = []; |
|||
|
|||
let binary_path = probe_rs_cli_util::build_artifact(&work_dir, &args); |
|||
|
|||
assert!(binary_path.is_err()) |
|||
} |
|||
|
|||
#[test] |
|||
fn library_with_example_specified() { |
|||
// When the example flag is specified, we should flash that example
|
|||
let work_dir = test_project_dir("library_with_example_project"); |
|||
let mut expected_path = work_dir.join("target"); |
|||
expected_path.push("debug"); |
|||
expected_path.push("examples"); |
|||
expected_path.push(host_binary_name("example")); |
|||
|
|||
let args = owned_args(&["--example", "example"]); |
|||
|
|||
let binary_path = |
|||
probe_rs_cli_util::build_artifact(&work_dir, &args).expect("Failed to get artifact path."); |
|||
|
|||
assert_eq!(binary_path, expected_path); |
|||
} |
|||
|
|||
/// Return the path to a test project, located in
|
|||
/// tests/data.
|
|||
fn test_project_dir(test_name: &str) -> PathBuf { |
|||
let mut manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); |
|||
|
|||
manifest_dir.push("tests"); |
|||
manifest_dir.push("data"); |
|||
|
|||
manifest_dir.push(test_name); |
|||
|
|||
manifest_dir |
|||
.canonicalize() |
|||
.expect("Failed to build canonicalized test_project_dir") |
|||
} |
|||
|
|||
fn owned_args(args: &[&str]) -> Vec<String> { |
|||
args.iter().map(|s| (*s).to_owned()).collect() |
|||
} |
|||
|
|||
#[cfg(not(windows))] |
|||
fn host_binary_name(name: &str) -> String { |
|||
name.to_string() |
|||
} |
|||
|
|||
#[cfg(windows)] |
|||
fn host_binary_name(name: &str) -> String { |
|||
name.to_string() + ".exe" |
|||
} |
@ -0,0 +1,5 @@ |
|||
# Ignore build output of test projects |
|||
**/target/ |
|||
|
|||
# Ignore Cargo.lock files |
|||
**/Cargo.lock |
@ -0,0 +1,2 @@ |
|||
[build] |
|||
target = "thumbv7m-none-eabi" # Cortex-M3 |
@ -0,0 +1,14 @@ |
|||
[package] |
|||
name = "binary_cargo_config" |
|||
version = "0.1.0" |
|||
authors = ["Dominik Boehi <dominik.boehi@gmail.com>"] |
|||
edition = "2018" |
|||
|
|||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html |
|||
|
|||
[dependencies] |
|||
panic-halt = "0.2.0" |
|||
cortex-m-rt = "0.6.10" |
|||
|
|||
# This package is not part of the probe-rs workspace |
|||
[workspace] |
@ -0,0 +1,13 @@ |
|||
#![no_std] |
|||
#![no_main] |
|||
|
|||
extern crate panic_halt; |
|||
|
|||
use cortex_m_rt::entry; |
|||
|
|||
#[entry] |
|||
unsafe fn entry() -> ! { |
|||
loop { |
|||
continue; |
|||
} |
|||
} |
@ -0,0 +1,2 @@ |
|||
[build] |
|||
target = "thumbv7m-none-eabi" # Cortex-M3 |
@ -0,0 +1,14 @@ |
|||
[package] |
|||
name = "binary_cargo_config_toml" |
|||
version = "0.1.0" |
|||
authors = ["Dominik Boehi <dominik.boehi@gmail.com>"] |
|||
edition = "2018" |
|||
|
|||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html |
|||
|
|||
[dependencies] |
|||
panic-halt = "0.2.0" |
|||
cortex-m-rt = "0.6.10" |
|||
|
|||
# This package is not part of the probe-rs workspace |
|||
[workspace] |
@ -0,0 +1,13 @@ |
|||
#![no_std] |
|||
#![no_main] |
|||
|
|||
extern crate panic_halt; |
|||
|
|||
use cortex_m_rt::entry; |
|||
|
|||
#[entry] |
|||
unsafe fn entry() -> ! { |
|||
loop { |
|||
continue; |
|||
} |
|||
} |
@ -0,0 +1,15 @@ |
|||
[package] |
|||
name = "binary_project" |
|||
version = "0.1.0" |
|||
authors = ["Dominik Boehi <dominik.boehi@gmail.com>"] |
|||
edition = "2018" |
|||
|
|||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html |
|||
|
|||
[package.metadata] |
|||
chip = "nrf51822" |
|||
|
|||
[dependencies] |
|||
|
|||
# This package is not part of the probe-rs workspace |
|||
[workspace] |
@ -0,0 +1,3 @@ |
|||
fn main() { |
|||
println!("Hello, world!"); |
|||
} |
@ -0,0 +1,12 @@ |
|||
[package] |
|||
name = "library_project" |
|||
version = "0.1.0" |
|||
authors = ["Dominik Boehi <dominik.boehi@gmail.com>"] |
|||
edition = "2018" |
|||
|
|||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html |
|||
|
|||
[dependencies] |
|||
|
|||
# This package is not part of the probe-rs workspace |
|||
[workspace] |
@ -0,0 +1,7 @@ |
|||
#[cfg(test)] |
|||
mod tests { |
|||
#[test] |
|||
fn it_works() { |
|||
assert_eq!(2 + 2, 4); |
|||
} |
|||
} |
@ -0,0 +1,12 @@ |
|||
[package] |
|||
name = "library_project" |
|||
version = "0.1.0" |
|||
authors = ["Dominik Boehi <dominik.boehi@gmail.com>"] |
|||
edition = "2018" |
|||
|
|||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html |
|||
|
|||
[dependencies] |
|||
|
|||
# This package is not part of the probe-rs workspace |
|||
[workspace] |
@ -0,0 +1,3 @@ |
|||
fn main() { |
|||
println!("Hello, example!"); |
|||
} |
@ -0,0 +1,7 @@ |
|||
#[cfg(test)] |
|||
mod tests { |
|||
#[test] |
|||
fn it_works() { |
|||
assert_eq!(2 + 2, 4); |
|||
} |
|||
} |
@ -0,0 +1,15 @@ |
|||
[package] |
|||
name = "binary_project" |
|||
version = "0.1.0" |
|||
authors = ["Dominik Boehi <dominik.boehi@gmail.com>"] |
|||
edition = "2018" |
|||
|
|||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html |
|||
|
|||
[package.metadata] |
|||
chip = "nrf51822" |
|||
|
|||
[dependencies] |
|||
|
|||
# This package is not part of the probe-rs workspace |
|||
[workspace] |
@ -0,0 +1,3 @@ |
|||
fn main() { |
|||
println!("Hello, world!"); |
|||
} |
@ -0,0 +1,3 @@ |
|||
fn main() { |
|||
println!("Hello, world b!"); |
|||
} |
@ -0,0 +1,6 @@ |
|||
[workspace] |
|||
|
|||
members = [ |
|||
"workspace_bin", |
|||
"workspace_lib" |
|||
] |
@ -0,0 +1,9 @@ |
|||
[package] |
|||
name = "workspace_bin" |
|||
version = "0.1.0" |
|||
authors = ["Dominik Boehi <dominik.boehi@gmail.com>"] |
|||
edition = "2018" |
|||
|
|||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html |
|||
|
|||
[dependencies] |
@ -0,0 +1,3 @@ |
|||
fn main() { |
|||
println!("Hello, world!"); |
|||
} |
@ -0,0 +1,9 @@ |
|||
[package] |
|||
name = "workspace_lib" |
|||
version = "0.1.0" |
|||
authors = ["Dominik Boehi <dominik.boehi@gmail.com>"] |
|||
edition = "2018" |
|||
|
|||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html |
|||
|
|||
[dependencies] |
@ -0,0 +1,7 @@ |
|||
#[cfg(test)] |
|||
mod tests { |
|||
#[test] |
|||
fn it_works() { |
|||
assert_eq!(2 + 2, 4); |
|||
} |
|||
} |
Loading…
Reference in new issue