project.rs 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221
  1. // Copyright 2019-2024 Tauri Programme within The Commons Conservancy
  2. // SPDX-License-Identifier: Apache-2.0
  3. // SPDX-License-Identifier: MIT
  4. use crate::{helpers::template, Result};
  5. use anyhow::Context;
  6. use cargo_mobile2::{
  7. android::{
  8. config::{Config, Metadata},
  9. target::Target,
  10. },
  11. config::app::DEFAULT_ASSET_DIR,
  12. os,
  13. target::TargetTrait as _,
  14. util::{
  15. self,
  16. cli::{Report, TextWrapper},
  17. prefix_path,
  18. },
  19. };
  20. use handlebars::Handlebars;
  21. use include_dir::{include_dir, Dir};
  22. use std::{
  23. ffi::OsStr,
  24. fs,
  25. path::{Path, PathBuf},
  26. };
  27. const TEMPLATE_DIR: Dir<'_> = include_dir!("$CARGO_MANIFEST_DIR/templates/mobile/android");
  28. pub fn gen(
  29. config: &Config,
  30. metadata: &Metadata,
  31. (handlebars, mut map): (Handlebars, template::JsonMap),
  32. wrapper: &TextWrapper,
  33. skip_targets_install: bool,
  34. ) -> Result<()> {
  35. if !skip_targets_install {
  36. let installed_targets =
  37. crate::interface::rust::installation::installed_targets().unwrap_or_default();
  38. let missing_targets = Target::all()
  39. .values()
  40. .filter(|t| !installed_targets.contains(&t.triple().into()))
  41. .collect::<Vec<&Target>>();
  42. if !missing_targets.is_empty() {
  43. println!("Installing Android Rust toolchains...");
  44. for target in missing_targets {
  45. target
  46. .install()
  47. .context("failed to install target with rustup")?;
  48. }
  49. }
  50. }
  51. println!("Generating Android Studio project...");
  52. let dest = config.project_dir();
  53. let asset_packs = metadata.asset_packs().unwrap_or_default();
  54. map.insert(
  55. "root-dir-rel",
  56. Path::new(&os::replace_path_separator(
  57. util::relativize_path(
  58. config.app().root_dir(),
  59. config.project_dir().join(config.app().name_snake()),
  60. )
  61. .into_os_string(),
  62. )),
  63. );
  64. map.insert("root-dir", config.app().root_dir());
  65. map.insert(
  66. "abi-list",
  67. Target::all()
  68. .values()
  69. .map(|target| target.abi)
  70. .collect::<Vec<_>>(),
  71. );
  72. map.insert("target-list", Target::all().keys().collect::<Vec<_>>());
  73. map.insert(
  74. "arch-list",
  75. Target::all()
  76. .values()
  77. .map(|target| target.arch)
  78. .collect::<Vec<_>>(),
  79. );
  80. map.insert("android-app-plugins", metadata.app_plugins());
  81. map.insert(
  82. "android-project-dependencies",
  83. metadata.project_dependencies(),
  84. );
  85. map.insert("android-app-dependencies", metadata.app_dependencies());
  86. map.insert(
  87. "android-app-dependencies-platform",
  88. metadata.app_dependencies_platform(),
  89. );
  90. map.insert(
  91. "has-code",
  92. metadata.project_dependencies().is_some()
  93. || metadata.app_dependencies().is_some()
  94. || metadata.app_dependencies_platform().is_some(),
  95. );
  96. map.insert("has-asset-packs", !asset_packs.is_empty());
  97. map.insert(
  98. "asset-packs",
  99. asset_packs
  100. .iter()
  101. .map(|p| p.name.as_str())
  102. .collect::<Vec<_>>(),
  103. );
  104. map.insert("windows", cfg!(windows));
  105. let domain = config.app().reverse_domain().replace('.', "/");
  106. let package_path = format!("java/{}/{}", domain, config.app().name_snake());
  107. map.insert("package-path", &package_path);
  108. let mut created_dirs = Vec::new();
  109. template::render_with_generator(
  110. &handlebars,
  111. map.inner(),
  112. &TEMPLATE_DIR,
  113. &dest,
  114. &mut |path| generate_out_file(&path, &dest, &package_path, &mut created_dirs),
  115. )
  116. .with_context(|| "failed to process template")?;
  117. if !asset_packs.is_empty() {
  118. Report::action_request(
  119. "When running from Android Studio, you must first set your deployment option to \"APK from app bundle\".",
  120. "Android Studio will not be able to find your asset packs otherwise. The option can be found under \"Run > Edit Configurations > Deploy\"."
  121. ).print(wrapper);
  122. }
  123. let source_dest = dest.join("app");
  124. for source in metadata.app_sources() {
  125. let source_src = config.app().root_dir().join(source);
  126. let source_file = source_src
  127. .file_name()
  128. .ok_or_else(|| anyhow::anyhow!("asset source {} is invalid", source_src.display()))?;
  129. fs::copy(&source_src, source_dest.join(source_file)).map_err(|cause| {
  130. anyhow::anyhow!(
  131. "failed to copy {} to {}: {}",
  132. source_src.display(),
  133. source_dest.display(),
  134. cause
  135. )
  136. })?;
  137. }
  138. let dest = prefix_path(dest, "app/src/main/");
  139. fs::create_dir_all(&dest).map_err(|cause| {
  140. anyhow::anyhow!(
  141. "failed to create directory at {}: {}",
  142. dest.display(),
  143. cause
  144. )
  145. })?;
  146. let asset_dir = dest.join(DEFAULT_ASSET_DIR);
  147. if !asset_dir.is_dir() {
  148. fs::create_dir_all(&asset_dir).map_err(|cause| {
  149. anyhow::anyhow!(
  150. "failed to create asset dir {path}: {cause}",
  151. path = asset_dir.display()
  152. )
  153. })?;
  154. }
  155. Ok(())
  156. }
  157. fn generate_out_file(
  158. path: &Path,
  159. dest: &Path,
  160. package_path: &str,
  161. created_dirs: &mut Vec<PathBuf>,
  162. ) -> std::io::Result<Option<fs::File>> {
  163. let mut iter = path.iter();
  164. let root = iter.next().unwrap().to_str().unwrap();
  165. let path_without_root: std::path::PathBuf = iter.collect();
  166. let path = match (
  167. root,
  168. path.extension().and_then(|o| o.to_str()),
  169. path_without_root.strip_prefix("src/main"),
  170. ) {
  171. ("app" | "buildSrc", Some("kt"), Ok(path)) => {
  172. let parent = path.parent().unwrap();
  173. let file_name = path.file_name().unwrap();
  174. let out_dir = dest
  175. .join(root)
  176. .join("src/main")
  177. .join(package_path)
  178. .join(parent);
  179. out_dir.join(file_name)
  180. }
  181. _ => dest.join(path),
  182. };
  183. let parent = path.parent().unwrap().to_path_buf();
  184. if !created_dirs.contains(&parent) {
  185. fs::create_dir_all(&parent)?;
  186. created_dirs.push(parent);
  187. }
  188. let mut options = fs::OpenOptions::new();
  189. options.write(true);
  190. #[cfg(unix)]
  191. if path.file_name().unwrap() == OsStr::new("gradlew") {
  192. use std::os::unix::fs::OpenOptionsExt;
  193. options.mode(0o755);
  194. }
  195. if path.file_name().unwrap() == OsStr::new("BuildTask.kt") {
  196. options.truncate(true).create(true).open(path).map(Some)
  197. } else if !path.exists() {
  198. options.create(true).open(path).map(Some)
  199. } else {
  200. Ok(None)
  201. }
  202. }