snapshot of https://github.com/tauri-apps/tauri.git (dev, 2026-09-23; blobs > 131072B omitted)
Some checks failed
Audit / audit-js (push) Failing after 0s
bench / bench (map[os:ubuntu-latest runner:xvfb-run --auto-servernum target:x86_64-unknown-linux-gnu], nightly) (push) Failing after 0s
covector version or publish / run-integration-tests (ubuntu-latest) (push) Failing after 0s
Audit / audit-rust (push) Failing after 0s
test `@tauri-apps/cli` / test (ubuntu-latest) (push) Failing after 0s
test `tauri-cli` / test (map[os:ubuntu-latest target:x86_64-unknown-linux-gnu]) (push) Failing after 0s
test core / test (map[args:--all-features key:all], map[cargo:cargo command:test os:ubuntu-latest target:x86_64-unknown-linux-gnu toolchain:1.90]) (push) Failing after 0s
test core / test (map[args:--no-default-features key:no-default], map[cargo:cargo command:test os:ubuntu-latest target:x86_64-unknown-linux-gnu toolchain:1.90]) (push) Failing after 0s
test core / test (map[args:--no-default-features key:no-default], map[cargo:cross command:build os:ubuntu-latest target:aarch64-linux-android toolchain:1.90]) (push) Failing after 0s
deploy schema worker / deploy (push) Failing after 0s
lint rust / clippy (map[cargo:cargo include: os:ubuntu-latest target:x86_64-unknown-linux-gnu]) (push) Failing after 0s
lint rust / clippy (map[cargo:cross include:--package api os:ubuntu-latest target:aarch64-linux-android]) (push) Failing after 0s
supply chain health status / check rust dependencies with cargo vet (push) Failing after 0s
test `@tauri-apps/api` e2e (mobile) / android (push) Failing after 0s
test `@tauri-apps/api` e2e / test (map[name:Linux os:ubuntu-latest]) (push) Failing after 0s
test core / test (map[args:--all-features key:all], map[cargo:cross command:build os:ubuntu-latest target:aarch64-linux-android toolchain:1.90]) (push) Failing after 0s
Udeps / changes (push) Failing after 0s
Udeps / setup (push) Has been skipped
Udeps / udeps (push) Has been skipped
bench / bench (map[os:macos-latest runner: target:aarch64-apple-darwin], nightly) (push) Has been cancelled
bench / bench (map[os:windows-latest runner: target:x86_64-pc-windows-msvc], nightly) (push) Has been cancelled
covector version or publish / run-integration-tests (macos-latest) (push) Has been cancelled
covector version or publish / run-integration-tests (windows-latest) (push) Has been cancelled
covector version or publish / version-or-publish (push) Has been cancelled
test `@tauri-apps/cli` / test (macos-latest) (push) Has been cancelled
test `tauri-cli` / test (map[args:--no-default-features --features native-tls-vendored os:windows-latest restore-only:true target:aarch64-pc-windows-msvc]) (push) Has been cancelled
test `tauri-cli` / test (map[os:macos-latest target:x86_64-apple-darwin]) (push) Has been cancelled
test `tauri-cli` / test (map[os:windows-latest target:x86_64-pc-windows-msvc]) (push) Has been cancelled
test core / test (map[args:--all-features key:all], map[cargo:cargo command:build os:macos-latest target:aarch64-apple-ios toolchain:1.90]) (push) Has been cancelled
test core / test (map[args:--all-features key:all], map[cargo:cargo command:test os:macos-14 target:aarch64-apple-darwin toolchain:1.90]) (push) Has been cancelled
lint rust / clippy (map[cargo:cargo include: os:macos-latest target:aarch64-apple-darwin]) (push) Has been cancelled
lint rust / clippy (map[cargo:cargo include: os:windows-latest target:x86_64-pc-windows-msvc]) (push) Has been cancelled
lint rust / clippy (map[cargo:cargo include:--package api os:macos-latest target:aarch64-apple-ios]) (push) Has been cancelled
test `@tauri-apps/api` e2e (mobile) / ios (push) Has been cancelled
test `@tauri-apps/api` e2e / test (map[name:Windows os:windows-latest]) (push) Has been cancelled
test `@tauri-apps/api` e2e / test (map[name:macOS os:macos-latest]) (push) Has been cancelled
test `@tauri-apps/cli` / test (windows-latest) (push) Has been cancelled
test core / test (map[args:--all-features key:all], map[cargo:cargo command:test os:windows-latest target:x86_64-pc-windows-msvc toolchain:1.90]) (push) Has been cancelled
test core / test (map[args:--no-default-features key:no-default], map[cargo:cargo command:build os:macos-latest target:aarch64-apple-ios toolchain:1.90]) (push) Has been cancelled
test core / test (map[args:--no-default-features key:no-default], map[cargo:cargo command:test os:macos-14 target:aarch64-apple-darwin toolchain:1.90]) (push) Has been cancelled
test core / test (map[args:--no-default-features key:no-default], map[cargo:cargo command:test os:windows-latest target:x86_64-pc-windows-msvc toolchain:1.90]) (push) Has been cancelled
Some checks failed
Audit / audit-js (push) Failing after 0s
bench / bench (map[os:ubuntu-latest runner:xvfb-run --auto-servernum target:x86_64-unknown-linux-gnu], nightly) (push) Failing after 0s
covector version or publish / run-integration-tests (ubuntu-latest) (push) Failing after 0s
Audit / audit-rust (push) Failing after 0s
test `@tauri-apps/cli` / test (ubuntu-latest) (push) Failing after 0s
test `tauri-cli` / test (map[os:ubuntu-latest target:x86_64-unknown-linux-gnu]) (push) Failing after 0s
test core / test (map[args:--all-features key:all], map[cargo:cargo command:test os:ubuntu-latest target:x86_64-unknown-linux-gnu toolchain:1.90]) (push) Failing after 0s
test core / test (map[args:--no-default-features key:no-default], map[cargo:cargo command:test os:ubuntu-latest target:x86_64-unknown-linux-gnu toolchain:1.90]) (push) Failing after 0s
test core / test (map[args:--no-default-features key:no-default], map[cargo:cross command:build os:ubuntu-latest target:aarch64-linux-android toolchain:1.90]) (push) Failing after 0s
deploy schema worker / deploy (push) Failing after 0s
lint rust / clippy (map[cargo:cargo include: os:ubuntu-latest target:x86_64-unknown-linux-gnu]) (push) Failing after 0s
lint rust / clippy (map[cargo:cross include:--package api os:ubuntu-latest target:aarch64-linux-android]) (push) Failing after 0s
supply chain health status / check rust dependencies with cargo vet (push) Failing after 0s
test `@tauri-apps/api` e2e (mobile) / android (push) Failing after 0s
test `@tauri-apps/api` e2e / test (map[name:Linux os:ubuntu-latest]) (push) Failing after 0s
test core / test (map[args:--all-features key:all], map[cargo:cross command:build os:ubuntu-latest target:aarch64-linux-android toolchain:1.90]) (push) Failing after 0s
Udeps / changes (push) Failing after 0s
Udeps / setup (push) Has been skipped
Udeps / udeps (push) Has been skipped
bench / bench (map[os:macos-latest runner: target:aarch64-apple-darwin], nightly) (push) Has been cancelled
bench / bench (map[os:windows-latest runner: target:x86_64-pc-windows-msvc], nightly) (push) Has been cancelled
covector version or publish / run-integration-tests (macos-latest) (push) Has been cancelled
covector version or publish / run-integration-tests (windows-latest) (push) Has been cancelled
covector version or publish / version-or-publish (push) Has been cancelled
test `@tauri-apps/cli` / test (macos-latest) (push) Has been cancelled
test `tauri-cli` / test (map[args:--no-default-features --features native-tls-vendored os:windows-latest restore-only:true target:aarch64-pc-windows-msvc]) (push) Has been cancelled
test `tauri-cli` / test (map[os:macos-latest target:x86_64-apple-darwin]) (push) Has been cancelled
test `tauri-cli` / test (map[os:windows-latest target:x86_64-pc-windows-msvc]) (push) Has been cancelled
test core / test (map[args:--all-features key:all], map[cargo:cargo command:build os:macos-latest target:aarch64-apple-ios toolchain:1.90]) (push) Has been cancelled
test core / test (map[args:--all-features key:all], map[cargo:cargo command:test os:macos-14 target:aarch64-apple-darwin toolchain:1.90]) (push) Has been cancelled
lint rust / clippy (map[cargo:cargo include: os:macos-latest target:aarch64-apple-darwin]) (push) Has been cancelled
lint rust / clippy (map[cargo:cargo include: os:windows-latest target:x86_64-pc-windows-msvc]) (push) Has been cancelled
lint rust / clippy (map[cargo:cargo include:--package api os:macos-latest target:aarch64-apple-ios]) (push) Has been cancelled
test `@tauri-apps/api` e2e (mobile) / ios (push) Has been cancelled
test `@tauri-apps/api` e2e / test (map[name:Windows os:windows-latest]) (push) Has been cancelled
test `@tauri-apps/api` e2e / test (map[name:macOS os:macos-latest]) (push) Has been cancelled
test `@tauri-apps/cli` / test (windows-latest) (push) Has been cancelled
test core / test (map[args:--all-features key:all], map[cargo:cargo command:test os:windows-latest target:x86_64-pc-windows-msvc toolchain:1.90]) (push) Has been cancelled
test core / test (map[args:--no-default-features key:no-default], map[cargo:cargo command:build os:macos-latest target:aarch64-apple-ios toolchain:1.90]) (push) Has been cancelled
test core / test (map[args:--no-default-features key:no-default], map[cargo:cargo command:test os:macos-14 target:aarch64-apple-darwin toolchain:1.90]) (push) Has been cancelled
test core / test (map[args:--no-default-features key:no-default], map[cargo:cargo command:test os:windows-latest target:x86_64-pc-windows-msvc toolchain:1.90]) (push) Has been cancelled
This commit is contained in:
452
crates/tauri-codegen/src/embedded_assets.rs
Normal file
452
crates/tauri-codegen/src/embedded_assets.rs
Normal file
@@ -0,0 +1,452 @@
|
||||
// Copyright 2019-2024 Tauri Programme within The Commons Conservancy
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
use base64::Engine;
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::{ToTokens, TokenStreamExt, quote};
|
||||
use sha2::{Digest, Sha256};
|
||||
use std::{
|
||||
collections::BTreeMap,
|
||||
fs::File,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
use tauri_utils::config::PatternKind;
|
||||
use tauri_utils::{assets::AssetKey, config::DisabledCspModificationKind};
|
||||
use thiserror::Error;
|
||||
use walkdir::{DirEntry, WalkDir};
|
||||
|
||||
#[cfg(feature = "compression")]
|
||||
use brotli::enc::backward_references::BrotliEncoderParams;
|
||||
|
||||
/// The subdirectory inside the target directory we want to place assets.
|
||||
const TARGET_PATH: &str = "tauri-codegen-assets";
|
||||
|
||||
/// (key, (original filepath, compressed bytes))
|
||||
type Asset = (AssetKey, (PathBuf, PathBuf));
|
||||
|
||||
/// All possible errors while reading and compressing an [`EmbeddedAssets`] directory
|
||||
#[derive(Debug, Error)]
|
||||
#[non_exhaustive]
|
||||
pub enum EmbeddedAssetsError {
|
||||
#[error("failed to read asset at {path} because {error}")]
|
||||
AssetRead {
|
||||
path: PathBuf,
|
||||
error: std::io::Error,
|
||||
},
|
||||
|
||||
#[error("failed to write asset from {path} to Vec<u8> because {error}")]
|
||||
AssetWrite {
|
||||
path: PathBuf,
|
||||
error: std::io::Error,
|
||||
},
|
||||
|
||||
#[error("failed to create hex from bytes because {0}")]
|
||||
Hex(std::fmt::Error),
|
||||
|
||||
#[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`")]
|
||||
InvalidImageExtension { extension: PathBuf, path: PathBuf },
|
||||
|
||||
#[error("failed to walk directory {path} because {error}")]
|
||||
Walkdir {
|
||||
path: PathBuf,
|
||||
error: walkdir::Error,
|
||||
},
|
||||
|
||||
#[error("OUT_DIR env var is not set, do you have a build script?")]
|
||||
OutDir,
|
||||
|
||||
#[error("version error: {0}")]
|
||||
Version(#[from] semver::Error),
|
||||
}
|
||||
|
||||
pub type EmbeddedAssetsResult<T> = Result<T, EmbeddedAssetsError>;
|
||||
|
||||
/// Represent a directory of assets that are compressed and embedded.
|
||||
///
|
||||
/// This is the compile time generation of [`tauri_utils::assets::Assets`] from a directory. Assets
|
||||
/// from the directory are added as compiler dependencies by dummy including the original,
|
||||
/// uncompressed assets.
|
||||
///
|
||||
/// The assets are compressed during this runtime, and can only be represented as a [`TokenStream`]
|
||||
/// through [`ToTokens`]. The generated code is meant to be injected into an application to include
|
||||
/// the compressed assets in that application's binary.
|
||||
#[derive(Default)]
|
||||
pub struct EmbeddedAssets {
|
||||
assets: BTreeMap<AssetKey, (PathBuf, PathBuf)>,
|
||||
csp_hashes: CspHashes,
|
||||
}
|
||||
|
||||
pub struct EmbeddedAssetsInput(Vec<PathBuf>);
|
||||
|
||||
impl From<PathBuf> for EmbeddedAssetsInput {
|
||||
fn from(path: PathBuf) -> Self {
|
||||
Self(vec![path])
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vec<PathBuf>> for EmbeddedAssetsInput {
|
||||
fn from(paths: Vec<PathBuf>) -> Self {
|
||||
Self(paths)
|
||||
}
|
||||
}
|
||||
|
||||
/// Holds a list of (prefix, entry)
|
||||
struct RawEmbeddedAssets {
|
||||
paths: Vec<(PathBuf, DirEntry)>,
|
||||
csp_hashes: CspHashes,
|
||||
}
|
||||
|
||||
impl RawEmbeddedAssets {
|
||||
/// Creates a new list of (prefix, entry) from a collection of inputs.
|
||||
fn new(input: EmbeddedAssetsInput, options: &AssetOptions) -> Result<Self, EmbeddedAssetsError> {
|
||||
let mut csp_hashes = CspHashes::default();
|
||||
|
||||
input
|
||||
.0
|
||||
.into_iter()
|
||||
.flat_map(|path| {
|
||||
let prefix = if path.is_dir() {
|
||||
path.clone()
|
||||
} else {
|
||||
path
|
||||
.parent()
|
||||
.expect("embedded file asset has no parent")
|
||||
.to_path_buf()
|
||||
};
|
||||
|
||||
WalkDir::new(&path)
|
||||
.follow_links(true)
|
||||
.contents_first(true)
|
||||
.into_iter()
|
||||
.map(move |entry| (prefix.clone(), entry))
|
||||
})
|
||||
.filter_map(|(prefix, entry)| {
|
||||
match entry {
|
||||
// we only serve files, not directory listings
|
||||
Ok(entry) if entry.file_type().is_dir() => None,
|
||||
|
||||
// compress all files encountered
|
||||
Ok(entry) => {
|
||||
if let Err(error) = csp_hashes
|
||||
.add_if_applicable(&entry, &options.dangerous_disable_asset_csp_modification)
|
||||
{
|
||||
Some(Err(error))
|
||||
} else {
|
||||
Some(Ok((prefix, entry)))
|
||||
}
|
||||
}
|
||||
|
||||
// pass down error through filter to fail when encountering any error
|
||||
Err(error) => Some(Err(EmbeddedAssetsError::Walkdir {
|
||||
path: prefix,
|
||||
error,
|
||||
})),
|
||||
}
|
||||
})
|
||||
.collect::<Result<Vec<(PathBuf, DirEntry)>, _>>()
|
||||
.map(|paths| Self { paths, csp_hashes })
|
||||
}
|
||||
}
|
||||
|
||||
/// Holds all hashes that we will apply on the CSP tag/header.
|
||||
#[derive(Debug, Default)]
|
||||
pub struct CspHashes {
|
||||
/// Scripts that are part of the asset collection (JS or MJS files).
|
||||
pub(crate) scripts: Vec<String>,
|
||||
/// Inline scripts (`<script>code</script>`). Maps a HTML path to a list of hashes.
|
||||
pub(crate) inline_scripts: BTreeMap<String, Vec<String>>,
|
||||
/// A list of hashes of the contents of all `style` elements.
|
||||
pub(crate) styles: Vec<String>,
|
||||
}
|
||||
|
||||
impl CspHashes {
|
||||
/// Only add a CSP hash to the appropriate category if we think the file matches
|
||||
///
|
||||
/// Note: this only checks the file extension, much like how a browser will assume a .js file is
|
||||
/// a JavaScript file unless HTTP headers tell it otherwise.
|
||||
pub fn add_if_applicable(
|
||||
&mut self,
|
||||
entry: &DirEntry,
|
||||
dangerous_disable_asset_csp_modification: &DisabledCspModificationKind,
|
||||
) -> Result<(), EmbeddedAssetsError> {
|
||||
let path = entry.path();
|
||||
|
||||
// we only hash JavaScript files for now, may expand to other CSP hashable types in the future
|
||||
if let Some("js") | Some("mjs") = path.extension().and_then(|os| os.to_str()) {
|
||||
if dangerous_disable_asset_csp_modification.can_modify("script-src") {
|
||||
let mut hasher = Sha256::new();
|
||||
hasher.update(
|
||||
&std::fs::read(path)
|
||||
.map(|b| tauri_utils::html2::normalize_script_for_csp(&b))
|
||||
.map_err(|error| EmbeddedAssetsError::AssetRead {
|
||||
path: path.to_path_buf(),
|
||||
error,
|
||||
})?,
|
||||
);
|
||||
let hash = hasher.finalize();
|
||||
self.scripts.push(format!(
|
||||
"'sha256-{}'",
|
||||
base64::engine::general_purpose::STANDARD.encode(hash)
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Options used to embed assets.
|
||||
#[derive(Default)]
|
||||
pub struct AssetOptions {
|
||||
pub(crate) csp: bool,
|
||||
pub(crate) pattern: PatternKind,
|
||||
pub(crate) freeze_prototype: bool,
|
||||
pub(crate) dangerous_disable_asset_csp_modification: DisabledCspModificationKind,
|
||||
#[cfg(feature = "isolation")]
|
||||
pub(crate) isolation_schema: String,
|
||||
}
|
||||
|
||||
impl AssetOptions {
|
||||
/// Creates the default asset options.
|
||||
pub fn new(pattern: PatternKind) -> Self {
|
||||
Self {
|
||||
csp: false,
|
||||
pattern,
|
||||
freeze_prototype: false,
|
||||
dangerous_disable_asset_csp_modification: DisabledCspModificationKind::Flag(false),
|
||||
#[cfg(feature = "isolation")]
|
||||
isolation_schema: format!("isolation-{}", uuid::Uuid::new_v4()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Instruct the asset handler to inject the CSP token to HTML files (Linux only) and add asset nonces and hashes to the policy.
|
||||
#[must_use]
|
||||
pub fn with_csp(mut self) -> Self {
|
||||
self.csp = true;
|
||||
self
|
||||
}
|
||||
|
||||
/// Instruct the asset handler to include a script to freeze the `Object.prototype` on all HTML files.
|
||||
#[must_use]
|
||||
pub fn freeze_prototype(mut self, freeze: bool) -> Self {
|
||||
self.freeze_prototype = freeze;
|
||||
self
|
||||
}
|
||||
|
||||
/// Instruct the asset handler to **NOT** modify the CSP. This is **NOT** recommended.
|
||||
pub fn dangerous_disable_asset_csp_modification(
|
||||
mut self,
|
||||
dangerous_disable_asset_csp_modification: DisabledCspModificationKind,
|
||||
) -> Self {
|
||||
self.dangerous_disable_asset_csp_modification = dangerous_disable_asset_csp_modification;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl EmbeddedAssets {
|
||||
/// Compress a collection of files and directories, ready to be generated into [`Assets`].
|
||||
///
|
||||
/// [`Assets`]: tauri_utils::assets::Assets
|
||||
pub fn new(
|
||||
input: impl Into<EmbeddedAssetsInput>,
|
||||
options: &AssetOptions,
|
||||
mut map: impl FnMut(
|
||||
&AssetKey,
|
||||
&Path,
|
||||
&mut Vec<u8>,
|
||||
&mut CspHashes,
|
||||
) -> Result<(), EmbeddedAssetsError>,
|
||||
) -> Result<Self, EmbeddedAssetsError> {
|
||||
// we need to pre-compute all files now, so that we can inject data from all files into a few
|
||||
let RawEmbeddedAssets { paths, csp_hashes } = RawEmbeddedAssets::new(input.into(), options)?;
|
||||
|
||||
struct CompressState {
|
||||
csp_hashes: CspHashes,
|
||||
assets: BTreeMap<AssetKey, (PathBuf, PathBuf)>,
|
||||
}
|
||||
|
||||
let CompressState { assets, csp_hashes } = paths.into_iter().try_fold(
|
||||
CompressState {
|
||||
csp_hashes,
|
||||
assets: BTreeMap::new(),
|
||||
},
|
||||
move |mut state, (prefix, entry)| {
|
||||
let (key, asset) =
|
||||
Self::compress_file(&prefix, entry.path(), &mut map, &mut state.csp_hashes)?;
|
||||
state.assets.insert(key, asset);
|
||||
Result::<_, EmbeddedAssetsError>::Ok(state)
|
||||
},
|
||||
)?;
|
||||
|
||||
Ok(Self { assets, csp_hashes })
|
||||
}
|
||||
|
||||
/// Use highest compression level for release, the fastest one for everything else
|
||||
#[cfg(feature = "compression")]
|
||||
fn compression_settings() -> BrotliEncoderParams {
|
||||
let mut settings = BrotliEncoderParams::default();
|
||||
|
||||
// the following compression levels are hand-picked and are not min-maxed.
|
||||
// they have a good balance of runtime vs size for the respective profile goals.
|
||||
// see the "brotli" section of this comment https://github.com/tauri-apps/tauri/issues/3571#issuecomment-1054847558
|
||||
if cfg!(debug_assertions) {
|
||||
settings.quality = 2
|
||||
} else {
|
||||
settings.quality = 9
|
||||
}
|
||||
|
||||
settings
|
||||
}
|
||||
|
||||
/// Compress a file and spit out the information in a [`BTreeMap`] friendly form.
|
||||
fn compress_file(
|
||||
prefix: &Path,
|
||||
path: &Path,
|
||||
map: &mut impl FnMut(
|
||||
&AssetKey,
|
||||
&Path,
|
||||
&mut Vec<u8>,
|
||||
&mut CspHashes,
|
||||
) -> Result<(), EmbeddedAssetsError>,
|
||||
csp_hashes: &mut CspHashes,
|
||||
) -> Result<Asset, EmbeddedAssetsError> {
|
||||
let mut input = std::fs::read(path).map_err(|error| EmbeddedAssetsError::AssetRead {
|
||||
path: path.to_owned(),
|
||||
error,
|
||||
})?;
|
||||
|
||||
// get a key to the asset path without the asset directory prefix
|
||||
let key = path
|
||||
.strip_prefix(prefix)
|
||||
.map(AssetKey::from) // format the path for use in assets
|
||||
.map_err(|_| EmbeddedAssetsError::PrefixInvalid {
|
||||
prefix: prefix.to_owned(),
|
||||
path: path.to_owned(),
|
||||
})?;
|
||||
|
||||
// perform any caller-requested input manipulation
|
||||
map(&key, path, &mut input, csp_hashes)?;
|
||||
|
||||
// we must canonicalize the base of our paths to allow long paths on windows
|
||||
let out_dir = std::env::var("OUT_DIR")
|
||||
.map_err(|_| EmbeddedAssetsError::OutDir)
|
||||
.map(PathBuf::from)
|
||||
.and_then(|p| p.canonicalize().map_err(|_| EmbeddedAssetsError::OutDir))
|
||||
.map(|p| p.join(TARGET_PATH))?;
|
||||
|
||||
// make sure that our output directory is created
|
||||
std::fs::create_dir_all(&out_dir).map_err(|_| EmbeddedAssetsError::OutDir)?;
|
||||
|
||||
// get a hash of the input - allows for caching existing files
|
||||
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()) {
|
||||
out_dir.join(format!("{hash}.{ext}"))
|
||||
} else {
|
||||
out_dir.join(hash)
|
||||
};
|
||||
|
||||
// only compress and write to the file if it doesn't already exist.
|
||||
if !out_path.exists() {
|
||||
#[allow(unused_mut)]
|
||||
let mut out_file =
|
||||
File::create(&out_path).map_err(|error| EmbeddedAssetsError::AssetWrite {
|
||||
path: out_path.clone(),
|
||||
error,
|
||||
})?;
|
||||
|
||||
#[cfg(not(feature = "compression"))]
|
||||
{
|
||||
use std::io::Write;
|
||||
out_file
|
||||
.write_all(&input)
|
||||
.map_err(|error| EmbeddedAssetsError::AssetWrite {
|
||||
path: path.to_owned(),
|
||||
error,
|
||||
})?;
|
||||
}
|
||||
|
||||
#[cfg(feature = "compression")]
|
||||
{
|
||||
let mut input = std::io::Cursor::new(input);
|
||||
// entirely write input to the output file path with compression
|
||||
brotli::BrotliCompress(&mut input, &mut out_file, &Self::compression_settings()).map_err(
|
||||
|error| EmbeddedAssetsError::AssetWrite {
|
||||
path: path.to_owned(),
|
||||
error,
|
||||
},
|
||||
)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok((key, (path.into(), out_path)))
|
||||
}
|
||||
}
|
||||
|
||||
impl ToTokens for EmbeddedAssets {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||
let mut assets = TokenStream::new();
|
||||
for (key, (input, output)) in &self.assets {
|
||||
let key: &str = key.as_ref();
|
||||
let input = input.display().to_string();
|
||||
let output = output.display().to_string();
|
||||
|
||||
// add original asset as a compiler dependency, rely on dead code elimination to clean it up
|
||||
assets.append_all(quote!(#key => {
|
||||
const _: &[u8] = include_bytes!(#input);
|
||||
include_bytes!(#output)
|
||||
},));
|
||||
}
|
||||
|
||||
let mut global_hashes = TokenStream::new();
|
||||
// Sort the hashes so the generated code does not depend on the filesystem
|
||||
// walk order the assets were collected in, which varies across machines
|
||||
let mut script_hashes: Vec<_> = self.csp_hashes.scripts.iter().collect();
|
||||
script_hashes.sort();
|
||||
for script_hash in script_hashes {
|
||||
let hash = script_hash.as_str();
|
||||
global_hashes.append_all(quote!(CspHash::Script(#hash),));
|
||||
}
|
||||
|
||||
let mut style_hashes: Vec<_> = self.csp_hashes.styles.iter().collect();
|
||||
style_hashes.sort();
|
||||
for style_hash in style_hashes {
|
||||
let hash = style_hash.as_str();
|
||||
global_hashes.append_all(quote!(CspHash::Style(#hash),));
|
||||
}
|
||||
|
||||
let mut html_hashes = TokenStream::new();
|
||||
for (path, hashes) in &self.csp_hashes.inline_scripts {
|
||||
let key = path.as_str();
|
||||
let mut value = TokenStream::new();
|
||||
for script_hash in hashes {
|
||||
let hash = script_hash.as_str();
|
||||
value.append_all(quote!(CspHash::Script(#hash),));
|
||||
}
|
||||
html_hashes.append_all(quote!(#key => &[#value],));
|
||||
}
|
||||
|
||||
// we expect phf related items to be in path when generating the path code
|
||||
tokens.append_all(quote! {{
|
||||
#[allow(unused_imports)]
|
||||
use ::tauri::utils::assets::{CspHash, EmbeddedAssets, phf, phf::phf_map};
|
||||
EmbeddedAssets::new(phf_map! { #assets }, &[#global_hashes], phf_map! { #html_hashes })
|
||||
}});
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn ensure_out_dir() -> EmbeddedAssetsResult<PathBuf> {
|
||||
let out_dir = std::env::var("OUT_DIR")
|
||||
.map_err(|_| EmbeddedAssetsError::OutDir)
|
||||
.map(PathBuf::from)
|
||||
.and_then(|p| p.canonicalize().map_err(|_| EmbeddedAssetsError::OutDir))?;
|
||||
|
||||
// make sure that our output directory is created
|
||||
std::fs::create_dir_all(&out_dir).map_err(|_| EmbeddedAssetsError::OutDir)?;
|
||||
Ok(out_dir)
|
||||
}
|
||||
Reference in New Issue
Block a user