probe_rs_cli_util/
lib.rs

1pub mod common_options;
2pub mod flash;
3pub mod logging;
4pub mod rtt;
5
6use cargo_toml::Manifest;
7use serde::Deserialize;
8use thiserror::Error;
9
10use cargo_metadata::Message;
11use std::{
12    path::{Path, PathBuf},
13    process::{Command, Stdio},
14};
15
16// Re-export crates to avoid version conflicts in the dependent crates.
17pub use clap;
18pub use indicatif;
19pub use log;
20
21#[derive(Debug, Error)]
22pub enum ArtifactError {
23    #[error("Failed to canonicalize path '{work_dir}'.")]
24    Canonicalize {
25        #[source]
26        source: std::io::Error,
27        work_dir: String,
28    },
29    #[error("An IO error occurred during the execution of 'cargo build'.")]
30    Io(#[source] std::io::Error),
31    #[error("Unable to read the Cargo.toml at '{path}'.")]
32    CargoToml {
33        #[source]
34        source: std::io::Error,
35        path: String,
36    },
37    #[error("Failed to run cargo build: exit code = {0:?}.")]
38    CargoBuild(Option<i32>),
39    #[error("Multiple binary artifacts were found.")]
40    MultipleArtifacts,
41    #[error("No binary artifacts were found.")]
42    NoArtifacts,
43}
44
45#[derive(Clone, Debug, PartialEq, Eq, Deserialize)]
46pub struct Meta {
47    pub chip: Option<String>,
48}
49
50/// Read chip information from Cargo.toml metadata
51pub fn read_metadata(work_dir: &Path) -> Result<Meta, ArtifactError> {
52    let cargo_toml = work_dir.join("Cargo.toml");
53
54    let cargo_toml_content = std::fs::read(&cargo_toml).map_err(|e| ArtifactError::CargoToml {
55        source: e,
56        path: format!("{}", cargo_toml.display()),
57    })?;
58
59    let meta = match Manifest::<Meta>::from_slice_with_metadata(&cargo_toml_content) {
60        Ok(m) => m.package.and_then(|p| p.metadata),
61        Err(_e) => None,
62    };
63
64    Ok(Meta {
65        chip: meta.and_then(|m| m.chip),
66    })
67}
68
69/// Represents compiled code that the compiler emitted during compilation.
70pub struct Artifact {
71    path: PathBuf,
72    fresh: bool,
73}
74
75impl Artifact {
76    /// Get the path of this output from the compiler.
77    pub fn path(&self) -> &Path {
78        &self.path
79    }
80
81    /// If `true`, then the artifact was unchanged during compilation.
82    pub fn fresh(&self) -> bool {
83        self.fresh
84    }
85}
86
87/// Run `cargo build` and return the generated binary artifact.
88///
89/// `args` will be passed to cargo build, and `--message-format json` will be
90/// added to the list of arguments.
91///
92/// The output of `cargo build` is parsed to detect the path to the generated binary artifact.
93/// If either no artifact, or more than a single artifact are created, an error is returned.
94pub fn build_artifact(work_dir: &Path, args: &[String]) -> Result<Artifact, ArtifactError> {
95    let work_dir = dunce::canonicalize(work_dir).map_err(|e| ArtifactError::Canonicalize {
96        source: e,
97        work_dir: format!("{}", work_dir.display()),
98    })?;
99
100    let cargo_executable = std::env::var("CARGO").unwrap_or_else(|_| "cargo".to_owned());
101
102    log::debug!(
103        "Running '{}' in directory {}",
104        cargo_executable,
105        work_dir.display()
106    );
107
108    // Build the project.
109    let cargo_command = Command::new(cargo_executable)
110        .current_dir(work_dir)
111        .arg("build")
112        .args(args)
113        .args(["--message-format", "json-diagnostic-rendered-ansi"])
114        .stdout(Stdio::piped())
115        .spawn()
116        .map_err(ArtifactError::Io)?;
117
118    let output = cargo_command
119        .wait_with_output()
120        .map_err(ArtifactError::Io)?;
121
122    // Parse build output.
123    let messages = Message::parse_stream(&output.stdout[..]);
124
125    // Find artifacts.
126    let mut target_artifact = None;
127
128    for message in messages {
129        match message.map_err(ArtifactError::Io)? {
130            Message::CompilerArtifact(artifact) => {
131                if artifact.executable.is_some() {
132                    if target_artifact.is_some() {
133                        // We found multiple binary artifacts,
134                        // so we don't know which one to use.
135                        return Err(ArtifactError::MultipleArtifacts);
136                    } else {
137                        target_artifact = Some(artifact);
138                    }
139                }
140            }
141            Message::CompilerMessage(message) => {
142                if let Some(rendered) = message.message.rendered {
143                    print!("{rendered}");
144                }
145            }
146            // Ignore other messages.
147            _ => (),
148        }
149    }
150
151    // Check if the command succeeded, otherwise return an error.
152    // Any error messages occurring during the build are shown above,
153    // when the compiler messages are rendered.
154    if !output.status.success() {
155        return Err(ArtifactError::CargoBuild(output.status.code()));
156    }
157
158    if let Some(artifact) = target_artifact {
159        // Unwrap is safe, we only store artifacts with an executable.
160        Ok(Artifact {
161            path: PathBuf::from(artifact.executable.unwrap().as_path()),
162            fresh: artifact.fresh,
163        })
164    } else {
165        // We did not find a binary, so we should return an error.
166        Err(ArtifactError::NoArtifacts)
167    }
168}