lib.rs 22 KB

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