Skip to content

Commit 3b23d9c

Browse files
authored
refactor: add more context to IO errors (#485)
* refactor: add more context to IO errors * fix linux build, fmt
1 parent 813630e commit 3b23d9c

File tree

32 files changed

+668
-338
lines changed

32 files changed

+668
-338
lines changed

.changes/refactor-errors.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"cargo-mobile2": minor
3+
---
4+
5+
Add more context to IO errors.

src/android/aab.rs

Lines changed: 26 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,19 @@ use crate::{
1515

1616
#[derive(Debug, Error)]
1717
pub enum AabError {
18-
#[error("Failed to build AAB: {0}")]
19-
BuildFailed(#[from] std::io::Error),
18+
#[error("Failed to run {command}: {error}")]
19+
CommandFailed {
20+
command: String,
21+
error: std::io::Error,
22+
},
2023
}
2124

2225
impl Reportable for AabError {
2326
fn report(&self) -> Report {
2427
match self {
25-
Self::BuildFailed(err) => Report::error("Failed to build AAB", err),
28+
Self::CommandFailed { command, error } => {
29+
Report::error(format!("Failed to run {command}"), error)
30+
}
2631
}
2732
}
2833
}
@@ -69,22 +74,30 @@ pub fn build(
6974

7075
args
7176
};
72-
gradlew(config, env)
73-
.before_spawn(move |cmd| {
74-
cmd.args(&gradle_args).arg(match noise_level {
75-
NoiseLevel::Polite => "--warn",
76-
NoiseLevel::LoudAndProud => "--info",
77-
NoiseLevel::FranklyQuitePedantic => "--debug",
78-
});
79-
Ok(())
80-
})
77+
let cmd = gradlew(config, env).before_spawn(move |cmd| {
78+
cmd.args(&gradle_args).arg(match noise_level {
79+
NoiseLevel::Polite => "--warn",
80+
NoiseLevel::LoudAndProud => "--info",
81+
NoiseLevel::FranklyQuitePedantic => "--debug",
82+
});
83+
Ok(())
84+
});
85+
cmd
8186
.start()
8287
.inspect_err(|err| {
8388
if err.kind() == std::io::ErrorKind::NotFound {
8489
log::error!("`gradlew` not found. Make sure you have the Android SDK installed and added to your PATH");
8590
}
91+
})
92+
.map_err(|error| AabError::CommandFailed {
93+
command: format!("{cmd:?}"),
94+
error,
8695
})?
87-
.wait()?;
96+
.wait()
97+
.map_err(|error| AabError::CommandFailed {
98+
command: format!("{cmd:?}"),
99+
error,
100+
})?;
88101

89102
let mut outputs = Vec::new();
90103
if split_per_abi {

src/android/adb/device_list.rs

Lines changed: 29 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,11 @@ pub enum Error {
2020
AbiFailed(get_prop::Error),
2121
#[error("{0:?} isn't a valid target ABI.")]
2222
AbiInvalid(String),
23-
#[error(transparent)]
24-
Io(#[from] std::io::Error),
23+
#[error("Failed to run {command}: {error}")]
24+
CommandFailed {
25+
command: String,
26+
error: std::io::Error,
27+
},
2528
}
2629

2730
impl Reportable for Error {
@@ -32,7 +35,9 @@ impl Reportable for Error {
3235
Self::NameFailed(err) => err.report(),
3336
Self::ModelFailed(err) | Self::AbiFailed(err) => err.report(),
3437
Self::AbiInvalid(_) => Report::error(msg, self),
35-
Self::Io(err) => Report::error(msg, err),
38+
Self::CommandFailed { command, error } => {
39+
Report::error(format!("Failed to run {command}"), error)
40+
}
3641
}
3742
}
3843
}
@@ -43,25 +48,27 @@ pub fn device_list(env: &Env) -> Result<BTreeSet<Device<'static>>, Error> {
4348
let mut cmd = Command::new(env.platform_tools_path().join("adb"));
4449
cmd.arg("devices").envs(env.explicit_env());
4550

46-
super::check_authorized(&cmd.output()?)
47-
.map(|raw_list| {
48-
regex_multi_line!(ADB_DEVICE_REGEX)
49-
.captures_iter(&raw_list)
50-
.map(|caps| {
51-
assert_eq!(caps.len(), 2);
52-
let serial_no = caps.get(1).unwrap().as_str().to_owned();
53-
let model = get_prop(env, &serial_no, "ro.product.model")
54-
.map_err(Error::ModelFailed)?;
55-
let name = device_name(env, &serial_no).unwrap_or_else(|_| model.clone());
56-
let abi = get_prop(env, &serial_no, "ro.product.cpu.abi")
57-
.map_err(Error::AbiFailed)?;
58-
let target =
59-
Target::for_abi(&abi).ok_or_else(|| Error::AbiInvalid(abi.clone()))?;
60-
Ok(Device::new(serial_no, name, model, target))
61-
})
62-
.collect()
63-
})
64-
.map_err(Error::DevicesFailed)?
51+
super::check_authorized(&cmd.output().map_err(|error| Error::CommandFailed {
52+
command: format!("{} devices", cmd.get_program().to_string_lossy()),
53+
error,
54+
})?)
55+
.map(|raw_list| {
56+
regex_multi_line!(ADB_DEVICE_REGEX)
57+
.captures_iter(&raw_list)
58+
.map(|caps| {
59+
assert_eq!(caps.len(), 2);
60+
let serial_no = caps.get(1).unwrap().as_str().to_owned();
61+
let model =
62+
get_prop(env, &serial_no, "ro.product.model").map_err(Error::ModelFailed)?;
63+
let name = device_name(env, &serial_no).unwrap_or_else(|_| model.clone());
64+
let abi =
65+
get_prop(env, &serial_no, "ro.product.cpu.abi").map_err(Error::AbiFailed)?;
66+
let target = Target::for_abi(&abi).ok_or_else(|| Error::AbiInvalid(abi.clone()))?;
67+
Ok(Device::new(serial_no, name, model, target))
68+
})
69+
.collect()
70+
})
71+
.map_err(Error::DevicesFailed)?
6572
}
6673

6774
#[cfg(test)]

src/android/adb/device_name.rs

Lines changed: 42 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -10,64 +10,79 @@ use thiserror::Error;
1010
pub enum Error {
1111
#[error("Failed to run `adb emu avd name`: {0}")]
1212
EmuFailed(#[source] super::RunCheckedError),
13+
#[error(transparent)]
14+
GetPropFailed(super::get_prop::Error),
1315
#[error("Failed to run `adb shell dumpsys bluetooth_manager`: {0}")]
1416
DumpsysFailed(#[source] super::RunCheckedError),
1517
#[error("Name regex didn't match anything.")]
1618
NotMatched,
17-
#[error(transparent)]
18-
Io(#[from] std::io::Error),
19+
#[error("Failed to run {command}: {error}")]
20+
CommandFailed {
21+
command: String,
22+
error: std::io::Error,
23+
},
1924
}
2025

2126
impl Reportable for Error {
2227
fn report(&self) -> Report {
2328
let msg = "Failed to get device name";
2429
match self {
2530
Self::EmuFailed(err) => err.report("Failed to run `adb emu avd name`"),
31+
Self::GetPropFailed(err) => err.report(),
2632
Self::DumpsysFailed(err) => {
2733
err.report("Failed to run `adb shell dumpsys bluetooth_manager`")
2834
}
2935
Self::NotMatched => Report::error(msg, self),
30-
Self::Io(err) => Report::error("IO error", err),
36+
Self::CommandFailed { command, error } => {
37+
Report::error(format!("Failed to run {command}"), error)
38+
}
3139
}
3240
}
3341
}
3442

3543
pub fn device_name(env: &Env, serial_no: &str) -> Result<String, Error> {
3644
if serial_no.starts_with("emulator") {
45+
let cmd = adb(env, ["-s", serial_no, "emu", "avd", "name"])
46+
.stderr_capture()
47+
.stdout_capture();
3748
let name = super::check_authorized(
38-
adb(env, ["-s", serial_no])
39-
.before_spawn(move |cmd| {
40-
cmd.args(["emu", "avd", "name"]);
41-
Ok(())
42-
})
43-
.stderr_capture()
44-
.stdout_capture()
45-
.start()?
46-
.wait()?,
49+
cmd.start()
50+
.map_err(|error| Error::CommandFailed {
51+
command: format!("{cmd:?}"),
52+
error,
53+
})?
54+
.wait()
55+
.map_err(|error| Error::CommandFailed {
56+
command: format!("{cmd:?}"),
57+
error,
58+
})?,
4759
)
4860
.map(|stdout| stdout.split('\n').next().unwrap().trim().to_string())
4961
.map_err(Error::EmuFailed)?;
5062
if name.is_empty() {
51-
super::get_prop::get_prop(env, serial_no, "ro.boot.qemu.avd_name").map_err(|e| {
52-
Error::EmuFailed(super::RunCheckedError::CommandFailed(std::io::Error::new(
53-
std::io::ErrorKind::Other,
54-
e,
55-
)))
56-
})
63+
super::get_prop::get_prop(env, serial_no, "ro.boot.qemu.avd_name")
64+
.map_err(Error::GetPropFailed)
5765
} else {
5866
Ok(name)
5967
}
6068
} else {
69+
let cmd = adb(
70+
env,
71+
["-s", serial_no, "shell", "dumpsys", "bluetooth_manager"],
72+
)
73+
.stderr_capture()
74+
.stdout_capture();
6175
super::check_authorized(
62-
adb(env, ["-s", serial_no])
63-
.before_spawn(move |cmd| {
64-
cmd.args(["shell", "dumpsys", "bluetooth_manager"]);
65-
Ok(())
66-
})
67-
.stderr_capture()
68-
.stdout_capture()
69-
.start()?
70-
.wait()?,
76+
cmd.start()
77+
.map_err(|error| Error::CommandFailed {
78+
command: format!("{cmd:?}"),
79+
error,
80+
})?
81+
.wait()
82+
.map_err(|error| Error::CommandFailed {
83+
command: format!("{cmd:?}"),
84+
error,
85+
})?,
7186
)
7287
.map_err(Error::DumpsysFailed)
7388
.and_then(|stdout| {

src/android/adb/get_prop.rs

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,18 @@ pub enum Error {
1414
prop: String,
1515
source: super::RunCheckedError,
1616
},
17-
#[error(transparent)]
18-
Io(#[from] std::io::Error),
17+
#[error("Failed to run {command}: {error}")]
18+
CommandFailed {
19+
command: String,
20+
error: std::io::Error,
21+
},
1922
}
2023

2124
impl Error {
2225
fn prop(&self) -> &str {
2326
match self {
2427
Self::LookupFailed { prop, .. } => prop,
25-
Self::Io(_) => unreachable!(),
28+
Self::CommandFailed { .. } => unreachable!(),
2629
}
2730
}
2831
}
@@ -32,24 +35,29 @@ impl Reportable for Error {
3235
let msg = format!("Failed to run `adb shell getprop {}`", self.prop());
3336
match self {
3437
Self::LookupFailed { source, .. } => source.report(&msg),
35-
Self::Io(err) => Report::error("IO error", err),
38+
Self::CommandFailed { command, error } => {
39+
Report::error(format!("Failed to run {command}"), error)
40+
}
3641
}
3742
}
3843
}
3944

4045
pub fn get_prop(env: &Env, serial_no: &str, prop: &str) -> Result<String, Error> {
41-
let prop_ = prop.to_string();
42-
let handle = adb(env, ["-s", serial_no])
43-
.before_spawn(move |cmd| {
44-
cmd.args(["shell", "getprop", &prop_]);
45-
Ok(())
46-
})
46+
let cmd = adb(env, ["-s", serial_no, "shell", "getprop", prop]);
47+
let handle = cmd
4748
.stdin_file(os_pipe::dup_stdin().unwrap())
4849
.stdout_capture()
4950
.stderr_capture()
50-
.start()?;
51+
.start()
52+
.map_err(|error| Error::CommandFailed {
53+
command: format!("{cmd:?}"),
54+
error,
55+
})?;
5156

52-
let output = handle.wait()?;
57+
let output = handle.wait().map_err(|error| Error::CommandFailed {
58+
command: format!("{cmd:?}"),
59+
error,
60+
})?;
5361
super::check_authorized(output).map_err(|source| Error::LookupFailed {
5462
prop: prop.to_owned(),
5563
source,

src/android/adb/mod.rs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,16 +23,13 @@ pub enum RunCheckedError {
2323
InvalidUtf8(#[from] FromUtf8Error),
2424
#[error("This device doesn't yet trust this computer. On the device, you should see a prompt like \"Allow USB debugging?\". Pressing \"Allow\" should fix this.")]
2525
Unauthorized,
26-
#[error(transparent)]
27-
CommandFailed(std::io::Error),
2826
}
2927

3028
impl RunCheckedError {
3129
pub fn report(&self, msg: &str) -> Report {
3230
match self {
3331
Self::InvalidUtf8(err) => Report::error(msg, err),
3432
Self::Unauthorized => Report::action_request(msg, self),
35-
Self::CommandFailed(err) => Report::error(msg, err),
3633
}
3734
}
3835
}

src/android/apk.rs

Lines changed: 26 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,20 @@ use crate::{
1818
pub enum ApkError {
1919
#[error(transparent)]
2020
LibSymlinkCleaningFailed(jnilibs::RemoveBrokenLinksError),
21-
#[error("Failed to assemble APK: {0}")]
22-
AssembleFailed(#[from] std::io::Error),
21+
#[error("Failed to assemble APK with {command}: {error}")]
22+
AssembleFailed {
23+
command: String,
24+
error: std::io::Error,
25+
},
2326
}
2427

2528
impl Reportable for ApkError {
2629
fn report(&self) -> Report {
2730
match self {
2831
Self::LibSymlinkCleaningFailed(err) => err.report(),
29-
Self::AssembleFailed(err) => Report::error("Failed to assemble APK", err),
32+
Self::AssembleFailed { command, error } => {
33+
Report::error(format!("Failed to assemble APK with {command}"), error)
34+
}
3035
}
3136
}
3237
}
@@ -96,22 +101,30 @@ pub fn build(
96101
args
97102
};
98103

99-
gradlew(config, env)
100-
.before_spawn(move |cmd| {
101-
cmd.args(&gradle_args).arg(match noise_level {
102-
NoiseLevel::Polite => "--warn",
103-
NoiseLevel::LoudAndProud => "--info",
104-
NoiseLevel::FranklyQuitePedantic => "--debug",
105-
});
106-
Ok(())
107-
})
104+
let cmd = gradlew(config, env).before_spawn(move |cmd| {
105+
cmd.args(&gradle_args).arg(match noise_level {
106+
NoiseLevel::Polite => "--warn",
107+
NoiseLevel::LoudAndProud => "--info",
108+
NoiseLevel::FranklyQuitePedantic => "--debug",
109+
});
110+
Ok(())
111+
});
112+
cmd
108113
.start()
109114
.inspect_err(|err| {
110115
if err.kind() == std::io::ErrorKind::NotFound {
111116
log::error!("`gradlew` not found. Make sure you have the Android SDK installed and added to your PATH");
112117
}
118+
})
119+
.map_err(|err| ApkError::AssembleFailed {
120+
command: format!("{cmd:?}"),
121+
error: err,
113122
})?
114-
.wait()?;
123+
.wait()
124+
.map_err(|err| ApkError::AssembleFailed {
125+
command: format!("{cmd:?}"),
126+
error: err,
127+
})?;
115128

116129
let mut outputs = Vec::new();
117130
if split_per_abi {

0 commit comments

Comments
 (0)