Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: allow specifying a resource map, closes #5844 #5950

Merged
merged 15 commits into from
Jul 13, 2023
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changes/resources-map-bundler.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"tauri-bundler": minor
---

Allow using a resource map instead of a simple array in `BundleSettings::resources_map`.
5 changes: 5 additions & 0 deletions .changes/resources-map.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"tauri-utils": minor
---

Allow specifying resources as a map specifying source and target paths.
35 changes: 21 additions & 14 deletions core/tauri-build/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ use cargo_toml::{Dependency, Manifest};
use heck::AsShoutySnakeCase;

use tauri_utils::{
config::Config,
resources::{external_binaries, resource_relpath, ResourcePaths},
config::{BundleResources, Config},
resources::{external_binaries, ResourcePaths},
};

use std::path::{Path, PathBuf};
Expand Down Expand Up @@ -71,11 +71,10 @@ fn copy_binaries(

/// Copies resources to a path.
fn copy_resources(resources: ResourcePaths<'_>, path: &Path) -> Result<()> {
for src in resources {
let src = src?;
println!("cargo:rerun-if-changed={}", src.display());
let dest = path.join(resource_relpath(&src));
copy_file(&src, dest)?;
for resource in resources.iter() {
let resource = resource?;
println!("cargo:rerun-if-changed={}", resource.path().display());
copy_file(resource.path(), path.join(resource.target()))?;
}
Ok(())
}
Expand Down Expand Up @@ -363,15 +362,23 @@ pub fn try_build(attributes: Attributes) -> Result<()> {
}

#[allow(unused_mut, clippy::redundant_clone)]
let mut resources = config.tauri.bundle.resources.clone().unwrap_or_default();
if target_triple.contains("windows") {
if let Some(fixed_webview2_runtime_path) =
&config.tauri.bundle.windows.webview_fixed_runtime_path
{
resources.push(fixed_webview2_runtime_path.display().to_string());
let mut resources = config
.tauri
.bundle
.resources
.clone()
.unwrap_or_else(|| BundleResources::List(Vec::new()));
if let Some(fixed_webview2_runtime_path) = &config.tauri.bundle.windows.webview_fixed_runtime_path
{
resources.push(fixed_webview2_runtime_path.display().to_string());
}

match resources {
BundleResources::List(res) => {
copy_resources(ResourcePaths::new(res.as_slice(), true), target_dir)?
}
BundleResources::Map(map) => copy_resources(ResourcePaths::from_map(&map, true), target_dir)?,
}
copy_resources(ResourcePaths::new(resources.as_slice(), true), target_dir)?;

if target_triple.contains("darwin") {
if let Some(version) = &config.tauri.bundle.macos.minimum_system_version {
Expand Down
34 changes: 27 additions & 7 deletions core/tauri-config-schema/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -1053,13 +1053,14 @@
},
"resources": {
"description": "App resources to bundle. Each resource is a path to a file or directory. Glob patterns are supported.",
"type": [
"array",
"null"
],
"items": {
"type": "string"
}
"anyOf": [
{
"$ref": "#/definitions/BundleResources"
},
{
"type": "null"
}
]
},
"copyright": {
"description": "A copyright string associated with your application.",
Expand Down Expand Up @@ -1237,6 +1238,25 @@
}
]
},
"BundleResources": {
"description": "Definition for bundle resources. Can be either a list of paths to include or a map of source to target paths.",
"anyOf": [
{
"description": "A list of paths to include.",
"type": "array",
"items": {
"type": "string"
}
},
{
"description": "A map of source to target paths.",
"type": "object",
"additionalProperties": {
"type": "string"
}
}
]
},
"AppImageConfig": {
"description": "Configuration for AppImage bundles.\n\nSee more: https://tauri.app/v1/api/config#appimageconfig",
"type": "object",
Expand Down
27 changes: 26 additions & 1 deletion core/tauri-utils/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -620,6 +620,31 @@ impl Default for WindowsConfig {
}
}

/// Definition for bundle resources.
/// Can be either a list of paths to include or a map of source to target paths.
#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(rename_all = "camelCase", deny_unknown_fields, untagged)]
pub enum BundleResources {
/// A list of paths to include.
List(Vec<String>),
/// A map of source to target paths.
Map(HashMap<String, String>),
}

impl BundleResources {
/// Adds a path to the resource collection.
pub fn push(&mut self, path: impl Into<String>) {
match self {
Self::List(l) => l.push(path.into()),
Self::Map(l) => {
let path = path.into();
l.insert(path.clone(), path);
}
}
}
}

/// Configuration for tauri-bundler.
///
/// See more: https://tauri.app/v1/api/config#bundleconfig
Expand Down Expand Up @@ -649,7 +674,7 @@ pub struct BundleConfig {
/// App resources to bundle.
/// Each resource is a path to a file or directory.
/// Glob patterns are supported.
pub resources: Option<Vec<String>>,
pub resources: Option<BundleResources>,
/// A copyright string associated with your application.
pub copyright: Option<String>,
/// The application kind.
Expand Down
171 changes: 145 additions & 26 deletions core/tauri-utils/src/resources.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT

use std::path::{Component, Path, PathBuf};
use std::{
collections::HashMap,
path::{Component, Path, PathBuf},
};

/// Given a path (absolute or relative) to a resource file, returns the
/// relative path from the bundle resources directory where that resource
Expand Down Expand Up @@ -39,40 +42,116 @@ pub fn external_binaries(external_binaries: &[String], target_triple: &str) -> V
paths
}

enum PatternIter<'a> {
Slice(std::slice::Iter<'a, String>),
Map(std::collections::hash_map::Iter<'a, String, String>),
}

/// A helper to iterate through resources.
pub struct ResourcePaths<'a> {
iter: ResourcePathsIter<'a>,
}

impl<'a> ResourcePaths<'a> {
/// Creates a new ResourcePaths from a slice of patterns to iterate
pub fn new(patterns: &'a [String], allow_walk: bool) -> ResourcePaths<'a> {
ResourcePaths {
iter: ResourcePathsIter {
pattern_iter: PatternIter::Slice(patterns.iter()),
glob_iter: None,
walk_iter: None,
allow_walk,
current_pattern: None,
current_pattern_is_valid: false,
current_dest: None,
},
}
}

/// Creates a new ResourcePaths from a slice of patterns to iterate
pub fn from_map(patterns: &'a HashMap<String, String>, allow_walk: bool) -> ResourcePaths<'a> {
ResourcePaths {
iter: ResourcePathsIter {
pattern_iter: PatternIter::Map(patterns.iter()),
glob_iter: None,
walk_iter: None,
allow_walk,
current_pattern: None,
current_pattern_is_valid: false,
current_dest: None,
},
}
}

/// Returns the resource iterator that yields the source and target paths.
/// Needed when using [`Self::from_map`].
pub fn iter(self) -> ResourcePathsIter<'a> {
self.iter
}
}

/// Iterator of a [`ResourcePaths`].
pub struct ResourcePathsIter<'a> {
/// the patterns to iterate.
pattern_iter: std::slice::Iter<'a, String>,
pattern_iter: PatternIter<'a>,
/// the glob iterator if the path from the current iteration is a glob pattern.
glob_iter: Option<glob::Paths>,
/// the walkdir iterator if the path from the current iteration is a directory.
walk_iter: Option<walkdir::IntoIter>,
/// whether the resource paths allows directories or not.
allow_walk: bool,
/// the pattern of the current iteration.
current_pattern: Option<String>,
current_pattern: Option<(String, PathBuf)>,
/// whether the current pattern is valid or not.
current_pattern_is_valid: bool,
/// Current destination path. Only set when the iterator comes from a Map.
current_dest: Option<PathBuf>,
}

impl<'a> ResourcePaths<'a> {
/// Creates a new ResourcePaths from a slice of patterns to iterate
pub fn new(patterns: &'a [String], allow_walk: bool) -> ResourcePaths<'a> {
ResourcePaths {
pattern_iter: patterns.iter(),
glob_iter: None,
walk_iter: None,
allow_walk,
current_pattern: None,
current_pattern_is_valid: false,
}
/// Information for a resource.
pub struct Resource {
path: PathBuf,
target: PathBuf,
}

impl Resource {
/// The path of the resource.
pub fn path(&self) -> &Path {
&self.path
}

/// The target location of the resource.
pub fn target(&self) -> &Path {
&self.target
}
}

impl<'a> Iterator for ResourcePaths<'a> {
type Item = crate::Result<PathBuf>;

fn next(&mut self) -> Option<crate::Result<PathBuf>> {
self.iter.next().map(|r| r.map(|res| res.path))
}
}

fn normalize(path: &Path) -> PathBuf {
let mut dest = PathBuf::new();
for component in path.components() {
match component {
Component::Prefix(_) => {}
Component::RootDir => dest.push("/"),
Component::CurDir => {}
Component::ParentDir => dest.push(".."),
Component::Normal(string) => dest.push(string),
}
}
dest
}

impl<'a> Iterator for ResourcePathsIter<'a> {
type Item = crate::Result<Resource>;

fn next(&mut self) -> Option<crate::Result<Resource>> {
loop {
if let Some(ref mut walk_entries) = self.walk_iter {
if let Some(entry) = walk_entries.next() {
Expand All @@ -85,7 +164,20 @@ impl<'a> Iterator for ResourcePaths<'a> {
continue;
}
self.current_pattern_is_valid = true;
return Some(Ok(path.to_path_buf()));
return Some(Ok(Resource {
target: if let (Some(current_dest), Some(current_pattern)) =
(&self.current_dest, &self.current_pattern)
{
if current_pattern.0.contains('*') {
current_dest.join(path.file_name().unwrap())
} else {
current_dest.join(path.strip_prefix(&current_pattern.1).unwrap())
}
} else {
resource_relpath(path)
},
path: path.to_path_buf(),
}));
}
}
self.walk_iter = None;
Expand All @@ -105,24 +197,51 @@ impl<'a> Iterator for ResourcePaths<'a> {
}
}
self.current_pattern_is_valid = true;
return Some(Ok(path));
return Some(Ok(Resource {
target: if let Some(current_dest) = &self.current_dest {
current_dest.join(path.file_name().unwrap())
} else {
resource_relpath(&path)
},
path,
}));
} else if let Some(current_path) = &self.current_pattern {
if !self.current_pattern_is_valid {
self.glob_iter = None;
return Some(Err(crate::Error::GlobPathNotFound(current_path.clone())));
return Some(Err(crate::Error::GlobPathNotFound(current_path.0.clone())));
}
}
}
self.glob_iter = None;
if let Some(pattern) = self.pattern_iter.next() {
self.current_pattern = Some(pattern.to_string());
self.current_pattern_is_valid = false;
let glob = match glob::glob(pattern) {
Ok(glob) => glob,
Err(error) => return Some(Err(error.into())),
};
self.glob_iter = Some(glob);
continue;
self.current_dest = None;
match &mut self.pattern_iter {
PatternIter::Slice(iter) => {
if let Some(pattern) = iter.next() {
self.current_pattern = Some((pattern.to_string(), normalize(Path::new(pattern))));
self.current_pattern_is_valid = false;
let glob = match glob::glob(pattern) {
Ok(glob) => glob,
Err(error) => return Some(Err(error.into())),
};
self.glob_iter = Some(glob);
continue;
}
}
PatternIter::Map(iter) => {
if let Some((pattern, dest)) = iter.next() {
self.current_pattern = Some((pattern.to_string(), normalize(Path::new(pattern))));
self.current_pattern_is_valid = false;
let glob = match glob::glob(pattern) {
Ok(glob) => glob,
Err(error) => return Some(Err(error.into())),
};
self
.current_dest
.replace(resource_relpath(&PathBuf::from(dest)));
self.glob_iter = Some(glob);
continue;
}
}
}
return None;
}
Expand Down
Loading