1#![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
13pub use self::context::{ContextData, context_codegen};
14use crate::embedded_assets::{EmbeddedAssetsError, ensure_out_dir};
15use proc_macro2::TokenStream;
16use quote::{ToTokens, TokenStreamExt, quote};
17use std::{
18 borrow::Cow,
19 fmt::{self, Write},
20 path::{Path, PathBuf},
21 time::{Duration, SystemTime},
22};
23pub use tauri_utils::config::{Config, parse::ConfigError};
24use tauri_utils::platform::Target;
25use tauri_utils::write_if_changed;
26
27mod context;
28pub mod embedded_assets;
29pub mod image;
30#[doc(hidden)]
31pub mod vendor;
32
33#[derive(Debug, thiserror::Error)]
35#[non_exhaustive]
36pub enum CodegenConfigError {
37 #[error("unable to access current working directory: {0}")]
38 CurrentDir(std::io::Error),
39
40 #[error(
42 "Tauri config file has no parent, this shouldn't be possible. file an issue on https://github.com/tauri-apps/tauri - target {0}"
43 )]
44 Parent(PathBuf),
45
46 #[error("unable to parse inline JSON TAURI_CONFIG env var: {0}")]
47 FormatInline(serde_json::Error),
48
49 #[error(transparent)]
50 Json(#[from] serde_json::Error),
51
52 #[error("{0}")]
53 ConfigError(#[from] ConfigError),
54}
55
56pub fn get_config(path: &Path) -> Result<(Config, PathBuf), CodegenConfigError> {
61 let path = if path.is_relative() {
62 let cwd = std::env::current_dir().map_err(CodegenConfigError::CurrentDir)?;
63 Cow::Owned(cwd.join(path))
64 } else {
65 Cow::Borrowed(path)
66 };
67
68 let parent = path
70 .parent()
71 .map(ToOwned::to_owned)
72 .ok_or_else(|| CodegenConfigError::Parent(path.into_owned()))?;
73
74 let target = std::env::var("TAURI_ENV_TARGET_TRIPLE")
75 .as_deref()
76 .map(Target::from_triple)
77 .unwrap_or_else(|_| Target::current());
78
79 let mut config =
84 serde_json::from_value(tauri_utils::config::parse::read_from(target, &parent)?.0)?;
85
86 if let Ok(env) = std::env::var("TAURI_CONFIG") {
87 let merge_config: serde_json::Value =
88 serde_json::from_str(&env).map_err(CodegenConfigError::FormatInline)?;
89 json_patch::merge(&mut config, &merge_config);
90 }
91
92 let old_cwd = std::env::current_dir().map_err(CodegenConfigError::CurrentDir)?;
94 std::env::set_current_dir(parent.clone()).map_err(CodegenConfigError::CurrentDir)?;
95
96 let config = serde_json::from_value(config)?;
97
98 std::env::set_current_dir(old_cwd).map_err(CodegenConfigError::CurrentDir)?;
100
101 Ok((config, parent))
102}
103
104fn checksum(bytes: &[u8]) -> Result<String, fmt::Error> {
106 let mut hasher = vendor::blake3_reference::Hasher::default();
107 hasher.update(bytes);
108
109 let mut bytes = [0u8; 32];
110 hasher.finalize(&mut bytes);
111
112 let mut hex = String::with_capacity(2 * bytes.len());
113 for b in bytes {
114 write!(hex, "{b:02x}")?;
115 }
116 Ok(hex)
117}
118
119fn backdate(path: &Path) {
128 let _ = std::fs::File::options()
129 .write(true)
130 .open(path)
131 .and_then(|file| file.set_modified(SystemTime::UNIX_EPOCH + Duration::from_secs(946_684_800)));
132}
133
134struct Cached {
139 checksum: String,
140}
141
142impl TryFrom<String> for Cached {
143 type Error = EmbeddedAssetsError;
144
145 fn try_from(value: String) -> Result<Self, Self::Error> {
146 Self::try_from(Vec::from(value))
147 }
148}
149
150impl TryFrom<Vec<u8>> for Cached {
151 type Error = EmbeddedAssetsError;
152
153 fn try_from(content: Vec<u8>) -> Result<Self, Self::Error> {
154 let checksum = checksum(content.as_ref()).map_err(EmbeddedAssetsError::Hex)?;
155 let path = ensure_out_dir()?.join(&checksum);
156
157 write_if_changed(&path, &content)
158 .map(|_| {
159 backdate(&path);
160 Self { checksum }
161 })
162 .map_err(|error| EmbeddedAssetsError::AssetWrite { path, error })
163 }
164}
165
166impl ToTokens for Cached {
167 fn to_tokens(&self, tokens: &mut TokenStream) {
168 let path = &self.checksum;
169 tokens.append_all(quote!(::std::concat!(::std::env!("OUT_DIR"), "/", #path)))
170 }
171}
172
173#[cfg(test)]
174mod tests {
175 use crate::{
176 Cached,
177 embedded_assets::{AssetOptions, EmbeddedAssets},
178 };
179 use std::time::{Duration, SystemTime};
180 use tauri_utils::config::PatternKind;
181
182 #[test]
183 fn out_dir_files_are_backdated() {
184 let dir = std::env::temp_dir().join(format!("tauri-codegen-backdate-{}", std::process::id()));
185 let _ = std::fs::remove_dir_all(&dir);
186 let out_dir = dir.join("out");
187 let dist = dir.join("dist");
188 std::fs::create_dir_all(&out_dir).unwrap();
189 std::fs::create_dir_all(&dist).unwrap();
190 std::fs::write(dist.join("index.html"), "<html></html>").unwrap();
191 unsafe { std::env::set_var("OUT_DIR", &out_dir) };
194
195 let cached = Cached::try_from(vec![1, 2, 3]).unwrap();
196 EmbeddedAssets::new(
197 dist,
198 &AssetOptions::new(PatternKind::Brownfield),
199 |_, _, _, _| Ok(()),
200 )
201 .unwrap();
202
203 let mut files = vec![out_dir.join(&cached.checksum)];
204 for entry in std::fs::read_dir(out_dir.join("tauri-codegen-assets")).unwrap() {
205 files.push(entry.unwrap().path());
206 }
207 let modified: Vec<_> = files
208 .iter()
209 .map(|file| std::fs::metadata(file).unwrap().modified().unwrap())
210 .collect();
211
212 unsafe { std::env::remove_var("OUT_DIR") };
216 std::fs::remove_dir_all(&dir).unwrap();
217
218 let expected = SystemTime::UNIX_EPOCH + Duration::from_secs(946_684_800);
220 assert_eq!(files.len(), 2);
221 for (file, modified) in files.iter().zip(modified) {
222 assert_eq!(modified, expected, "{}", file.display());
223 }
224 }
225}