init.rs 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373
  1. // Copyright 2019-2024 Tauri Programme within The Commons Conservancy
  2. // SPDX-License-Identifier: Apache-2.0
  3. // SPDX-License-Identifier: MIT
  4. use super::{get_app, Target};
  5. use crate::{
  6. helpers::{config::get as get_tauri_config, template::JsonMap},
  7. interface::{AppInterface, Interface},
  8. Result,
  9. };
  10. use cargo_mobile2::{
  11. android::env::Env as AndroidEnv,
  12. config::app::App,
  13. reserved_names::KOTLIN_ONLY_KEYWORDS,
  14. util::{
  15. self,
  16. cli::{Report, TextWrapper},
  17. },
  18. };
  19. use handlebars::{
  20. Context, Handlebars, Helper, HelperResult, Output, RenderContext, RenderError, RenderErrorReason,
  21. };
  22. use std::{env::var_os, path::PathBuf};
  23. pub fn command(
  24. target: Target,
  25. ci: bool,
  26. reinstall_deps: bool,
  27. skip_targets_install: bool,
  28. ) -> Result<()> {
  29. let wrapper = TextWrapper::default();
  30. exec(target, &wrapper, ci, reinstall_deps, skip_targets_install)
  31. .map_err(|e| anyhow::anyhow!("{:#}", e))?;
  32. Ok(())
  33. }
  34. pub fn exec(
  35. target: Target,
  36. wrapper: &TextWrapper,
  37. #[allow(unused_variables)] non_interactive: bool,
  38. #[allow(unused_variables)] reinstall_deps: bool,
  39. skip_targets_install: bool,
  40. ) -> Result<App> {
  41. let tauri_config = get_tauri_config(target.platform_target(), None)?;
  42. let tauri_config_guard = tauri_config.lock().unwrap();
  43. let tauri_config_ = tauri_config_guard.as_ref().unwrap();
  44. let app = get_app(
  45. target,
  46. tauri_config_,
  47. &AppInterface::new(tauri_config_, None)?,
  48. );
  49. let (handlebars, mut map) = handlebars(&app);
  50. let mut args = std::env::args_os();
  51. let (binary, mut build_args) = args
  52. .next()
  53. .map(|bin| {
  54. let bin_path = PathBuf::from(&bin);
  55. let mut build_args = vec!["tauri"];
  56. if let Some(bin_stem) = bin_path.file_stem() {
  57. let r = regex::Regex::new("(nodejs|node)\\-?([1-9]*)*$").unwrap();
  58. if r.is_match(&bin_stem.to_string_lossy()) {
  59. if var_os("PNPM_PACKAGE_NAME").is_some() {
  60. return ("pnpm".into(), build_args);
  61. } else if let Some(npm_execpath) = var_os("npm_execpath") {
  62. let manager_stem = PathBuf::from(&npm_execpath)
  63. .file_stem()
  64. .unwrap()
  65. .to_os_string();
  66. let is_npm = manager_stem == "npm-cli";
  67. let binary = if is_npm {
  68. "npm".into()
  69. } else if manager_stem == "npx-cli" {
  70. "npx".into()
  71. } else {
  72. manager_stem
  73. };
  74. if is_npm {
  75. build_args.insert(0, "run");
  76. build_args.insert(1, "--");
  77. }
  78. return (binary, build_args);
  79. }
  80. } else if bin_stem == "deno" {
  81. build_args.insert(0, "task");
  82. return (std::ffi::OsString::from("deno"), build_args);
  83. } else if !cfg!(debug_assertions) && bin_stem == "cargo-tauri" {
  84. return (std::ffi::OsString::from("cargo"), build_args);
  85. }
  86. }
  87. (bin, build_args)
  88. })
  89. .unwrap_or_else(|| (std::ffi::OsString::from("cargo"), vec!["tauri"]));
  90. build_args.push(target.command_name());
  91. build_args.push(target.ide_build_script_name());
  92. map.insert("tauri-binary", binary.to_string_lossy());
  93. map.insert("tauri-binary-args", &build_args);
  94. map.insert("tauri-binary-args-str", build_args.join(" "));
  95. let app = match target {
  96. // Generate Android Studio project
  97. Target::Android => match AndroidEnv::new() {
  98. Ok(_env) => {
  99. let (config, metadata) =
  100. super::android::get_config(&app, tauri_config_, None, &Default::default());
  101. map.insert("android", &config);
  102. super::android::project::gen(
  103. &config,
  104. &metadata,
  105. (handlebars, map),
  106. wrapper,
  107. skip_targets_install,
  108. )?;
  109. app
  110. }
  111. Err(err) => {
  112. if err.sdk_or_ndk_issue() {
  113. Report::action_request(
  114. " to initialize Android environment; Android support won't be usable until you fix the issue below and re-run `tauri android init`!",
  115. err,
  116. )
  117. .print(wrapper);
  118. app
  119. } else {
  120. return Err(err.into());
  121. }
  122. }
  123. },
  124. #[cfg(target_os = "macos")]
  125. // Generate Xcode project
  126. Target::Ios => {
  127. let (config, metadata) =
  128. super::ios::get_config(&app, tauri_config_, None, &Default::default());
  129. map.insert("apple", &config);
  130. super::ios::project::gen(
  131. tauri_config_,
  132. &config,
  133. &metadata,
  134. (handlebars, map),
  135. wrapper,
  136. non_interactive,
  137. reinstall_deps,
  138. skip_targets_install,
  139. )?;
  140. app
  141. }
  142. };
  143. Report::victory(
  144. "Project generated successfully!",
  145. "Make cool apps! 🌻 🐕 🎉",
  146. )
  147. .print(wrapper);
  148. Ok(app)
  149. }
  150. fn handlebars(app: &App) -> (Handlebars<'static>, JsonMap) {
  151. let mut h = Handlebars::new();
  152. h.register_escape_fn(handlebars::no_escape);
  153. h.register_helper("html-escape", Box::new(html_escape));
  154. h.register_helper("join", Box::new(join));
  155. h.register_helper("quote-and-join", Box::new(quote_and_join));
  156. h.register_helper(
  157. "quote-and-join-colon-prefix",
  158. Box::new(quote_and_join_colon_prefix),
  159. );
  160. h.register_helper("snake-case", Box::new(snake_case));
  161. h.register_helper("escape-kotlin-keyword", Box::new(escape_kotlin_keyword));
  162. // don't mix these up or very bad things will happen to all of us
  163. h.register_helper("prefix-path", Box::new(prefix_path));
  164. h.register_helper("unprefix-path", Box::new(unprefix_path));
  165. let mut map = JsonMap::default();
  166. map.insert("app", app);
  167. (h, map)
  168. }
  169. fn get_str<'a>(helper: &'a Helper) -> &'a str {
  170. helper
  171. .param(0)
  172. .and_then(|v| v.value().as_str())
  173. .unwrap_or("")
  174. }
  175. fn get_str_array(helper: &Helper, formatter: impl Fn(&str) -> String) -> Option<Vec<String>> {
  176. helper.param(0).and_then(|v| {
  177. v.value()
  178. .as_array()
  179. .and_then(|arr| arr.iter().map(|val| val.as_str().map(&formatter)).collect())
  180. })
  181. }
  182. fn html_escape(
  183. helper: &Helper,
  184. _: &Handlebars,
  185. _ctx: &Context,
  186. _: &mut RenderContext,
  187. out: &mut dyn Output,
  188. ) -> HelperResult {
  189. out
  190. .write(&handlebars::html_escape(get_str(helper)))
  191. .map_err(Into::into)
  192. }
  193. fn join(
  194. helper: &Helper,
  195. _: &Handlebars,
  196. _: &Context,
  197. _: &mut RenderContext,
  198. out: &mut dyn Output,
  199. ) -> HelperResult {
  200. out
  201. .write(
  202. &get_str_array(helper, |s| s.to_string())
  203. .ok_or_else(|| {
  204. RenderErrorReason::ParamTypeMismatchForName("join", "0".to_owned(), "array".to_owned())
  205. })?
  206. .join(", "),
  207. )
  208. .map_err(Into::into)
  209. }
  210. fn quote_and_join(
  211. helper: &Helper,
  212. _: &Handlebars,
  213. _: &Context,
  214. _: &mut RenderContext,
  215. out: &mut dyn Output,
  216. ) -> HelperResult {
  217. out
  218. .write(
  219. &get_str_array(helper, |s| format!("{s:?}"))
  220. .ok_or_else(|| {
  221. RenderErrorReason::ParamTypeMismatchForName(
  222. "quote-and-join",
  223. "0".to_owned(),
  224. "array".to_owned(),
  225. )
  226. })?
  227. .join(", "),
  228. )
  229. .map_err(Into::into)
  230. }
  231. fn quote_and_join_colon_prefix(
  232. helper: &Helper,
  233. _: &Handlebars,
  234. _: &Context,
  235. _: &mut RenderContext,
  236. out: &mut dyn Output,
  237. ) -> HelperResult {
  238. out
  239. .write(
  240. &get_str_array(helper, |s| format!("{:?}", format!(":{s}")))
  241. .ok_or_else(|| {
  242. RenderErrorReason::ParamTypeMismatchForName(
  243. "quote-and-join-colon-prefix",
  244. "0".to_owned(),
  245. "array".to_owned(),
  246. )
  247. })?
  248. .join(", "),
  249. )
  250. .map_err(Into::into)
  251. }
  252. fn snake_case(
  253. helper: &Helper,
  254. _: &Handlebars,
  255. _: &Context,
  256. _: &mut RenderContext,
  257. out: &mut dyn Output,
  258. ) -> HelperResult {
  259. use heck::ToSnekCase as _;
  260. out
  261. .write(&get_str(helper).to_snek_case())
  262. .map_err(Into::into)
  263. }
  264. fn escape_kotlin_keyword(
  265. helper: &Helper,
  266. _: &Handlebars,
  267. _: &Context,
  268. _: &mut RenderContext,
  269. out: &mut dyn Output,
  270. ) -> HelperResult {
  271. let escaped_result = get_str(helper)
  272. .split('.')
  273. .map(|s| {
  274. if KOTLIN_ONLY_KEYWORDS.contains(&s) {
  275. format!("`{}`", s)
  276. } else {
  277. s.to_string()
  278. }
  279. })
  280. .collect::<Vec<_>>()
  281. .join(".");
  282. out.write(&escaped_result).map_err(Into::into)
  283. }
  284. fn app_root(ctx: &Context) -> Result<&str, RenderError> {
  285. let app_root = ctx
  286. .data()
  287. .get("app")
  288. .ok_or_else(|| RenderErrorReason::Other("`app` missing from template data.".to_owned()))?
  289. .get("root-dir")
  290. .ok_or_else(|| {
  291. RenderErrorReason::Other("`app.root-dir` missing from template data.".to_owned())
  292. })?;
  293. app_root.as_str().ok_or_else(|| {
  294. RenderErrorReason::Other("`app.root-dir` contained invalid UTF-8.".to_owned()).into()
  295. })
  296. }
  297. fn prefix_path(
  298. helper: &Helper,
  299. _: &Handlebars,
  300. ctx: &Context,
  301. _: &mut RenderContext,
  302. out: &mut dyn Output,
  303. ) -> HelperResult {
  304. out
  305. .write(
  306. util::prefix_path(app_root(ctx)?, get_str(helper))
  307. .to_str()
  308. .ok_or_else(|| {
  309. RenderErrorReason::Other(
  310. "Either the `app.root-dir` or the specified path contained invalid UTF-8.".to_owned(),
  311. )
  312. })?,
  313. )
  314. .map_err(Into::into)
  315. }
  316. fn unprefix_path(
  317. helper: &Helper,
  318. _: &Handlebars,
  319. ctx: &Context,
  320. _: &mut RenderContext,
  321. out: &mut dyn Output,
  322. ) -> HelperResult {
  323. out
  324. .write(
  325. util::unprefix_path(app_root(ctx)?, get_str(helper))
  326. .map_err(|_| {
  327. RenderErrorReason::Other(
  328. "Attempted to unprefix a path that wasn't in the app root dir.".to_owned(),
  329. )
  330. })?
  331. .to_str()
  332. .ok_or_else(|| {
  333. RenderErrorReason::Other(
  334. "Either the `app.root-dir` or the specified path contained invalid UTF-8.".to_owned(),
  335. )
  336. })?,
  337. )
  338. .map_err(Into::into)
  339. }