123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448 |
- // Copyright 2019-2023 Tauri Programme within The Commons Conservancy
- // SPDX-License-Identifier: Apache-2.0
- // SPDX-License-Identifier: MIT
- use super::{get_app, Target};
- use crate::{
- helpers::{app_paths::tauri_dir, config::get as get_tauri_config, template::JsonMap},
- interface::{AppInterface, Interface},
- Result,
- };
- use cargo_mobile2::{
- android::{
- config::Config as AndroidConfig, env::Env as AndroidEnv, target::Target as AndroidTarget,
- },
- config::app::App,
- dot_cargo,
- target::TargetTrait as _,
- util::{
- self,
- cli::{Report, TextWrapper},
- relativize_path,
- },
- };
- use handlebars::{
- Context, Handlebars, Helper, HelperResult, Output, RenderContext, RenderError, RenderErrorReason,
- };
- use std::{
- env::{current_dir, var, var_os},
- path::PathBuf,
- };
- pub fn command(
- target: Target,
- ci: bool,
- reinstall_deps: bool,
- skip_targets_install: bool,
- ) -> Result<()> {
- let wrapper = TextWrapper::default();
- exec(target, &wrapper, ci, reinstall_deps, skip_targets_install)
- .map_err(|e| anyhow::anyhow!("{:#}", e))?;
- Ok(())
- }
- pub fn configure_cargo(
- app: &App,
- android: Option<(&mut AndroidEnv, &AndroidConfig)>,
- ) -> Result<()> {
- if let Some((env, config)) = android {
- for target in AndroidTarget::all().values() {
- let config = target.generate_cargo_config(config, env)?;
- let target_var_name = target.triple.replace('-', "_").to_uppercase();
- if let Some(linker) = config.linker {
- env.base.insert_env_var(
- format!("CARGO_TARGET_{target_var_name}_LINKER"),
- linker.into(),
- );
- }
- env.base.insert_env_var(
- format!("CARGO_TARGET_{target_var_name}_RUSTFLAGS"),
- config.rustflags.join(" ").into(),
- );
- }
- }
- let mut dot_cargo = dot_cargo::DotCargo::load(app)?;
- // Mysteriously, builds that don't specify `--target` seem to fight over
- // the build cache with builds that use `--target`! This means that
- // alternating between i.e. `cargo run` and `cargo apple run` would
- // result in clean builds being made each time you switched... which is
- // pretty nightmarish. Specifying `build.target` in `.cargo/config`
- // fortunately has the same effect as specifying `--target`, so now we can
- // `cargo run` with peace of mind!
- //
- // This behavior could be explained here:
- // https://doc.rust-lang.org/cargo/reference/config.html#buildrustflags
- dot_cargo.set_default_target(util::host_target_triple()?);
- dot_cargo.write(app).map_err(Into::into)
- }
- pub fn exec(
- target: Target,
- wrapper: &TextWrapper,
- #[allow(unused_variables)] non_interactive: bool,
- #[allow(unused_variables)] reinstall_deps: bool,
- skip_targets_install: bool,
- ) -> Result<App> {
- let current_dir = current_dir()?;
- let tauri_config = get_tauri_config(target.platform_target(), None)?;
- let tauri_config_guard = tauri_config.lock().unwrap();
- let tauri_config_ = tauri_config_guard.as_ref().unwrap();
- let app = get_app(tauri_config_, &AppInterface::new(tauri_config_, None)?);
- let (handlebars, mut map) = handlebars(&app);
- // the CWD used when the the IDE runs the android-studio-script or the xcode-script
- let ide_run_cwd = if target == Target::Android {
- tauri_dir()
- } else {
- tauri_dir().join("gen/apple")
- };
- let mut args = std::env::args_os();
- let mut binary = args
- .next()
- .map(|bin| {
- let path = PathBuf::from(&bin);
- if path.exists() {
- let absolute_path = util::prefix_path(¤t_dir, path);
- return relativize_path(absolute_path, &ide_run_cwd).into_os_string();
- }
- bin
- })
- .unwrap_or_else(|| std::ffi::OsString::from("cargo"));
- let mut build_args = Vec::new();
- for arg in args {
- let path = PathBuf::from(&arg);
- if path.exists() {
- let absolute_path = util::prefix_path(¤t_dir, path);
- build_args.push(
- relativize_path(absolute_path, &ide_run_cwd)
- .to_string_lossy()
- .into_owned(),
- );
- continue;
- }
- let is_mobile_cmd_arg = arg == "android" || arg == "ios";
- build_args.push(arg.to_string_lossy().into_owned());
- if is_mobile_cmd_arg {
- break;
- }
- }
- build_args.push(target.ide_build_script_name().into());
- let binary_path = PathBuf::from(&binary);
- let bin_stem = binary_path.file_stem().unwrap().to_string_lossy();
- let r = regex::Regex::new("(nodejs|node)\\-?([1-9]*)*$").unwrap();
- if r.is_match(&bin_stem) {
- if let Some(npm_execpath) = var_os("npm_execpath").map(PathBuf::from) {
- let manager_stem = npm_execpath.file_stem().unwrap().to_os_string();
- let is_npm = manager_stem == "npm-cli";
- let is_npx = manager_stem == "npx-cli";
- binary = if is_npm {
- "npm".into()
- } else if is_npx {
- "npx".into()
- } else {
- manager_stem
- };
- if !(build_args.is_empty() || is_npx) {
- // remove script path, we'll use `npm_lifecycle_event` instead
- build_args.remove(0);
- }
- if is_npm {
- build_args.insert(0, "--".into());
- }
- if !is_npx {
- build_args.insert(0, var("npm_lifecycle_event").unwrap());
- }
- if is_npm {
- build_args.insert(0, "run".into());
- }
- }
- }
- map.insert("tauri-binary", binary.to_string_lossy());
- map.insert("tauri-binary-args", &build_args);
- map.insert("tauri-binary-args-str", build_args.join(" "));
- let app = match target {
- // Generate Android Studio project
- Target::Android => match AndroidEnv::new() {
- Ok(_env) => {
- let (config, metadata) =
- super::android::get_config(&app, tauri_config_, &Default::default());
- map.insert("android", &config);
- super::android::project::gen(
- &config,
- &metadata,
- (handlebars, map),
- wrapper,
- skip_targets_install,
- )?;
- app
- }
- Err(err) => {
- if err.sdk_or_ndk_issue() {
- Report::action_request(
- " to initialize Android environment; Android support won't be usable until you fix the issue below and re-run `tauri android init`!",
- err,
- )
- .print(wrapper);
- app
- } else {
- return Err(err.into());
- }
- }
- },
- #[cfg(target_os = "macos")]
- // Generate Xcode project
- Target::Ios => {
- let (config, metadata) = super::ios::get_config(&app, tauri_config_, &Default::default());
- map.insert("apple", &config);
- super::ios::project::gen(
- &config,
- &metadata,
- (handlebars, map),
- wrapper,
- non_interactive,
- reinstall_deps,
- skip_targets_install,
- )?;
- app
- }
- };
- Report::victory(
- "Project generated successfully!",
- "Make cool apps! 🌻 🐕 🎉",
- )
- .print(wrapper);
- Ok(app)
- }
- fn handlebars(app: &App) -> (Handlebars<'static>, JsonMap) {
- let mut h = Handlebars::new();
- h.register_escape_fn(handlebars::no_escape);
- h.register_helper("html-escape", Box::new(html_escape));
- h.register_helper("join", Box::new(join));
- h.register_helper("quote-and-join", Box::new(quote_and_join));
- h.register_helper(
- "quote-and-join-colon-prefix",
- Box::new(quote_and_join_colon_prefix),
- );
- h.register_helper("snake-case", Box::new(snake_case));
- h.register_helper("reverse-domain", Box::new(reverse_domain));
- h.register_helper(
- "reverse-domain-snake-case",
- Box::new(reverse_domain_snake_case),
- );
- // don't mix these up or very bad things will happen to all of us
- h.register_helper("prefix-path", Box::new(prefix_path));
- h.register_helper("unprefix-path", Box::new(unprefix_path));
- let mut map = JsonMap::default();
- map.insert("app", app);
- (h, map)
- }
- fn get_str<'a>(helper: &'a Helper) -> &'a str {
- helper
- .param(0)
- .and_then(|v| v.value().as_str())
- .unwrap_or("")
- }
- fn get_str_array(helper: &Helper, formatter: impl Fn(&str) -> String) -> Option<Vec<String>> {
- helper.param(0).and_then(|v| {
- v.value().as_array().and_then(|arr| {
- arr
- .iter()
- .map(|val| {
- val.as_str().map(
- #[allow(clippy::redundant_closure)]
- |s| formatter(s),
- )
- })
- .collect()
- })
- })
- }
- fn html_escape(
- helper: &Helper,
- _: &Handlebars,
- _ctx: &Context,
- _: &mut RenderContext,
- out: &mut dyn Output,
- ) -> HelperResult {
- out
- .write(&handlebars::html_escape(get_str(helper)))
- .map_err(Into::into)
- }
- fn join(
- helper: &Helper,
- _: &Handlebars,
- _: &Context,
- _: &mut RenderContext,
- out: &mut dyn Output,
- ) -> HelperResult {
- out
- .write(
- &get_str_array(helper, |s| s.to_string())
- .ok_or_else(|| {
- RenderErrorReason::ParamTypeMismatchForName("join", "0".to_owned(), "array".to_owned())
- })?
- .join(", "),
- )
- .map_err(Into::into)
- }
- fn quote_and_join(
- helper: &Helper,
- _: &Handlebars,
- _: &Context,
- _: &mut RenderContext,
- out: &mut dyn Output,
- ) -> HelperResult {
- out
- .write(
- &get_str_array(helper, |s| format!("{s:?}"))
- .ok_or_else(|| {
- RenderErrorReason::ParamTypeMismatchForName(
- "quote-and-join",
- "0".to_owned(),
- "array".to_owned(),
- )
- })?
- .join(", "),
- )
- .map_err(Into::into)
- }
- fn quote_and_join_colon_prefix(
- helper: &Helper,
- _: &Handlebars,
- _: &Context,
- _: &mut RenderContext,
- out: &mut dyn Output,
- ) -> HelperResult {
- out
- .write(
- &get_str_array(helper, |s| format!("{:?}", format!(":{s}")))
- .ok_or_else(|| {
- RenderErrorReason::ParamTypeMismatchForName(
- "quote-and-join-colon-prefix",
- "0".to_owned(),
- "array".to_owned(),
- )
- })?
- .join(", "),
- )
- .map_err(Into::into)
- }
- fn snake_case(
- helper: &Helper,
- _: &Handlebars,
- _: &Context,
- _: &mut RenderContext,
- out: &mut dyn Output,
- ) -> HelperResult {
- use heck::ToSnekCase as _;
- out
- .write(&get_str(helper).to_snek_case())
- .map_err(Into::into)
- }
- fn reverse_domain(
- helper: &Helper,
- _: &Handlebars,
- _: &Context,
- _: &mut RenderContext,
- out: &mut dyn Output,
- ) -> HelperResult {
- out
- .write(&util::reverse_domain(get_str(helper)))
- .map_err(Into::into)
- }
- fn reverse_domain_snake_case(
- helper: &Helper,
- _: &Handlebars,
- _: &Context,
- _: &mut RenderContext,
- out: &mut dyn Output,
- ) -> HelperResult {
- use heck::ToSnekCase as _;
- out
- .write(&util::reverse_domain(get_str(helper)).to_snek_case())
- .map_err(Into::into)
- }
- fn app_root(ctx: &Context) -> Result<&str, RenderError> {
- let app_root = ctx
- .data()
- .get("app")
- .ok_or_else(|| RenderErrorReason::Other("`app` missing from template data.".to_owned()))?
- .get("root-dir")
- .ok_or_else(|| {
- RenderErrorReason::Other("`app.root-dir` missing from template data.".to_owned())
- })?;
- app_root.as_str().ok_or_else(|| {
- RenderErrorReason::Other("`app.root-dir` contained invalid UTF-8.".to_owned()).into()
- })
- }
- fn prefix_path(
- helper: &Helper,
- _: &Handlebars,
- ctx: &Context,
- _: &mut RenderContext,
- out: &mut dyn Output,
- ) -> HelperResult {
- out
- .write(
- util::prefix_path(app_root(ctx)?, get_str(helper))
- .to_str()
- .ok_or_else(|| {
- RenderErrorReason::Other(
- "Either the `app.root-dir` or the specified path contained invalid UTF-8.".to_owned(),
- )
- })?,
- )
- .map_err(Into::into)
- }
- fn unprefix_path(
- helper: &Helper,
- _: &Handlebars,
- ctx: &Context,
- _: &mut RenderContext,
- out: &mut dyn Output,
- ) -> HelperResult {
- out
- .write(
- util::unprefix_path(app_root(ctx)?, get_str(helper))
- .map_err(|_| {
- RenderErrorReason::Other(
- "Attempted to unprefix a path that wasn't in the app root dir.".to_owned(),
- )
- })?
- .to_str()
- .ok_or_else(|| {
- RenderErrorReason::Other(
- "Either the `app.root-dir` or the specified path contained invalid UTF-8.".to_owned(),
- )
- })?,
- )
- .map_err(Into::into)
- }
|