Provide better output when downloading versions fails

- include stdout/stderr when utf8_output() fails
- don't swallow the error returned by handle_Version_install_or_update
- skip codesigning when NODMG set

Closes #4224
This commit is contained in:
Damien Elmes 2025-07-24 20:12:39 +07:00
parent aee71afebe
commit 00bc0354c9
3 changed files with 69 additions and 32 deletions

View file

@ -38,7 +38,8 @@ cp ../pyproject.toml "$APP_LAUNCHER/Contents/Resources/"
cp ../../../.python-version "$APP_LAUNCHER/Contents/Resources/"
cp ../versions.py "$APP_LAUNCHER/Contents/Resources/"
# Codesign
# Codesign/bundle
if [ -z "$NODMG" ]; then
for i in "$APP_LAUNCHER/Contents/MacOS/uv" "$APP_LAUNCHER/Contents/MacOS/launcher" "$APP_LAUNCHER"; do
codesign --force -vvvv -o runtime -s "Developer ID Application:" \
--entitlements entitlements.python.xml \
@ -49,8 +50,7 @@ done
codesign -vvv "$APP_LAUNCHER"
spctl -a "$APP_LAUNCHER"
# Notarize and bundle (skip if NODMG is set)
if [ -z "$NODMG" ]; then
# Notarize and build dmg
./notarize.sh "$OUTPUT_DIR"
./dmg/build.sh "$OUTPUT_DIR"
fi

View file

@ -378,9 +378,7 @@ fn main_menu_loop(state: &State) -> Result<()> {
continue;
}
choice @ (MainMenuChoice::Latest | MainMenuChoice::Version(_)) => {
if handle_version_install_or_update(state, choice.clone()).is_err() {
continue;
}
handle_version_install_or_update(state, choice.clone())?;
break;
}
}
@ -650,7 +648,13 @@ fn fetch_versions(state: &State) -> Result<Vec<String>> {
.args(["run", "--no-project"])
.arg(&versions_script);
let output = cmd.utf8_output()?;
let output = match cmd.utf8_output() {
Ok(output) => output,
Err(e) => {
print!("Unable to check for Anki versions. Please check your internet connection.\n\n");
return Err(e.into());
}
};
let versions = serde_json::from_str(&output.stdout).context("Failed to parse versions JSON")?;
Ok(versions)
}

View file

@ -11,6 +11,24 @@ use snafu::ensure;
use snafu::ResultExt;
use snafu::Snafu;
#[derive(Debug)]
pub struct CodeDisplay(Option<i32>);
impl std::fmt::Display for CodeDisplay {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self.0 {
Some(code) => write!(f, "{code}"),
None => write!(f, "?"),
}
}
}
impl From<Option<i32>> for CodeDisplay {
fn from(code: Option<i32>) -> Self {
CodeDisplay(code)
}
}
#[derive(Debug, Snafu)]
pub enum Error {
#[snafu(display("Failed to execute: {cmdline}"))]
@ -18,8 +36,15 @@ pub enum Error {
cmdline: String,
source: std::io::Error,
},
#[snafu(display("Failed with code {code:?}: {cmdline}"))]
ReturnedError { cmdline: String, code: Option<i32> },
#[snafu(display("Failed to run ({code}): {cmdline}"))]
ReturnedError { cmdline: String, code: CodeDisplay },
#[snafu(display("Failed to run ({code}): {cmdline}: {stdout}{stderr}"))]
ReturnedWithOutputError {
cmdline: String,
code: CodeDisplay,
stdout: String,
stderr: String,
},
#[snafu(display("Couldn't decode stdout/stderr as utf8"))]
InvalidUtf8 {
cmdline: String,
@ -71,31 +96,36 @@ impl CommandExt for Command {
status.success(),
ReturnedSnafu {
cmdline: get_cmdline(self),
code: status.code(),
code: CodeDisplay::from(status.code()),
}
);
Ok(self)
}
fn utf8_output(&mut self) -> Result<Utf8Output> {
let cmdline = get_cmdline(self);
let output = self.output().with_context(|_| DidNotExecuteSnafu {
cmdline: get_cmdline(self),
cmdline: cmdline.clone(),
})?;
let stdout = String::from_utf8(output.stdout).with_context(|_| InvalidUtf8Snafu {
cmdline: cmdline.clone(),
})?;
let stderr = String::from_utf8(output.stderr).with_context(|_| InvalidUtf8Snafu {
cmdline: cmdline.clone(),
})?;
ensure!(
output.status.success(),
ReturnedSnafu {
cmdline: get_cmdline(self),
code: output.status.code(),
ReturnedWithOutputSnafu {
cmdline,
code: CodeDisplay::from(output.status.code()),
stdout: stdout.clone(),
stderr: stderr.clone(),
}
);
Ok(Utf8Output {
stdout: String::from_utf8(output.stdout).with_context(|_| InvalidUtf8Snafu {
cmdline: get_cmdline(self),
})?,
stderr: String::from_utf8(output.stderr).with_context(|_| InvalidUtf8Snafu {
cmdline: get_cmdline(self),
})?,
})
Ok(Utf8Output { stdout, stderr })
}
fn ensure_spawn(&mut self) -> Result<std::process::Child> {
@ -135,7 +165,10 @@ mod test {
#[cfg(not(windows))]
assert!(matches!(
Command::new("false").ensure_success(),
Err(Error::ReturnedError { code: Some(1), .. })
Err(Error::ReturnedError {
code: CodeDisplay(_),
..
})
));
}
}