Skip to main content

tauri_build/
lib.rs

1// Copyright 2019-2024 Tauri Programme within The Commons Conservancy
2// SPDX-License-Identifier: Apache-2.0
3// SPDX-License-Identifier: MIT
4
5//! Build-time helpers for Tauri applications.
6//!
7//! Every Tauri application must run [`build()`] (or [`try_build()`]) from its `build.rs`,
8//! on every target platform. It sets up everything the [`tauri`] crate expects to find at
9//! compile time and at runtime:
10//!
11//! - emits `cargo:rerun-if-changed` instructions for the Tauri configuration files;
12//! - defines the `dev`, `desktop` and `mobile` [cfg aliases] used across the Tauri crates;
13//! - parses the permissions of the application and of its plugins, resolves the capabilities
14//!   (from the `capabilities` directory and from `app > security > capabilities` in the
15//!   configuration file) and validates them, writing the resulting Access Control List to the
16//!   `OUT_DIR` so [`tauri::generate_context!`] can embed it;
17//!   it also writes the capability JSON schemas to `gen/schemas`;
18//! - copies the configured `bundle > externalBin` sidecars and `bundle > resources` next to
19//!   the compiled binary so they are available when running the app with `cargo run`;
20//! - on macOS and iOS, sets the deployment target from the configuration and links the
21//!   configured frameworks;
22//! - on Windows, compiles a resource file with the application icon, version information and
23//!   the [application manifest], and optionally statically links the Visual C++ runtime
24//!   (see [`WindowsAttributes`]);
25//! - on Android, generates the Gradle files of the mobile project and updates the Android
26//!   manifest with the configured file associations;
27//! - optionally runs the context code generation at build time instead of at macro expansion
28//!   time (see `Attributes::codegen`, requires the `codegen` Cargo feature).
29//!
30//! # Examples
31//!
32//! The default `build.rs` of a Tauri application:
33//!
34//! ```rust,ignore
35//! // build.rs
36//! fn main() {
37//!   tauri_build::build()
38//! }
39//! ```
40//!
41//! Customizing the build with [`Attributes`]:
42//!
43//! ```rust,ignore
44//! // build.rs
45//! fn main() {
46//!   let attributes = tauri_build::Attributes::new()
47//!     // the app commands that get a `allow-$command`/`deny-$command` permission generated
48//!     .app_manifest(tauri_build::AppManifest::new().commands(&["my_command"]))
49//!     // a plugin that lives in the app crate instead of its own crate
50//!     .plugin(
51//!       "my-plugin",
52//!       tauri_build::InlinedPlugin::new().commands(&["do_something"]),
53//!     )
54//!     .windows_attributes(
55//!       tauri_build::WindowsAttributes::new().window_icon_path("icons/icon.ico"),
56//!     );
57//!   tauri_build::try_build(attributes).expect("failed to run tauri-build");
58//! }
59//! ```
60//!
61//! See [`Attributes`] for the complete list of options: [`Attributes::config_path`],
62//! [`Attributes::capabilities_path_pattern`], [`Attributes::plugin`] / [`InlinedPlugin`],
63//! [`Attributes::app_manifest`] / [`AppManifest`], [`Attributes::windows_attributes`] /
64//! [`WindowsAttributes`] and `Attributes::codegen` / `CodegenContext` (`codegen` feature).
65//!
66//! # Environment variables
67//!
68//! In addition to the [environment variables set by cargo] for build scripts, the following
69//! variables are read:
70//!
71//! - `TAURI_CONFIG`: a JSON string that is merged into the parsed configuration file.
72//!   Set by the Tauri CLI when the configuration is changed from the command line
73//!   (e.g. `tauri build --config`). The build script reruns when it changes.
74//! - `TAURI_ANDROID_PROJECT_PATH`: path to the Android Studio project of the application,
75//!   set by the Tauri CLI on Android builds. When set, the Gradle files of the project are
76//!   regenerated and the Android manifest is updated with the configured file associations.
77//!   (the `tauri-plugin` crate reads the matching `TAURI_IOS_PROJECT_PATH` for iOS projects).
78//! - `STATIC_VCRUNTIME`: **deprecated**, use `build > windows > staticVCRuntime` in the Tauri
79//!   configuration or [`WindowsAttributes::static_vc_runtime`] instead. Any value other than
80//!   `false` statically links the Visual C++ runtime.
81//!
82//! [`tauri`]: https://docs.rs/tauri/latest/tauri/
83//! [`tauri::generate_context!`]: https://docs.rs/tauri/latest/tauri/macro.generate_context.html
84//! [cfg aliases]: https://doc.rust-lang.org/cargo/reference/build-scripts.html#cargorustc-cfgkeyvalue
85//! [application manifest]: https://learn.microsoft.com/en-us/windows/win32/sbscs/application-manifests
86//! [environment variables set by cargo]: https://doc.rust-lang.org/cargo/reference/environment-variables.html#environment-variables-cargo-sets-for-build-scripts
87
88#![doc(
89  html_logo_url = "https://github.com/tauri-apps/tauri/raw/dev/.github/icon.png",
90  html_favicon_url = "https://github.com/tauri-apps/tauri/raw/dev/.github/icon.png"
91)]
92#![cfg_attr(docsrs, feature(doc_cfg))]
93#![warn(missing_docs)]
94
95use anyhow::Context;
96pub use anyhow::Result;
97use cargo_toml::Manifest;
98
99use tauri_utils::{
100  config::{BundleResources, Config, WebviewInstallMode},
101  resources::{ResourcePaths, external_binaries},
102};
103
104use std::{
105  collections::HashMap,
106  env,
107  ffi::OsStr,
108  fs,
109  path::{Path, PathBuf},
110};
111
112mod acl;
113#[cfg(feature = "codegen")]
114mod codegen;
115mod manifest;
116mod mobile;
117
118#[cfg(feature = "codegen")]
119#[cfg_attr(docsrs, doc(cfg(feature = "codegen")))]
120pub use codegen::context::CodegenContext;
121
122pub use acl::{AppManifest, DefaultPermissionRule, InlinedPlugin};
123
124fn copy_file(from: impl AsRef<Path>, to: impl AsRef<Path>) -> Result<()> {
125  let from = from.as_ref();
126  let to = to.as_ref();
127  if !from.exists() {
128    return Err(anyhow::anyhow!("{:?} does not exist", from));
129  }
130  if !from.is_file() {
131    return Err(anyhow::anyhow!("{:?} is not a file", from));
132  }
133  let dest_dir = to.parent().expect("No data in parent");
134  fs::create_dir_all(dest_dir)?;
135  fs::copy(from, to)?;
136  Ok(())
137}
138
139fn copy_binaries(
140  binaries: ResourcePaths,
141  target_triple: &str,
142  path: &Path,
143  package_name: Option<&str>,
144) -> Result<()> {
145  for src in binaries {
146    let src = src?;
147    println!("cargo:rerun-if-changed={}", src.display());
148    let file_name = src
149      .file_name()
150      .expect("failed to extract external binary filename")
151      .to_string_lossy()
152      .replace(&format!("-{target_triple}"), "");
153
154    if package_name == Some(&file_name) {
155      return Err(anyhow::anyhow!(
156        "Cannot define a sidecar with the same name as the Cargo package name `{}`. Please change the sidecar name in the filesystem and the Tauri configuration.",
157        file_name
158      ));
159    }
160
161    let dest = path.join(file_name);
162    if dest.exists() {
163      fs::remove_file(&dest).unwrap();
164    }
165    copy_file(&src, &dest)?;
166  }
167  Ok(())
168}
169
170/// Copies resources to a path.
171fn copy_resources(resources: ResourcePaths<'_>, path: &Path) -> Result<()> {
172  let path = path.canonicalize()?;
173  let mut resources = resources.iter();
174  for resource in resources.by_ref() {
175    let resource = resource?;
176
177    // avoid copying the resource if target is the same as source
178    let src = resource.path().canonicalize()?;
179    let target = path.join(resource.target());
180    if src != target {
181      copy_file(src, target)?;
182    }
183  }
184
185  for path in resources.rerun_if_changed() {
186    println!("cargo:rerun-if-changed={}", path.display());
187  }
188
189  Ok(())
190}
191
192#[cfg(unix)]
193fn symlink_dir(src: &Path, dst: &Path) -> std::io::Result<()> {
194  std::os::unix::fs::symlink(src, dst)
195}
196
197/// Makes a symbolic link to a directory.
198#[cfg(windows)]
199fn symlink_dir(src: &Path, dst: &Path) -> std::io::Result<()> {
200  std::os::windows::fs::symlink_dir(src, dst)
201}
202
203/// Makes a symbolic link to a file.
204#[cfg(unix)]
205fn symlink_file(src: &Path, dst: &Path) -> std::io::Result<()> {
206  std::os::unix::fs::symlink(src, dst)
207}
208
209/// Makes a symbolic link to a file.
210#[cfg(windows)]
211fn symlink_file(src: &Path, dst: &Path) -> std::io::Result<()> {
212  std::os::windows::fs::symlink_file(src, dst)
213}
214
215fn copy_dir(from: &Path, to: &Path) -> Result<()> {
216  for entry in walkdir::WalkDir::new(from) {
217    let entry = entry?;
218    debug_assert!(entry.path().starts_with(from));
219    let rel_path = entry.path().strip_prefix(from)?;
220    let dest_path = to.join(rel_path);
221    if entry.file_type().is_symlink() {
222      let target = fs::read_link(entry.path())?;
223      if entry.path().is_dir() {
224        symlink_dir(&target, &dest_path)?;
225      } else {
226        symlink_file(&target, &dest_path)?;
227      }
228    } else if entry.file_type().is_dir() {
229      fs::create_dir(dest_path)?;
230    } else {
231      fs::copy(entry.path(), dest_path)?;
232    }
233  }
234  Ok(())
235}
236
237// Copies the framework under `{src_dir}/{framework}.framework` to `{dest_dir}/{framework}.framework`.
238fn copy_framework_from(src_dir: &Path, framework: &str, dest_dir: &Path) -> Result<bool> {
239  let src_name = format!("{framework}.framework");
240  let src_path = src_dir.join(&src_name);
241  if src_path.exists() {
242    copy_dir(&src_path, &dest_dir.join(&src_name))?;
243    Ok(true)
244  } else {
245    Ok(false)
246  }
247}
248
249// Copies the macOS application bundle frameworks to the target folder
250fn copy_frameworks(dest_dir: &Path, frameworks: &[String]) -> Result<()> {
251  fs::create_dir_all(dest_dir)
252    .with_context(|| format!("Failed to create frameworks output directory at {dest_dir:?}"))?;
253  for framework in frameworks.iter() {
254    if framework.ends_with(".framework") {
255      let src_path = Path::new(framework);
256      let src_name = src_path
257        .file_name()
258        .expect("Couldn't get framework filename");
259      let dest_path = dest_dir.join(src_name);
260      copy_dir(src_path, &dest_path)?;
261      continue;
262    } else if framework.ends_with(".dylib") {
263      let src_path = Path::new(framework);
264      if !src_path.exists() {
265        return Err(anyhow::anyhow!("Library not found: {}", framework));
266      }
267      let src_name = src_path.file_name().expect("Couldn't get library filename");
268      let dest_path = dest_dir.join(src_name);
269      copy_file(src_path, &dest_path)?;
270      continue;
271    } else if framework.contains('/') {
272      return Err(anyhow::anyhow!(
273        "Framework path should have .framework extension: {}",
274        framework
275      ));
276    }
277    if let Some(home_dir) = dirs::home_dir() {
278      if copy_framework_from(&home_dir.join("Library/Frameworks/"), framework, dest_dir)? {
279        continue;
280      }
281    }
282    if copy_framework_from("/Library/Frameworks/".as_ref(), framework, dest_dir)?
283      || copy_framework_from("/Network/Library/Frameworks/".as_ref(), framework, dest_dir)?
284    {
285      continue;
286    }
287  }
288  Ok(())
289}
290
291// TODO: far from ideal, but there's no other way to get the target dir, see <https://github.com/rust-lang/cargo/issues/5457>
292// resolves the target dir from `OUT_DIR`, which is `<target dir>/build/<pkg>-<hash>/out` on stable
293// and `<target dir>/build/<pkg>/<hash>/out` on recent nightlies, so we walk up to the `build` dir
294// and take its parent instead of assuming a fixed depth.
295fn target_dir_from_out_dir(out_dir: &Path) -> Option<&Path> {
296  out_dir
297    .ancestors()
298    .find(|path| path.file_name() == Some(OsStr::new("build")))
299    .and_then(|build_dir| build_dir.parent())
300}
301
302// creates a cfg alias if `has_feature` is true.
303// `alias` must be a snake case string.
304fn cfg_alias(alias: &str, has_feature: bool) {
305  println!("cargo:rustc-check-cfg=cfg({alias})");
306  if has_feature {
307    println!("cargo:rustc-cfg={alias}");
308  }
309}
310
311/// Attributes used on Windows.
312#[allow(dead_code)]
313#[derive(Debug)]
314pub struct WindowsAttributes {
315  window_icon_path: Option<PathBuf>,
316  /// Whether to statically link the Visual C++ runtime into the application binary on Windows MSVC targets
317  static_vc_runtime: Option<bool>,
318  /// A string containing an [application manifest] to be included with the application on Windows.
319  ///
320  /// Defaults to:
321  /// ```text
322  #[doc = include_str!("windows-app-manifest.xml")]
323  /// ```
324  ///
325  /// ## Warning
326  ///
327  /// if you are using tauri's dialog APIs, you need to specify a dependency on Common Control v6 by adding the following to your custom manifest:
328  /// ```text
329  ///  <dependency>
330  ///    <dependentAssembly>
331  ///      <assemblyIdentity
332  ///        type="win32"
333  ///        name="Microsoft.Windows.Common-Controls"
334  ///        version="6.0.0.0"
335  ///        processorArchitecture="*"
336  ///        publicKeyToken="6595b64144ccf1df"
337  ///        language="*"
338  ///      />
339  ///    </dependentAssembly>
340  ///  </dependency>
341  /// ```
342  ///
343  /// [application manifest]: https://learn.microsoft.com/en-us/windows/win32/sbscs/application-manifests
344  app_manifest: Option<String>,
345  /// A series of strings containing additional .rc content to be appended to the generated resource file on Windows.
346  append_rc_content: Vec<String>,
347}
348
349impl Default for WindowsAttributes {
350  fn default() -> Self {
351    Self::new()
352  }
353}
354
355impl WindowsAttributes {
356  /// Creates the default attribute set.
357  pub fn new() -> Self {
358    Self {
359      static_vc_runtime: None,
360      app_manifest: Some(include_str!("windows-app-manifest.xml").into()),
361      window_icon_path: None,
362      append_rc_content: Vec::new(),
363    }
364  }
365
366  /// Creates the default attribute set without the default app manifest.
367  #[must_use]
368  pub fn new_without_app_manifest() -> Self {
369    Self {
370      app_manifest: None,
371      window_icon_path: None,
372      static_vc_runtime: None,
373      append_rc_content: Vec::new(),
374    }
375  }
376
377  /// Sets the icon to use as the application icon and default window icon.
378  /// It must be in `ico` format.
379  ///
380  /// If not set, we will search for a `.ico` from the `bundle > icon` in your tauri config file, then `icons/icon.ico`.
381  #[must_use]
382  pub fn window_icon_path<P: AsRef<Path>>(mut self, window_icon_path: P) -> Self {
383    self
384      .window_icon_path
385      .replace(window_icon_path.as_ref().into());
386    self
387  }
388
389  /// Sets whether to statically link the Visual C++ runtime into the application binary on Windows MSVC targets.
390  ///
391  /// If unset, this is read from `build > windows > staticVCRuntime` in the Tauri configuration.
392  #[must_use]
393  pub fn static_vc_runtime(mut self, static_vc_runtime: bool) -> Self {
394    self.static_vc_runtime.replace(static_vc_runtime);
395    self
396  }
397
398  /// Sets the [application manifest] to be included with the application on Windows.
399  ///
400  /// Defaults to:
401  /// ```text
402  #[doc = include_str!("windows-app-manifest.xml")]
403  /// ```
404  ///
405  /// ## Warning
406  ///
407  /// if you are using tauri's dialog APIs, you need to specify a dependency on Common Control v6 by adding the following to your custom manifest:
408  /// ```text
409  ///  <dependency>
410  ///    <dependentAssembly>
411  ///      <assemblyIdentity
412  ///        type="win32"
413  ///        name="Microsoft.Windows.Common-Controls"
414  ///        version="6.0.0.0"
415  ///        processorArchitecture="*"
416  ///        publicKeyToken="6595b64144ccf1df"
417  ///        language="*"
418  ///      />
419  ///    </dependentAssembly>
420  ///  </dependency>
421  /// ```
422  ///
423  /// # Example
424  ///
425  /// The following manifest will brand the exe as requesting administrator privileges.
426  /// Thus, every time it is executed, a Windows UAC dialog will appear.
427  ///
428  /// ```rust,no_run
429  /// let mut windows = tauri_build::WindowsAttributes::new();
430  /// windows = windows.app_manifest(r#"
431  /// <assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
432  ///   <trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
433  ///       <security>
434  ///           <requestedPrivileges>
435  ///               <requestedExecutionLevel level="requireAdministrator" uiAccess="false" />
436  ///           </requestedPrivileges>
437  ///       </security>
438  ///   </trustInfo>
439  /// </assembly>
440  /// "#);
441  /// let attrs =  tauri_build::Attributes::new().windows_attributes(windows);
442  /// tauri_build::try_build(attrs).expect("failed to run build script");
443  /// ```
444  ///
445  /// Note that you can move the manifest contents to a separate file and use `include_str!("manifest.xml")`
446  /// instead of the inline string.
447  ///
448  /// [manifest]: https://learn.microsoft.com/en-us/windows/win32/sbscs/application-manifests
449  #[must_use]
450  pub fn app_manifest<S: AsRef<str>>(mut self, manifest: S) -> Self {
451    self.app_manifest = Some(manifest.as_ref().to_string());
452    self
453  }
454
455  /// Append additional .rc content to the generated resource file on Windows.
456  /// This can be called multiple times to append multiple contents.
457  #[must_use]
458  pub fn append_rc_content<S: Into<String>>(mut self, content: S) -> Self {
459    self.append_rc_content.push(content.into());
460    self
461  }
462}
463
464/// The attributes used on the build.
465#[derive(Debug, Default)]
466pub struct Attributes {
467  #[allow(dead_code)]
468  windows_attributes: WindowsAttributes,
469  capabilities_path_pattern: Option<&'static str>,
470  config_path: Option<PathBuf>,
471  #[cfg(feature = "codegen")]
472  codegen: Option<codegen::context::CodegenContext>,
473  inlined_plugins: HashMap<&'static str, InlinedPlugin>,
474  app_manifest: AppManifest,
475}
476
477impl Attributes {
478  /// Creates the default attribute set.
479  pub fn new() -> Self {
480    Self::default()
481  }
482
483  /// Sets the [`WindowsAttributes`], the Windows-specific options of the build script.
484  ///
485  /// They are used to configure the [application manifest], the application icon, additional
486  /// `.rc` content and the static linking of the Visual C++ runtime.
487  /// Setting them has no effect when compiling for other platforms.
488  ///
489  /// # Examples
490  ///
491  /// ```rust,no_run
492  /// let attributes = tauri_build::Attributes::new().windows_attributes(
493  ///   tauri_build::WindowsAttributes::new()
494  ///     .window_icon_path("icons/icon.ico")
495  ///     .static_vc_runtime(true),
496  /// );
497  /// tauri_build::try_build(attributes).expect("failed to run tauri-build");
498  /// ```
499  ///
500  /// [application manifest]: https://learn.microsoft.com/en-us/windows/win32/sbscs/application-manifests
501  #[must_use]
502  pub fn windows_attributes(mut self, windows_attributes: WindowsAttributes) -> Self {
503    self.windows_attributes = windows_attributes;
504    self
505  }
506
507  /// Set the glob pattern to be used to find the capabilities.
508  ///
509  /// **WARNING:** The `removeUnusedCommands` option does not work with a custom capabilities path.
510  ///
511  /// **Note:** You must emit [rerun-if-changed] instructions for your capabilities directory.
512  ///
513  /// [rerun-if-changed]: https://doc.rust-lang.org/cargo/reference/build-scripts.html#rerun-if-changed
514  #[must_use]
515  pub fn capabilities_path_pattern(mut self, pattern: &'static str) -> Self {
516    self.capabilities_path_pattern.replace(pattern);
517    self
518  }
519
520  /// Adds the given plugin to the list of inlined plugins (a plugin that is part of your application).
521  ///
522  /// See [`InlinedPlugin`] for more information.
523  pub fn plugin(mut self, name: &'static str, plugin: InlinedPlugin) -> Self {
524    self.inlined_plugins.insert(name, plugin);
525    self
526  }
527
528  /// Adds the given list of plugins to the list of inlined plugins (a plugin that is part of your application).
529  ///
530  /// See [`InlinedPlugin`] for more information.
531  pub fn plugins<I>(mut self, plugins: I) -> Self
532  where
533    I: IntoIterator<Item = (&'static str, InlinedPlugin)>,
534  {
535    self.inlined_plugins.extend(plugins);
536    self
537  }
538
539  /// Set the path to the `tauri.conf.json` (relative to the crate's directory).
540  ///
541  /// This defaults to a file called `tauri.conf.json` inside of the current working directory of
542  /// the crate compiling; does not need to be set manually if that config file is in the same
543  /// directory as your `Cargo.toml`.
544  pub fn config_path(mut self, config_path: impl Into<PathBuf>) -> Self {
545    self.config_path = Some(config_path.into());
546    self
547  }
548
549  /// Sets the application manifest for the Access Control List.
550  ///
551  /// See [`AppManifest`] for more information.
552  pub fn app_manifest(mut self, manifest: AppManifest) -> Self {
553    self.app_manifest = manifest;
554    self
555  }
556
557  /// Generates the Tauri application context at build time instead of at macro expansion time.
558  ///
559  /// The context is written to a file in the `OUT_DIR` that the
560  /// [`tauri::tauri_build_context!`] macro includes, so the application uses
561  /// `tauri::Builder::run(tauri::tauri_build_context!())` instead of
562  /// `tauri::Builder::run(tauri::generate_context!())`.
563  ///
564  /// This moves the asset embedding and the ACL resolution to the build script, which means the
565  /// `cargo:rerun-if-changed` instructions emitted for the frontend assets, icons and
566  /// configuration files are respected - with `generate_context!` the code is only regenerated
567  /// when the crate itself is recompiled.
568  ///
569  /// See [`CodegenContext`] for the available options.
570  ///
571  /// Requires the `codegen` Cargo feature.
572  ///
573  /// # Examples
574  ///
575  /// ```rust,no_run
576  /// let attributes = tauri_build::Attributes::new().codegen(tauri_build::CodegenContext::new());
577  /// tauri_build::try_build(attributes).expect("failed to run tauri-build");
578  /// ```
579  ///
580  /// [`tauri::tauri_build_context!`]: https://docs.rs/tauri/latest/tauri/macro.tauri_build_context.html
581  #[cfg(feature = "codegen")]
582  #[cfg_attr(docsrs, doc(cfg(feature = "codegen")))]
583  #[must_use]
584  pub fn codegen(mut self, codegen: codegen::context::CodegenContext) -> Self {
585    self.codegen.replace(codegen);
586    self
587  }
588}
589
590/// Whether the app is being compiled for development (`tauri dev`) or not.
591///
592/// It reads the `DEP_TAURI_DEV` environment variable, which is set by the build script of the
593/// `tauri` crate from the `cargo:dev` instruction, and is `true` when the `tauri` crate is
594/// compiled without the `custom-protocol` Cargo feature.
595///
596/// [`try_build`] uses it to define the `dev` [cfg alias], so prefer `#[cfg(dev)]` on your app
597/// code; this function is meant for `build.rs` code that must branch on the development build.
598///
599/// # Panics
600///
601/// Panics if the `DEP_TAURI_DEV` environment variable is not set, which means the crate calling
602/// it does not depend on a `tauri` version that emits the `cargo:dev` instruction.
603/// Update the `tauri` dependency to the latest version to fix it.
604///
605/// [cfg alias]: https://doc.rust-lang.org/cargo/reference/build-scripts.html#cargorustc-cfgkeyvalue
606pub fn is_dev() -> bool {
607  env::var_os("DEP_TAURI_DEV")
608    .expect("missing `cargo:dev` instruction, please update tauri to latest")
609    == "true"
610}
611
612/// Run all build time helpers for your Tauri Application.
613///
614/// To provide extra configuration, such as [`AppManifest::commands`]
615/// for fine-grained control over command permissions, see [`try_build`].
616/// See [`Attributes`] for the complete list of configuration options.
617///
618/// # Platforms
619///
620/// [`build()`] should be called inside of `build.rs` regardless of the platform, so **DO NOT** use a [conditional compilation]
621/// check that prevents it from running on any of your targets.
622///
623/// Platform specific code is handled by the helpers automatically.
624///
625/// A build script is required in order to activate some cargo environmental variables that are
626/// used when generating code and embedding assets.
627///
628/// # Panics
629///
630/// If any of the build time helpers fail, they will [`std::panic!`] with the related error message.
631/// This is typically desirable when running inside a build script; see [`try_build`] for no panics.
632///
633/// [conditional compilation]: https://web.mit.edu/rust-lang_v1.25/arch/amd64_ubuntu1404/share/doc/rust/html/book/first-edition/conditional-compilation.html
634pub fn build() {
635  if let Err(error) = try_build(Attributes::default()) {
636    let error = format!("{error:#}");
637    println!("{error}");
638    if error.starts_with("unknown field") {
639      print!(
640        "found an unknown configuration field. This usually means that you are using a CLI version that is newer than `tauri-build` and is incompatible. "
641      );
642      println!(
643        "Please try updating the Rust crates by running `cargo update` in the Tauri app folder."
644      );
645    }
646    std::process::exit(1);
647  }
648}
649
650/// Same as [`build()`], but takes an extra configuration argument, and does not panic.
651#[allow(unused_variables)]
652pub fn try_build(attributes: Attributes) -> Result<()> {
653  use anyhow::anyhow;
654
655  println!("cargo:rerun-if-env-changed=TAURI_CONFIG");
656
657  let target_os = env::var_os("CARGO_CFG_TARGET_OS").unwrap();
658  let mobile = target_os == "ios" || target_os == "android";
659  cfg_alias("desktop", !mobile);
660  cfg_alias("mobile", mobile);
661
662  let target_triple = env::var("TARGET").unwrap();
663  let target = tauri_utils::platform::Target::from_triple(&target_triple);
664
665  let config_root = if let Some(config_path) = &attributes.config_path {
666    config_path.parent().with_context(|| {
667      format!(
668        "`config_path` '{}' doesn't have a parent directory",
669        config_path.display()
670      )
671    })?
672  } else {
673    &env::current_dir().unwrap()
674  };
675
676  let (mut config, config_paths) = tauri_utils::config::parse::read_from(target, config_root)?;
677
678  for config_file_path in config_paths {
679    println!("cargo:rerun-if-changed={}", config_file_path.display());
680  }
681  if let Ok(env) = env::var("TAURI_CONFIG") {
682    let merge_config: serde_json::Value = serde_json::from_str(&env)?;
683    json_patch::merge(&mut config, &merge_config);
684  }
685  let config: Config = serde_json::from_value(config)?;
686  let static_vc_runtime = should_static_link_vc_runtime(&config, &attributes);
687
688  let s = config.identifier.split('.');
689  let last = s.clone().count() - 1;
690  let mut android_package_prefix = String::new();
691  for (i, w) in s.enumerate() {
692    if i == last {
693      println!(
694        "cargo:rustc-env=TAURI_ANDROID_PACKAGE_NAME_APP_NAME={}",
695        w.replace('-', "_")
696      );
697    } else {
698      android_package_prefix.push_str(&w.replace(['_', '-'], "_1"));
699      android_package_prefix.push('_');
700    }
701  }
702  android_package_prefix.pop();
703  println!("cargo:rustc-env=TAURI_ANDROID_PACKAGE_NAME_PREFIX={android_package_prefix}");
704
705  if let Some(project_dir) = env::var_os("TAURI_ANDROID_PROJECT_PATH").map(PathBuf::from) {
706    mobile::generate_gradle_files(project_dir)?;
707
708    // Update Android manifest with file associations
709    if let Some(associations) = config.bundle.file_associations.as_ref() {
710      mobile::update_android_manifest_file_associations(associations)?;
711    }
712  }
713
714  cfg_alias("dev", is_dev());
715
716  let cargo_toml_path = Path::new("Cargo.toml").canonicalize()?;
717  let mut manifest = Manifest::<cargo_toml::Value>::from_path_with_metadata(cargo_toml_path)?;
718
719  let out_dir = PathBuf::from(env::var_os("OUT_DIR").unwrap());
720
721  manifest::check(&config, &mut manifest)?;
722
723  acl::build(&out_dir, target, &config, &attributes)?;
724
725  tauri_utils::plugin::save_global_api_scripts_paths(&out_dir, None);
726
727  println!("cargo:rustc-env=TAURI_ENV_TARGET_TRIPLE={target_triple}");
728  // when running codegen in this build script, we need to access the env var directly
729  // FIXME: This can be accessed from multiple threads
730  unsafe { env::set_var("TAURI_ENV_TARGET_TRIPLE", &target_triple) };
731
732  let target_dir = target_dir_from_out_dir(&out_dir)
733    .with_context(|| format!("failed to resolve the target directory from {out_dir:?}"))?;
734
735  if let Some(paths) = &config.bundle.external_bin {
736    copy_binaries(
737      ResourcePaths::new(&external_binaries(paths, &target_triple, &target), true),
738      &target_triple,
739      target_dir,
740      manifest.package.as_ref().map(|p| p.name.as_ref()),
741    )?;
742  }
743
744  let mut resources = config
745    .bundle
746    .resources
747    .clone()
748    .unwrap_or(BundleResources::List(Vec::new()));
749  if target_triple.contains("windows") {
750    if let Some(fixed_webview2_runtime_path) = match &config.bundle.windows.webview_install_mode {
751      WebviewInstallMode::FixedRuntime { path } => Some(path),
752      _ => None,
753    } {
754      resources.push(fixed_webview2_runtime_path.display().to_string());
755    }
756  }
757  match resources {
758    BundleResources::List(res) => {
759      copy_resources(ResourcePaths::new(res.as_slice(), true), target_dir)?
760    }
761    BundleResources::Map(map) => copy_resources(ResourcePaths::from_map(&map, true), target_dir)?,
762  }
763
764  if target_triple.contains("darwin") {
765    if let Some(frameworks) = &config.bundle.macos.frameworks {
766      if !frameworks.is_empty() {
767        let frameworks_dir = target_dir.parent().unwrap().join("Frameworks");
768        let _ = fs::remove_dir_all(&frameworks_dir);
769        // copy frameworks to the root `target` folder (instead of `target/debug` for instance)
770        // because the rpath is set to `@executable_path/../Frameworks`.
771        copy_frameworks(&frameworks_dir, frameworks)?;
772
773        // If we have frameworks, we need to set the @rpath
774        // https://github.com/tauri-apps/tauri/issues/7710
775        println!("cargo:rustc-link-arg=-Wl,-rpath,@executable_path/../Frameworks");
776      }
777    }
778
779    if !is_dev() {
780      if let Some(version) = &config.bundle.macos.minimum_system_version {
781        println!("cargo:rustc-env=MACOSX_DEPLOYMENT_TARGET={version}");
782      }
783    }
784  }
785
786  if target_triple.contains("ios") {
787    println!(
788      "cargo:rustc-env=IPHONEOS_DEPLOYMENT_TARGET={}",
789      config.bundle.ios.minimum_system_version
790    );
791  }
792
793  if target_triple.contains("windows") {
794    use semver::Version;
795    use tauri_winres::{VersionInfo, WindowsResource};
796
797    let window_icon_path = attributes
798      .windows_attributes
799      .window_icon_path
800      .unwrap_or_else(|| {
801        // icon paths in the config are relative to the config file
802        config_root.join(
803          config
804            .bundle
805            .icon
806            .iter()
807            .find(|i| i.ends_with(".ico"))
808            .map(AsRef::as_ref)
809            .unwrap_or("icons/icon.ico"),
810        )
811      });
812
813    let mut res = WindowsResource::new();
814
815    if let Some(manifest) = attributes.windows_attributes.app_manifest {
816      res.set_manifest(&manifest);
817    }
818
819    for content in attributes.windows_attributes.append_rc_content {
820      res.append_rc_content(&content);
821    }
822
823    if let Some(version_str) = &config.version {
824      if let Ok(v) = Version::parse(version_str) {
825        let version = to_winres_version(&v);
826        res.set_version_info(VersionInfo::FILEVERSION, version);
827        res.set_version_info(VersionInfo::PRODUCTVERSION, version);
828        res.set("FileVersion", version_str);
829        res.set("ProductVersion", version_str);
830      }
831    }
832
833    if let Some(product_name) = &config.product_name {
834      res.set("ProductName", product_name);
835    }
836
837    let company_name = config.bundle.publisher.unwrap_or_else(|| {
838      config
839        .identifier
840        .split('.')
841        .nth(1)
842        .unwrap_or(&config.identifier)
843        .to_string()
844    });
845
846    res.set("CompanyName", &company_name);
847
848    let file_description = config
849      .product_name
850      .or_else(|| manifest.package.as_ref().map(|p| p.name.clone()))
851      .or_else(|| std::env::var("CARGO_PKG_NAME").ok());
852
853    res.set("FileDescription", &file_description.unwrap());
854
855    if let Some(copyright) = &config.bundle.copyright {
856      res.set("LegalCopyright", copyright);
857    }
858
859    if window_icon_path.exists() {
860      res.set_icon_with_id(
861        &window_icon_path.display().to_string(),
862        &tauri_utils::platform::WINDOWS_APP_ICON_RESOURCE_ID.to_string(),
863      );
864    } else {
865      return Err(anyhow!(format!(
866        "`{}` not found; required for generating a Windows Resource file during tauri-build",
867        window_icon_path.display()
868      )));
869    }
870
871    res.compile().with_context(|| {
872      format!(
873        "failed to compile `{}` into a Windows Resource file during tauri-build",
874        window_icon_path.display()
875      )
876    })?;
877
878    let target_env = env::var("CARGO_CFG_TARGET_ENV").unwrap();
879    match target_env.as_str() {
880      "gnu" => {
881        let target_arch = match env::var("CARGO_CFG_TARGET_ARCH").unwrap().as_str() {
882          "x86_64" => Some("x64"),
883          "x86" => Some("x86"),
884          "aarch64" => Some("arm64"),
885          arch => None,
886        };
887        if let Some(target_arch) = target_arch {
888          // The `out` dir is
889          //   - `<target dir>/build/<pkg>-<hash>/out` on stable
890          //   - `<target dir>/build/<pkg>/<hash>/out` on recent nightlies
891          let build_dir = target_dir.join("build");
892          let webview2_com_sys_build = build_dir.join("webview2-com-sys");
893          let (build_dir, new_layout) = if webview2_com_sys_build.exists() {
894            (webview2_com_sys_build, true)
895          } else {
896            (build_dir, false)
897          };
898          for entry in fs::read_dir(build_dir)? {
899            let path = entry?.path();
900            let webview2_loader_path = path
901              .join("out")
902              .join(target_arch)
903              .join("WebView2Loader.dll");
904            if webview2_loader_path.exists()
905              && (new_layout || path.to_string_lossy().contains("webview2-com-sys"))
906            {
907              fs::copy(webview2_loader_path, target_dir.join("WebView2Loader.dll"))?;
908              break;
909            }
910          }
911        }
912      }
913      "msvc" if static_vc_runtime => {
914        static_vcruntime::metabuild();
915      }
916      _ => (),
917    }
918  }
919
920  #[cfg(feature = "codegen")]
921  if let Some(mut codegen) = attributes.codegen {
922    if codegen.config_path.is_none() {
923      codegen.config_path = attributes.config_path;
924    }
925    codegen.try_build()?;
926  }
927
928  Ok(())
929}
930
931fn to_winres_version(v: &semver::Version) -> u64 {
932  let build = v.build.parse::<u16>().map(u64::from).unwrap_or(0);
933
934  (v.major << 48) | (v.minor << 32) | (v.patch << 16) | build
935}
936
937fn should_static_link_vc_runtime(config: &Config, attributes: &Attributes) -> bool {
938  if let Some(value) = env::var_os("STATIC_VCRUNTIME") {
939    println!(
940      "cargo:warning=STATIC_VCRUNTIME is deprecated; use build.windows.staticVCRuntime in tauri.conf.json or tauri_build::WindowsAttributes::static_vc_runtime instead."
941    );
942    value != "false"
943  } else {
944    attributes
945      .windows_attributes
946      .static_vc_runtime
947      .unwrap_or(config.build.windows.static_vc_runtime)
948  }
949}
950
951#[cfg(test)]
952mod tests {
953  use semver::Version;
954  use std::path::Path;
955
956  #[test]
957  fn target_dir_from_stable_out_dir() {
958    let out_dir = Path::new("/app/target/debug/build/app-63ba68eead531e35/out");
959
960    assert_eq!(
961      crate::target_dir_from_out_dir(out_dir),
962      Some(Path::new("/app/target/debug"))
963    );
964  }
965
966  #[test]
967  fn target_dir_from_nightly_out_dir() {
968    let out_dir = Path::new("/app/target/debug/build/app/63ba68eead531e35/out");
969
970    assert_eq!(
971      crate::target_dir_from_out_dir(out_dir),
972      Some(Path::new("/app/target/debug"))
973    );
974  }
975
976  #[test]
977  fn target_dir_from_out_dir_with_triple() {
978    let out_dir =
979      Path::new("/app/target/aarch64-apple-darwin/release/build/app/63ba68eead531e35/out");
980
981    assert_eq!(
982      crate::target_dir_from_out_dir(out_dir),
983      Some(Path::new("/app/target/aarch64-apple-darwin/release"))
984    );
985  }
986
987  #[test]
988  fn version_uses_numeric_build_metadata() {
989    let version = Version::parse("1.2.3+42").unwrap();
990
991    assert_eq!(
992      crate::to_winres_version(&version),
993      (1 << 48) | (2 << 32) | (3 << 16) | 42
994    );
995  }
996
997  #[test]
998  fn version_ignores_non_numeric_composite_build_metadata() {
999    let version = Version::parse("1.2.3+42.sha").unwrap();
1000
1001    assert_eq!(
1002      crate::to_winres_version(&version),
1003      (1 << 48) | (2 << 32) | (3 << 16)
1004    );
1005  }
1006
1007  #[test]
1008  fn version_ignores_non_numeric_build_metadata() {
1009    let version = Version::parse("1.2.3+abc").unwrap();
1010
1011    assert_eq!(
1012      crate::to_winres_version(&version),
1013      (1 << 48) | (2 << 32) | (3 << 16)
1014    );
1015  }
1016
1017  #[test]
1018  fn version_ignores_build_metadata_that_does_not_fit_in_u16() {
1019    let version = Version::parse("1.2.3+70000").unwrap();
1020
1021    assert_eq!(
1022      crate::to_winres_version(&version),
1023      (1 << 48) | (2 << 32) | (3 << 16)
1024    );
1025  }
1026
1027  #[test]
1028  #[serial_test::serial]
1029  fn static_vc_runtime_chain() {
1030    // 1. Nothing is set, should default to true
1031    let config = tauri_utils::config::Config::default();
1032    let attributes = crate::Attributes::new();
1033    assert!(crate::should_static_link_vc_runtime(&config, &attributes));
1034
1035    // 2. Set to anything but "false" in env, should be true
1036    unsafe { std::env::set_var("STATIC_VCRUNTIME", "qweqe") };
1037    let config = tauri_utils::config::Config::default();
1038    let attributes = crate::Attributes::new();
1039    assert!(crate::should_static_link_vc_runtime(&config, &attributes));
1040    unsafe { std::env::remove_var("STATIC_VCRUNTIME") };
1041
1042    // 3. Set to "false" in env, should be false
1043    unsafe { std::env::set_var("STATIC_VCRUNTIME", "false") };
1044    let config = tauri_utils::config::Config::default();
1045    let attributes = crate::Attributes::new();
1046    assert!(!crate::should_static_link_vc_runtime(&config, &attributes));
1047    unsafe { std::env::remove_var("STATIC_VCRUNTIME") };
1048
1049    // 4. Set to true in attributes, should be true
1050    let config = tauri_utils::config::Config::default();
1051    let attributes = crate::Attributes::new()
1052      .windows_attributes(crate::WindowsAttributes::new().static_vc_runtime(true));
1053    assert!(crate::should_static_link_vc_runtime(&config, &attributes));
1054
1055    // 5. Set to false in attributes, should be false
1056    let config = tauri_utils::config::Config::default();
1057    let attributes = crate::Attributes::new()
1058      .windows_attributes(crate::WindowsAttributes::new().static_vc_runtime(false));
1059    assert!(!crate::should_static_link_vc_runtime(&config, &attributes));
1060
1061    // 6. Set to true in config, should be true
1062    let config = tauri_utils::config::Config {
1063      build: tauri_utils::config::BuildConfig {
1064        windows: tauri_utils::config::WindowsBuildConfig {
1065          static_vc_runtime: true,
1066        },
1067        ..Default::default()
1068      },
1069      ..Default::default()
1070    };
1071    let attributes = crate::Attributes::new();
1072    assert!(crate::should_static_link_vc_runtime(&config, &attributes));
1073
1074    // 7. Set to false in config, should be false
1075    let config = tauri_utils::config::Config {
1076      build: tauri_utils::config::BuildConfig {
1077        windows: tauri_utils::config::WindowsBuildConfig {
1078          static_vc_runtime: false,
1079        },
1080        ..Default::default()
1081      },
1082      ..Default::default()
1083    };
1084    let attributes = crate::Attributes::new();
1085    assert!(!crate::should_static_link_vc_runtime(&config, &attributes));
1086
1087    // 8. Set to true in config and false in attributes, should be false because attributes takes precedence over config
1088    let config = tauri_utils::config::Config {
1089      build: tauri_utils::config::BuildConfig {
1090        windows: tauri_utils::config::WindowsBuildConfig {
1091          static_vc_runtime: true,
1092        },
1093        ..Default::default()
1094      },
1095      ..Default::default()
1096    };
1097    let attributes = crate::Attributes::new()
1098      .windows_attributes(crate::WindowsAttributes::new().static_vc_runtime(false));
1099    assert!(!crate::should_static_link_vc_runtime(&config, &attributes));
1100
1101    // 9. Set to false in env and true in attributes, should be false because env takes precedence over attributes
1102    unsafe { std::env::set_var("STATIC_VCRUNTIME", "false") };
1103    let config = tauri_utils::config::Config::default();
1104    let attributes = crate::Attributes::new()
1105      .windows_attributes(crate::WindowsAttributes::new().static_vc_runtime(true));
1106    assert!(!crate::should_static_link_vc_runtime(&config, &attributes));
1107    unsafe { std::env::remove_var("STATIC_VCRUNTIME") };
1108  }
1109}