Browse Source

Create helper library for CLI applications

This is an import from cargo-flash.
staging
Dominik Boehi 2 years ago
parent
commit
5c0fa40c15
  1. 1
      Cargo.toml
  2. 24
      probe-rs-cli-util/Cargo.toml
  3. 120
      probe-rs-cli-util/src/lib.rs
  4. 236
      probe-rs-cli-util/tests/cargo_integration.rs
  5. 5
      probe-rs-cli-util/tests/data/.gitignore
  6. 2
      probe-rs-cli-util/tests/data/binary_cargo_config/.cargo/config
  7. 14
      probe-rs-cli-util/tests/data/binary_cargo_config/Cargo.toml
  8. 13
      probe-rs-cli-util/tests/data/binary_cargo_config/src/main.rs
  9. 2
      probe-rs-cli-util/tests/data/binary_cargo_config_toml/.cargo/config.toml
  10. 14
      probe-rs-cli-util/tests/data/binary_cargo_config_toml/Cargo.toml
  11. 13
      probe-rs-cli-util/tests/data/binary_cargo_config_toml/src/main.rs
  12. 15
      probe-rs-cli-util/tests/data/binary_project/Cargo.toml
  13. 3
      probe-rs-cli-util/tests/data/binary_project/src/main.rs
  14. 12
      probe-rs-cli-util/tests/data/library_project/Cargo.toml
  15. 7
      probe-rs-cli-util/tests/data/library_project/src/lib.rs
  16. 12
      probe-rs-cli-util/tests/data/library_with_example_project/Cargo.toml
  17. 3
      probe-rs-cli-util/tests/data/library_with_example_project/examples/example.rs
  18. 7
      probe-rs-cli-util/tests/data/library_with_example_project/src/lib.rs
  19. 15
      probe-rs-cli-util/tests/data/multiple_binary_project/Cargo.toml
  20. 3
      probe-rs-cli-util/tests/data/multiple_binary_project/src/bin/bin_a.rs
  21. 3
      probe-rs-cli-util/tests/data/multiple_binary_project/src/bin/bin_b.rs
  22. 6
      probe-rs-cli-util/tests/data/workspace_project/Cargo.toml
  23. 9
      probe-rs-cli-util/tests/data/workspace_project/workspace_bin/Cargo.toml
  24. 3
      probe-rs-cli-util/tests/data/workspace_project/workspace_bin/src/main.rs
  25. 9
      probe-rs-cli-util/tests/data/workspace_project/workspace_lib/Cargo.toml
  26. 7
      probe-rs-cli-util/tests/data/workspace_project/workspace_lib/src/lib.rs

1
Cargo.toml

@ -2,6 +2,7 @@
members = [
"probe-rs",
"probe-rs-t2rust",
"probe-rs-cli-util",
"cli",
"gdb-server",
]

24
probe-rs-cli-util/Cargo.toml

@ -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"

120
probe-rs-cli-util/src/lib.rs

@ -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."
))
}
}

236
probe-rs-cli-util/tests/cargo_integration.rs

@ -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"
}

5
probe-rs-cli-util/tests/data/.gitignore

@ -0,0 +1,5 @@
# Ignore build output of test projects
**/target/
# Ignore Cargo.lock files
**/Cargo.lock

2
probe-rs-cli-util/tests/data/binary_cargo_config/.cargo/config

@ -0,0 +1,2 @@
[build]
target = "thumbv7m-none-eabi" # Cortex-M3

14
probe-rs-cli-util/tests/data/binary_cargo_config/Cargo.toml

@ -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]

13
probe-rs-cli-util/tests/data/binary_cargo_config/src/main.rs

@ -0,0 +1,13 @@
#![no_std]
#![no_main]
extern crate panic_halt;
use cortex_m_rt::entry;
#[entry]
unsafe fn entry() -> ! {
loop {
continue;
}
}

2
probe-rs-cli-util/tests/data/binary_cargo_config_toml/.cargo/config.toml

@ -0,0 +1,2 @@
[build]
target = "thumbv7m-none-eabi" # Cortex-M3

14
probe-rs-cli-util/tests/data/binary_cargo_config_toml/Cargo.toml

@ -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]

13
probe-rs-cli-util/tests/data/binary_cargo_config_toml/src/main.rs

@ -0,0 +1,13 @@
#![no_std]
#![no_main]
extern crate panic_halt;
use cortex_m_rt::entry;
#[entry]
unsafe fn entry() -> ! {
loop {
continue;
}
}

15
probe-rs-cli-util/tests/data/binary_project/Cargo.toml

@ -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]

3
probe-rs-cli-util/tests/data/binary_project/src/main.rs

@ -0,0 +1,3 @@
fn main() {
println!("Hello, world!");
}

12
probe-rs-cli-util/tests/data/library_project/Cargo.toml

@ -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]

7
probe-rs-cli-util/tests/data/library_project/src/lib.rs

@ -0,0 +1,7 @@
#[cfg(test)]
mod tests {
#[test]
fn it_works() {
assert_eq!(2 + 2, 4);
}
}

12
probe-rs-cli-util/tests/data/library_with_example_project/Cargo.toml

@ -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]

3
probe-rs-cli-util/tests/data/library_with_example_project/examples/example.rs

@ -0,0 +1,3 @@
fn main() {
println!("Hello, example!");
}

7
probe-rs-cli-util/tests/data/library_with_example_project/src/lib.rs

@ -0,0 +1,7 @@
#[cfg(test)]
mod tests {
#[test]
fn it_works() {
assert_eq!(2 + 2, 4);
}
}

15
probe-rs-cli-util/tests/data/multiple_binary_project/Cargo.toml

@ -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]

3
probe-rs-cli-util/tests/data/multiple_binary_project/src/bin/bin_a.rs

@ -0,0 +1,3 @@
fn main() {
println!("Hello, world!");
}

3
probe-rs-cli-util/tests/data/multiple_binary_project/src/bin/bin_b.rs

@ -0,0 +1,3 @@
fn main() {
println!("Hello, world b!");
}

6
probe-rs-cli-util/tests/data/workspace_project/Cargo.toml

@ -0,0 +1,6 @@
[workspace]
members = [
"workspace_bin",
"workspace_lib"
]

9
probe-rs-cli-util/tests/data/workspace_project/workspace_bin/Cargo.toml

@ -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]

3
probe-rs-cli-util/tests/data/workspace_project/workspace_bin/src/main.rs

@ -0,0 +1,3 @@
fn main() {
println!("Hello, world!");
}

9
probe-rs-cli-util/tests/data/workspace_project/workspace_lib/Cargo.toml

@ -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]

7
probe-rs-cli-util/tests/data/workspace_project/workspace_lib/src/lib.rs

@ -0,0 +1,7 @@
#[cfg(test)]
mod tests {
#[test]
fn it_works() {
assert_eq!(2 + 2, 4);
}
}
Loading…
Cancel
Save