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