Skip to content

Commit 703bc09

Browse files
committed
improve error reporting in CLI
Signed-off-by: Jorge Prendes <jorge.prendes@gmail.com>
1 parent 8ce7ef6 commit 703bc09

7 files changed

Lines changed: 370 additions & 134 deletions

File tree

Cargo.lock

Lines changed: 32 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ edition = "2024"
66
[dependencies]
77
anyhow = "1.0.100"
88
clap = { version = "4.5", features = ["derive"] }
9+
console = "0.16.1"
910
const_format = "0.2.34"
1011
glob = "0.3.3"
1112
libc = "0.2.176"

src/cargo_cmd.rs

Lines changed: 48 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use std::collections::HashMap;
22
use std::env;
33
use std::ffi::{OsStr, OsString};
4-
use std::path::Path;
4+
use std::path::{Path, PathBuf};
55
use std::process::{Command, Stdio};
66

77
use anyhow::{Result, bail};
@@ -25,16 +25,36 @@ pub trait CargoCmd {
2525
fn checked_status(&mut self) -> Result<()>;
2626
}
2727

28-
pub fn cargo_cmd() -> Result<Command> {
28+
#[derive(Clone, Hash)]
29+
pub struct CargoBinary {
30+
pub path: PathBuf,
31+
pub rustup_toolchain: Option<OsString>,
32+
}
33+
34+
impl CargoBinary {
35+
pub fn command(&self) -> Command {
36+
let mut cmd = Command::new(&self.path);
37+
if let Some(rustup_toolchain) = &self.rustup_toolchain {
38+
cmd.env("RUSTUP_TOOLCHAIN", rustup_toolchain);
39+
}
40+
cmd
41+
}
42+
}
43+
44+
pub fn find_cargo() -> Result<CargoBinary> {
2945
let cargo = match env::var_os("CARGO") {
3046
Some(cargo) => Path::new(&cargo).canonicalize()?,
3147
None => which::which("cargo")?.canonicalize()?,
3248
};
33-
let mut cmd = Command::new(cargo);
34-
if let Some(rustup_toolchain) = env::var_os("RUSTUP_TOOLCHAIN") {
35-
cmd.env("RUSTUP_TOOLCHAIN", rustup_toolchain);
36-
}
37-
Ok(cmd)
49+
let rustup_toolchain = env::var_os("RUSTUP_TOOLCHAIN");
50+
Ok(CargoBinary {
51+
path: cargo,
52+
rustup_toolchain,
53+
})
54+
}
55+
56+
pub fn cargo_cmd() -> Result<Command> {
57+
Ok(find_cargo()?.command())
3858
}
3959

4060
pub struct CheckedOutput {
@@ -152,20 +172,7 @@ impl CargoCmd for Command {
152172
&self,
153173
base: impl IntoIterator<Item = (impl AsRef<OsStr>, impl AsRef<OsStr>)>,
154174
) -> HashMap<OsString, OsString> {
155-
let mut envs = base
156-
.into_iter()
157-
.map(|(k, v)| (k.as_ref().to_owned(), v.as_ref().to_owned()))
158-
.collect::<HashMap<_, _>>();
159-
160-
for (k, v) in self.get_envs() {
161-
if let Some(v) = v {
162-
envs.insert(k.to_owned(), v.to_owned());
163-
} else {
164-
envs.remove(k);
165-
}
166-
}
167-
168-
envs
175+
merge_env(base, self.get_envs())
169176
}
170177

171178
fn checked_output(&mut self) -> Result<CheckedOutput> {
@@ -204,3 +211,23 @@ fn get_env(cmd: &Command, key: &str) -> Option<OsString> {
204211
None => std::env::var_os(key),
205212
}
206213
}
214+
215+
pub fn merge_env(
216+
base: impl IntoIterator<Item = (impl AsRef<OsStr>, impl AsRef<OsStr>)>,
217+
envs: impl IntoIterator<Item = (impl AsRef<OsStr>, Option<impl AsRef<OsStr>>)>,
218+
) -> HashMap<OsString, OsString> {
219+
let mut base = base
220+
.into_iter()
221+
.map(|(k, v)| (k.as_ref().to_owned(), v.as_ref().to_owned()))
222+
.collect::<HashMap<_, _>>();
223+
224+
for (k, v) in envs {
225+
if let Some(v) = v {
226+
base.insert(k.as_ref().to_owned(), v.as_ref().to_owned());
227+
} else {
228+
base.remove(k.as_ref());
229+
}
230+
}
231+
232+
base
233+
}

src/cli.rs

Lines changed: 128 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,64 +1,183 @@
11
use std::collections::HashMap;
2+
use std::convert::Infallible;
23
use std::env;
34
use std::env::consts::ARCH;
45
use std::ffi::OsString;
6+
use std::fmt::Debug;
57
use std::path::PathBuf;
68

79
use anyhow::{Context, Result};
810
use clap::{Parser, Subcommand};
911
use const_format::formatcp;
1012

1113
use crate::cargo_cmd::{CargoCmd as _, cargo_cmd};
14+
use crate::toolchain;
1215

1316
pub struct Args {
1417
pub manifest_path: Option<PathBuf>,
1518
pub target_dir: PathBuf,
1619
pub target: String,
1720
pub env: HashMap<OsString, OsString>,
1821
pub current_dir: PathBuf,
22+
pub clang: Option<PathBuf>,
23+
pub ar: Option<PathBuf>,
24+
}
25+
26+
pub trait WarningLevel {
27+
type Error;
28+
fn warning<T: Debug>(
29+
&self,
30+
msg: &str,
31+
err: impl Into<anyhow::Error>,
32+
default: T,
33+
) -> Result<T, Self::Error>;
34+
}
35+
36+
pub struct Warning;
37+
38+
#[doc(hidden)]
39+
pub mod warning {
40+
pub struct WarningIgnore;
41+
pub struct WarningWarn;
42+
#[allow(dead_code)]
43+
pub struct WarningError;
44+
}
45+
46+
impl Warning {
47+
pub const IGNORE: warning::WarningIgnore = warning::WarningIgnore;
48+
pub const WARN: warning::WarningWarn = warning::WarningWarn;
49+
#[allow(dead_code)]
50+
pub const ERROR: warning::WarningError = warning::WarningError;
51+
}
52+
53+
impl WarningLevel for warning::WarningIgnore {
54+
type Error = Infallible;
55+
fn warning<T: Debug>(
56+
&self,
57+
_msg: &str,
58+
_err: impl Into<anyhow::Error>,
59+
default: T,
60+
) -> Result<T, Self::Error> {
61+
Ok(default)
62+
}
63+
}
64+
65+
impl WarningLevel for warning::WarningWarn {
66+
type Error = Infallible;
67+
fn warning<T: Debug>(
68+
&self,
69+
msg: &str,
70+
err: impl Into<anyhow::Error>,
71+
default: T,
72+
) -> Result<T, Self::Error> {
73+
warning(msg);
74+
warning(format!("using {default:?}"));
75+
println!("{:?}", err.into());
76+
Ok(default)
77+
}
78+
}
79+
80+
impl WarningLevel for warning::WarningError {
81+
type Error = anyhow::Error;
82+
fn warning<T: Debug>(
83+
&self,
84+
msg: &str,
85+
err: impl Into<anyhow::Error>,
86+
_default: T,
87+
) -> Result<T, Self::Error> {
88+
Err(err.into()).context(msg.to_string())
89+
}
1990
}
2091

2192
impl Args {
22-
pub fn parse_from(
93+
pub fn parse<W: WarningLevel>(
2394
args: impl IntoIterator<Item = impl Into<OsString> + Clone>,
2495
env: impl IntoIterator<Item = (impl Into<OsString>, impl Into<OsString>)>,
2596
cwd: Option<impl Into<PathBuf>>,
26-
) -> Result<Args> {
97+
warn: W,
98+
) -> Result<Args, W::Error> {
2799
let mut args = ArgsImpl::parse_from(args);
28100
args.env = env.into_iter().map(|(k, v)| (k.into(), v.into())).collect();
29-
args.current_dir = match cwd {
101+
let cwd = match cwd {
30102
Some(cwd) => cwd.into(),
31-
None => env::current_dir().context("Failed to get current directory")?,
103+
None => match env::current_dir() {
104+
Ok(cwd) => cwd,
105+
Err(err) => {
106+
warn.warning("Could not get current directory", err, PathBuf::from("."))?
107+
}
108+
},
32109
};
33-
args.try_into()
110+
args.current_dir = cwd.clone();
111+
Args::try_from_with_defaults(warn, args)
34112
}
35113
}
36114

115+
fn warning(msg: impl AsRef<str>) {
116+
eprintln!(
117+
"{}{}{}",
118+
console::style("warning").yellow().bold(),
119+
console::style(": ").bold(),
120+
console::style(msg.as_ref()).bold(),
121+
);
122+
}
123+
37124
impl TryFrom<ArgsImpl> for Args {
38125
type Error = anyhow::Error;
39126

40127
fn try_from(value: ArgsImpl) -> Result<Self> {
128+
Args::try_from_with_defaults(Warning::ERROR, value)
129+
}
130+
}
131+
132+
impl Args {
133+
fn try_from_with_defaults<W: WarningLevel>(warn: W, value: ArgsImpl) -> Result<Self, W::Error> {
41134
let manifest_path = value.manifest_path;
42135

43136
let target_dir = match value.target_dir {
44137
Some(dir) => dir,
45-
None => resolve_target_dir(&manifest_path, &value.env, &value.current_dir)?,
138+
None => match resolve_target_dir(&manifest_path, &value.env, &value.current_dir) {
139+
Ok(dir) => dir,
140+
Err(err) => warn.warning(
141+
"could not resolve target directory",
142+
err,
143+
value.current_dir.join("target"),
144+
)?,
145+
},
46146
};
47147

48148
let target = match value.target {
49149
Some(triplet) => triplet,
50-
None => resolve_target(&value.env, &value.current_dir)?,
150+
None => match resolve_target(&value.env, &value.current_dir) {
151+
Ok(triplet) => triplet,
152+
Err(err) => warn.warning(
153+
"could not resolve target triple",
154+
err,
155+
DEFAULT_TARGET.to_string(),
156+
)?,
157+
},
158+
};
159+
160+
let target = if target.ends_with("-hyperlight-none") {
161+
target
162+
} else {
163+
let (arch, _) = target.split_once('-').unwrap_or((&target, ""));
164+
warn.warning(
165+
"requested target is not a hyperlight target",
166+
anyhow::anyhow!("invalid hyperlight target: {target}"),
167+
format!("{arch}-hyperlight-none"),
168+
)?
51169
};
52170

53-
let cwd = env::current_dir().context("Failed to get current directory")?;
54-
let target_dir = cwd.join(target_dir);
171+
let target_dir = value.current_dir.join(target_dir);
55172

56173
Ok(Args {
57174
manifest_path,
58175
target_dir,
59176
target,
60177
env: value.env,
61178
current_dir: value.current_dir,
179+
clang: toolchain::find_cc().ok(),
180+
ar: toolchain::find_ar().ok(),
62181
})
63182
}
64183
}

0 commit comments

Comments
 (0)