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