init.rs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448
  1. // Copyright 2019-2023 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::{app_paths::tauri_dir, config::get as get_tauri_config, template::JsonMap},
  7. interface::{AppInterface, Interface},
  8. Result,
  9. };
  10. use cargo_mobile2::{
  11. android::{
  12. config::Config as AndroidConfig, env::Env as AndroidEnv, target::Target as AndroidTarget,
  13. },
  14. config::app::App,
  15. dot_cargo,
  16. target::TargetTrait as _,
  17. util::{
  18. self,
  19. cli::{Report, TextWrapper},
  20. relativize_path,
  21. },
  22. };
  23. use handlebars::{
  24. Context, Handlebars, Helper, HelperResult, Output, RenderContext, RenderError, RenderErrorReason,
  25. };
  26. use std::{
  27. env::{current_dir, var, var_os},
  28. path::PathBuf,
  29. };
  30. pub fn command(
  31. target: Target,
  32. ci: bool,
  33. reinstall_deps: bool,
  34. skip_targets_install: bool,
  35. ) -> Result<()> {
  36. let wrapper = TextWrapper::default();
  37. exec(target, &wrapper, ci, reinstall_deps, skip_targets_install)
  38. .map_err(|e| anyhow::anyhow!("{:#}", e))?;
  39. Ok(())
  40. }
  41. pub fn configure_cargo(
  42. app: &App,
  43. android: Option<(&mut AndroidEnv, &AndroidConfig)>,
  44. ) -> Result<()> {
  45. if let Some((env, config)) = android {
  46. for target in AndroidTarget::all().values() {
  47. let config = target.generate_cargo_config(config, env)?;
  48. let target_var_name = target.triple.replace('-', "_").to_uppercase();
  49. if let Some(linker) = config.linker {
  50. env.base.insert_env_var(
  51. format!("CARGO_TARGET_{target_var_name}_LINKER"),
  52. linker.into(),
  53. );
  54. }
  55. env.base.insert_env_var(
  56. format!("CARGO_TARGET_{target_var_name}_RUSTFLAGS"),
  57. config.rustflags.join(" ").into(),
  58. );
  59. }
  60. }
  61. let mut dot_cargo = dot_cargo::DotCargo::load(app)?;
  62. // Mysteriously, builds that don't specify `--target` seem to fight over
  63. // the build cache with builds that use `--target`! This means that
  64. // alternating between i.e. `cargo run` and `cargo apple run` would
  65. // result in clean builds being made each time you switched... which is
  66. // pretty nightmarish. Specifying `build.target` in `.cargo/config`
  67. // fortunately has the same effect as specifying `--target`, so now we can
  68. // `cargo run` with peace of mind!
  69. //
  70. // This behavior could be explained here:
  71. // https://doc.rust-lang.org/cargo/reference/config.html#buildrustflags
  72. dot_cargo.set_default_target(util::host_target_triple()?);
  73. dot_cargo.write(app).map_err(Into::into)
  74. }
  75. pub fn exec(
  76. target: Target,
  77. wrapper: &TextWrapper,
  78. #[allow(unused_variables)] non_interactive: bool,
  79. #[allow(unused_variables)] reinstall_deps: bool,
  80. skip_targets_install: bool,
  81. ) -> Result<App> {
  82. let current_dir = current_dir()?;
  83. let tauri_config = get_tauri_config(target.platform_target(), None)?;
  84. let tauri_config_guard = tauri_config.lock().unwrap();
  85. let tauri_config_ = tauri_config_guard.as_ref().unwrap();
  86. let app = get_app(tauri_config_, &AppInterface::new(tauri_config_, None)?);
  87. let (handlebars, mut map) = handlebars(&app);
  88. // the CWD used when the the IDE runs the android-studio-script or the xcode-script
  89. let ide_run_cwd = if target == Target::Android {
  90. tauri_dir()
  91. } else {
  92. tauri_dir().join("gen/apple")
  93. };
  94. let mut args = std::env::args_os();
  95. let mut binary = args
  96. .next()
  97. .map(|bin| {
  98. let path = PathBuf::from(&bin);
  99. if path.exists() {
  100. let absolute_path = util::prefix_path(&current_dir, path);
  101. return relativize_path(absolute_path, &ide_run_cwd).into_os_string();
  102. }
  103. bin
  104. })
  105. .unwrap_or_else(|| std::ffi::OsString::from("cargo"));
  106. let mut build_args = Vec::new();
  107. for arg in args {
  108. let path = PathBuf::from(&arg);
  109. if path.exists() {
  110. let absolute_path = util::prefix_path(&current_dir, path);
  111. build_args.push(
  112. relativize_path(absolute_path, &ide_run_cwd)
  113. .to_string_lossy()
  114. .into_owned(),
  115. );
  116. continue;
  117. }
  118. let is_mobile_cmd_arg = arg == "android" || arg == "ios";
  119. build_args.push(arg.to_string_lossy().into_owned());
  120. if is_mobile_cmd_arg {
  121. break;
  122. }
  123. }
  124. build_args.push(target.ide_build_script_name().into());
  125. let binary_path = PathBuf::from(&binary);
  126. let bin_stem = binary_path.file_stem().unwrap().to_string_lossy();
  127. let r = regex::Regex::new("(nodejs|node)\\-?([1-9]*)*$").unwrap();
  128. if r.is_match(&bin_stem) {
  129. if let Some(npm_execpath) = var_os("npm_execpath").map(PathBuf::from) {
  130. let manager_stem = npm_execpath.file_stem().unwrap().to_os_string();
  131. let is_npm = manager_stem == "npm-cli";
  132. let is_npx = manager_stem == "npx-cli";
  133. binary = if is_npm {
  134. "npm".into()
  135. } else if is_npx {
  136. "npx".into()
  137. } else {
  138. manager_stem
  139. };
  140. if !(build_args.is_empty() || is_npx) {
  141. // remove script path, we'll use `npm_lifecycle_event` instead
  142. build_args.remove(0);
  143. }
  144. if is_npm {
  145. build_args.insert(0, "--".into());
  146. }
  147. if !is_npx {
  148. build_args.insert(0, var("npm_lifecycle_event").unwrap());
  149. }
  150. if is_npm {
  151. build_args.insert(0, "run".into());
  152. }
  153. }
  154. }
  155. map.insert("tauri-binary", binary.to_string_lossy());
  156. map.insert("tauri-binary-args", &build_args);
  157. map.insert("tauri-binary-args-str", build_args.join(" "));
  158. let app = match target {
  159. // Generate Android Studio project
  160. Target::Android => match AndroidEnv::new() {
  161. Ok(_env) => {
  162. let (config, metadata) =
  163. super::android::get_config(&app, tauri_config_, &Default::default());
  164. map.insert("android", &config);
  165. super::android::project::gen(
  166. &config,
  167. &metadata,
  168. (handlebars, map),
  169. wrapper,
  170. skip_targets_install,
  171. )?;
  172. app
  173. }
  174. Err(err) => {
  175. if err.sdk_or_ndk_issue() {
  176. Report::action_request(
  177. " to initialize Android environment; Android support won't be usable until you fix the issue below and re-run `tauri android init`!",
  178. err,
  179. )
  180. .print(wrapper);
  181. app
  182. } else {
  183. return Err(err.into());
  184. }
  185. }
  186. },
  187. #[cfg(target_os = "macos")]
  188. // Generate Xcode project
  189. Target::Ios => {
  190. let (config, metadata) = super::ios::get_config(&app, tauri_config_, &Default::default());
  191. map.insert("apple", &config);
  192. super::ios::project::gen(
  193. &config,
  194. &metadata,
  195. (handlebars, map),
  196. wrapper,
  197. non_interactive,
  198. reinstall_deps,
  199. skip_targets_install,
  200. )?;
  201. app
  202. }
  203. };
  204. Report::victory(
  205. "Project generated successfully!",
  206. "Make cool apps! 🌻 🐕 🎉",
  207. )
  208. .print(wrapper);
  209. Ok(app)
  210. }
  211. fn handlebars(app: &App) -> (Handlebars<'static>, JsonMap) {
  212. let mut h = Handlebars::new();
  213. h.register_escape_fn(handlebars::no_escape);
  214. h.register_helper("html-escape", Box::new(html_escape));
  215. h.register_helper("join", Box::new(join));
  216. h.register_helper("quote-and-join", Box::new(quote_and_join));
  217. h.register_helper(
  218. "quote-and-join-colon-prefix",
  219. Box::new(quote_and_join_colon_prefix),
  220. );
  221. h.register_helper("snake-case", Box::new(snake_case));
  222. h.register_helper("reverse-domain", Box::new(reverse_domain));
  223. h.register_helper(
  224. "reverse-domain-snake-case",
  225. Box::new(reverse_domain_snake_case),
  226. );
  227. // don't mix these up or very bad things will happen to all of us
  228. h.register_helper("prefix-path", Box::new(prefix_path));
  229. h.register_helper("unprefix-path", Box::new(unprefix_path));
  230. let mut map = JsonMap::default();
  231. map.insert("app", app);
  232. (h, map)
  233. }
  234. fn get_str<'a>(helper: &'a Helper) -> &'a str {
  235. helper
  236. .param(0)
  237. .and_then(|v| v.value().as_str())
  238. .unwrap_or("")
  239. }
  240. fn get_str_array(helper: &Helper, formatter: impl Fn(&str) -> String) -> Option<Vec<String>> {
  241. helper.param(0).and_then(|v| {
  242. v.value().as_array().and_then(|arr| {
  243. arr
  244. .iter()
  245. .map(|val| {
  246. val.as_str().map(
  247. #[allow(clippy::redundant_closure)]
  248. |s| formatter(s),
  249. )
  250. })
  251. .collect()
  252. })
  253. })
  254. }
  255. fn html_escape(
  256. helper: &Helper,
  257. _: &Handlebars,
  258. _ctx: &Context,
  259. _: &mut RenderContext,
  260. out: &mut dyn Output,
  261. ) -> HelperResult {
  262. out
  263. .write(&handlebars::html_escape(get_str(helper)))
  264. .map_err(Into::into)
  265. }
  266. fn join(
  267. helper: &Helper,
  268. _: &Handlebars,
  269. _: &Context,
  270. _: &mut RenderContext,
  271. out: &mut dyn Output,
  272. ) -> HelperResult {
  273. out
  274. .write(
  275. &get_str_array(helper, |s| s.to_string())
  276. .ok_or_else(|| {
  277. RenderErrorReason::ParamTypeMismatchForName("join", "0".to_owned(), "array".to_owned())
  278. })?
  279. .join(", "),
  280. )
  281. .map_err(Into::into)
  282. }
  283. fn quote_and_join(
  284. helper: &Helper,
  285. _: &Handlebars,
  286. _: &Context,
  287. _: &mut RenderContext,
  288. out: &mut dyn Output,
  289. ) -> HelperResult {
  290. out
  291. .write(
  292. &get_str_array(helper, |s| format!("{s:?}"))
  293. .ok_or_else(|| {
  294. RenderErrorReason::ParamTypeMismatchForName(
  295. "quote-and-join",
  296. "0".to_owned(),
  297. "array".to_owned(),
  298. )
  299. })?
  300. .join(", "),
  301. )
  302. .map_err(Into::into)
  303. }
  304. fn quote_and_join_colon_prefix(
  305. helper: &Helper,
  306. _: &Handlebars,
  307. _: &Context,
  308. _: &mut RenderContext,
  309. out: &mut dyn Output,
  310. ) -> HelperResult {
  311. out
  312. .write(
  313. &get_str_array(helper, |s| format!("{:?}", format!(":{s}")))
  314. .ok_or_else(|| {
  315. RenderErrorReason::ParamTypeMismatchForName(
  316. "quote-and-join-colon-prefix",
  317. "0".to_owned(),
  318. "array".to_owned(),
  319. )
  320. })?
  321. .join(", "),
  322. )
  323. .map_err(Into::into)
  324. }
  325. fn snake_case(
  326. helper: &Helper,
  327. _: &Handlebars,
  328. _: &Context,
  329. _: &mut RenderContext,
  330. out: &mut dyn Output,
  331. ) -> HelperResult {
  332. use heck::ToSnekCase as _;
  333. out
  334. .write(&get_str(helper).to_snek_case())
  335. .map_err(Into::into)
  336. }
  337. fn reverse_domain(
  338. helper: &Helper,
  339. _: &Handlebars,
  340. _: &Context,
  341. _: &mut RenderContext,
  342. out: &mut dyn Output,
  343. ) -> HelperResult {
  344. out
  345. .write(&util::reverse_domain(get_str(helper)))
  346. .map_err(Into::into)
  347. }
  348. fn reverse_domain_snake_case(
  349. helper: &Helper,
  350. _: &Handlebars,
  351. _: &Context,
  352. _: &mut RenderContext,
  353. out: &mut dyn Output,
  354. ) -> HelperResult {
  355. use heck::ToSnekCase as _;
  356. out
  357. .write(&util::reverse_domain(get_str(helper)).to_snek_case())
  358. .map_err(Into::into)
  359. }
  360. fn app_root(ctx: &Context) -> Result<&str, RenderError> {
  361. let app_root = ctx
  362. .data()
  363. .get("app")
  364. .ok_or_else(|| RenderErrorReason::Other("`app` missing from template data.".to_owned()))?
  365. .get("root-dir")
  366. .ok_or_else(|| {
  367. RenderErrorReason::Other("`app.root-dir` missing from template data.".to_owned())
  368. })?;
  369. app_root.as_str().ok_or_else(|| {
  370. RenderErrorReason::Other("`app.root-dir` contained invalid UTF-8.".to_owned()).into()
  371. })
  372. }
  373. fn prefix_path(
  374. helper: &Helper,
  375. _: &Handlebars,
  376. ctx: &Context,
  377. _: &mut RenderContext,
  378. out: &mut dyn Output,
  379. ) -> HelperResult {
  380. out
  381. .write(
  382. util::prefix_path(app_root(ctx)?, get_str(helper))
  383. .to_str()
  384. .ok_or_else(|| {
  385. RenderErrorReason::Other(
  386. "Either the `app.root-dir` or the specified path contained invalid UTF-8.".to_owned(),
  387. )
  388. })?,
  389. )
  390. .map_err(Into::into)
  391. }
  392. fn unprefix_path(
  393. helper: &Helper,
  394. _: &Handlebars,
  395. ctx: &Context,
  396. _: &mut RenderContext,
  397. out: &mut dyn Output,
  398. ) -> HelperResult {
  399. out
  400. .write(
  401. util::unprefix_path(app_root(ctx)?, get_str(helper))
  402. .map_err(|_| {
  403. RenderErrorReason::Other(
  404. "Attempted to unprefix a path that wasn't in the app root dir.".to_owned(),
  405. )
  406. })?
  407. .to_str()
  408. .ok_or_else(|| {
  409. RenderErrorReason::Other(
  410. "Either the `app.root-dir` or the specified path contained invalid UTF-8.".to_owned(),
  411. )
  412. })?,
  413. )
  414. .map_err(Into::into)
  415. }