lib.rs 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152
  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. //! - Embed, hash, and compress assets, including icons for the app as well as the tray icon.
  7. //! - Parse `tauri.conf.json` at compile time and generate the Config struct.
  8. #![doc(
  9. html_logo_url = "https://github.com/tauri-apps/tauri/raw/dev/.github/icon.png",
  10. html_favicon_url = "https://github.com/tauri-apps/tauri/raw/dev/.github/icon.png"
  11. )]
  12. pub use self::context::{context_codegen, ContextData};
  13. use crate::embedded_assets::{ensure_out_dir, EmbeddedAssetsError};
  14. use proc_macro2::TokenStream;
  15. use quote::{quote, ToTokens, TokenStreamExt};
  16. use std::{
  17. borrow::Cow,
  18. fmt::{self, Write},
  19. path::{Path, PathBuf},
  20. };
  21. pub use tauri_utils::config::{parse::ConfigError, Config};
  22. use tauri_utils::platform::Target;
  23. use tauri_utils::write_if_changed;
  24. mod context;
  25. pub mod embedded_assets;
  26. pub mod image;
  27. #[doc(hidden)]
  28. pub mod vendor;
  29. /// Represents all the errors that can happen while reading the config during codegen.
  30. #[derive(Debug, thiserror::Error)]
  31. #[non_exhaustive]
  32. pub enum CodegenConfigError {
  33. #[error("unable to access current working directory: {0}")]
  34. CurrentDir(std::io::Error),
  35. // this error should be "impossible" because we use std::env::current_dir() - cover it anyways
  36. #[error("Tauri config file has no parent, this shouldn't be possible. file an issue on https://github.com/tauri-apps/tauri - target {0}")]
  37. Parent(PathBuf),
  38. #[error("unable to parse inline JSON TAURI_CONFIG env var: {0}")]
  39. FormatInline(serde_json::Error),
  40. #[error(transparent)]
  41. Json(#[from] serde_json::Error),
  42. #[error("{0}")]
  43. ConfigError(#[from] ConfigError),
  44. }
  45. /// Get the [`Config`] from the `TAURI_CONFIG` environmental variable, or read from the passed path.
  46. ///
  47. /// If the passed path is relative, it should be relative to the current working directory of the
  48. /// compiling crate.
  49. pub fn get_config(path: &Path) -> Result<(Config, PathBuf), CodegenConfigError> {
  50. let path = if path.is_relative() {
  51. let cwd = std::env::current_dir().map_err(CodegenConfigError::CurrentDir)?;
  52. Cow::Owned(cwd.join(path))
  53. } else {
  54. Cow::Borrowed(path)
  55. };
  56. // this should be impossible because of the use of `current_dir()` above, but handle it anyways
  57. let parent = path
  58. .parent()
  59. .map(ToOwned::to_owned)
  60. .ok_or_else(|| CodegenConfigError::Parent(path.into_owned()))?;
  61. let target = std::env::var("TAURI_ENV_TARGET_TRIPLE")
  62. .as_deref()
  63. .map(Target::from_triple)
  64. .unwrap_or_else(|_| Target::current());
  65. // in the future we may want to find a way to not need the TAURI_CONFIG env var so that
  66. // it is impossible for the content of two separate configs to get mixed up. The chances are
  67. // already unlikely unless the developer goes out of their way to run the cli on a different
  68. // project than the target crate.
  69. let mut config =
  70. serde_json::from_value(tauri_utils::config::parse::read_from(target, parent.clone())?.0)?;
  71. if let Ok(env) = std::env::var("TAURI_CONFIG") {
  72. let merge_config: serde_json::Value =
  73. serde_json::from_str(&env).map_err(CodegenConfigError::FormatInline)?;
  74. json_patch::merge(&mut config, &merge_config);
  75. }
  76. // Set working directory to where `tauri.config.json` is, so that relative paths in it are parsed correctly.
  77. let old_cwd = std::env::current_dir().map_err(CodegenConfigError::CurrentDir)?;
  78. std::env::set_current_dir(parent.clone()).map_err(CodegenConfigError::CurrentDir)?;
  79. let config = serde_json::from_value(config)?;
  80. // Reset working directory.
  81. std::env::set_current_dir(old_cwd).map_err(CodegenConfigError::CurrentDir)?;
  82. Ok((config, parent))
  83. }
  84. /// Create a blake3 checksum of the passed bytes.
  85. fn checksum(bytes: &[u8]) -> Result<String, fmt::Error> {
  86. let mut hasher = vendor::blake3_reference::Hasher::default();
  87. hasher.update(bytes);
  88. let mut bytes = [0u8; 32];
  89. hasher.finalize(&mut bytes);
  90. let mut hex = String::with_capacity(2 * bytes.len());
  91. for b in bytes {
  92. write!(hex, "{b:02x}")?;
  93. }
  94. Ok(hex)
  95. }
  96. /// Cache the data to `$OUT_DIR`, only if it does not already exist.
  97. ///
  98. /// Due to using a checksum as the filename, an existing file should be the exact same content
  99. /// as the data being checked.
  100. struct Cached {
  101. checksum: String,
  102. }
  103. impl TryFrom<String> for Cached {
  104. type Error = EmbeddedAssetsError;
  105. fn try_from(value: String) -> Result<Self, Self::Error> {
  106. Self::try_from(Vec::from(value))
  107. }
  108. }
  109. impl TryFrom<Vec<u8>> for Cached {
  110. type Error = EmbeddedAssetsError;
  111. fn try_from(content: Vec<u8>) -> Result<Self, Self::Error> {
  112. let checksum = checksum(content.as_ref()).map_err(EmbeddedAssetsError::Hex)?;
  113. let path = ensure_out_dir()?.join(&checksum);
  114. write_if_changed(&path, &content)
  115. .map(|_| Self { checksum })
  116. .map_err(|error| EmbeddedAssetsError::AssetWrite { path, error })
  117. }
  118. }
  119. impl ToTokens for Cached {
  120. fn to_tokens(&self, tokens: &mut TokenStream) {
  121. let path = &self.checksum;
  122. tokens.append_all(quote!(::std::concat!(::std::env!("OUT_DIR"), "/", #path)))
  123. }
  124. }