mirror of
https://github.com/tauri-apps/tauri.git
synced 2024-12-25 19:54:07 +03:00
prevent unnecessary rebuilds when working in the cargo workspace (#10442)
* hash codegen image cache output * remove left over dbg! statement * prevent info.plist from workspace rebuilds * prevent schema generation from workspace rebuilds * use new `Cached` struct in `CachedIcon` * fmt * use full import for cached plist * use `to_vec()` for raw icons
This commit is contained in:
parent
6755af2302
commit
b32295de18
@ -2,33 +2,33 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
use std::{
|
||||
error::Error,
|
||||
fs::File,
|
||||
io::{BufWriter, Write},
|
||||
path::PathBuf,
|
||||
use std::{error::Error, path::PathBuf};
|
||||
|
||||
use schemars::schema_for;
|
||||
use tauri_utils::{
|
||||
acl::capability::Capability,
|
||||
acl::{Permission, Scopes},
|
||||
write_if_changed,
|
||||
};
|
||||
|
||||
use schemars::schema::RootSchema;
|
||||
macro_rules! schema {
|
||||
($name:literal, $path:ty) => {
|
||||
(concat!($name, "-schema.json"), schema_for!($path))
|
||||
};
|
||||
}
|
||||
|
||||
pub fn main() -> Result<(), Box<dyn Error>> {
|
||||
let cap_schema = schemars::schema_for!(tauri_utils::acl::capability::Capability);
|
||||
let perm_schema = schemars::schema_for!(tauri_utils::acl::Permission);
|
||||
let scope_schema = schemars::schema_for!(tauri_utils::acl::Scopes);
|
||||
let schemas = [
|
||||
schema!("capability", Capability),
|
||||
schema!("permission", Permission),
|
||||
schema!("scope", Scopes),
|
||||
];
|
||||
|
||||
let crate_dir = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR")?);
|
||||
|
||||
write_schema_file(cap_schema, crate_dir.join("capability-schema.json"))?;
|
||||
write_schema_file(perm_schema, crate_dir.join("permission-schema.json"))?;
|
||||
write_schema_file(scope_schema, crate_dir.join("scope-schema.json"))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_schema_file(schema: RootSchema, outpath: PathBuf) -> Result<(), Box<dyn Error>> {
|
||||
let schema_str = serde_json::to_string_pretty(&schema).unwrap();
|
||||
let mut schema_file = BufWriter::new(File::create(outpath)?);
|
||||
write!(schema_file, "{schema_str}")?;
|
||||
let out = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR")?);
|
||||
for (filename, schema) in schemas {
|
||||
let schema = serde_json::to_string_pretty(&schema)?;
|
||||
write_if_changed(out.join(filename), schema)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -7,28 +7,28 @@ use std::convert::identity;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::{ffi::OsStr, str::FromStr};
|
||||
|
||||
use crate::{
|
||||
embedded_assets::{
|
||||
ensure_out_dir, AssetOptions, CspHashes, EmbeddedAssets, EmbeddedAssetsResult,
|
||||
},
|
||||
image::CachedIcon,
|
||||
};
|
||||
use base64::Engine;
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::quote;
|
||||
use sha2::{Digest, Sha256};
|
||||
|
||||
use syn::Expr;
|
||||
use tauri_utils::acl::capability::{Capability, CapabilityFile};
|
||||
use tauri_utils::acl::manifest::Manifest;
|
||||
use tauri_utils::acl::resolved::Resolved;
|
||||
use tauri_utils::assets::AssetKey;
|
||||
use tauri_utils::config::{CapabilityEntry, Config, FrontendDist, PatternKind};
|
||||
use tauri_utils::html::{
|
||||
inject_nonce_token, parse as parse_html, serialize_node as serialize_html_node, NodeRef,
|
||||
use tauri_utils::{
|
||||
acl::capability::{Capability, CapabilityFile},
|
||||
acl::manifest::Manifest,
|
||||
acl::resolved::Resolved,
|
||||
assets::AssetKey,
|
||||
config::{CapabilityEntry, Config, FrontendDist, PatternKind},
|
||||
html::{inject_nonce_token, parse as parse_html, serialize_node as serialize_html_node, NodeRef},
|
||||
platform::Target,
|
||||
plugin::GLOBAL_API_SCRIPT_FILE_LIST_PATH,
|
||||
tokens::{map_lit, str_lit},
|
||||
};
|
||||
use tauri_utils::platform::Target;
|
||||
use tauri_utils::plugin::GLOBAL_API_SCRIPT_FILE_LIST_PATH;
|
||||
use tauri_utils::tokens::{map_lit, str_lit};
|
||||
|
||||
use crate::embedded_assets::{
|
||||
ensure_out_dir, AssetOptions, CspHashes, EmbeddedAssets, EmbeddedAssetsResult,
|
||||
};
|
||||
use crate::image::{ico_icon, image_icon, png_icon, raw_icon};
|
||||
|
||||
const ACL_MANIFESTS_FILE_NAME: &str = "acl-manifests.json";
|
||||
const CAPABILITIES_FILE_NAME: &str = "capabilities.json";
|
||||
@ -221,8 +221,8 @@ pub fn context_codegen(data: ContextData) -> EmbeddedAssetsResult<TokenStream> {
|
||||
"icons/icon.ico",
|
||||
);
|
||||
if icon_path.exists() {
|
||||
ico_icon(&root, &out_dir, &icon_path, "default-window-icon.png")
|
||||
.map(|i| quote!(::std::option::Option::Some(#i)))?
|
||||
let icon = CachedIcon::new(&root, &icon_path)?;
|
||||
quote!(::std::option::Option::Some(#icon))
|
||||
} else {
|
||||
let icon_path = find_icon(
|
||||
&config,
|
||||
@ -230,8 +230,8 @@ pub fn context_codegen(data: ContextData) -> EmbeddedAssetsResult<TokenStream> {
|
||||
|i| i.ends_with(".png"),
|
||||
"icons/icon.png",
|
||||
);
|
||||
png_icon(&root, &out_dir, &icon_path, "default-window-icon.png")
|
||||
.map(|i| quote!(::std::option::Option::Some(#i)))?
|
||||
let icon = CachedIcon::new(&root, &icon_path)?;
|
||||
quote!(::std::option::Option::Some(#icon))
|
||||
}
|
||||
} else {
|
||||
// handle default window icons for Unix targets
|
||||
@ -241,8 +241,8 @@ pub fn context_codegen(data: ContextData) -> EmbeddedAssetsResult<TokenStream> {
|
||||
|i| i.ends_with(".png"),
|
||||
"icons/icon.png",
|
||||
);
|
||||
png_icon(&root, &out_dir, &icon_path, "default-window-icon.png")
|
||||
.map(|i| quote!(::std::option::Option::Some(#i)))?
|
||||
let icon = CachedIcon::new(&root, &icon_path)?;
|
||||
quote!(::std::option::Option::Some(#icon))
|
||||
}
|
||||
};
|
||||
|
||||
@ -261,7 +261,9 @@ pub fn context_codegen(data: ContextData) -> EmbeddedAssetsResult<TokenStream> {
|
||||
"icons/icon.png",
|
||||
);
|
||||
}
|
||||
raw_icon(&out_dir, &icon_path, "dev-macos-icon.png")?
|
||||
|
||||
let icon = CachedIcon::new_raw(&root, &icon_path)?;
|
||||
quote!(::std::option::Option::Some(#icon.to_vec()))
|
||||
} else {
|
||||
quote!(::std::option::Option::None)
|
||||
};
|
||||
@ -290,8 +292,8 @@ pub fn context_codegen(data: ContextData) -> EmbeddedAssetsResult<TokenStream> {
|
||||
let with_tray_icon_code = if target.is_desktop() {
|
||||
if let Some(tray) = &config.app.tray_icon {
|
||||
let tray_icon_icon_path = config_parent.join(&tray.icon_path);
|
||||
image_icon(&root, &out_dir, &tray_icon_icon_path, "tray-icon")
|
||||
.map(|i| quote!(context.set_tray_icon(Some(#i));))?
|
||||
let icon = CachedIcon::new(&root, &tray_icon_icon_path)?;
|
||||
quote!(context.set_tray_icon(::std::option::Option::Some(#icon));)
|
||||
} else {
|
||||
quote!()
|
||||
}
|
||||
@ -319,8 +321,6 @@ pub fn context_codegen(data: ContextData) -> EmbeddedAssetsResult<TokenStream> {
|
||||
}
|
||||
}
|
||||
|
||||
let plist_file = out_dir.join("Info.plist");
|
||||
|
||||
let mut plist_contents = std::io::BufWriter::new(Vec::new());
|
||||
info_plist
|
||||
.to_writer_xml(&mut plist_contents)
|
||||
@ -328,12 +328,9 @@ pub fn context_codegen(data: ContextData) -> EmbeddedAssetsResult<TokenStream> {
|
||||
let plist_contents =
|
||||
String::from_utf8_lossy(&plist_contents.into_inner().unwrap()).into_owned();
|
||||
|
||||
if plist_contents != std::fs::read_to_string(&plist_file).unwrap_or_default() {
|
||||
std::fs::write(&plist_file, &plist_contents).expect("failed to write Info.plist");
|
||||
}
|
||||
|
||||
let plist = crate::Cached::try_from(plist_contents)?;
|
||||
quote!({
|
||||
tauri::embed_plist::embed_info_plist!(concat!(std::env!("OUT_DIR"), "/Info.plist"));
|
||||
tauri::embed_plist::embed_info_plist!(#plist);
|
||||
})
|
||||
} else {
|
||||
quote!(())
|
||||
|
@ -8,7 +8,6 @@ use quote::{quote, ToTokens, TokenStreamExt};
|
||||
use sha2::{Digest, Sha256};
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
fmt::Write,
|
||||
fs::File,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
@ -48,7 +47,7 @@ pub enum EmbeddedAssetsError {
|
||||
#[error("invalid prefix {prefix} used while including path {path}")]
|
||||
PrefixInvalid { prefix: PathBuf, path: PathBuf },
|
||||
|
||||
#[error("invalid extension {extension} used for image {path}, must be `ico` or `png`")]
|
||||
#[error("invalid extension `{extension}` used for image {path}, must be `ico` or `png`")]
|
||||
InvalidImageExtension { extension: PathBuf, path: PathBuf },
|
||||
|
||||
#[error("failed to walk directory {path} because {error}")]
|
||||
@ -341,19 +340,7 @@ impl EmbeddedAssets {
|
||||
std::fs::create_dir_all(&out_dir).map_err(|_| EmbeddedAssetsError::OutDir)?;
|
||||
|
||||
// get a hash of the input - allows for caching existing files
|
||||
let hash = {
|
||||
let mut hasher = crate::vendor::blake3_reference::Hasher::default();
|
||||
hasher.update(&input);
|
||||
|
||||
let mut bytes = [0u8; 32];
|
||||
hasher.finalize(&mut bytes);
|
||||
|
||||
let mut hex = String::with_capacity(2 * bytes.len());
|
||||
for b in bytes {
|
||||
write!(hex, "{b:02x}").map_err(EmbeddedAssetsError::Hex)?;
|
||||
}
|
||||
hex
|
||||
};
|
||||
let hash = crate::checksum(&input).map_err(EmbeddedAssetsError::Hex)?;
|
||||
|
||||
// use the content hash to determine filename, keep extensions that exist
|
||||
let out_path = if let Some(ext) = path.extension().and_then(|e| e.to_str()) {
|
||||
|
@ -2,141 +2,117 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
use crate::embedded_assets::{ensure_out_dir, EmbeddedAssetsError, EmbeddedAssetsResult};
|
||||
use proc_macro2::{Span, TokenStream};
|
||||
use quote::{quote, ToTokens};
|
||||
use std::path::Path;
|
||||
use syn::{punctuated::Punctuated, Ident, PathArguments, PathSegment, Token};
|
||||
use crate::{
|
||||
embedded_assets::{EmbeddedAssetsError, EmbeddedAssetsResult},
|
||||
Cached,
|
||||
};
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::{quote, ToTokens, TokenStreamExt};
|
||||
use std::{ffi::OsStr, io::Cursor, path::Path};
|
||||
|
||||
pub fn include_image_codegen(
|
||||
path: &Path,
|
||||
out_file_name: &str,
|
||||
) -> EmbeddedAssetsResult<TokenStream> {
|
||||
let out_dir = ensure_out_dir()?;
|
||||
/// The format the Icon is consumed as.
|
||||
pub(crate) enum IconFormat {
|
||||
/// The image, completely unmodified.
|
||||
Raw,
|
||||
|
||||
let mut segments = Punctuated::new();
|
||||
segments.push(PathSegment {
|
||||
ident: Ident::new("tauri", Span::call_site()),
|
||||
arguments: PathArguments::None,
|
||||
});
|
||||
let root = syn::Path {
|
||||
leading_colon: Some(Token![::](Span::call_site())),
|
||||
segments,
|
||||
};
|
||||
|
||||
image_icon(&root.to_token_stream(), &out_dir, path, out_file_name)
|
||||
/// RGBA raw data, meant to be consumed by [`tauri::image::Image`].
|
||||
Image { width: u32, height: u32 },
|
||||
}
|
||||
|
||||
pub(crate) fn image_icon(
|
||||
root: &TokenStream,
|
||||
out_dir: &Path,
|
||||
path: &Path,
|
||||
out_file_name: &str,
|
||||
) -> EmbeddedAssetsResult<TokenStream> {
|
||||
let extension = path.extension().unwrap_or_default();
|
||||
if extension == "ico" {
|
||||
ico_icon(root, out_dir, path, out_file_name)
|
||||
} else if extension == "png" {
|
||||
png_icon(root, out_dir, path, out_file_name)
|
||||
} else {
|
||||
Err(EmbeddedAssetsError::InvalidImageExtension {
|
||||
extension: extension.into(),
|
||||
path: path.to_path_buf(),
|
||||
})
|
||||
}
|
||||
pub struct CachedIcon {
|
||||
cache: Cached,
|
||||
format: IconFormat,
|
||||
root: TokenStream,
|
||||
}
|
||||
|
||||
pub(crate) fn raw_icon(
|
||||
out_dir: &Path,
|
||||
path: &Path,
|
||||
out_file_name: &str,
|
||||
) -> EmbeddedAssetsResult<TokenStream> {
|
||||
let bytes =
|
||||
std::fs::read(path).unwrap_or_else(|e| panic!("failed to read icon {}: {}", path.display(), e));
|
||||
|
||||
let out_path = out_dir.join(out_file_name);
|
||||
write_if_changed(&out_path, &bytes).map_err(|error| EmbeddedAssetsError::AssetWrite {
|
||||
path: path.to_owned(),
|
||||
error,
|
||||
})?;
|
||||
|
||||
let icon = quote!(::std::option::Option::Some(
|
||||
include_bytes!(concat!(std::env!("OUT_DIR"), "/", #out_file_name)).to_vec()
|
||||
));
|
||||
Ok(icon)
|
||||
}
|
||||
|
||||
pub(crate) fn ico_icon(
|
||||
root: &TokenStream,
|
||||
out_dir: &Path,
|
||||
path: &Path,
|
||||
out_file_name: &str,
|
||||
) -> EmbeddedAssetsResult<TokenStream> {
|
||||
let file = std::fs::File::open(path)
|
||||
.unwrap_or_else(|e| panic!("failed to open icon {}: {}", path.display(), e));
|
||||
let icon_dir = ico::IconDir::read(file)
|
||||
.unwrap_or_else(|e| panic!("failed to parse icon {}: {}", path.display(), e));
|
||||
let entry = &icon_dir.entries()[0];
|
||||
let rgba = entry
|
||||
.decode()
|
||||
.unwrap_or_else(|e| panic!("failed to decode icon {}: {}", path.display(), e))
|
||||
.rgba_data()
|
||||
.to_vec();
|
||||
let width = entry.width();
|
||||
let height = entry.height();
|
||||
|
||||
let out_path = out_dir.join(out_file_name);
|
||||
write_if_changed(&out_path, &rgba).map_err(|error| EmbeddedAssetsError::AssetWrite {
|
||||
path: path.to_owned(),
|
||||
error,
|
||||
})?;
|
||||
|
||||
let icon = quote!(#root::image::Image::new(include_bytes!(concat!(std::env!("OUT_DIR"), "/", #out_file_name)), #width, #height));
|
||||
Ok(icon)
|
||||
}
|
||||
|
||||
pub(crate) fn png_icon(
|
||||
root: &TokenStream,
|
||||
out_dir: &Path,
|
||||
path: &Path,
|
||||
out_file_name: &str,
|
||||
) -> EmbeddedAssetsResult<TokenStream> {
|
||||
let file = std::fs::File::open(path)
|
||||
.unwrap_or_else(|e| panic!("failed to open icon {}: {}", path.display(), e));
|
||||
let decoder = png::Decoder::new(file);
|
||||
let mut reader = decoder
|
||||
.read_info()
|
||||
.unwrap_or_else(|e| panic!("failed to read icon {}: {}", path.display(), e));
|
||||
|
||||
let (color_type, _) = reader.output_color_type();
|
||||
|
||||
if color_type != png::ColorType::Rgba {
|
||||
panic!("icon {} is not RGBA", path.display());
|
||||
}
|
||||
|
||||
let mut buffer: Vec<u8> = Vec::new();
|
||||
while let Ok(Some(row)) = reader.next_row() {
|
||||
buffer.extend(row.data());
|
||||
}
|
||||
let width = reader.info().width;
|
||||
let height = reader.info().height;
|
||||
|
||||
let out_path = out_dir.join(out_file_name);
|
||||
write_if_changed(&out_path, &buffer).map_err(|error| EmbeddedAssetsError::AssetWrite {
|
||||
path: path.to_owned(),
|
||||
error,
|
||||
})?;
|
||||
|
||||
let icon = quote!(#root::image::Image::new(include_bytes!(concat!(std::env!("OUT_DIR"), "/", #out_file_name)), #width, #height));
|
||||
Ok(icon)
|
||||
}
|
||||
|
||||
fn write_if_changed(out_path: &Path, data: &[u8]) -> std::io::Result<()> {
|
||||
if let Ok(curr) = std::fs::read(out_path) {
|
||||
if curr == data {
|
||||
return Ok(());
|
||||
impl CachedIcon {
|
||||
pub fn new(root: &TokenStream, icon: &Path) -> EmbeddedAssetsResult<Self> {
|
||||
match icon.extension().map(OsStr::to_string_lossy).as_deref() {
|
||||
Some("png") => Self::new_png(root, icon),
|
||||
Some("ico") => Self::new_ico(root, icon),
|
||||
unknown => Err(EmbeddedAssetsError::InvalidImageExtension {
|
||||
extension: unknown.unwrap_or_default().into(),
|
||||
path: icon.to_path_buf(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
std::fs::write(out_path, data)
|
||||
/// Cache the icon without any manipulation.
|
||||
pub fn new_raw(root: &TokenStream, icon: &Path) -> EmbeddedAssetsResult<Self> {
|
||||
let buf = Self::open(icon);
|
||||
Cached::try_from(buf).map(|cache| Self {
|
||||
cache,
|
||||
root: root.clone(),
|
||||
format: IconFormat::Raw,
|
||||
})
|
||||
}
|
||||
|
||||
/// Cache an ICO icon as RGBA data, see [`ImageFormat::Image`].
|
||||
pub fn new_ico(root: &TokenStream, icon: &Path) -> EmbeddedAssetsResult<Self> {
|
||||
let buf = Self::open(icon);
|
||||
|
||||
let icon_dir = ico::IconDir::read(Cursor::new(&buf))
|
||||
.unwrap_or_else(|e| panic!("failed to parse icon {}: {}", icon.display(), e));
|
||||
|
||||
let entry = &icon_dir.entries()[0];
|
||||
let rgba = entry
|
||||
.decode()
|
||||
.unwrap_or_else(|e| panic!("failed to decode icon {}: {}", icon.display(), e))
|
||||
.rgba_data()
|
||||
.to_vec();
|
||||
|
||||
Cached::try_from(rgba).map(|cache| Self {
|
||||
cache,
|
||||
root: root.clone(),
|
||||
format: IconFormat::Image {
|
||||
width: entry.width(),
|
||||
height: entry.height(),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
/// Cache a PNG icon as RGBA data, see [`ImageFormat::Image`].
|
||||
pub fn new_png(root: &TokenStream, icon: &Path) -> EmbeddedAssetsResult<Self> {
|
||||
let buf = Self::open(icon);
|
||||
let decoder = png::Decoder::new(Cursor::new(&buf));
|
||||
let mut reader = decoder
|
||||
.read_info()
|
||||
.unwrap_or_else(|e| panic!("failed to read icon {}: {}", icon.display(), e));
|
||||
|
||||
if reader.output_color_type().0 != png::ColorType::Rgba {
|
||||
panic!("icon {} is not RGBA", icon.display());
|
||||
}
|
||||
|
||||
let mut rgba = Vec::with_capacity(reader.output_buffer_size());
|
||||
while let Ok(Some(row)) = reader.next_row() {
|
||||
rgba.extend(row.data());
|
||||
}
|
||||
|
||||
Cached::try_from(rgba).map(|cache| Self {
|
||||
cache,
|
||||
root: root.clone(),
|
||||
format: IconFormat::Image {
|
||||
width: reader.info().width,
|
||||
height: reader.info().height,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
fn open(path: &Path) -> Vec<u8> {
|
||||
std::fs::read(path).unwrap_or_else(|e| panic!("failed to open icon {}: {}", path.display(), e))
|
||||
}
|
||||
}
|
||||
|
||||
impl ToTokens for CachedIcon {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||
let root = &self.root;
|
||||
let cache = &self.cache;
|
||||
let raw = quote!(::std::include_bytes!(#cache));
|
||||
tokens.append_all(match self.format {
|
||||
IconFormat::Raw => raw,
|
||||
IconFormat::Image { width, height } => {
|
||||
quote!(#root::image::Image::new(#raw, #width, #height))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -13,17 +13,21 @@
|
||||
)]
|
||||
|
||||
pub use self::context::{context_codegen, ContextData};
|
||||
pub use self::image::include_image_codegen;
|
||||
use crate::embedded_assets::{ensure_out_dir, EmbeddedAssetsError};
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::{quote, ToTokens, TokenStreamExt};
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
fmt::{self, Write},
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
pub use tauri_utils::config::{parse::ConfigError, Config};
|
||||
use tauri_utils::platform::Target;
|
||||
use tauri_utils::write_if_changed;
|
||||
|
||||
mod context;
|
||||
pub mod embedded_assets;
|
||||
mod image;
|
||||
pub mod image;
|
||||
#[doc(hidden)]
|
||||
pub mod vendor;
|
||||
|
||||
@ -97,3 +101,54 @@ pub fn get_config(path: &Path) -> Result<(Config, PathBuf), CodegenConfigError>
|
||||
|
||||
Ok((config, parent))
|
||||
}
|
||||
|
||||
/// Create a blake3 checksum of the passed bytes.
|
||||
fn checksum(bytes: &[u8]) -> Result<String, fmt::Error> {
|
||||
let mut hasher = vendor::blake3_reference::Hasher::default();
|
||||
hasher.update(bytes);
|
||||
|
||||
let mut bytes = [0u8; 32];
|
||||
hasher.finalize(&mut bytes);
|
||||
|
||||
let mut hex = String::with_capacity(2 * bytes.len());
|
||||
for b in bytes {
|
||||
write!(hex, "{b:02x}")?;
|
||||
}
|
||||
Ok(hex)
|
||||
}
|
||||
|
||||
/// Cache the data to `$OUT_DIR`, only if it does not already exist.
|
||||
///
|
||||
/// Due to using a checksum as the filename, an existing file should be the exact same content
|
||||
/// as the data being checked.
|
||||
struct Cached {
|
||||
checksum: String,
|
||||
}
|
||||
|
||||
impl TryFrom<String> for Cached {
|
||||
type Error = EmbeddedAssetsError;
|
||||
|
||||
fn try_from(value: String) -> Result<Self, Self::Error> {
|
||||
Self::try_from(Vec::from(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<Vec<u8>> for Cached {
|
||||
type Error = EmbeddedAssetsError;
|
||||
|
||||
fn try_from(content: Vec<u8>) -> Result<Self, Self::Error> {
|
||||
let checksum = checksum(content.as_ref()).map_err(EmbeddedAssetsError::Hex)?;
|
||||
let path = ensure_out_dir()?.join(&checksum);
|
||||
|
||||
write_if_changed(&path, &content)
|
||||
.map(|_| Self { checksum })
|
||||
.map_err(|error| EmbeddedAssetsError::AssetWrite { path, error })
|
||||
}
|
||||
}
|
||||
|
||||
impl ToTokens for Cached {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||
let path = &self.checksum;
|
||||
tokens.append_all(quote!(::std::concat!(::std::env!("OUT_DIR"), "/", #path)))
|
||||
}
|
||||
}
|
||||
|
@ -2,23 +2,15 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
use std::{
|
||||
error::Error,
|
||||
fs::File,
|
||||
io::{BufWriter, Write},
|
||||
path::PathBuf,
|
||||
};
|
||||
use std::{error::Error, path::PathBuf};
|
||||
use tauri_utils::{config::Config, write_if_changed};
|
||||
|
||||
pub fn main() -> Result<(), Box<dyn Error>> {
|
||||
let schema = schemars::schema_for!(tauri_utils::config::Config);
|
||||
let schema_str = serde_json::to_string_pretty(&schema).unwrap();
|
||||
let crate_dir = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR")?);
|
||||
for file in [
|
||||
crate_dir.join("schema.json"),
|
||||
crate_dir.join("../../tooling/cli/schema.json"),
|
||||
] {
|
||||
let mut schema_file = BufWriter::new(File::create(file)?);
|
||||
write!(schema_file, "{schema_str}")?;
|
||||
let schema = schemars::schema_for!(Config);
|
||||
let schema = serde_json::to_string_pretty(&schema)?;
|
||||
let out = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR")?);
|
||||
for path in ["schema.json", "../../tooling/cli/schema.json"] {
|
||||
write_if_changed(out.join(path), &schema)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
@ -15,8 +15,9 @@ use std::path::PathBuf;
|
||||
|
||||
use crate::context::ContextItems;
|
||||
use proc_macro::TokenStream;
|
||||
use quote::quote;
|
||||
use quote::{quote, ToTokens};
|
||||
use syn::{parse2, parse_macro_input, LitStr};
|
||||
use tauri_codegen::image::CachedIcon;
|
||||
|
||||
mod command;
|
||||
mod menu;
|
||||
@ -203,13 +204,9 @@ pub fn include_image(tokens: TokenStream) -> TokenStream {
|
||||
);
|
||||
return quote!(compile_error!(#error_string)).into();
|
||||
}
|
||||
match tauri_codegen::include_image_codegen(
|
||||
&resolved_path,
|
||||
resolved_path.file_name().unwrap().to_str().unwrap(),
|
||||
)
|
||||
.map_err(|error| error.to_string())
|
||||
{
|
||||
Ok(output) => output,
|
||||
|
||||
match CachedIcon::new("e!(::tauri), &resolved_path).map_err(|error| error.to_string()) {
|
||||
Ok(icon) => icon.into_token_stream(),
|
||||
Err(error) => quote!(compile_error!(#error)),
|
||||
}
|
||||
.into()
|
||||
|
@ -380,3 +380,20 @@ pub fn display_path<P: AsRef<Path>>(p: P) -> String {
|
||||
.display()
|
||||
.to_string()
|
||||
}
|
||||
|
||||
/// Write the file only if the content of the existing file (if any) is different.
|
||||
///
|
||||
/// This will always write unless the file exists with identical content.
|
||||
pub fn write_if_changed<P, C>(path: P, content: C) -> std::io::Result<()>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
C: AsRef<[u8]>,
|
||||
{
|
||||
if let Ok(existing) = std::fs::read(&path) {
|
||||
if existing == content.as_ref() {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
std::fs::write(path, content)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user