lib.rs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371
  1. // Copyright 2019-2021 Tauri Programme within The Commons Conservancy
  2. // SPDX-License-Identifier: Apache-2.0
  3. // SPDX-License-Identifier: MIT
  4. #![cfg_attr(doc_cfg, feature(doc_cfg))]
  5. pub use anyhow::Result;
  6. use tauri_utils::resources::{external_binaries, resource_relpath, ResourcePaths};
  7. use std::path::{Path, PathBuf};
  8. #[cfg(feature = "codegen")]
  9. mod codegen;
  10. #[cfg(feature = "codegen")]
  11. #[cfg_attr(doc_cfg, doc(cfg(feature = "codegen")))]
  12. pub use codegen::context::CodegenContext;
  13. fn copy_file(from: impl AsRef<Path>, to: impl AsRef<Path>) -> Result<()> {
  14. let from = from.as_ref();
  15. let to = to.as_ref();
  16. if !from.exists() {
  17. return Err(anyhow::anyhow!("{:?} does not exist", from));
  18. }
  19. if !from.is_file() {
  20. return Err(anyhow::anyhow!("{:?} is not a file", from));
  21. }
  22. let dest_dir = to.parent().expect("No data in parent");
  23. std::fs::create_dir_all(dest_dir)?;
  24. std::fs::copy(from, to)?;
  25. Ok(())
  26. }
  27. fn copy_binaries<'a>(binaries: ResourcePaths<'a>, target_triple: &str, path: &Path) -> Result<()> {
  28. for src in binaries {
  29. let src = src?;
  30. println!("cargo:rerun-if-changed={}", src.display());
  31. let dest = path.join(
  32. src
  33. .file_name()
  34. .expect("failed to extract external binary filename")
  35. .to_string_lossy()
  36. .replace(&format!("-{}", target_triple), ""),
  37. );
  38. copy_file(&src, &dest)?;
  39. }
  40. Ok(())
  41. }
  42. /// Copies resources to a path.
  43. fn copy_resources(resources: ResourcePaths<'_>, path: &Path) -> Result<()> {
  44. for src in resources {
  45. let src = src?;
  46. println!("cargo:rerun-if-changed={}", src.display());
  47. let dest = path.join(resource_relpath(&src));
  48. copy_file(&src, &dest)?;
  49. }
  50. Ok(())
  51. }
  52. /// Attributes used on Windows.
  53. #[allow(dead_code)]
  54. #[derive(Debug)]
  55. pub struct WindowsAttributes {
  56. window_icon_path: PathBuf,
  57. /// The path to the sdk location. This can be a absolute or relative path. If not supplied
  58. /// this defaults to whatever `winres` crate determines is the best. See the
  59. /// [winres documentation](https://docs.rs/winres/*/winres/struct.WindowsResource.html#method.set_toolkit_path)
  60. sdk_dir: Option<PathBuf>,
  61. }
  62. impl Default for WindowsAttributes {
  63. fn default() -> Self {
  64. Self {
  65. window_icon_path: PathBuf::from("icons/icon.ico"),
  66. sdk_dir: None,
  67. }
  68. }
  69. }
  70. impl WindowsAttributes {
  71. /// Creates the default attribute set.
  72. pub fn new() -> Self {
  73. Self::default()
  74. }
  75. /// Sets the icon to use on the window. Currently only used on Windows.
  76. /// It must be in `ico` format. Defaults to `icons/icon.ico`.
  77. #[must_use]
  78. pub fn window_icon_path<P: AsRef<Path>>(mut self, window_icon_path: P) -> Self {
  79. self.window_icon_path = window_icon_path.as_ref().into();
  80. self
  81. }
  82. /// Sets the sdk dir for windows. Currently only used on Windows. This must be a vaild UTF-8
  83. /// path. Defaults to whatever the `winres` crate determines is best.
  84. #[must_use]
  85. pub fn sdk_dir<P: AsRef<Path>>(mut self, sdk_dir: P) -> Self {
  86. self.sdk_dir = Some(sdk_dir.as_ref().into());
  87. self
  88. }
  89. }
  90. /// The attributes used on the build.
  91. #[derive(Debug, Default)]
  92. pub struct Attributes {
  93. #[allow(dead_code)]
  94. windows_attributes: WindowsAttributes,
  95. }
  96. impl Attributes {
  97. /// Creates the default attribute set.
  98. pub fn new() -> Self {
  99. Self::default()
  100. }
  101. /// Sets the icon to use on the window. Currently only used on Windows.
  102. #[must_use]
  103. pub fn windows_attributes(mut self, windows_attributes: WindowsAttributes) -> Self {
  104. self.windows_attributes = windows_attributes;
  105. self
  106. }
  107. }
  108. /// Run all build time helpers for your Tauri Application.
  109. ///
  110. /// The current helpers include the following:
  111. /// * Generates a Windows Resource file when targeting Windows.
  112. ///
  113. /// # Platforms
  114. ///
  115. /// [`build()`] should be called inside of `build.rs` regardless of the platform:
  116. /// * New helpers may target more platforms in the future.
  117. /// * Platform specific code is handled by the helpers automatically.
  118. /// * A build script is required in order to activate some cargo environmental variables that are
  119. /// used when generating code and embedding assets - so [`build()`] may as well be called.
  120. ///
  121. /// In short, this is saying don't put the call to [`build()`] behind a `#[cfg(windows)]`.
  122. ///
  123. /// # Panics
  124. ///
  125. /// If any of the build time helpers fail, they will [`std::panic!`] with the related error message.
  126. /// This is typically desirable when running inside a build script; see [`try_build`] for no panics.
  127. pub fn build() {
  128. if let Err(error) = try_build(Attributes::default()) {
  129. panic!("error found during tauri-build: {:#?}", error);
  130. }
  131. }
  132. /// Non-panicking [`build()`].
  133. #[allow(unused_variables)]
  134. pub fn try_build(attributes: Attributes) -> Result<()> {
  135. use anyhow::anyhow;
  136. use cargo_toml::{Dependency, Manifest};
  137. use tauri_utils::config::{Config, TauriConfig};
  138. println!("cargo:rerun-if-env-changed=TAURI_CONFIG");
  139. println!("cargo:rerun-if-changed=tauri.conf.json");
  140. #[cfg(feature = "config-json5")]
  141. println!("cargo:rerun-if-changed=tauri.conf.json5");
  142. let config: Config = if let Ok(env) = std::env::var("TAURI_CONFIG") {
  143. serde_json::from_str(&env)?
  144. } else {
  145. serde_json::from_value(tauri_utils::config::parse::read_from(
  146. std::env::current_dir().unwrap(),
  147. )?)?
  148. };
  149. let mut manifest = Manifest::from_path("Cargo.toml")?;
  150. if let Some(tauri) = manifest.dependencies.remove("tauri") {
  151. let features = match tauri {
  152. Dependency::Simple(_) => Vec::new(),
  153. Dependency::Detailed(dep) => dep.features,
  154. };
  155. let all_cli_managed_features = TauriConfig::all_features();
  156. let diff = features_diff(
  157. &features
  158. .into_iter()
  159. .filter(|f| all_cli_managed_features.contains(&f.as_str()))
  160. .collect::<Vec<String>>(),
  161. &config
  162. .tauri
  163. .features()
  164. .into_iter()
  165. .map(|f| f.to_string())
  166. .collect::<Vec<String>>(),
  167. );
  168. let mut error_message = String::new();
  169. if !diff.remove.is_empty() {
  170. error_message.push_str("remove the `");
  171. error_message.push_str(&diff.remove.join(", "));
  172. error_message.push_str(if diff.remove.len() == 1 {
  173. "` feature"
  174. } else {
  175. "` features"
  176. });
  177. if !diff.add.is_empty() {
  178. error_message.push_str(" and ");
  179. }
  180. }
  181. if !diff.add.is_empty() {
  182. error_message.push_str("add the `");
  183. error_message.push_str(&diff.add.join(", "));
  184. error_message.push_str(if diff.add.len() == 1 {
  185. "` feature"
  186. } else {
  187. "` features"
  188. });
  189. }
  190. if !error_message.is_empty() {
  191. return Err(anyhow!("
  192. The `tauri` dependency features on the `Cargo.toml` file does not match the allowlist defined under `tauri.conf.json`.
  193. Please run `tauri dev` or `tauri build` or {}.
  194. ", error_message));
  195. }
  196. }
  197. let target_triple = std::env::var("TARGET").unwrap();
  198. let out_dir = std::env::var("OUT_DIR").unwrap();
  199. // TODO: far from ideal, but there's no other way to get the target dir, see <https://github.com/rust-lang/cargo/issues/5457>
  200. let target_dir = Path::new(&out_dir)
  201. .parent()
  202. .unwrap()
  203. .parent()
  204. .unwrap()
  205. .parent()
  206. .unwrap();
  207. if let Some(paths) = config.tauri.bundle.external_bin {
  208. copy_binaries(
  209. ResourcePaths::new(external_binaries(&paths, &target_triple).as_slice(), true),
  210. &target_triple,
  211. target_dir,
  212. )?;
  213. }
  214. #[allow(unused_mut)]
  215. let mut resources = config.tauri.bundle.resources.unwrap_or_default();
  216. #[cfg(target_os = "linux")]
  217. if let Some(tray) = config.tauri.system_tray {
  218. resources.push(tray.icon_path.display().to_string());
  219. }
  220. copy_resources(ResourcePaths::new(resources.as_slice(), true), target_dir)?;
  221. #[cfg(target_os = "macos")]
  222. {
  223. if let Some(version) = config.tauri.bundle.macos.minimum_system_version {
  224. println!("cargo:rustc-env=MACOSX_DEPLOYMENT_TARGET={}", version);
  225. }
  226. }
  227. #[cfg(windows)]
  228. {
  229. use anyhow::Context;
  230. use semver::Version;
  231. use winres::{VersionInfo, WindowsResource};
  232. let icon_path_string = attributes
  233. .windows_attributes
  234. .window_icon_path
  235. .to_string_lossy()
  236. .into_owned();
  237. if attributes.windows_attributes.window_icon_path.exists() {
  238. let mut res = WindowsResource::new();
  239. if let Some(sdk_dir) = &attributes.windows_attributes.sdk_dir {
  240. if let Some(sdk_dir_str) = sdk_dir.to_str() {
  241. res.set_toolkit_path(sdk_dir_str);
  242. } else {
  243. return Err(anyhow!(
  244. "sdk_dir path is not valid; only UTF-8 characters are allowed"
  245. ));
  246. }
  247. }
  248. if let Some(version) = &config.package.version {
  249. if let Ok(v) = Version::parse(version) {
  250. let version = v.major << 48 | v.minor << 32 | v.patch << 16;
  251. res.set_version_info(VersionInfo::FILEVERSION, version);
  252. res.set_version_info(VersionInfo::PRODUCTVERSION, version);
  253. }
  254. res.set("FileVersion", version);
  255. res.set("ProductVersion", version);
  256. }
  257. if let Some(product_name) = &config.package.product_name {
  258. res.set("ProductName", product_name);
  259. res.set("FileDescription", product_name);
  260. }
  261. res.set_icon_with_id(&icon_path_string, "32512");
  262. res.compile().with_context(|| {
  263. format!(
  264. "failed to compile `{}` into a Windows Resource file during tauri-build",
  265. icon_path_string
  266. )
  267. })?;
  268. } else {
  269. return Err(anyhow!(format!(
  270. "`{}` not found; required for generating a Windows Resource file during tauri-build",
  271. icon_path_string
  272. )));
  273. }
  274. }
  275. Ok(())
  276. }
  277. #[derive(Debug, Default, PartialEq, Eq)]
  278. struct Diff {
  279. remove: Vec<String>,
  280. add: Vec<String>,
  281. }
  282. fn features_diff(current: &[String], expected: &[String]) -> Diff {
  283. let mut remove = Vec::new();
  284. let mut add = Vec::new();
  285. for feature in current {
  286. if !expected.contains(feature) {
  287. remove.push(feature.clone());
  288. }
  289. }
  290. for feature in expected {
  291. if !current.contains(feature) {
  292. add.push(feature.clone());
  293. }
  294. }
  295. Diff { remove, add }
  296. }
  297. #[cfg(test)]
  298. mod tests {
  299. use super::Diff;
  300. #[test]
  301. fn array_diff() {
  302. for (current, expected, result) in [
  303. (vec![], vec![], Default::default()),
  304. (
  305. vec!["a".into()],
  306. vec![],
  307. Diff {
  308. remove: vec!["a".into()],
  309. add: vec![],
  310. },
  311. ),
  312. (vec!["a".into()], vec!["a".into()], Default::default()),
  313. (
  314. vec!["a".into(), "b".into()],
  315. vec!["a".into()],
  316. Diff {
  317. remove: vec!["b".into()],
  318. add: vec![],
  319. },
  320. ),
  321. (
  322. vec!["a".into(), "b".into()],
  323. vec!["a".into(), "c".into()],
  324. Diff {
  325. remove: vec!["b".into()],
  326. add: vec!["c".into()],
  327. },
  328. ),
  329. ] {
  330. assert_eq!(super::features_diff(&current, &expected), result);
  331. }
  332. }
  333. }