Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changes/refactor-errors.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"cargo-mobile2": minor
---

Add more context to IO errors.
39 changes: 26 additions & 13 deletions src/android/aab.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,19 @@ use crate::{

#[derive(Debug, Error)]
pub enum AabError {
#[error("Failed to build AAB: {0}")]
BuildFailed(#[from] std::io::Error),
#[error("Failed to run {command}: {error}")]
CommandFailed {
command: String,
error: std::io::Error,
},
}

impl Reportable for AabError {
fn report(&self) -> Report {
match self {
Self::BuildFailed(err) => Report::error("Failed to build AAB", err),
Self::CommandFailed { command, error } => {
Report::error(format!("Failed to run {command}"), error)
}
}
}
}
Expand Down Expand Up @@ -69,22 +74,30 @@ pub fn build(

args
};
gradlew(config, env)
.before_spawn(move |cmd| {
cmd.args(&gradle_args).arg(match noise_level {
NoiseLevel::Polite => "--warn",
NoiseLevel::LoudAndProud => "--info",
NoiseLevel::FranklyQuitePedantic => "--debug",
});
Ok(())
})
let cmd = gradlew(config, env).before_spawn(move |cmd| {
cmd.args(&gradle_args).arg(match noise_level {
NoiseLevel::Polite => "--warn",
NoiseLevel::LoudAndProud => "--info",
NoiseLevel::FranklyQuitePedantic => "--debug",
});
Ok(())
});
cmd
.start()
.inspect_err(|err| {
if err.kind() == std::io::ErrorKind::NotFound {
log::error!("`gradlew` not found. Make sure you have the Android SDK installed and added to your PATH");
}
})
.map_err(|error| AabError::CommandFailed {
command: format!("{cmd:?}"),
error,
})?
.wait()?;
.wait()
.map_err(|error| AabError::CommandFailed {
command: format!("{cmd:?}"),
error,
})?;

let mut outputs = Vec::new();
if split_per_abi {
Expand Down
51 changes: 29 additions & 22 deletions src/android/adb/device_list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,11 @@ pub enum Error {
AbiFailed(get_prop::Error),
#[error("{0:?} isn't a valid target ABI.")]
AbiInvalid(String),
#[error(transparent)]
Io(#[from] std::io::Error),
#[error("Failed to run {command}: {error}")]
CommandFailed {
command: String,
error: std::io::Error,
},
}

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

super::check_authorized(&cmd.output()?)
.map(|raw_list| {
regex_multi_line!(ADB_DEVICE_REGEX)
.captures_iter(&raw_list)
.map(|caps| {
assert_eq!(caps.len(), 2);
let serial_no = caps.get(1).unwrap().as_str().to_owned();
let model = get_prop(env, &serial_no, "ro.product.model")
.map_err(Error::ModelFailed)?;
let name = device_name(env, &serial_no).unwrap_or_else(|_| model.clone());
let abi = get_prop(env, &serial_no, "ro.product.cpu.abi")
.map_err(Error::AbiFailed)?;
let target =
Target::for_abi(&abi).ok_or_else(|| Error::AbiInvalid(abi.clone()))?;
Ok(Device::new(serial_no, name, model, target))
})
.collect()
})
.map_err(Error::DevicesFailed)?
super::check_authorized(&cmd.output().map_err(|error| Error::CommandFailed {
command: format!("{} devices", cmd.get_program().to_string_lossy()),
error,
})?)
.map(|raw_list| {
regex_multi_line!(ADB_DEVICE_REGEX)
.captures_iter(&raw_list)
.map(|caps| {
assert_eq!(caps.len(), 2);
let serial_no = caps.get(1).unwrap().as_str().to_owned();
let model =
get_prop(env, &serial_no, "ro.product.model").map_err(Error::ModelFailed)?;
let name = device_name(env, &serial_no).unwrap_or_else(|_| model.clone());
let abi =
get_prop(env, &serial_no, "ro.product.cpu.abi").map_err(Error::AbiFailed)?;
let target = Target::for_abi(&abi).ok_or_else(|| Error::AbiInvalid(abi.clone()))?;
Ok(Device::new(serial_no, name, model, target))
})
.collect()
})
.map_err(Error::DevicesFailed)?
}

#[cfg(test)]
Expand Down
69 changes: 42 additions & 27 deletions src/android/adb/device_name.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,64 +10,79 @@ use thiserror::Error;
pub enum Error {
#[error("Failed to run `adb emu avd name`: {0}")]
EmuFailed(#[source] super::RunCheckedError),
#[error(transparent)]
GetPropFailed(super::get_prop::Error),
#[error("Failed to run `adb shell dumpsys bluetooth_manager`: {0}")]
DumpsysFailed(#[source] super::RunCheckedError),
#[error("Name regex didn't match anything.")]
NotMatched,
#[error(transparent)]
Io(#[from] std::io::Error),
#[error("Failed to run {command}: {error}")]
CommandFailed {
command: String,
error: std::io::Error,
},
}

impl Reportable for Error {
fn report(&self) -> Report {
let msg = "Failed to get device name";
match self {
Self::EmuFailed(err) => err.report("Failed to run `adb emu avd name`"),
Self::GetPropFailed(err) => err.report(),
Self::DumpsysFailed(err) => {
err.report("Failed to run `adb shell dumpsys bluetooth_manager`")
}
Self::NotMatched => Report::error(msg, self),
Self::Io(err) => Report::error("IO error", err),
Self::CommandFailed { command, error } => {
Report::error(format!("Failed to run {command}"), error)
}
}
}
}

pub fn device_name(env: &Env, serial_no: &str) -> Result<String, Error> {
if serial_no.starts_with("emulator") {
let cmd = adb(env, ["-s", serial_no, "emu", "avd", "name"])
.stderr_capture()
.stdout_capture();
let name = super::check_authorized(
adb(env, ["-s", serial_no])
.before_spawn(move |cmd| {
cmd.args(["emu", "avd", "name"]);
Ok(())
})
.stderr_capture()
.stdout_capture()
.start()?
.wait()?,
cmd.start()
.map_err(|error| Error::CommandFailed {
command: format!("{cmd:?}"),
error,
})?
.wait()
.map_err(|error| Error::CommandFailed {
command: format!("{cmd:?}"),
error,
})?,
)
.map(|stdout| stdout.split('\n').next().unwrap().trim().to_string())
.map_err(Error::EmuFailed)?;
if name.is_empty() {
super::get_prop::get_prop(env, serial_no, "ro.boot.qemu.avd_name").map_err(|e| {
Error::EmuFailed(super::RunCheckedError::CommandFailed(std::io::Error::new(
std::io::ErrorKind::Other,
e,
)))
})
super::get_prop::get_prop(env, serial_no, "ro.boot.qemu.avd_name")
.map_err(Error::GetPropFailed)
} else {
Ok(name)
}
} else {
let cmd = adb(
env,
["-s", serial_no, "shell", "dumpsys", "bluetooth_manager"],
)
.stderr_capture()
.stdout_capture();
super::check_authorized(
adb(env, ["-s", serial_no])
.before_spawn(move |cmd| {
cmd.args(["shell", "dumpsys", "bluetooth_manager"]);
Ok(())
})
.stderr_capture()
.stdout_capture()
.start()?
.wait()?,
cmd.start()
.map_err(|error| Error::CommandFailed {
command: format!("{cmd:?}"),
error,
})?
.wait()
.map_err(|error| Error::CommandFailed {
command: format!("{cmd:?}"),
error,
})?,
)
.map_err(Error::DumpsysFailed)
.and_then(|stdout| {
Expand Down
32 changes: 20 additions & 12 deletions src/android/adb/get_prop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,18 @@ pub enum Error {
prop: String,
source: super::RunCheckedError,
},
#[error(transparent)]
Io(#[from] std::io::Error),
#[error("Failed to run {command}: {error}")]
CommandFailed {
command: String,
error: std::io::Error,
},
}

impl Error {
fn prop(&self) -> &str {
match self {
Self::LookupFailed { prop, .. } => prop,
Self::Io(_) => unreachable!(),
Self::CommandFailed { .. } => unreachable!(),
}
}
}
Expand All @@ -32,24 +35,29 @@ impl Reportable for Error {
let msg = format!("Failed to run `adb shell getprop {}`", self.prop());
match self {
Self::LookupFailed { source, .. } => source.report(&msg),
Self::Io(err) => Report::error("IO error", err),
Self::CommandFailed { command, error } => {
Report::error(format!("Failed to run {command}"), error)
}
}
}
}

pub fn get_prop(env: &Env, serial_no: &str, prop: &str) -> Result<String, Error> {
let prop_ = prop.to_string();
let handle = adb(env, ["-s", serial_no])
.before_spawn(move |cmd| {
cmd.args(["shell", "getprop", &prop_]);
Ok(())
})
let cmd = adb(env, ["-s", serial_no, "shell", "getprop", prop]);
let handle = cmd
.stdin_file(os_pipe::dup_stdin().unwrap())
.stdout_capture()
.stderr_capture()
.start()?;
.start()
.map_err(|error| Error::CommandFailed {
command: format!("{cmd:?}"),
error,
})?;

let output = handle.wait()?;
let output = handle.wait().map_err(|error| Error::CommandFailed {
command: format!("{cmd:?}"),
error,
})?;
super::check_authorized(output).map_err(|source| Error::LookupFailed {
prop: prop.to_owned(),
source,
Expand Down
3 changes: 0 additions & 3 deletions src/android/adb/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,13 @@ pub enum RunCheckedError {
InvalidUtf8(#[from] FromUtf8Error),
#[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.")]
Unauthorized,
#[error(transparent)]
CommandFailed(std::io::Error),
}

impl RunCheckedError {
pub fn report(&self, msg: &str) -> Report {
match self {
Self::InvalidUtf8(err) => Report::error(msg, err),
Self::Unauthorized => Report::action_request(msg, self),
Self::CommandFailed(err) => Report::error(msg, err),
}
}
}
Expand Down
39 changes: 26 additions & 13 deletions src/android/apk.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,20 @@ use crate::{
pub enum ApkError {
#[error(transparent)]
LibSymlinkCleaningFailed(jnilibs::RemoveBrokenLinksError),
#[error("Failed to assemble APK: {0}")]
AssembleFailed(#[from] std::io::Error),
#[error("Failed to assemble APK with {command}: {error}")]
AssembleFailed {
command: String,
error: std::io::Error,
},
}

impl Reportable for ApkError {
fn report(&self) -> Report {
match self {
Self::LibSymlinkCleaningFailed(err) => err.report(),
Self::AssembleFailed(err) => Report::error("Failed to assemble APK", err),
Self::AssembleFailed { command, error } => {
Report::error(format!("Failed to assemble APK with {command}"), error)
}
}
}
}
Expand Down Expand Up @@ -96,22 +101,30 @@ pub fn build(
args
};

gradlew(config, env)
.before_spawn(move |cmd| {
cmd.args(&gradle_args).arg(match noise_level {
NoiseLevel::Polite => "--warn",
NoiseLevel::LoudAndProud => "--info",
NoiseLevel::FranklyQuitePedantic => "--debug",
});
Ok(())
})
let cmd = gradlew(config, env).before_spawn(move |cmd| {
cmd.args(&gradle_args).arg(match noise_level {
NoiseLevel::Polite => "--warn",
NoiseLevel::LoudAndProud => "--info",
NoiseLevel::FranklyQuitePedantic => "--debug",
});
Ok(())
});
cmd
.start()
.inspect_err(|err| {
if err.kind() == std::io::ErrorKind::NotFound {
log::error!("`gradlew` not found. Make sure you have the Android SDK installed and added to your PATH");
}
})
.map_err(|err| ApkError::AssembleFailed {
command: format!("{cmd:?}"),
error: err,
})?
.wait()?;
.wait()
.map_err(|err| ApkError::AssembleFailed {
command: format!("{cmd:?}"),
error: err,
})?;

let mut outputs = Vec::new();
if split_per_abi {
Expand Down
Loading
Loading