Browse Source

switch to anyhow for the entire CLI

Jonathan Kelley 6 days ago
parent
commit
3a5543aeec

+ 3 - 4
packages/cli/src/build/assets.rs

@@ -34,7 +34,7 @@ use std::{
 };
 
 use crate::Result;
-use anyhow::Context;
+use anyhow::{bail, Context};
 use const_serialize::{ConstVec, SerializeConst};
 use dioxus_cli_opt::AssetManifest;
 use manganis::BundledAsset;
@@ -352,11 +352,10 @@ pub(crate) fn extract_assets_from_file(path: impl AsRef<Path>) -> Result<AssetMa
             .output()?;
 
         if !output.status.success() {
-            return Err(anyhow::anyhow!(
+            bail!(
                 "Failed to re-sign the binary with codesign after finalizing the assets: {}",
                 String::from_utf8_lossy(&output.stderr)
-            )
-            .into());
+            );
         }
     }
 

+ 37 - 24
packages/cli/src/build/builder.rs

@@ -2,7 +2,7 @@ use crate::{
     serve::WebServer, BuildArtifacts, BuildRequest, BuildStage, BuilderUpdate, Platform,
     ProgressRx, ProgressTx, Result, StructuredOutput,
 };
-use anyhow::Context;
+use anyhow::{bail, Context};
 use dioxus_cli_opt::process_file_to;
 use futures_util::{future::OptionFuture, pin_mut, FutureExt};
 use itertools::Itertools;
@@ -122,25 +122,13 @@ impl AppBuilder {
     ///   updates (e.g., `wait`, `finish_build`).
     /// - The build process is designed to be cancellable and restartable using methods like `abort_all`
     ///   or `rebuild`.
-    pub(crate) fn start(request: &BuildRequest, mode: BuildMode) -> Result<Self> {
+    pub(crate) fn new(request: &BuildRequest) -> Result<Self> {
         let (tx, rx) = futures_channel::mpsc::unbounded();
 
         Ok(Self {
             build: request.clone(),
             stage: BuildStage::Initializing,
-            build_task: tokio::spawn({
-                let request = request.clone();
-                let tx = tx.clone();
-                async move {
-                    let ctx = BuildContext {
-                        mode,
-                        tx: tx.clone(),
-                    };
-                    request.verify_tooling(&ctx).await?;
-                    request.prepare_build_dir()?;
-                    request.build(&ctx).await
-                }
-            }),
+            build_task: tokio::task::spawn(std::future::pending()),
             tx,
             rx,
             patches: vec![],
@@ -164,6 +152,29 @@ impl AppBuilder {
         })
     }
 
+    /// Create a new `AppBuilder` and immediately start a build process.
+    pub fn started(request: &BuildRequest, mode: BuildMode) -> Result<Self> {
+        let mut builder = Self::new(request)?;
+        builder.start(mode);
+        Ok(builder)
+    }
+
+    pub(crate) fn start(&mut self, mode: BuildMode) {
+        self.build_task = tokio::spawn({
+            let request = self.build.clone();
+            let tx = self.tx.clone();
+            async move {
+                let ctx = BuildContext {
+                    mode,
+                    tx: tx.clone(),
+                };
+                request.verify_tooling(&ctx).await?;
+                request.prepare_build_dir()?;
+                request.build(&ctx).await
+            }
+        });
+    }
+
     /// Wait for any new updates to the builder - either it completed or gave us a message etc
     pub(crate) async fn wait(&mut self) -> BuilderUpdate {
         use futures_util::StreamExt;
@@ -178,7 +189,7 @@ impl AppBuilder {
                 match bundle {
                     Ok(Ok(bundle)) => BuilderUpdate::BuildReady { bundle },
                     Ok(Err(err)) => BuilderUpdate::BuildFailed { err },
-                    Err(err) => BuilderUpdate::BuildFailed { err: crate::Error::Runtime(format!("Build panicked! {:#?}", err)) },
+                    Err(err) => BuilderUpdate::BuildFailed { err: anyhow::anyhow!("Build panicked! {:#?}", err) },
                 }
             },
             Some(Ok(Some(msg))) = OptionFuture::from(self.stdout.as_mut().map(|f| f.next_line())) => {
@@ -891,7 +902,7 @@ impl AppBuilder {
                 .context("Failed to parse xcrun output")?;
             let device_uuid = json["result"]["devices"][0]["identifier"]
                 .as_str()
-                .ok_or("Failed to extract device UUID")?
+                .context("Failed to extract device UUID")?
                 .to_string();
 
             Ok(device_uuid)
@@ -918,14 +929,14 @@ impl AppBuilder {
                 .await?;
 
             if !output.status.success() {
-                return Err(format!("Failed to install app: {:?}", output).into());
+                bail!("Failed to install app: {:?}", output);
             }
 
             let json: Value = serde_json::from_str(&std::fs::read_to_string(tmpfile.path())?)
                 .context("Failed to parse xcrun output")?;
             let installation_url = json["result"]["installedApplications"][0]["installationURL"]
                 .as_str()
-                .ok_or("Failed to extract installation URL")?
+                .context("Failed to extract installation URL from xcrun output")?
                 .to_string();
 
             Ok(installation_url)
@@ -953,7 +964,7 @@ impl AppBuilder {
                 .await?;
 
             if !output.status.success() {
-                return Err(format!("Failed to launch app: {:?}", output).into());
+                bail!("Failed to launch app: {:?}", output);
             }
 
             let json: Value = serde_json::from_str(&std::fs::read_to_string(tmpfile.path())?)
@@ -961,7 +972,7 @@ impl AppBuilder {
 
             let status_pid = json["result"]["process"]["processIdentifier"]
                 .as_u64()
-                .ok_or("Failed to extract process identifier")?;
+                .context("Failed to extract process identifier")?;
 
             let output = Command::new("xcrun")
                 .args([
@@ -978,7 +989,7 @@ impl AppBuilder {
                 .await?;
 
             if !output.status.success() {
-                return Err(format!("Failed to resume app: {:?}", output).into());
+                bail!("Failed to resume app: {:?}", output);
             }
 
             Ok(())
@@ -1134,8 +1145,10 @@ We checked the folders:
             .context("Failed to codesign the app")?;
 
         if !output.status.success() {
-            let stderr = String::from_utf8(output.stderr).unwrap_or_default();
-            return Err(format!("Failed to codesign the app: {stderr}").into());
+            bail!(
+                "Failed to codesign the app: {}",
+                String::from_utf8(output.stderr).unwrap_or_default()
+            );
         }
 
         Ok(())

+ 39 - 33
packages/cli/src/build/request.rs

@@ -320,7 +320,7 @@ use crate::{
     RustcArgs, TargetArgs, TraceSrc, WasmBindgen, WasmOptConfig, Workspace,
     DX_RUSTC_WRAPPER_ENV_VAR,
 };
-use anyhow::Context;
+use anyhow::{bail, Context};
 use cargo_metadata::diagnostic::Diagnostic;
 use dioxus_cli_config::format_base_path_meta_element;
 use dioxus_cli_config::{APP_TITLE_ENV, ASSET_ROOT_ENV};
@@ -569,13 +569,10 @@ impl BuildRequest {
             },
             None if !using_dioxus_explicitly => Platform::autodetect_from_cargo_feature("desktop").unwrap(),
             None => match enabled_platforms.len() {
-                0 => return Err(anyhow::anyhow!("No platform specified and no platform marked as default in Cargo.toml. Try specifying a platform with `--platform`").into()),
+                0 => bail!("No platform specified and no platform marked as default in Cargo.toml. Try specifying a platform with `--platform`"),
                 1 => enabled_platforms[0],
                 _ => {
-                    return Err(anyhow::anyhow!(
-                        "Multiple platforms enabled in Cargo.toml. Please specify a platform with `--platform` or set a default platform in Cargo.toml"
-                    )
-                    .into())
+                    bail!("Multiple platforms enabled in Cargo.toml. Please specify a platform with `--platform` or set a default platform in Cargo.toml")
                 }
             },
         };
@@ -793,12 +790,24 @@ impl BuildRequest {
                 ctx.status_start_bundle();
 
                 self.write_executable(ctx, &artifacts.exe, &mut artifacts.assets)
-                    .await?;
-                self.write_frameworks(ctx, &artifacts.direct_rustc).await?;
-                self.write_assets(ctx, &artifacts.assets).await?;
-                self.write_metadata().await?;
-                self.optimize(ctx).await?;
-                self.assemble(ctx).await?;
+                    .await
+                    .context("Failed to write executable")?;
+
+                self.write_frameworks(ctx, &artifacts.direct_rustc)
+                    .await
+                    .context("Failed to write frameworks")?;
+                self.write_assets(ctx, &artifacts.assets)
+                    .await
+                    .context("Failed to write assets")?;
+                self.write_metadata()
+                    .await
+                    .context("Failed to write metadata")?;
+                self.optimize(ctx)
+                    .await
+                    .context("Failed to optimize build")?;
+                self.assemble(ctx)
+                    .await
+                    .context("Failed to assemble build")?;
 
                 tracing::debug!("Bundle created at {}", self.root_dir().display());
             }
@@ -916,10 +925,7 @@ impl BuildRequest {
                 //       since that is a really bad user experience.
                 Message::BuildFinished(finished) => {
                     if !finished.success {
-                        return Err(anyhow::anyhow!(
-                            "Cargo build failed, signaled by the compiler. Toggle tracing mode (press `t`) for more information."
-                        )
-                        .into());
+                        bail!("Cargo build failed, signaled by the compiler.")
                     }
                 }
                 _ => {}
@@ -1561,7 +1567,7 @@ impl BuildRequest {
             }
 
             LinkerFlavor::Unsupported => {
-                return Err(anyhow::anyhow!("Unsupported platform for thin linking").into())
+                bail!("Unsupported platform for thin linking")
             }
         }
 
@@ -2485,7 +2491,7 @@ impl BuildRequest {
             .await?;
 
         if !output.status.success() {
-            return Err(anyhow::anyhow!("Failed to get unit count").into());
+            bail!("Failed to get unit count");
         }
 
         let output_text = String::from_utf8(output.stdout).context("Failed to get unit count")?;
@@ -3297,10 +3303,7 @@ impl BuildRequest {
             ctx.status_splitting_bundle();
 
             if !will_wasm_opt {
-                return Err(anyhow::anyhow!(
-                    "Bundle splitting should automatically enable wasm-opt, but it was not enabled."
-                )
-                .into());
+                bail!("Bundle splitting should automatically enable wasm-opt, but it was not enabled.");
             }
 
             // Load the contents of these binaries since we need both of them
@@ -3555,7 +3558,7 @@ __wbg_init({{module_or_path: "/{}/{wasm_path}"}}).then((wasm) => {{
                     },
                 )
                 .map_err(|e| e.into()),
-            _ => Err(anyhow::anyhow!("Unsupported platform for Info.plist").into()),
+            _ => Err(anyhow::anyhow!("Unsupported platform for Info.plist")),
         }
     }
 
@@ -3580,8 +3583,10 @@ __wbg_init({{module_or_path: "/{}/{wasm_path}"}}).then((wasm) => {{
                 .await?;
 
             if !output.status.success() {
-                let err = String::from_utf8_lossy(&output.stderr);
-                return Err(anyhow::anyhow!("Failed to assemble apk: {err}").into());
+                bail!(
+                    "Failed to assemble apk: {}",
+                    String::from_utf8_lossy(&output.stderr)
+                );
             }
         }
 
@@ -3600,7 +3605,10 @@ __wbg_init({{module_or_path: "/{}/{wasm_path}"}}).then((wasm) => {{
             .context("Failed to run gradle bundleRelease")?;
 
         if !output.status.success() {
-            return Err(anyhow::anyhow!("Failed to bundleRelease: {output:?}").into());
+            bail!(
+                "Failed to bundleRelease: {}",
+                String::from_utf8_lossy(&output.stderr)
+            );
         }
 
         let app_release = self
@@ -3695,7 +3703,7 @@ __wbg_init({{module_or_path: "/{}/{wasm_path}"}}).then((wasm) => {{
         });
 
         if let Err(e) = success.as_ref() {
-            return Err(format!("Failed to initialize build directory: {e}").into());
+            bail!("Failed to initialize build directory: {e}");
         }
 
         Ok(())
@@ -3823,9 +3831,7 @@ __wbg_init({{module_or_path: "/{}/{wasm_path}"}}).then((wasm) => {{
 
         // Ensure target is installed.
         if !self.workspace.has_wasm32_unknown_unknown() {
-            return Err(Error::Other(anyhow::anyhow!(
-                "Missing target wasm32-unknown-unknown."
-            )));
+            bail!("Missing target wasm32-unknown-unknown.");
         }
 
         // Wasm bindgen
@@ -3891,9 +3897,9 @@ __wbg_init({{module_or_path: "/{}/{wasm_path}"}}).then((wasm) => {{
             return Ok(());
         }
 
-        Err(anyhow::anyhow!(
-            "Android linker not found. Please set the `ANDROID_NDK_HOME` environment variable to the root of your NDK installation."
-        ).into())
+        bail!(
+            "Android linker not found at {linker:?}. Please set the `ANDROID_NDK_HOME` environment variable to the root of your NDK installation."
+        );
     }
 
     /// Ensure the right dependencies are installed for linux apps.

+ 22 - 40
packages/cli/src/cli/autoformat.rs

@@ -1,6 +1,6 @@
 use super::{check::collect_rs_files, *};
 use crate::Workspace;
-use anyhow::Context;
+use anyhow::{bail, Context};
 use dioxus_autofmt::{IndentOptions, IndentType};
 use rayon::prelude::*;
 use std::{borrow::Cow, fs, path::Path};
@@ -54,11 +54,9 @@ impl Autoformat {
         } else if let Some(raw) = raw {
             // Format raw text.
             let indent = indentation_for(".", self.split_line_attributes)?;
-            if let Some(inner) = dioxus_autofmt::fmt_block(&raw, 0, indent) {
-                println!("{}", inner);
-            } else {
-                return Err("error formatting codeblock".into());
-            }
+            let formatted =
+                dioxus_autofmt::fmt_block(&raw, 0, indent).context("error formatting codeblock")?;
+            println!("{}", formatted);
         } else {
             // Default to formatting the project.
             let crate_dir = if let Some(package) = self.package {
@@ -76,11 +74,8 @@ impl Autoformat {
                 Cow::Borrowed(Path::new("."))
             };
 
-            if let Err(e) =
-                autoformat_project(check, split_line_attributes, format_rust_code, crate_dir)
-            {
-                return Err(format!("error formatting project: {}", e).into());
-            }
+            autoformat_project(check, split_line_attributes, format_rust_code, crate_dir)
+                .context("error autoformatting project")?;
         }
 
         Ok(StructuredOutput::Success)
@@ -100,19 +95,15 @@ fn refactor_file(
     } else {
         fs::read_to_string(&file)
     };
-    let Ok(mut s) = file_content else {
-        return Err(format!("failed to open file: {}", file_content.unwrap_err()).into());
-    };
+    let mut s = file_content.context("failed to open file")?;
 
     if format_rust_code {
         s = format_rust(&s)?;
     }
 
-    let Ok(Ok(edits)) =
-        syn::parse_file(&s).map(|file| dioxus_autofmt::try_fmt_file(&s, &file, indent))
-    else {
-        return Err(format!("failed to format file: {}", s).into());
-    };
+    let parsed = syn::parse_file(&s).context("failed to parse file")?;
+    let edits =
+        dioxus_autofmt::try_fmt_file(&s, &parsed, indent).context("failed to format file")?;
 
     let out = dioxus_autofmt::apply_formats(&s, edits);
 
@@ -135,18 +126,16 @@ fn format_file(
     let mut contents = fs::read_to_string(&path)?;
     let mut if_write = false;
     if format_rust_code {
-        let formatted = format_rust(&contents)
-            .map_err(|err| Error::Parse(format!("Syntax Error:\n{}", err)))?;
+        let formatted = format_rust(&contents).context("Syntax Error")?;
         if contents != formatted {
             if_write = true;
             contents = formatted;
         }
     }
 
-    let parsed = syn::parse_file(&contents)
-        .map_err(|err| Error::Parse(format!("Failed to parse file: {}", err)))?;
+    let parsed = syn::parse_file(&contents).context("Failed to parse file")?;
     let edits = dioxus_autofmt::try_fmt_file(&contents, &parsed, indent)
-        .map_err(|err| Error::Parse(format!("Failed to format file: {}", err)))?;
+        .context("Failed to format file")?;
     let len = edits.len();
 
     if !edits.is_empty() {
@@ -202,7 +191,7 @@ fn autoformat_project(
     let files_formatted: usize = counts.into_iter().flatten().sum();
 
     if files_formatted > 0 && check {
-        return Err(format!("{} files needed formatting", files_formatted).into());
+        bail!("{} files needed formatting", files_formatted);
     }
 
     Ok(())
@@ -220,9 +209,7 @@ fn indentation_for(
         .output()?;
 
     if !out.status.success() {
-        return Err(Error::Runtime(format!(
-            "cargo fmt failed with status: {out:?}"
-        )));
+        bail!("cargo fmt failed with status: {out:?}");
     }
 
     let config = String::from_utf8_lossy(&out.stdout);
@@ -232,18 +219,14 @@ fn indentation_for(
         .find(|line| line.starts_with("hard_tabs "))
         .and_then(|line| line.split_once('='))
         .map(|(_, value)| value.trim() == "true")
-        .ok_or_else(|| {
-            Error::Runtime("Could not find hard_tabs option in rustfmt config".into())
-        })?;
+        .context("Could not find hard_tabs option in rustfmt config")?;
     let tab_spaces = config
         .lines()
         .find(|line| line.starts_with("tab_spaces "))
         .and_then(|line| line.split_once('='))
         .map(|(_, value)| value.trim().parse::<usize>())
-        .ok_or_else(|| Error::Runtime("Could not find tab_spaces option in rustfmt config".into()))?
-        .map_err(|_| {
-            Error::Runtime("Could not parse tab_spaces option in rustfmt config".into())
-        })?;
+        .context("Could not find tab_spaces option in rustfmt config")?
+        .context("Could not parse tab_spaces option in rustfmt config")?;
 
     Ok(IndentOptions::new(
         if hard_tabs {
@@ -258,7 +241,9 @@ fn indentation_for(
 
 /// Format rust code using prettyplease
 fn format_rust(input: &str) -> Result<String> {
-    let syntax_tree = syn::parse_file(input).map_err(format_syn_error)?;
+    let syntax_tree = syn::parse_file(input)
+        .map_err(format_syn_error)
+        .context("Failed to parse Rust syntax")?;
     let output = prettyplease::unparse(&syntax_tree);
     Ok(output)
 }
@@ -267,10 +252,7 @@ fn format_syn_error(err: syn::Error) -> Error {
     let start = err.span().start();
     let line = start.line;
     let column = start.column;
-    Error::Parse(format!(
-        "Syntax Error in line {} column {}:\n{}",
-        line, column, err
-    ))
+    anyhow::anyhow!("Syntax Error in line {} column {}:\n{}", line, column, err)
 }
 
 #[tokio::test]

+ 2 - 2
packages/cli/src/cli/build.rs

@@ -70,7 +70,7 @@ impl CommandWithPlatformOverrides<BuildArgs> {
         let ssg = self.shared.ssg;
         let targets = self.into_targets().await?;
 
-        AppBuilder::start(&targets.client, BuildMode::Base)?
+        AppBuilder::started(&targets.client, BuildMode::Base)?
             .finish_build()
             .await?;
 
@@ -78,7 +78,7 @@ impl CommandWithPlatformOverrides<BuildArgs> {
 
         if let Some(server) = targets.server.as_ref() {
             // If the server is present, we need to build it as well
-            let mut server_build = AppBuilder::start(server, BuildMode::Base)?;
+            let mut server_build = AppBuilder::started(server, BuildMode::Base)?;
             server_build.finish_build().await?;
 
             // Run SSG and cache static routes

+ 5 - 5
packages/cli/src/cli/bundle.rs

@@ -1,5 +1,5 @@
 use crate::{AppBuilder, BuildArgs, BuildMode, BuildRequest, Platform};
-use anyhow::{anyhow, Context};
+use anyhow::{bail, Context};
 use path_absolutize::Absolutize;
 use std::collections::HashMap;
 use tauri_bundler::{BundleBinary, BundleSettings, PackageSettings, SettingsBuilder};
@@ -39,7 +39,7 @@ impl Bundle {
 
         let BuildTargets { client, server } = self.args.into_targets().await?;
 
-        AppBuilder::start(&client, BuildMode::Base)?
+        AppBuilder::started(&client, BuildMode::Base)?
             .finish_build()
             .await?;
 
@@ -47,7 +47,7 @@ impl Bundle {
 
         if let Some(server) = server.as_ref() {
             // If the server is present, we need to build it as well
-            AppBuilder::start(server, BuildMode::Base)?
+            AppBuilder::started(server, BuildMode::Base)?
                 .finish_build()
                 .await?;
 
@@ -167,10 +167,10 @@ impl Bundle {
 
         // Check if required fields are provided instead of failing silently.
         if bundle_settings.identifier.is_none() {
-            return Err(anyhow!("\n\nBundle identifier was not provided in `Dioxus.toml`. Add it as:\n\n[bundle]\nidentifier = \"com.mycompany\"\n\n").into());
+            bail!("\n\nBundle identifier was not provided in `Dioxus.toml`. Add it as:\n\n[bundle]\nidentifier = \"com.mycompany\"\n\n");
         }
         if bundle_settings.publisher.is_none() {
-            return Err(anyhow!("\n\nBundle publisher was not provided in `Dioxus.toml`. Add it as:\n\n[bundle]\npublisher = \"MyCompany\"\n\n").into());
+            bail!("\n\nBundle publisher was not provided in `Dioxus.toml`. Add it as:\n\n[bundle]\npublisher = \"MyCompany\"\n\n");
         }
 
         if cfg!(windows) {

+ 3 - 3
packages/cli/src/cli/check.rs

@@ -5,7 +5,7 @@
 
 use super::*;
 use crate::BuildRequest;
-use anyhow::Context;
+use anyhow::{anyhow, Context};
 use futures_util::{stream::FuturesUnordered, StreamExt};
 use std::path::Path;
 
@@ -118,8 +118,8 @@ async fn check_files_and_report(files_to_check: Vec<PathBuf>) -> Result<()> {
             tracing::info!("No issues found.");
             Ok(())
         }
-        1 => Err("1 issue found.".into()),
-        _ => Err(format!("{} issues found.", total_issues).into()),
+        1 => Err(anyhow!("1 issue found.")),
+        _ => Err(anyhow!("{} issues found.", total_issues)),
     }
 }
 

+ 3 - 1
packages/cli/src/cli/clean.rs

@@ -1,3 +1,5 @@
+use anyhow::bail;
+
 use super::*;
 use crate::Result;
 
@@ -16,7 +18,7 @@ impl Clean {
             .await?;
 
         if !output.status.success() {
-            return Err(anyhow::anyhow!("Cargo clean failed.").into());
+            bail!("Cargo clean failed.");
         }
 
         Ok(StructuredOutput::Success)

+ 7 - 9
packages/cli/src/cli/create.rs

@@ -1,5 +1,6 @@
 use super::*;
 use crate::TraceSrc;
+use anyhow::{bail, Context};
 use cargo_generate::{GenerateArgs, TemplatePath, Vcs};
 use std::path::Path;
 
@@ -147,9 +148,9 @@ pub(crate) fn name_from_path(path: &Path) -> Result<String> {
         .absolutize()?
         .to_path_buf()
         .file_name()
-        .ok_or("Current path does not include directory name".to_string())?
+        .context("Current path does not include directory name".to_string())?
         .to_str()
-        .ok_or("Current directory name is not a valid UTF-8 string".to_string())?
+        .context("Current directory name is not a valid UTF-8 string".to_string())?
         .to_string())
 }
 
@@ -167,10 +168,7 @@ pub(crate) fn post_create(path: &Path, vcs: &Vcs) -> Result<()> {
             // Only 1 error means that CWD isn't a cargo project.
             Err(cargo_metadata::Error::CargoMetadata { .. }) => None,
             Err(err) => {
-                return Err(Error::Other(anyhow::anyhow!(
-                    "Couldn't retrieve cargo metadata: {:?}",
-                    err
-                )));
+                anyhow::bail!("Couldn't retrieve cargo metadata: {:?}", err)
             }
         }
     };
@@ -286,9 +284,9 @@ pub(crate) async fn connectivity_check() -> Result<()> {
         }
     }
 
-    Err(Error::Network(
-        "Error connecting to template repository. Try cloning the template manually or add `dioxus` to a `cargo new` project.".to_string(),
-    ))
+    bail!(
+        "Error connecting to template repository. Try cloning the template manually or add `dioxus` to a `cargo new` project."
+    )
 }
 
 // todo: re-enable these tests with better parallelization

+ 8 - 11
packages/cli/src/cli/run.rs

@@ -1,8 +1,9 @@
 use super::*;
 use crate::{
     serve::{AppServer, ServeUpdate, WebServer},
-    BuilderUpdate, Error, Platform, Result,
+    BuilderUpdate, Platform, Result,
 };
+use anyhow::bail;
 use dioxus_dx_wire_format::BuildStage;
 
 /// Run the project with the given arguments
@@ -29,9 +30,11 @@ impl RunArgs {
         self.args.hot_reload = Some(false);
         self.args.watch = Some(false);
 
-        let mut builder = AppServer::start(self.args).await?;
+        let mut builder = AppServer::new(self.args).await?;
         let mut devserver = WebServer::start(&builder)?;
 
+        builder.initialize();
+
         loop {
             let msg = tokio::select! {
                 msg = builder.wait() => msg,
@@ -109,15 +112,11 @@ impl RunArgs {
                             }
                             BuildStage::Failed => {
                                 tracing::error!("[{platform}] Build failed");
-                                return Err(Error::Cargo(format!(
-                                    "Build failed for platform: {platform}"
-                                )));
+                                bail!("Build failed for platform: {platform}");
                             }
                             BuildStage::Aborted => {
                                 tracing::error!("[{platform}] Build aborted");
-                                return Err(Error::Cargo(format!(
-                                    "Build aborted for platform: {platform}"
-                                )));
+                                bail!("Build aborted for platform: {platform}");
                             }
                             _ => {}
                         },
@@ -139,9 +138,7 @@ impl RunArgs {
                                 tracing::error!(
                                     "Application [{platform}] exited with error: {status}"
                                 );
-                                return Err(Error::Runtime(format!(
-                                    "Application [{platform}] exited with error: {status}"
-                                )));
+                                bail!("Application [{platform}] exited with error: {status}");
                             }
 
                             break;

+ 1 - 1
packages/cli/src/cli/serve.rs

@@ -181,7 +181,7 @@ impl ServeArgs {
                     })
                     .unwrap_or_else(|| format!("dx serve panicked: {as_str}"));
 
-                Err(crate::error::Error::CapturedPanic(message))
+                Err(anyhow::anyhow!(message))
             }
         }
     }

+ 3 - 2
packages/cli/src/cli/translate.rs

@@ -1,5 +1,6 @@
 use super::*;
 use crate::{Result, StructuredOutput};
+use anyhow::bail;
 use dioxus_rsx::{BodyNode, CallBody, TemplateBody};
 
 /// Translate some source file into Dioxus code
@@ -108,7 +109,7 @@ fn determine_input(file: Option<String>, raw: Option<String>) -> Result<String>
 
     // Make sure not both are specified
     if file.is_some() && raw.is_some() {
-        return Err("Only one of --file or --raw should be specified.".into());
+        bail!("Only one of --file or --raw should be specified.");
     }
 
     if let Some(raw) = raw {
@@ -121,7 +122,7 @@ fn determine_input(file: Option<String>, raw: Option<String>) -> Result<String>
 
     // If neither exist, we try to read from stdin
     if std::io::stdin().is_terminal() {
-        return Err(anyhow::anyhow!("No input file, source, or stdin to translate from.").into());
+        bail!("No input file, source, or stdin to translate from.");
     }
 
     let mut buffer = String::new();

+ 5 - 8
packages/cli/src/cli/update.rs

@@ -1,6 +1,6 @@
 use super::*;
 use crate::{Result, Workspace};
-use anyhow::Context;
+use anyhow::{bail, Context};
 use itertools::Itertools;
 use self_update::cargo_crate_version;
 
@@ -93,7 +93,7 @@ impl SelfUpdate {
             } else if cfg!(target_arch = "aarch64") {
                 "aarch64"
             } else {
-                return Err(Error::Unique("Unsupported architecture".to_string()));
+                bail!("Unsupported architecture");
             };
 
             let cur_os = if cfg!(target_os = "windows") {
@@ -103,7 +103,7 @@ impl SelfUpdate {
             } else if cfg!(target_os = "macos") {
                 "darwin"
             } else {
-                return Err(Error::Unique("Unsupported OS".to_string()));
+                bail!("Unsupported OS");
             };
 
             let zip_ext = "zip";
@@ -118,7 +118,7 @@ impl SelfUpdate {
                         && a.name.contains(cur_arch)
                         && a.name.ends_with(zip_ext)
                 })
-                .ok_or_else(|| Error::Unique("No suitable release found found".to_string()))?;
+                .context("No suitable asset found")?;
 
             let install_dir = Workspace::dioxus_home_dir().join("self-update");
             std::fs::create_dir_all(&install_dir).context("Failed to create install directory")?;
@@ -161,10 +161,7 @@ impl SelfUpdate {
 
             let executable = install_dir.join("dx");
             if !executable.exists() {
-                return Err(Error::Unique(format!(
-                    "Executable not found in {}",
-                    install_dir.display()
-                )));
+                bail!("Executable not found in {}", install_dir.display());
             }
 
             tracing::info!(

+ 16 - 73
packages/cli/src/error.rs

@@ -1,77 +1,20 @@
-use std::fmt::Debug;
-use thiserror::Error as ThisError;
-
 pub(crate) type Result<T, E = Error> = std::result::Result<T, E>;
-
-#[derive(ThisError, Debug)]
-pub(crate) enum Error {
-    /// Used when errors need to propagate but are too unique to be typed
-    #[error("{0}")]
-    Unique(String),
-
-    #[error("I/O Error: {0}")]
-    IO(#[from] std::io::Error),
-
-    #[error("Format Error: {0}")]
-    Format(#[from] std::fmt::Error),
-
-    #[error("Format failed: {0}")]
-    Parse(String),
-
-    #[error("Runtime Error: {0}")]
-    Runtime(String),
-
-    #[error("Cargo Error: {0}")]
-    Cargo(String),
-
-    #[error("Invalid proxy URL: {0}")]
-    InvalidProxy(#[from] hyper::http::uri::InvalidUri),
-
-    #[error("Establishing proxy: {0}")]
-    ProxySetup(String),
-
-    #[error("Bundling project: {0}")]
-    BundleFailed(#[from] tauri_bundler::Error),
-
-    #[error("Performing hotpatch: {0}")]
-    PatchingFailed(#[from] crate::build::PatchError),
-
-    #[error("Reading object file: {0}")]
-    ObjectReadFailed(#[from] object::Error),
-
-    #[error("{0}")]
-    CapturedPanic(String),
-
-    #[error("Rendering template error: {0}")]
-    TemplateParse(#[from] handlebars::RenderError),
-
-    #[error("Network connectivity error: {0}")]
-    Network(String),
-
-    #[error(transparent)]
-    Other(#[from] anyhow::Error),
-}
-
-impl From<&str> for Error {
-    fn from(s: &str) -> Self {
-        Error::Unique(s.to_string())
+pub use anyhow::Error;
+
+pub fn log_stacktrace(err: &anyhow::Error) -> String {
+    let mut trace = format!(
+        "{ERROR_STYLE}{err}{ERROR_STYLE:#}",
+        ERROR_STYLE = crate::styles::ERROR_STYLE,
+    );
+
+    for (idx, cause) in err.chain().enumerate().skip(1) {
+        trace.push_str(&format!(
+            "\n- {IDX_STYLE}{idx}{IDX_STYLE:#}: {ERROR_STYLE}{}{ERROR_STYLE:#}",
+            cause,
+            IDX_STYLE = crate::styles::GLOW_STYLE,
+            ERROR_STYLE = crate::styles::ERROR_STYLE
+        ));
     }
-}
 
-impl From<String> for Error {
-    fn from(s: String) -> Self {
-        Error::Unique(s)
-    }
-}
-
-impl From<html_parser::Error> for Error {
-    fn from(e: html_parser::Error) -> Self {
-        Self::Parse(e.to_string())
-    }
-}
-
-impl From<hyper::Error> for Error {
-    fn from(e: hyper::Error) -> Self {
-        Self::Runtime(e.to_string())
-    }
+    trace
 }

+ 2 - 5
packages/cli/src/main.rs

@@ -71,17 +71,14 @@ async fn main() {
             tracing::debug!(json = ?output);
         }
         Err(err) => {
-            eprintln!(
-                "{ERROR_STYLE}failed:{ERROR_STYLE:#} {err}",
-                ERROR_STYLE = styles::ERROR_STYLE
-            );
-
             tracing::error!(
                 json = ?StructuredOutput::Error {
                     message: format!("{err:?}"),
                 },
             );
 
+            eprintln!("{}", crate::error::log_stacktrace(&err));
+
             std::process::exit(1);
         }
     };

+ 17 - 16
packages/cli/src/serve/mod.rs

@@ -1,7 +1,6 @@
 use crate::{
     styles::{GLOW_STYLE, LINK_STYLE},
-    AppBuilder, BuildId, BuildMode, BuilderUpdate, Error, Platform, Result, ServeArgs,
-    TraceController,
+    AppBuilder, BuildId, BuildMode, BuilderUpdate, Platform, Result, ServeArgs, TraceController,
 };
 
 mod ansi_buffer;
@@ -12,6 +11,7 @@ mod runner;
 mod server;
 mod update;
 
+use anyhow::bail;
 use dioxus_dx_wire_format::BuildStage;
 pub(crate) use output::*;
 pub(crate) use runner::*;
@@ -41,7 +41,7 @@ pub(crate) use update::*;
 pub(crate) async fn serve_all(args: ServeArgs, tracer: &mut TraceController) -> Result<()> {
     // Load the args into a plan, resolving all tooling, build dirs, arguments, decoding the multi-target, etc
     let exit_on_error = args.exit_on_error;
-    let mut builder = AppServer::start(args).await?;
+    let mut builder = AppServer::new(args).await?;
     let mut devserver = WebServer::start(&builder)?;
     let mut screen = Output::start(builder.interactive).await?;
 
@@ -66,6 +66,8 @@ pub(crate) async fn serve_all(args: ServeArgs, tracer: &mut TraceController) ->
         }
     );
 
+    builder.initialize();
+
     loop {
         // Draw the state of the server to the screen
         screen.render(&builder, &devserver);
@@ -145,18 +147,14 @@ pub(crate) async fn serve_all(args: ServeArgs, tracer: &mut TraceController) ->
                         stage: BuildStage::Failed,
                     } => {
                         if exit_on_error {
-                            return Err(Error::Cargo(format!(
-                                "Build failed for platform: {platform}"
-                            )));
+                            bail!("Build failed for platform: {platform}");
                         }
                     }
                     BuilderUpdate::Progress {
                         stage: BuildStage::Aborted,
                     } => {
                         if exit_on_error {
-                            return Err(Error::Cargo(format!(
-                                "Build aborted for platform: {platform}"
-                            )));
+                            bail!("Build aborted for platform: {platform}");
                         }
                     }
                     BuilderUpdate::Progress { .. } => {}
@@ -164,7 +162,7 @@ pub(crate) async fn serve_all(args: ServeArgs, tracer: &mut TraceController) ->
                         screen.push_cargo_log(message);
                     }
                     BuilderUpdate::BuildFailed { err } => {
-                        tracing::error!("Build failed: {}", err);
+                        tracing::error!("Build failed: {}", crate::error::log_stacktrace(&err));
                         if exit_on_error {
                             return Err(err);
                         }
@@ -184,7 +182,9 @@ pub(crate) async fn serve_all(args: ServeArgs, tracer: &mut TraceController) ->
                                 Err(err) => {
                                     tracing::error!("Failed to hot-patch app: {err}");
 
-                                    if matches!(err, crate::Error::PatchingFailed(_)) {
+                                    if let Some(_patching) =
+                                        err.downcast_ref::<crate::build::PatchError>()
+                                    {
                                         tracing::info!("Starting full rebuild: {err}");
                                         builder.full_rebuild().await;
                                         devserver.send_reload_start().await;
@@ -216,9 +216,7 @@ pub(crate) async fn serve_all(args: ServeArgs, tracer: &mut TraceController) ->
                         } else {
                             tracing::error!("Application [{platform}] exited with error: {status}");
                             if exit_on_error {
-                                return Err(Error::Runtime(format!(
-                                    "Application [{platform}] exited with error: {status}"
-                                )));
+                                bail!("Application [{platform}] exited with error: {status}");
                             }
                         }
                     }
@@ -248,7 +246,10 @@ pub(crate) async fn serve_all(args: ServeArgs, tracer: &mut TraceController) ->
                 }
                 _ => {
                     if let Err(err) = builder.open_all(&devserver, true).await {
-                        tracing::error!("Failed to open app: {err}")
+                        tracing::error!(
+                            "Failed to open app: {}",
+                            crate::error::log_stacktrace(&err)
+                        )
                     }
                 }
             },
@@ -278,7 +279,7 @@ pub(crate) async fn serve_all(args: ServeArgs, tracer: &mut TraceController) ->
                 _ = devserver.shutdown().await;
 
                 match error {
-                    Some(err) => return Err(anyhow::anyhow!("{}", err).into()),
+                    Some(err) => return Err(err),
                     None => return Ok(()),
                 }
             }

+ 1 - 1
packages/cli/src/serve/output.rs

@@ -204,7 +204,7 @@ impl Output {
                 Ok(Some(update)) => return update,
                 Err(ee) => {
                     return ServeUpdate::Exit {
-                        error: Some(Box::new(ee)),
+                        error: Some(anyhow::anyhow!(ee)),
                     }
                 }
                 Ok(None) => {}

+ 4 - 21
packages/cli/src/serve/proxy.rs

@@ -2,7 +2,7 @@ use crate::config::WebProxyConfig;
 use crate::TraceSrc;
 use crate::{Error, Result};
 
-use anyhow::{anyhow, Context};
+use anyhow::{bail, Context};
 use axum::body::Body;
 use axum::http::request::Parts;
 use axum::{body::Body as MyBody, response::IntoResponse};
@@ -47,7 +47,7 @@ impl ProxyClient {
         self.inner
             .request(req)
             .await
-            .map_err(|err| crate::error::Error::Other(anyhow!(err)))
+            .context("Failed to send proxy request")
     }
 }
 
@@ -64,11 +64,11 @@ pub(crate) fn add_proxy(mut router: Router, proxy: &WebProxyConfig) -> Result<Ro
     let trimmed_path = path.trim_start_matches('/');
 
     if trimmed_path.is_empty() {
-        return Err(crate::Error::ProxySetup(format!(
+        bail!(
             "Proxy backend URL must have a non-empty path, e.g. {}/api instead of {}",
             proxy.backend.trim_end_matches('/'),
             proxy.backend
-        )));
+        );
     }
 
     let method_router = proxy_to(url, false, handle_proxy_error);
@@ -268,21 +268,4 @@ mod test {
     async fn add_proxy_trailing_slash() {
         test_proxy_requests("/api/".to_string()).await;
     }
-
-    #[test]
-    fn add_proxy_empty_path() {
-        let config = WebProxyConfig {
-            backend: "http://localhost:8000".to_string(),
-        };
-        let router = super::add_proxy(Router::new(), &config);
-        match router.unwrap_err() {
-            crate::Error::ProxySetup(e) => {
-                assert_eq!(
-                    e,
-                    "Proxy backend URL must have a non-empty path, e.g. http://localhost:8000/api instead of http://localhost:8000"
-                );
-            }
-            e => panic!("Unexpected error type: {}", e),
-        }
-    }
 }

+ 20 - 14
packages/cli/src/serve/runner.rs

@@ -1,10 +1,10 @@
 use super::{AppBuilder, ServeUpdate, WebServer};
 use crate::{
     platform_override::CommandWithPlatformOverrides, BuildArtifacts, BuildId, BuildMode,
-    BuildTargets, BuilderUpdate, Error, HotpatchModuleCache, Platform, Result, ServeArgs,
-    TailwindCli, TraceSrc, Workspace,
+    BuildTargets, BuilderUpdate, HotpatchModuleCache, Platform, Result, ServeArgs, TailwindCli,
+    TraceSrc, Workspace,
 };
-use anyhow::Context;
+use anyhow::{bail, Context};
 use dioxus_core::internal::{
     HotReloadTemplateWithLocation, HotReloadedTemplate, TemplateGlobalKey,
 };
@@ -92,7 +92,7 @@ pub(crate) struct CachedFile {
 
 impl AppServer {
     /// Create the AppRunner and then initialize the filemap with the crate directory.
-    pub(crate) async fn start(args: ServeArgs) -> Result<Self> {
+    pub(crate) async fn new(args: ServeArgs) -> Result<Self> {
         let workspace = Workspace::current().await?;
 
         // Resolve the simpler args
@@ -115,7 +115,7 @@ impl AppServer {
 
         let open_browser = args
             .open
-            .unwrap_or_else(|| workspace.settings.always_open_browser.unwrap_or_default());
+            .unwrap_or_else(|| workspace.settings.always_open_browser.unwrap_or(true));
 
         let wsl_file_poll_interval = args
             .wsl_file_poll_interval
@@ -161,15 +161,9 @@ impl AppServer {
 
         let watch_fs = args.watch.unwrap_or(true);
         let use_hotpatch_engine = args.hot_patch;
-        let build_mode = match use_hotpatch_engine {
-            true => BuildMode::Fat,
-            false => BuildMode::Base,
-        };
 
-        let client = AppBuilder::start(&client, build_mode.clone())?;
-        let server = server
-            .map(|server| AppBuilder::start(&server, build_mode))
-            .transpose()?;
+        let client = AppBuilder::new(&client)?;
+        let server = server.map(|server| AppBuilder::new(&server)).transpose()?;
 
         let tw_watcher = TailwindCli::serve(
             client.build.package_manifest_dir(),
@@ -229,6 +223,18 @@ impl AppServer {
         Ok(runner)
     }
 
+    pub(crate) fn initialize(&mut self) {
+        let build_mode = match self.use_hotpatch_engine {
+            true => BuildMode::Fat,
+            false => BuildMode::Base,
+        };
+
+        self.client.start(build_mode.clone());
+        if let Some(server) = self.server.as_mut() {
+            server.start(build_mode);
+        }
+    }
+
     pub(crate) async fn rebuild_ssg(&mut self, devserver: &WebServer) {
         if self.client.stage != BuildStage::Success {
             return;
@@ -685,7 +691,7 @@ impl AppServer {
                     .hotpatch(res, cache)
                     .await
             }
-            _ => return Err(Error::Runtime("Invalid build id".into())),
+            _ => bail!("Invalid build id"),
         }?;
 
         if id == BuildId::CLIENT {

+ 3 - 4
packages/cli/src/serve/server.rs

@@ -2,7 +2,7 @@ use crate::{
     config::WebHttpsConfig, serve::ServeUpdate, BuildId, BuildStage, BuilderUpdate, Platform,
     Result, TraceSrc,
 };
-use anyhow::Context;
+use anyhow::{bail, Context};
 use axum::{
     body::Body,
     extract::{
@@ -635,8 +635,7 @@ async fn get_rustls(web_config: &WebHttpsConfig) -> Result<(String, String)> {
         {
             return Ok((cert, key));
         } else {
-            // missing cert or key
-            return Err("https is enabled but cert or key path is missing".into());
+            bail!("https is enabled but cert or key path is missing");
         }
     }
 
@@ -682,7 +681,7 @@ async fn get_rustls(web_config: &WebHttpsConfig) -> Result<(String, String)> {
                     tracing::error!(dx_src = ?TraceSrc::Dev, "An error occurred while generating mkcert certificates: {}", e.to_string())
                 }
             };
-            return Err("failed to generate mkcert certificates".into());
+            bail!("failed to generate mkcert certificates");
         }
         Ok(mut cmd) => {
             cmd.wait().await?;

+ 2 - 2
packages/cli/src/serve/update.rs

@@ -1,4 +1,4 @@
-use crate::{BuildId, BuilderUpdate, Platform, TraceMsg};
+use crate::{BuildId, BuilderUpdate, Error, Platform, TraceMsg};
 use axum::extract::ws::Message as WsMessage;
 use std::path::PathBuf;
 
@@ -44,6 +44,6 @@ pub(crate) enum ServeUpdate {
     },
 
     Exit {
-        error: Option<Box<dyn std::error::Error + Send + Sync>>,
+        error: Option<Error>,
     },
 }

+ 3 - 4
packages/cli/src/settings.rs

@@ -1,4 +1,5 @@
 use crate::{Result, TraceSrc};
+use anyhow::bail;
 use serde::{Deserialize, Serialize};
 use std::sync::LazyLock;
 use std::{fs, path::PathBuf, sync::Arc};
@@ -89,16 +90,14 @@ impl CliSettings {
                 ?path,
                 "failed to create directories for settings file"
             );
-            return Err(
-                anyhow::anyhow!("failed to create directories for settings file: {e}").into(),
-            );
+            bail!("failed to create directories for settings file: {e}");
         }
 
         // Write the data.
         let result = fs::write(&path, data.clone());
         if let Err(e) = result {
             error!(?data, ?path, "failed to save global cli settings");
-            return Err(anyhow::anyhow!("failed to save global cli settings: {e}").into());
+            bail!("failed to save global cli settings: {e}");
         }
 
         Ok(())

+ 11 - 12
packages/cli/src/wasm_opt.rs

@@ -1,6 +1,6 @@
 use crate::config::WasmOptLevel;
 use crate::{CliSettings, Result, WasmOptConfig, Workspace};
-use anyhow::{anyhow, Context};
+use anyhow::{anyhow, bail, Context};
 use flate2::read::GzDecoder;
 use std::path::{Path, PathBuf};
 use tar::Archive;
@@ -16,8 +16,11 @@ pub async fn write_wasm(bytes: &[u8], output_path: &Path, cfg: &WasmOptConfig) -
 pub async fn optimize(input_path: &Path, output_path: &Path, cfg: &WasmOptConfig) -> Result<()> {
     let wasm_opt = WasmOpt::new(input_path, output_path, cfg)
         .await
-        .inspect_err(|err| tracing::error!("Failed to create wasm-opt instance: {}", err))?;
-    wasm_opt.optimize().await?;
+        .context("Failed to create wasm-opt instance")?;
+    wasm_opt
+        .optimize()
+        .await
+        .context("Failed to run wasm-opt")?;
 
     Ok(())
 }
@@ -170,9 +173,7 @@ async fn find_latest_wasm_opt_download_url() -> anyhow::Result<String> {
     } else if cfg!(all(target_os = "macos", target_arch = "aarch64")) {
         "arm64-macos"
     } else {
-        return Err(anyhow::anyhow!(
-            "Unknown platform for wasm-opt installation. Please install wasm-opt manually from https://github.com/WebAssembly/binaryen/releases and add it to your PATH."
-        ));
+        bail!("Unknown platform for wasm-opt installation. Please install wasm-opt manually from https://github.com/WebAssembly/binaryen/releases and add it to your PATH.");
     };
 
     // Find the first asset with a name that contains the platform string
@@ -184,12 +185,10 @@ async fn find_latest_wasm_opt_download_url() -> anyhow::Result<String> {
                 .and_then(|name| name.as_str())
                 .is_some_and(|name| name.contains(platform) && !name.ends_with("sha256"))
         })
-        .ok_or_else(|| {
-            anyhow::anyhow!(
-                "No suitable wasm-opt binary found for platform: {}. Please install wasm-opt manually from https://github.com/WebAssembly/binaryen/releases and add it to your PATH.",
-                platform
-            )
-        })?;
+        .with_context(|| anyhow!(
+            "No suitable wasm-opt binary found for platform: {}. Please install wasm-opt manually from https://github.com/WebAssembly/binaryen/releases and add it to your PATH.",
+            platform
+        ))?;
 
     // Extract the download URL from the asset
     let download_url = asset

+ 8 - 14
packages/cli/src/workspace.rs

@@ -2,7 +2,7 @@ use crate::styles::GLOW_STYLE;
 use crate::CliSettings;
 use crate::Result;
 use crate::{config::DioxusConfig, AndroidTools};
-use anyhow::Context;
+use anyhow::{bail, Context};
 use ignore::gitignore::Gitignore;
 use krates::{semver::Version, KrateDetails, LockOptions};
 use krates::{Cmd, Krates, NodeId};
@@ -77,9 +77,7 @@ impl Workspace {
 
         let krates = tokio::select! {
             f = krates_future => f.context("failed to run cargo metadata")??,
-            _ = spin_future => return Err(crate::Error::Network(
-                "cargo metadata took too long to respond, try again with --offline".to_string(),
-            )),
+            _ = spin_future => bail!("cargo metadata took too long to respond, try again with --offline"),
         };
 
         let settings = CliSettings::global_or_default();
@@ -141,10 +139,10 @@ impl Workspace {
     }
 
     pub fn android_tools(&self) -> Result<Arc<AndroidTools>> {
-        Ok(self
+        self
             .android_tools
             .clone()
-            .context("Android not installed properly. Please set the `ANDROID_NDK_HOME` environment variable to the root of your NDK installation.")?)
+            .context("Android not installed properly. Please set the `ANDROID_NDK_HOME` environment variable to the root of your NDK installation.")
     }
 
     pub fn is_release_profile(&self, profile: &str) -> bool {
@@ -377,7 +375,7 @@ impl Workspace {
 
         toml::from_str::<DioxusConfig>(&std::fs::read_to_string(&dioxus_conf_file)?)
             .map_err(|err| {
-                anyhow::anyhow!("Failed to parse Dioxus.toml at {dioxus_conf_file:?}: {err}").into()
+                anyhow::anyhow!("Failed to parse Dioxus.toml at {dioxus_conf_file:?}: {err}")
             })
             .map(Some)
     }
@@ -459,17 +457,13 @@ impl Workspace {
                 }
                 None
             })
-            .ok_or_else(|| {
-                crate::Error::Cargo("Failed to find directory containing Cargo.toml".to_string())
-            })
+            .context("Failed to find directory containing Cargo.toml")
     }
 
     /// Returns the properly canonicalized path to the dx executable, used for linking and wrapping rustc
     pub(crate) fn path_to_dx() -> Result<PathBuf> {
-        Ok(
-            dunce::canonicalize(std::env::current_exe().context("Failed to find dx")?)
-                .context("Failed to find dx")?,
-        )
+        dunce::canonicalize(std::env::current_exe().context("Failed to find dx")?)
+            .context("Failed to find dx")
     }
 
     /// Returns the path to the dioxus home directory, used to install tools and other things