Skip to main content

rustferry_android/
error.rs

1use std::io;
2
3use camino::Utf8PathBuf;
4use thiserror::Error;
5
6/// Failure while discovering, building, signing, or validating an Android artifact.
7#[derive(Debug, Error)]
8pub enum AndroidError {
9    /// Project icon or splash asset validation failed.
10    #[error(transparent)]
11    Assets(#[from] rustferry_core::AssetError),
12    /// Deterministic platform asset generation or cache validation failed.
13    #[error(transparent)]
14    AssetPipeline(#[from] rustferry_codegen::AssetPipelineError),
15    /// A request value cannot safely be used by the build pipeline.
16    #[error("invalid Android build request: {0}")]
17    InvalidRequest(String),
18    /// A filesystem path cannot safely cross an external tool process boundary.
19    #[error("invalid external tool path: {reason}")]
20    InvalidExternalToolPath {
21        /// Secret-free reason for rejecting the path.
22        reason: &'static str,
23    },
24    /// `ferry.toml` failed semantic validation.
25    #[error("invalid RustFerry configuration: {0}")]
26    InvalidConfig(String),
27    /// A required tool or SDK component was not found.
28    #[error("Android tool `{tool}` was not found; searched: {searched:?}. {fix}")]
29    ToolMissing {
30        /// Human-readable tool name.
31        tool: String,
32        /// Paths inspected during discovery.
33        searched: Vec<Utf8PathBuf>,
34        /// Concrete remediation.
35        fix: String,
36    },
37    /// No suitable Android platform is installed.
38    #[error(
39        "Android platform `{requested}` was not found; installed API levels: {installed:?}. {fix}"
40    )]
41    PlatformMissing {
42        /// Requested platform selector.
43        requested: String,
44        /// Installed API levels.
45        installed: Vec<u32>,
46        /// Concrete remediation.
47        fix: String,
48    },
49    /// No complete Android SDK Build Tools installation is available.
50    #[error("complete Android SDK Build Tools were not found; searched: {searched:?}. {fix}")]
51    BuildToolsMissing {
52        /// Build Tools directories inspected.
53        searched: Vec<Utf8PathBuf>,
54        /// Concrete remediation.
55        fix: String,
56    },
57    /// The selected NDK has no linker for an ABI.
58    #[error("Android NDK linker for `{target}` was not found; searched: {searched:?}. {fix}")]
59    NdkLinkerMissing {
60        /// Rust target triple.
61        target: String,
62        /// Candidate linker paths.
63        searched: Vec<Utf8PathBuf>,
64        /// Concrete remediation.
65        fix: String,
66    },
67    /// Cargo completed without reporting a matching native library.
68    #[error("Cargo did not produce a cdylib for `{target}`; inspected artifacts: {searched:?}")]
69    NativeLibraryMissing {
70        /// Rust target triple.
71        target: String,
72        /// Artifact paths reported by Cargo.
73        searched: Vec<Utf8PathBuf>,
74    },
75    /// A build requires JVM bytecode but no dependency or bridge DEX was emitted.
76    #[error(
77        "Android DEX is required, but no .dex file was found in Cargo build-script OUT_DIRs: {searched:?}. Ensure the Android backend feature is enabled, then rebuild with Cargo JSON messages."
78    )]
79    MissingDex {
80        /// Build-script output directories searched recursively.
81        searched: Vec<Utf8PathBuf>,
82    },
83    /// A manifest component has no matching class in the merged DEX.
84    #[error(
85        "Android component `{class_name}` is enabled, but its class was not found in merged DEX files: {searched:?}. Add the generated or prebuilt bridge input that implements this component."
86    )]
87    MissingDexClass {
88        /// Fully qualified JVM class name required by generated manifest metadata.
89        class_name: String,
90        /// Merged DEX files inspected.
91        searched: Vec<Utf8PathBuf>,
92    },
93    /// Cargo emitted malformed or unusable JSON output.
94    #[error("could not interpret Cargo JSON output: {0}")]
95    CargoOutput(String),
96    /// An external command could not be started.
97    #[error("could not start `{program}` during {stage}: {source}")]
98    CommandSpawn {
99        /// Build stage.
100        stage: String,
101        /// Executable path.
102        program: Utf8PathBuf,
103        /// Operating-system error.
104        #[source]
105        source: io::Error,
106    },
107    /// An external command exceeded its deadline.
108    #[error("`{program}` timed out during {stage}; log: {log:?}")]
109    CommandTimedOut {
110        /// Build stage.
111        stage: String,
112        /// Executable path.
113        program: Utf8PathBuf,
114        /// Diagnostic log, when requested.
115        log: Option<Utf8PathBuf>,
116    },
117    /// An external command exceeded the per-stream output memory limit.
118    #[error(
119        "`{program}` produced more than {limit_bytes} bytes on {stream} during {stage}; its process tree was stopped; log: {log:?}"
120    )]
121    ProcessOutputTooLarge {
122        /// Build stage.
123        stage: String,
124        /// Executable path.
125        program: Utf8PathBuf,
126        /// Stream that crossed the limit.
127        stream: String,
128        /// Maximum retained bytes for each stream.
129        limit_bytes: usize,
130        /// Diagnostic log containing only the retained prefixes.
131        log: Option<Utf8PathBuf>,
132    },
133    /// Ctrl+C interrupted an external build tool.
134    #[error("`{program}` was interrupted during {stage}")]
135    CommandInterrupted {
136        /// Build stage.
137        stage: String,
138        /// Executable path.
139        program: Utf8PathBuf,
140    },
141    /// An external tool returned a non-zero status.
142    #[error("`{program}` failed during {stage} with status {status}; {summary}; log: {log:?}")]
143    CommandFailed {
144        /// Build stage.
145        stage: String,
146        /// Executable path.
147        program: Utf8PathBuf,
148        /// Process status code or signal description.
149        status: String,
150        /// Short stderr/stdout excerpt.
151        summary: Box<String>,
152        /// Diagnostic log, when requested.
153        log: Option<Utf8PathBuf>,
154    },
155    /// A filesystem operation failed.
156    #[error("could not {operation} `{path}`: {source}")]
157    Io {
158        /// Operation being attempted.
159        operation: &'static str,
160        /// Affected path.
161        path: Utf8PathBuf,
162        /// Operating-system error.
163        #[source]
164        source: io::Error,
165    },
166    /// ZIP parsing or writing failed.
167    #[error("APK ZIP operation failed for `{path}`: {message}")]
168    Zip {
169        /// APK path.
170        path: Utf8PathBuf,
171        /// Archive error without an unhelpful generic wrapper.
172        message: String,
173    },
174    /// Independent artifact validation rejected the APK.
175    #[error("APK validation failed for `{path}`: {reason}")]
176    InvalidArtifact {
177        /// APK path.
178        path: Utf8PathBuf,
179        /// Exact failed invariant.
180        reason: String,
181    },
182    /// A platform path could not be represented by the UTF-8 API.
183    #[error("path is not valid UTF-8: {0:?}")]
184    NonUtf8Path(std::path::PathBuf),
185}
186
187pub(crate) fn io_error(
188    operation: &'static str,
189    path: impl Into<Utf8PathBuf>,
190    source: io::Error,
191) -> AndroidError {
192    AndroidError::Io {
193        operation,
194        path: path.into(),
195        source,
196    }
197}