waterui_cli/android/
backend.rs1use std::path::{Path, PathBuf};
2
3use serde::{Deserialize, Serialize};
4
5use crate::{
6 android::platform::{AndroidAbi, AndroidPlatform, clean_android, is_android_platform},
7 backend::Backend,
8 build::BuildOptions,
9 device::Artifact,
10 platform::{PackageOptions, TargetBackend, TargetPlatform},
11 project::Project,
12 templates::{self, TemplateContext},
13};
14
15#[derive(Debug, Serialize, Deserialize, Clone)]
19pub struct AndroidBackend {
20 #[serde(
21 default = "default_android_project_path",
22 skip_serializing_if = "is_default_android_project_path"
23 )]
24 project_path: PathBuf,
25 #[serde(skip_serializing_if = "Option::is_none")]
26 version: Option<String>,
27}
28
29impl AndroidBackend {
30 #[must_use]
32 pub fn new() -> Self {
33 Self {
34 project_path: default_android_project_path(),
35 version: None,
36 }
37 }
38
39 #[must_use]
41 pub fn with_project_path(mut self, path: impl Into<PathBuf>) -> Self {
42 self.project_path = path.into();
43 self
44 }
45
46 #[must_use]
48 pub const fn project_path(&self) -> &PathBuf {
49 &self.project_path
50 }
51
52 #[must_use]
54 pub fn gradlew_path(&self) -> PathBuf {
55 let base = &self.project_path;
56 if cfg!(windows) {
57 base.join("gradlew.bat")
58 } else {
59 base.join("gradlew")
60 }
61 }
62}
63
64impl Default for AndroidBackend {
65 fn default() -> Self {
66 Self::new()
67 }
68}
69
70impl Backend for AndroidBackend {
71 const DEFAULT_PATH: &'static str = "android";
72
73 const CACHE_PATHS: &'static [&'static str] = &[".gradle", "build", "app"];
75
76 fn path(&self) -> &Path {
77 &self.project_path
78 }
79
80 async fn init(project: &Project) -> Result<Self, crate::backend::FailToInitBackend> {
81 let manifest = project.manifest();
82
83 let app_name = manifest
85 .package
86 .name
87 .chars()
88 .filter(|c| c.is_alphanumeric())
89 .collect::<String>();
90
91 let project_path = default_android_project_path();
92
93 match crate::assets::scan_required_permissions(project).await {
96 Ok(required) => crate::assets::warn_missing_permissions(project, &required, |key| {
97 key.android_permission_name().is_some()
98 }),
99 Err(error) => tracing::debug!("skipped permission audit: {error}"),
100 }
101
102 let android_permissions = manifest
104 .permissions
105 .iter()
106 .filter(|(_, entry)| entry.is_enabled())
107 .filter_map(|(key, _)| {
108 key.android_permission_name()
109 .map(|name| templates::AndroidPermissionTemplateEntry { name })
110 })
111 .collect();
112
113 let ctx = TemplateContext::for_project_manifest(
114 manifest,
115 project.crate_name().clone(),
116 app_name,
117 &project
118 .resolved_framework()
119 .await
120 .map_err(crate::backend::FailToInitBackend::Config)?,
121 )
122 .with_backend_project_path(project.backend_path::<Self>())
123 .with_project_root_path(project.root().to_path_buf())
124 .with_android_permissions(android_permissions);
125
126 templates::android::scaffold(&project.backend_path::<Self>(), &ctx)
127 .await
128 .map_err(crate::backend::FailToInitBackend::Io)?;
129
130 Ok(Self {
131 project_path,
132 version: None,
133 })
134 }
135
136 fn supports(&self, platform: TargetPlatform) -> bool {
137 is_android_platform(platform)
138 }
139
140 async fn build(
141 &self,
142 project: &Project,
143 platform: TargetPlatform,
144 options: BuildOptions,
145 ) -> eyre::Result<PathBuf> {
146 debug_assert_eq!(platform, TargetPlatform::Android);
147 project
148 .browser_runtime_plan(platform, TargetBackend::Android)
149 .await?;
150 AndroidPlatform::arm64().build(project, options).await
151 }
152
153 async fn package(
154 &self,
155 project: &Project,
156 platform: TargetPlatform,
157 options: PackageOptions,
158 ) -> eyre::Result<Artifact> {
159 debug_assert_eq!(platform, TargetPlatform::Android);
160 AndroidPlatform::package_with_abis(project, options, &[AndroidAbi::Arm64V8a]).await
161 }
162
163 async fn clean(&self, project: &Project, _platform: TargetPlatform) -> eyre::Result<()> {
164 clean_android(project).await
165 }
166}
167
168fn default_android_project_path() -> PathBuf {
169 PathBuf::from("android")
170}
171
172fn is_default_android_project_path(s: &Path) -> bool {
173 s == Path::new("android")
174}