lib.rs 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770
  1. // Copyright 2019-2023 Tauri Programme within The Commons Conservancy
  2. // SPDX-License-Identifier: Apache-2.0
  3. // SPDX-License-Identifier: MIT
  4. //! [![](https://github.com/tauri-apps/tauri/raw/dev/.github/splash.png)](https://tauri.app)
  5. //!
  6. //! This applies the macros at build-time in order to rig some special features needed by `cargo`.
  7. #![doc(
  8. html_logo_url = "https://github.com/tauri-apps/tauri/raw/dev/app-icon.png",
  9. html_favicon_url = "https://github.com/tauri-apps/tauri/raw/dev/app-icon.png"
  10. )]
  11. #![cfg_attr(docsrs, feature(doc_cfg))]
  12. use anyhow::Context;
  13. pub use anyhow::Result;
  14. use cargo_toml::Manifest;
  15. use tauri_utils::{
  16. acl::build::parse_capabilities,
  17. config::{BundleResources, Config, WebviewInstallMode},
  18. resources::{external_binaries, ResourcePaths},
  19. };
  20. use std::{
  21. collections::HashMap,
  22. env::var_os,
  23. fs::copy,
  24. path::{Path, PathBuf},
  25. };
  26. mod acl;
  27. #[cfg(feature = "codegen")]
  28. mod codegen;
  29. mod manifest;
  30. mod mobile;
  31. mod static_vcruntime;
  32. #[cfg(feature = "codegen")]
  33. #[cfg_attr(docsrs, doc(cfg(feature = "codegen")))]
  34. pub use codegen::context::CodegenContext;
  35. const PLUGIN_MANIFESTS_FILE_NAME: &str = "plugin-manifests.json";
  36. const CAPABILITIES_FILE_NAME: &str = "capabilities.json";
  37. fn copy_file(from: impl AsRef<Path>, to: impl AsRef<Path>) -> Result<()> {
  38. let from = from.as_ref();
  39. let to = to.as_ref();
  40. if !from.exists() {
  41. return Err(anyhow::anyhow!("{:?} does not exist", from));
  42. }
  43. if !from.is_file() {
  44. return Err(anyhow::anyhow!("{:?} is not a file", from));
  45. }
  46. let dest_dir = to.parent().expect("No data in parent");
  47. std::fs::create_dir_all(dest_dir)?;
  48. std::fs::copy(from, to)?;
  49. Ok(())
  50. }
  51. fn copy_binaries(
  52. binaries: ResourcePaths,
  53. target_triple: &str,
  54. path: &Path,
  55. package_name: Option<&String>,
  56. ) -> Result<()> {
  57. for src in binaries {
  58. let src = src?;
  59. println!("cargo:rerun-if-changed={}", src.display());
  60. let file_name = src
  61. .file_name()
  62. .expect("failed to extract external binary filename")
  63. .to_string_lossy()
  64. .replace(&format!("-{target_triple}"), "");
  65. if package_name.map_or(false, |n| n == &file_name) {
  66. return Err(anyhow::anyhow!(
  67. "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.",
  68. file_name
  69. ));
  70. }
  71. let dest = path.join(file_name);
  72. if dest.exists() {
  73. std::fs::remove_file(&dest).unwrap();
  74. }
  75. copy_file(&src, &dest)?;
  76. }
  77. Ok(())
  78. }
  79. /// Copies resources to a path.
  80. fn copy_resources(resources: ResourcePaths<'_>, path: &Path) -> Result<()> {
  81. for resource in resources.iter() {
  82. let resource = resource?;
  83. println!("cargo:rerun-if-changed={}", resource.path().display());
  84. copy_file(resource.path(), path.join(resource.target()))?;
  85. }
  86. Ok(())
  87. }
  88. #[cfg(unix)]
  89. fn symlink_dir(src: &Path, dst: &Path) -> std::io::Result<()> {
  90. std::os::unix::fs::symlink(src, dst)
  91. }
  92. /// Makes a symbolic link to a directory.
  93. #[cfg(windows)]
  94. fn symlink_dir(src: &Path, dst: &Path) -> std::io::Result<()> {
  95. std::os::windows::fs::symlink_dir(src, dst)
  96. }
  97. /// Makes a symbolic link to a file.
  98. #[cfg(unix)]
  99. fn symlink_file(src: &Path, dst: &Path) -> std::io::Result<()> {
  100. std::os::unix::fs::symlink(src, dst)
  101. }
  102. /// Makes a symbolic link to a file.
  103. #[cfg(windows)]
  104. fn symlink_file(src: &Path, dst: &Path) -> std::io::Result<()> {
  105. std::os::windows::fs::symlink_file(src, dst)
  106. }
  107. fn copy_dir(from: &Path, to: &Path) -> Result<()> {
  108. for entry in walkdir::WalkDir::new(from) {
  109. let entry = entry?;
  110. debug_assert!(entry.path().starts_with(from));
  111. let rel_path = entry.path().strip_prefix(from)?;
  112. let dest_path = to.join(rel_path);
  113. if entry.file_type().is_symlink() {
  114. let target = std::fs::read_link(entry.path())?;
  115. if entry.path().is_dir() {
  116. symlink_dir(&target, &dest_path)?;
  117. } else {
  118. symlink_file(&target, &dest_path)?;
  119. }
  120. } else if entry.file_type().is_dir() {
  121. std::fs::create_dir(dest_path)?;
  122. } else {
  123. std::fs::copy(entry.path(), dest_path)?;
  124. }
  125. }
  126. Ok(())
  127. }
  128. // Copies the framework under `{src_dir}/{framework}.framework` to `{dest_dir}/{framework}.framework`.
  129. fn copy_framework_from(src_dir: &Path, framework: &str, dest_dir: &Path) -> Result<bool> {
  130. let src_name = format!("{}.framework", framework);
  131. let src_path = src_dir.join(&src_name);
  132. if src_path.exists() {
  133. copy_dir(&src_path, &dest_dir.join(&src_name))?;
  134. Ok(true)
  135. } else {
  136. Ok(false)
  137. }
  138. }
  139. // Copies the macOS application bundle frameworks to the target folder
  140. fn copy_frameworks(dest_dir: &Path, frameworks: &[String]) -> Result<()> {
  141. std::fs::create_dir_all(dest_dir).with_context(|| {
  142. format!(
  143. "Failed to create frameworks output directory at {:?}",
  144. dest_dir
  145. )
  146. })?;
  147. for framework in frameworks.iter() {
  148. if framework.ends_with(".framework") {
  149. let src_path = PathBuf::from(framework);
  150. let src_name = src_path
  151. .file_name()
  152. .expect("Couldn't get framework filename");
  153. let dest_path = dest_dir.join(src_name);
  154. copy_dir(&src_path, &dest_path)?;
  155. continue;
  156. } else if framework.ends_with(".dylib") {
  157. let src_path = PathBuf::from(framework);
  158. if !src_path.exists() {
  159. return Err(anyhow::anyhow!("Library not found: {}", framework));
  160. }
  161. let src_name = src_path.file_name().expect("Couldn't get library filename");
  162. let dest_path = dest_dir.join(src_name);
  163. copy_file(&src_path, &dest_path)?;
  164. continue;
  165. } else if framework.contains('/') {
  166. return Err(anyhow::anyhow!(
  167. "Framework path should have .framework extension: {}",
  168. framework
  169. ));
  170. }
  171. if let Some(home_dir) = dirs_next::home_dir() {
  172. if copy_framework_from(&home_dir.join("Library/Frameworks/"), framework, dest_dir)? {
  173. continue;
  174. }
  175. }
  176. if copy_framework_from(&PathBuf::from("/Library/Frameworks/"), framework, dest_dir)?
  177. || copy_framework_from(
  178. &PathBuf::from("/Network/Library/Frameworks/"),
  179. framework,
  180. dest_dir,
  181. )?
  182. {
  183. continue;
  184. }
  185. }
  186. Ok(())
  187. }
  188. // creates a cfg alias if `has_feature` is true.
  189. // `alias` must be a snake case string.
  190. fn cfg_alias(alias: &str, has_feature: bool) {
  191. if has_feature {
  192. println!("cargo:rustc-cfg={alias}");
  193. }
  194. }
  195. /// Attributes used on Windows.
  196. #[allow(dead_code)]
  197. #[derive(Debug, Default)]
  198. pub struct WindowsAttributes {
  199. window_icon_path: Option<PathBuf>,
  200. /// A string containing an [application manifest] to be included with the application on Windows.
  201. ///
  202. /// Defaults to:
  203. /// ```text
  204. #[doc = include_str!("window-app-manifest.xml")]
  205. /// ```
  206. ///
  207. /// ## Warning
  208. ///
  209. /// 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:
  210. /// ```text
  211. /// <dependency>
  212. /// <dependentAssembly>
  213. /// <assemblyIdentity
  214. /// type="win32"
  215. /// name="Microsoft.Windows.Common-Controls"
  216. /// version="6.0.0.0"
  217. /// processorArchitecture="*"
  218. /// publicKeyToken="6595b64144ccf1df"
  219. /// language="*"
  220. /// />
  221. /// </dependentAssembly>
  222. /// </dependency>
  223. /// ```
  224. ///
  225. /// [application manifest]: https://learn.microsoft.com/en-us/windows/win32/sbscs/application-manifests
  226. app_manifest: Option<String>,
  227. }
  228. impl WindowsAttributes {
  229. /// Creates the default attribute set.
  230. pub fn new() -> Self {
  231. Self::default()
  232. }
  233. /// Sets the icon to use on the window. Currently only used on Windows.
  234. /// It must be in `ico` format. Defaults to `icons/icon.ico`.
  235. #[must_use]
  236. pub fn window_icon_path<P: AsRef<Path>>(mut self, window_icon_path: P) -> Self {
  237. self
  238. .window_icon_path
  239. .replace(window_icon_path.as_ref().into());
  240. self
  241. }
  242. /// Sets the [application manifest] to be included with the application on Windows.
  243. ///
  244. /// Defaults to:
  245. /// ```text
  246. #[doc = include_str!("window-app-manifest.xml")]
  247. /// ```
  248. ///
  249. /// ## Warning
  250. ///
  251. /// 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:
  252. /// ```text
  253. /// <dependency>
  254. /// <dependentAssembly>
  255. /// <assemblyIdentity
  256. /// type="win32"
  257. /// name="Microsoft.Windows.Common-Controls"
  258. /// version="6.0.0.0"
  259. /// processorArchitecture="*"
  260. /// publicKeyToken="6595b64144ccf1df"
  261. /// language="*"
  262. /// />
  263. /// </dependentAssembly>
  264. /// </dependency>
  265. /// ```
  266. ///
  267. /// # Example
  268. ///
  269. /// The following manifest will brand the exe as requesting administrator privileges.
  270. /// Thus, everytime it is executed, a Windows UAC dialog will appear.
  271. ///
  272. /// ```rust,no_run
  273. /// let mut windows = tauri_build::WindowsAttributes::new();
  274. /// windows = windows.app_manifest(r#"
  275. /// <assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
  276. /// <trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
  277. /// <security>
  278. /// <requestedPrivileges>
  279. /// <requestedExecutionLevel level="requireAdministrator" uiAccess="false" />
  280. /// </requestedPrivileges>
  281. /// </security>
  282. /// </trustInfo>
  283. /// </assembly>
  284. /// "#);
  285. /// let attrs = tauri_build::Attributes::new().windows_attributes(windows);
  286. /// tauri_build::try_build(attrs).expect("failed to run build script");
  287. /// ```
  288. ///
  289. /// Note that you can move the manifest contents to a separate file and use `include_str!("manifest.xml")`
  290. /// instead of the inline string.
  291. ///
  292. /// [manifest]: https://learn.microsoft.com/en-us/windows/win32/sbscs/application-manifests
  293. #[must_use]
  294. pub fn app_manifest<S: AsRef<str>>(mut self, manifest: S) -> Self {
  295. self.app_manifest = Some(manifest.as_ref().to_string());
  296. self
  297. }
  298. }
  299. /// Definition of a plugin that is part of the Tauri application instead of having its own crate.
  300. ///
  301. /// By default it generates a plugin manifest that parses permissions from the `permissions/$plugin-name` directory.
  302. /// To change the glob pattern that is used to find permissions, use [`Self::permissions_path_pattern`].
  303. ///
  304. /// To autogenerate permissions for each of the plugin commands, see [`Self::commands`].
  305. #[derive(Debug, Default)]
  306. pub struct InlinedPlugin {
  307. commands: &'static [&'static str],
  308. permissions_path_pattern: Option<&'static str>,
  309. }
  310. impl InlinedPlugin {
  311. pub fn new() -> Self {
  312. Self::default()
  313. }
  314. /// Define a list of commands that gets permissions autogenerated in the format of `allow-$command` and `deny-$command`
  315. /// where $command is the command in kebab-case.
  316. pub fn commands(mut self, commands: &'static [&'static str]) -> Self {
  317. self.commands = commands;
  318. self
  319. }
  320. /// Sets a glob pattern that is used to find the permissions of this inlined plugin.
  321. ///
  322. /// **Note:** You must emit [rerun-if-changed] instructions for the plugin permissions directory.
  323. ///
  324. /// By default it is `./permissions/$plugin-name/**/*`
  325. pub fn permissions_path_pattern(mut self, pattern: &'static str) -> Self {
  326. self.permissions_path_pattern.replace(pattern);
  327. self
  328. }
  329. }
  330. /// The attributes used on the build.
  331. #[derive(Debug, Default)]
  332. pub struct Attributes {
  333. #[allow(dead_code)]
  334. windows_attributes: WindowsAttributes,
  335. capabilities_path_pattern: Option<&'static str>,
  336. #[cfg(feature = "codegen")]
  337. codegen: Option<codegen::context::CodegenContext>,
  338. inlined_plugins: HashMap<&'static str, InlinedPlugin>,
  339. }
  340. impl Attributes {
  341. /// Creates the default attribute set.
  342. pub fn new() -> Self {
  343. Self::default()
  344. }
  345. /// Sets the icon to use on the window. Currently only used on Windows.
  346. #[must_use]
  347. pub fn windows_attributes(mut self, windows_attributes: WindowsAttributes) -> Self {
  348. self.windows_attributes = windows_attributes;
  349. self
  350. }
  351. /// Set the glob pattern to be used to find the capabilities.
  352. ///
  353. /// **Note:** You must emit [rerun-if-changed] instructions for your capabilities directory.
  354. ///
  355. /// [rerun-if-changed]: https://doc.rust-lang.org/cargo/reference/build-scripts.html#rerun-if-changed
  356. #[must_use]
  357. pub fn capabilities_path_pattern(mut self, pattern: &'static str) -> Self {
  358. self.capabilities_path_pattern.replace(pattern);
  359. self
  360. }
  361. /// Adds the given plugin to the list of inlined plugins (a plugin that is part of your application).
  362. ///
  363. /// See [`InlinedPlugin`] for more information.
  364. pub fn plugin(mut self, name: &'static str, plugin: InlinedPlugin) -> Self {
  365. self.inlined_plugins.insert(name, plugin);
  366. self
  367. }
  368. #[cfg(feature = "codegen")]
  369. #[cfg_attr(docsrs, doc(cfg(feature = "codegen")))]
  370. #[must_use]
  371. pub fn codegen(mut self, codegen: codegen::context::CodegenContext) -> Self {
  372. self.codegen.replace(codegen);
  373. self
  374. }
  375. }
  376. pub fn dev() -> bool {
  377. std::env::var("DEP_TAURI_DEV")
  378. .expect("missing `cargo:dev` instruction, please update tauri to latest")
  379. == "true"
  380. }
  381. /// Run all build time helpers for your Tauri Application.
  382. ///
  383. /// The current helpers include the following:
  384. /// * Generates a Windows Resource file when targeting Windows.
  385. ///
  386. /// # Platforms
  387. ///
  388. /// [`build()`] should be called inside of `build.rs` regardless of the platform:
  389. /// * New helpers may target more platforms in the future.
  390. /// * Platform specific code is handled by the helpers automatically.
  391. /// * A build script is required in order to activate some cargo environmental variables that are
  392. /// used when generating code and embedding assets - so [`build()`] may as well be called.
  393. ///
  394. /// In short, this is saying don't put the call to [`build()`] behind a `#[cfg(windows)]`.
  395. ///
  396. /// # Panics
  397. ///
  398. /// If any of the build time helpers fail, they will [`std::panic!`] with the related error message.
  399. /// This is typically desirable when running inside a build script; see [`try_build`] for no panics.
  400. pub fn build() {
  401. if let Err(error) = try_build(Attributes::default()) {
  402. let error = format!("{error:#}");
  403. println!("{error}");
  404. if error.starts_with("unknown field") {
  405. print!("found an unknown configuration field. This usually means that you are using a CLI version that is newer than `tauri-build` and is incompatible. ");
  406. println!(
  407. "Please try updating the Rust crates by running `cargo update` in the Tauri app folder."
  408. );
  409. }
  410. std::process::exit(1);
  411. }
  412. }
  413. /// Non-panicking [`build()`].
  414. #[allow(unused_variables)]
  415. pub fn try_build(attributes: Attributes) -> Result<()> {
  416. use anyhow::anyhow;
  417. println!("cargo:rerun-if-env-changed=TAURI_CONFIG");
  418. #[cfg(feature = "config-json")]
  419. println!("cargo:rerun-if-changed=tauri.conf.json");
  420. #[cfg(feature = "config-json5")]
  421. println!("cargo:rerun-if-changed=tauri.conf.json5");
  422. #[cfg(feature = "config-toml")]
  423. println!("cargo:rerun-if-changed=Tauri.toml");
  424. let target_os = std::env::var("CARGO_CFG_TARGET_OS").unwrap();
  425. let mobile = target_os == "ios" || target_os == "android";
  426. cfg_alias("desktop", !mobile);
  427. cfg_alias("mobile", mobile);
  428. let target_triple = std::env::var("TARGET").unwrap();
  429. let target = tauri_utils::platform::Target::from_triple(&target_triple);
  430. let mut config = serde_json::from_value(tauri_utils::config::parse::read_from(
  431. target,
  432. std::env::current_dir().unwrap(),
  433. )?)?;
  434. if let Ok(env) = std::env::var("TAURI_CONFIG") {
  435. let merge_config: serde_json::Value = serde_json::from_str(&env)?;
  436. json_patch::merge(&mut config, &merge_config);
  437. }
  438. let config: Config = serde_json::from_value(config)?;
  439. let s = config.identifier.split('.');
  440. let last = s.clone().count() - 1;
  441. let mut android_package_prefix = String::new();
  442. for (i, w) in s.enumerate() {
  443. if i == 0 || i != last {
  444. android_package_prefix.push_str(w);
  445. android_package_prefix.push('_');
  446. }
  447. }
  448. android_package_prefix.pop();
  449. println!("cargo:rustc-env=TAURI_ANDROID_PACKAGE_PREFIX={android_package_prefix}");
  450. if let Some(project_dir) = var_os("TAURI_ANDROID_PROJECT_PATH").map(PathBuf::from) {
  451. mobile::generate_gradle_files(project_dir)?;
  452. }
  453. cfg_alias("dev", dev());
  454. let ws_path = get_workspace_dir()?;
  455. let mut manifest =
  456. Manifest::<cargo_toml::Value>::from_slice_with_metadata(&std::fs::read("Cargo.toml")?)?;
  457. if let Ok(ws_manifest) = Manifest::from_path(ws_path.join("Cargo.toml")) {
  458. Manifest::complete_from_path_and_workspace(
  459. &mut manifest,
  460. Path::new("Cargo.toml"),
  461. Some((&ws_manifest, ws_path.as_path())),
  462. )?;
  463. } else {
  464. Manifest::complete_from_path(&mut manifest, Path::new("Cargo.toml"))?;
  465. }
  466. let out_dir = PathBuf::from(std::env::var("OUT_DIR").unwrap());
  467. manifest::check(&config, &mut manifest)?;
  468. let mut plugin_manifests = acl::get_plugin_manifests()?;
  469. for (name, plugin) in attributes.inlined_plugins {
  470. let plugin_out_dir = out_dir.join("plugins").join(name);
  471. let mut permission_files = if plugin.commands.is_empty() {
  472. Vec::new()
  473. } else {
  474. tauri_utils::acl::build::autogenerate_command_permissions(
  475. &plugin_out_dir,
  476. plugin.commands,
  477. "",
  478. );
  479. tauri_utils::acl::build::define_permissions(
  480. &plugin_out_dir.join("*").to_string_lossy(),
  481. name,
  482. &plugin_out_dir,
  483. )?
  484. };
  485. if let Some(pattern) = plugin.permissions_path_pattern {
  486. permission_files.extend(tauri_utils::acl::build::define_permissions(
  487. pattern,
  488. name,
  489. &plugin_out_dir,
  490. )?);
  491. } else {
  492. let default_permissions_path = Path::new("permissions").join(name);
  493. println!(
  494. "cargo:rerun-if-changed={}",
  495. default_permissions_path.display()
  496. );
  497. permission_files.extend(tauri_utils::acl::build::define_permissions(
  498. &default_permissions_path
  499. .join("**")
  500. .join("*")
  501. .to_string_lossy(),
  502. name,
  503. &plugin_out_dir,
  504. )?);
  505. }
  506. let manifest = tauri_utils::acl::plugin::Manifest::new(permission_files, None);
  507. plugin_manifests.insert(name.into(), manifest);
  508. }
  509. std::fs::write(
  510. out_dir.join(PLUGIN_MANIFESTS_FILE_NAME),
  511. serde_json::to_string(&plugin_manifests)?,
  512. )?;
  513. let capabilities = if let Some(pattern) = attributes.capabilities_path_pattern {
  514. parse_capabilities(pattern)?
  515. } else {
  516. println!("cargo:rerun-if-changed=capabilities");
  517. parse_capabilities("./capabilities/**/*")?
  518. };
  519. acl::generate_schema(&plugin_manifests, target)?;
  520. acl::validate_capabilities(&plugin_manifests, &capabilities)?;
  521. let capabilities_path = acl::save_capabilities(&capabilities)?;
  522. copy(capabilities_path, out_dir.join(CAPABILITIES_FILE_NAME))?;
  523. acl::save_plugin_manifests(&plugin_manifests)?;
  524. println!("cargo:rustc-env=TAURI_ENV_TARGET_TRIPLE={target_triple}");
  525. // TODO: far from ideal, but there's no other way to get the target dir, see <https://github.com/rust-lang/cargo/issues/5457>
  526. let target_dir = out_dir
  527. .parent()
  528. .unwrap()
  529. .parent()
  530. .unwrap()
  531. .parent()
  532. .unwrap();
  533. if let Some(paths) = &config.bundle.external_bin {
  534. copy_binaries(
  535. ResourcePaths::new(external_binaries(paths, &target_triple).as_slice(), true),
  536. &target_triple,
  537. target_dir,
  538. manifest.package.as_ref().map(|p| &p.name),
  539. )?;
  540. }
  541. #[allow(unused_mut, clippy::redundant_clone)]
  542. let mut resources = config
  543. .bundle
  544. .resources
  545. .clone()
  546. .unwrap_or_else(|| BundleResources::List(Vec::new()));
  547. if target_triple.contains("windows") {
  548. if let Some(fixed_webview2_runtime_path) =
  549. match &config.bundle.windows.webview_fixed_runtime_path {
  550. Some(path) => Some(path),
  551. None => match &config.bundle.windows.webview_install_mode {
  552. WebviewInstallMode::FixedRuntime { path } => Some(path),
  553. _ => None,
  554. },
  555. }
  556. {
  557. resources.push(fixed_webview2_runtime_path.display().to_string());
  558. }
  559. }
  560. match resources {
  561. BundleResources::List(res) => {
  562. copy_resources(ResourcePaths::new(res.as_slice(), true), target_dir)?
  563. }
  564. BundleResources::Map(map) => copy_resources(ResourcePaths::from_map(&map, true), target_dir)?,
  565. }
  566. if target_triple.contains("darwin") {
  567. if let Some(frameworks) = &config.bundle.macos.frameworks {
  568. if !frameworks.is_empty() {
  569. let frameworks_dir = target_dir.parent().unwrap().join("Frameworks");
  570. let _ = std::fs::remove_dir_all(&frameworks_dir);
  571. // copy frameworks to the root `target` folder (instead of `target/debug` for instance)
  572. // because the rpath is set to `@executable_path/../Frameworks`.
  573. copy_frameworks(&frameworks_dir, frameworks)?;
  574. // If we have frameworks, we need to set the @rpath
  575. // https://github.com/tauri-apps/tauri/issues/7710
  576. println!("cargo:rustc-link-arg=-Wl,-rpath,@executable_path/../Frameworks");
  577. }
  578. }
  579. if let Some(version) = &config.bundle.macos.minimum_system_version {
  580. println!("cargo:rustc-env=MACOSX_DEPLOYMENT_TARGET={version}");
  581. }
  582. }
  583. if target_triple.contains("windows") {
  584. use semver::Version;
  585. use tauri_winres::{VersionInfo, WindowsResource};
  586. fn find_icon<F: Fn(&&String) -> bool>(config: &Config, predicate: F, default: &str) -> PathBuf {
  587. let icon_path = config
  588. .bundle
  589. .icon
  590. .iter()
  591. .find(|i| predicate(i))
  592. .cloned()
  593. .unwrap_or_else(|| default.to_string());
  594. icon_path.into()
  595. }
  596. let window_icon_path = attributes
  597. .windows_attributes
  598. .window_icon_path
  599. .unwrap_or_else(|| find_icon(&config, |i| i.ends_with(".ico"), "icons/icon.ico"));
  600. let mut res = WindowsResource::new();
  601. if let Some(manifest) = attributes.windows_attributes.app_manifest {
  602. res.set_manifest(&manifest);
  603. } else {
  604. res.set_manifest(include_str!("window-app-manifest.xml"));
  605. }
  606. if let Some(version_str) = &config.version {
  607. if let Ok(v) = Version::parse(version_str) {
  608. let version = v.major << 48 | v.minor << 32 | v.patch << 16;
  609. res.set_version_info(VersionInfo::FILEVERSION, version);
  610. res.set_version_info(VersionInfo::PRODUCTVERSION, version);
  611. }
  612. }
  613. if let Some(product_name) = &config.product_name {
  614. res.set("ProductName", product_name);
  615. }
  616. if let Some(short_description) = &config.bundle.short_description {
  617. res.set("FileDescription", short_description);
  618. }
  619. if let Some(copyright) = &config.bundle.copyright {
  620. res.set("LegalCopyright", copyright);
  621. }
  622. if window_icon_path.exists() {
  623. res.set_icon_with_id(&window_icon_path.display().to_string(), "32512");
  624. } else {
  625. return Err(anyhow!(format!(
  626. "`{}` not found; required for generating a Windows Resource file during tauri-build",
  627. window_icon_path.display()
  628. )));
  629. }
  630. res.compile().with_context(|| {
  631. format!(
  632. "failed to compile `{}` into a Windows Resource file during tauri-build",
  633. window_icon_path.display()
  634. )
  635. })?;
  636. let target_env = std::env::var("CARGO_CFG_TARGET_ENV").unwrap();
  637. match target_env.as_str() {
  638. "gnu" => {
  639. let target_arch = match std::env::var("CARGO_CFG_TARGET_ARCH").unwrap().as_str() {
  640. "x86_64" => Some("x64"),
  641. "x86" => Some("x86"),
  642. "aarch64" => Some("arm64"),
  643. arch => None,
  644. };
  645. if let Some(target_arch) = target_arch {
  646. for entry in std::fs::read_dir(target_dir.join("build"))? {
  647. let path = entry?.path();
  648. let webview2_loader_path = path
  649. .join("out")
  650. .join(target_arch)
  651. .join("WebView2Loader.dll");
  652. if path.to_string_lossy().contains("webview2-com-sys") && webview2_loader_path.exists()
  653. {
  654. std::fs::copy(webview2_loader_path, target_dir.join("WebView2Loader.dll"))?;
  655. break;
  656. }
  657. }
  658. }
  659. }
  660. "msvc" => {
  661. if std::env::var("STATIC_VCRUNTIME").map_or(false, |v| v == "true") {
  662. static_vcruntime::build();
  663. }
  664. }
  665. _ => (),
  666. }
  667. }
  668. #[cfg(feature = "codegen")]
  669. if let Some(codegen) = attributes.codegen {
  670. codegen.try_build()?;
  671. }
  672. Ok(())
  673. }
  674. #[derive(serde::Deserialize)]
  675. struct CargoMetadata {
  676. workspace_root: PathBuf,
  677. }
  678. fn get_workspace_dir() -> Result<PathBuf> {
  679. let output = std::process::Command::new("cargo")
  680. .args(["metadata", "--no-deps", "--format-version", "1"])
  681. .output()?;
  682. if !output.status.success() {
  683. return Err(anyhow::anyhow!(
  684. "cargo metadata command exited with a non zero exit code: {}",
  685. String::from_utf8(output.stderr)?
  686. ));
  687. }
  688. Ok(serde_json::from_slice::<CargoMetadata>(&output.stdout)?.workspace_root)
  689. }