build.rs 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195
  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::{
  5. bundle::BundleFormat,
  6. helpers::{
  7. self,
  8. app_paths::tauri_dir,
  9. config::{get as get_config, ConfigHandle, FrontendDist},
  10. },
  11. interface::{AppInterface, Interface},
  12. ConfigValue, Result,
  13. };
  14. use anyhow::Context;
  15. use clap::{ArgAction, Parser};
  16. use std::env::set_current_dir;
  17. use tauri_utils::platform::Target;
  18. #[derive(Debug, Clone, Parser)]
  19. #[clap(
  20. about = "Build your app in release mode and generate bundles and installers",
  21. long_about = "Build your app in release mode and generate bundles and installers. It makes use of the `build.frontendDist` property from your `tauri.conf.json` file. It also runs your `build.beforeBuildCommand` which usually builds your frontend into `build.frontendDist`. This will also run `build.beforeBundleCommand` before generating the bundles and installers of your app."
  22. )]
  23. pub struct Options {
  24. /// Binary to use to build the application, defaults to `cargo`
  25. #[clap(short, long)]
  26. pub runner: Option<String>,
  27. /// Builds with the debug flag
  28. #[clap(short, long)]
  29. pub debug: bool,
  30. /// Target triple to build against.
  31. ///
  32. /// It must be one of the values outputted by `$rustc --print target-list` or `universal-apple-darwin` for an universal macOS application.
  33. ///
  34. /// Note that compiling an universal macOS application requires both `aarch64-apple-darwin` and `x86_64-apple-darwin` targets to be installed.
  35. #[clap(short, long)]
  36. pub target: Option<String>,
  37. /// Space or comma separated list of features to activate
  38. #[clap(short, long, action = ArgAction::Append, num_args(0..))]
  39. pub features: Option<Vec<String>>,
  40. /// Space or comma separated list of bundles to package.
  41. ///
  42. /// Note that the `updater` bundle is not automatically added so you must specify it if the updater is enabled.
  43. #[clap(short, long, action = ArgAction::Append, num_args(0..), value_delimiter = ',')]
  44. pub bundles: Option<Vec<BundleFormat>>,
  45. /// Skip the bundling step even if `bundle > active` is `true` in tauri config.
  46. #[clap(long)]
  47. pub no_bundle: bool,
  48. /// JSON string or path to JSON file to merge with tauri.conf.json
  49. #[clap(short, long)]
  50. pub config: Option<ConfigValue>,
  51. /// Command line arguments passed to the runner. Use `--` to explicitly mark the start of the arguments.
  52. pub args: Vec<String>,
  53. /// Skip prompting for values
  54. #[clap(long, env = "CI")]
  55. pub ci: bool,
  56. }
  57. pub fn command(mut options: Options, verbosity: u8) -> Result<()> {
  58. crate::helpers::app_paths::resolve();
  59. let ci = options.ci;
  60. let target = options
  61. .target
  62. .as_deref()
  63. .map(Target::from_triple)
  64. .unwrap_or_else(Target::current);
  65. let config = get_config(target, options.config.as_ref().map(|c| &c.0))?;
  66. let mut interface = AppInterface::new(
  67. config.lock().unwrap().as_ref().unwrap(),
  68. options.target.clone(),
  69. )?;
  70. setup(&interface, &mut options, config.clone(), false)?;
  71. let config_guard = config.lock().unwrap();
  72. let config_ = config_guard.as_ref().unwrap();
  73. let app_settings = interface.app_settings();
  74. let interface_options = options.clone().into();
  75. let out_dir = app_settings.out_dir(&interface_options)?;
  76. let bin_path = interface.build(interface_options)?;
  77. log::info!(action ="Built"; "application at: {}", tauri_utils::display_path(&bin_path));
  78. let app_settings = interface.app_settings();
  79. if !options.no_bundle && (config_.bundle.active || options.bundles.is_some()) {
  80. crate::bundle::bundle(
  81. &options.into(),
  82. verbosity,
  83. ci,
  84. &interface,
  85. &app_settings,
  86. config_,
  87. &out_dir,
  88. )?;
  89. }
  90. Ok(())
  91. }
  92. pub fn setup(
  93. interface: &AppInterface,
  94. options: &mut Options,
  95. config: ConfigHandle,
  96. mobile: bool,
  97. ) -> Result<()> {
  98. let tauri_path = tauri_dir();
  99. set_current_dir(tauri_path).with_context(|| "failed to change current working directory")?;
  100. let config_guard = config.lock().unwrap();
  101. let config_ = config_guard.as_ref().unwrap();
  102. let bundle_identifier_source = config_
  103. .find_bundle_identifier_overwriter()
  104. .unwrap_or_else(|| "tauri.conf.json".into());
  105. if config_.identifier == "com.tauri.dev" {
  106. log::error!(
  107. "You must change the bundle identifier in `{} identifier`. The default value `com.tauri.dev` is not allowed as it must be unique across applications.",
  108. bundle_identifier_source
  109. );
  110. std::process::exit(1);
  111. }
  112. if config_
  113. .identifier
  114. .chars()
  115. .any(|ch| !(ch.is_alphanumeric() || ch == '-' || ch == '.'))
  116. {
  117. log::error!(
  118. "The bundle identifier \"{}\" set in `{} identifier`. The bundle identifier string must contain only alphanumeric characters (A-Z, a-z, and 0-9), hyphens (-), and periods (.).",
  119. config_.identifier,
  120. bundle_identifier_source
  121. );
  122. std::process::exit(1);
  123. }
  124. if let Some(before_build) = config_.build.before_build_command.clone() {
  125. helpers::run_hook("beforeBuildCommand", before_build, interface, options.debug)?;
  126. }
  127. if let Some(FrontendDist::Directory(web_asset_path)) = &config_.build.frontend_dist {
  128. if !web_asset_path.exists() {
  129. let absolute_path = web_asset_path
  130. .parent()
  131. .and_then(|p| p.canonicalize().ok())
  132. .map(|p| p.join(web_asset_path.file_name().unwrap()))
  133. .unwrap_or_else(|| std::env::current_dir().unwrap().join(web_asset_path));
  134. return Err(anyhow::anyhow!(
  135. "Unable to find your web assets, did you forget to build your web app? Your frontendDist is set to \"{}\" (which is `{}`).",
  136. web_asset_path.display(), absolute_path.display(),
  137. ));
  138. }
  139. if web_asset_path.canonicalize()?.file_name() == Some(std::ffi::OsStr::new("src-tauri")) {
  140. return Err(anyhow::anyhow!(
  141. "The configured frontendDist is the `src-tauri` folder. Please isolate your web assets on a separate folder and update `tauri.conf.json > build > frontendDist`.",
  142. ));
  143. }
  144. let mut out_folders = Vec::new();
  145. for folder in &["node_modules", "src-tauri", "target"] {
  146. if web_asset_path.join(folder).is_dir() {
  147. out_folders.push(folder.to_string());
  148. }
  149. }
  150. if !out_folders.is_empty() {
  151. return Err(anyhow::anyhow!(
  152. "The configured frontendDist includes the `{:?}` {}. Please isolate your web assets on a separate folder and update `tauri.conf.json > build > frontendDist`.",
  153. out_folders,
  154. if out_folders.len() == 1 { "folder" }else { "folders" }
  155. )
  156. );
  157. }
  158. }
  159. if options.runner.is_none() {
  160. options.runner.clone_from(&config_.build.runner);
  161. }
  162. options
  163. .features
  164. .get_or_insert(Vec::new())
  165. .extend(config_.build.features.clone().unwrap_or_default());
  166. interface.build_options(&mut options.args, &mut options.features, mobile);
  167. Ok(())
  168. }