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
16pub 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
50pub 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
69pub struct Artifact {
71 path: PathBuf,
72 fresh: bool,
73}
74
75impl Artifact {
76 pub fn path(&self) -> &Path {
78 &self.path
79 }
80
81 pub fn fresh(&self) -> bool {
83 self.fresh
84 }
85}
86
87pub 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 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 let messages = Message::parse_stream(&output.stdout[..]);
124
125 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 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 _ => (),
148 }
149 }
150
151 if !output.status.success() {
155 return Err(ArtifactError::CargoBuild(output.status.code()));
156 }
157
158 if let Some(artifact) = target_artifact {
159 Ok(Artifact {
161 path: PathBuf::from(artifact.executable.unwrap().as_path()),
162 fresh: artifact.fresh,
163 })
164 } else {
165 Err(ArtifactError::NoArtifacts)
167 }
168}