feat(bundler&tauri) add wix resource bundling and utils to get the path to the platform resource dir (#352)

* feat(bundler) copy resources to the out dir

* feat(utils) add resource_dir fn

* feat(examples) spawn node with resource JS instead of pkg bin

* feat(bundler) WIP on windows resource bundler

* feat(utils) add windows, macos resource_dir logic

* fix(bundler) resource folder iteration on wix

* chore(bundler) add comments to generate_resource_data fn

* chore(bundler) add comments to the get_wix_data fn

* change minor items.

* run `cargo fmt`

* run `rust fmt` and `clippy` and add fmt.toml

* remove unnessecary rustfmt.toml files.

Co-authored-by: Tensor-Programming <abeltensor@tensor-programming.com>
This commit is contained in:
Lucas Fernandes Nogueira 2020-01-26 21:57:09 -03:00 committed by Tensor-Programming
parent 92d87137cc
commit b7a6bc0f42
19 changed files with 230 additions and 45 deletions

View File

@ -42,6 +42,7 @@ pub fn bundle_project(settings: Settings) -> crate::Result<Vec<PathBuf>> {
});
}
settings.copy_resources(settings.project_out_directory())?;
settings.copy_binaries(settings.project_out_directory())?;
Ok(paths)

View File

@ -210,13 +210,7 @@ fn generate_md5sums(control_dir: &Path, data_dir: &Path) -> crate::Result<()> {
/// `data_dir`.
fn transfer_resource_files(settings: &Settings, data_dir: &Path) -> crate::Result<()> {
let resource_dir = data_dir.join("usr/lib").join(settings.binary_name());
for src in settings.resource_files() {
let src = src?;
let dest = resource_dir.join(common::resource_relpath(&src));
common::copy_file(&src, &dest)
.chain_err(|| format!("Failed to copy resource file {:?}", src))?;
}
Ok(())
settings.copy_resources(&resource_dir)
}
/// Generate the icon files and store them under the `data_dir`.

View File

@ -6,10 +6,10 @@ use handlebars::Handlebars;
use lazy_static::lazy_static;
use std::collections::BTreeMap;
use std::fs::{File, write};
use std::fs::{write, File};
use std::io::Write;
use std::path::PathBuf;
use std::process::{Command, Stdio};
use std::io::Write;
// Create handlebars template for shell scripts
lazy_static! {
@ -55,7 +55,9 @@ pub fn bundle_project(settings: &Settings) -> crate::Result<Vec<PathBuf>> {
let seticon = include_bytes!("templates/seticon");
let seticon_out = &output_path.join("seticon");
let mut seticon_buffer = File::create(seticon_out).or_else(|e| Err(e.to_string()))?;
seticon_buffer.write_all(seticon).or_else(|e| Err(e.to_string()))?;
seticon_buffer
.write_all(seticon)
.or_else(|e| Err(e.to_string()))?;
// chmod script for execution

View File

@ -36,12 +36,7 @@ pub fn bundle_project(settings: &Settings) -> crate::Result<Vec<PathBuf>> {
fs::create_dir_all(&bundle_dir)
.chain_err(|| format!("Failed to create bundle directory at {:?}", bundle_dir))?;
for src in settings.resource_files() {
let src = src?;
let dest = bundle_dir.join(common::resource_relpath(&src));
common::copy_file(&src, &dest)
.chain_err(|| format!("Failed to copy resource file {:?}", src))?;
}
settings.copy_resources(bundle_dir);
let icon_filenames =
generate_icon_files(&bundle_dir, settings).chain_err(|| "Failed to create app icons")?;

View File

@ -61,12 +61,7 @@ pub fn bundle_project(settings: &Settings) -> crate::Result<Vec<PathBuf>> {
copy_frameworks_to_bundle(&bundle_directory, settings)
.chain_err(|| "Failed to bundle frameworks")?;
for src in settings.resource_files() {
let src = src?;
let dest = resources_dir.join(common::resource_relpath(&src));
common::copy_file(&src, &dest)
.chain_err(|| format!("Failed to copy resource file {:?}", src))?;
}
settings.copy_resources(&resources_dir)?;
settings
.copy_binaries(&bin_dir)

View File

@ -445,6 +445,17 @@ impl Settings {
Ok(())
}
// copy resources to a path
pub fn copy_resources(&self, path: &Path) -> crate::Result<()> {
for src in self.resource_files() {
let src = src?;
let dest = path.join(common::resource_relpath(&src));
common::copy_file(&src, &dest)
.map_err(|_| format!("Failed to copy resource file {:?}", src))?;
}
Ok(())
}
pub fn version_string(&self) -> &str {
self
.bundle_settings

View File

@ -48,6 +48,7 @@
<File Id="PathFile_{{ external_bin.id }}" Source="{{external_bin.path}}" />
</Component>
{{/each~}}
{{{resources}}}
</Directory>
</Directory>
</Directory>
@ -85,6 +86,10 @@
Display="expand"
Absent="disallow">
{{#each resource_file_ids as |resource_file_id| ~}}
<ComponentRef Id="{{ resource_file_id }}"/>
{{/each~}}
<Feature Id="ShortcutsFeature"
Title="Shortcuts"
Level="1">

View File

@ -53,6 +53,8 @@ lazy_static! {
};
}
type ResourceMap = BTreeMap<String, ResourceDirectory>;
#[derive(Serialize)]
struct ExternalBinary {
guid: String,
@ -60,6 +62,58 @@ struct ExternalBinary {
path: String,
}
#[derive(Serialize, Clone)]
struct ResourceFile {
guid: String,
id: String,
path: String,
}
#[derive(Serialize)]
struct ResourceDirectory {
name: String,
files: Vec<ResourceFile>,
directories: Vec<ResourceDirectory>,
}
impl ResourceDirectory {
fn add_file(&mut self, file: ResourceFile) {
self.files.push(file);
}
// generates the wix XML string to bundle this directory resources recursively
fn get_wix_data(self) -> crate::Result<(String, Vec<String>)> {
let mut files = String::from("");
let mut file_ids = Vec::new();
for file in self.files {
file_ids.push(file.id.clone());
files.push_str(
format!(
r#"<Component Id="{id}" Guid="{guid}" Win64="$(var.Win64)" KeyPath="yes"><File Id="PathFile_{id}" Source="{path}" /></Component>"#,
id = file.id,
guid = file.guid,
path = file.path
).as_str()
);
}
let mut directories = String::from("");
for directory in self.directories {
let (wix_string, ids) = directory.get_wix_data()?;
for id in ids {
file_ids.push(id)
}
directories.push_str(wix_string.as_str());
}
let wix_string = format!(
r#"<Directory Id="{name}" Name="{name}">{contents}</Directory>"#,
name = self.name,
contents = format!("{}{}", files, directories)
);
Ok((wix_string, file_ids))
}
}
fn copy_icons(settings: &Settings) -> crate::Result<PathBuf> {
let base_dir = settings.binary_path();
let base_dir = base_dir.parent().expect("Failed to get dir");
@ -378,6 +432,20 @@ pub fn build_wix_app_installer(
let external_binaries_json = to_json(&external_binaries);
data.insert("external_binaries", external_binaries_json);
let resources = generate_resource_data(&settings)?;
let mut resources_wix_string = String::from("");
let mut files_ids = Vec::new();
for (_, dir) in resources {
let (wix_string, ids) = dir.get_wix_data()?;
resources_wix_string.push_str(wix_string.as_str());
for id in ids {
files_ids.push(id);
}
}
data.insert("resources", to_json(resources_wix_string));
data.insert("resource_file_ids", to_json(files_ids));
let app_exe_source = settings.binary_path().display().to_string();
data.insert("app_exe_source", to_json(&app_exe_source));
@ -448,3 +516,92 @@ fn generate_external_binary_data(settings: &Settings) -> crate::Result<Vec<Exter
Ok(external_binaries)
}
// generates the data required for the resource bundling on wix
fn generate_resource_data(settings: &Settings) -> crate::Result<ResourceMap> {
let mut resources = ResourceMap::new();
let regex = Regex::new(r"[^\w\d\.]")?;
let cwd = std::env::current_dir()?;
for src in settings.resource_files() {
let src = src?;
let filename = src
.file_name()
.expect("failed to extract resource filename")
.to_os_string()
.into_string()
.expect("failed to convert resource filename to string");
let resource_path = cwd
.join(src.clone())
.into_os_string()
.into_string()
.expect("failed to read resource path");
let resource_entry = ResourceFile {
guid: generate_guid(filename.as_bytes()).to_string(),
path: resource_path,
id: regex.replace_all(&filename, "").to_string(),
};
// split the resource path directories
let mut directories = src
.components()
.filter(|component| {
let comp = component.as_os_str();
comp != "." && comp != ".."
})
.collect::<Vec<_>>();
directories.truncate(directories.len() - 1);
// transform the directory structure to a chained vec structure
for directory in directories {
let directory_name = directory
.as_os_str()
.to_os_string()
.into_string()
.expect("failed to read resource folder name");
// if the directory is already on the map
if resources.contains_key(&directory_name) {
let directory_entry = &mut resources
.get_mut(&directory_name)
.expect("Unable to handle resources");
if directory_entry.name == directory_name {
// the directory entry is the root of the chain
directory_entry.add_file(resource_entry.clone());
} else {
let index = directory_entry
.directories
.iter()
.position(|f| f.name == directory_name);
if index.is_some() {
// the directory entry is already a part of the chain
let dir = directory_entry
.directories
.get_mut(index.expect("Unable to get index"))
.expect("Unable to get directory");
dir.add_file(resource_entry.clone());
} else {
// push it to the chain
directory_entry.directories.push(ResourceDirectory {
name: directory_name.clone(),
directories: vec![],
files: vec![resource_entry.clone()],
});
}
}
} else {
resources.insert(
directory_name.clone(),
ResourceDirectory {
name: directory_name.clone(),
directories: vec![],
files: vec![resource_entry.clone()],
},
);
}
}
}
Ok(resources)
}

View File

@ -19,7 +19,7 @@ icon = [
"icons/icon.icns",
"icons/icon.ico"
]
external_bin = [ "bin/packaged-node" ]
resources = [ "resources" ]
[dependencies]
serde_json = "1.0.44"

View File

@ -16,11 +16,12 @@ fn main() {
.setup(|_webview| {
let handle1 = _webview.handle();
std::thread::spawn(move || {
let stdout = tauri::api::command::spawn_relative_command(
tauri::api::command::binary_command("packaged-node".to_string()).expect("failed to get binary command"),
Vec::new(),
std::process::Stdio::piped(),
)
let resource_dir = tauri::api::platform::resource_dir().expect("failed to get resource dir");
let node_package_path = resource_dir.join("resources/packaged-node.js");
let stdout = std::process::Command::new("node")
.args(vec!(node_package_path))
.stdout(std::process::Stdio::piped())
.spawn()
.expect("Failed to spawn packaged node")
.stdout.expect("Failed to get packaged node stdout");
let reader = std::io::BufReader::new(stdout);

View File

@ -21,10 +21,8 @@ either = "1.5.3"
tar = "0.4"
flate2 = "1"
error-chain = "0.12"
tauri-utils = {version = "0.3.0", path = "../tauri-utils"}
[dev-dependencies]
quickcheck = "0.9.2"
quickcheck_macros = "0.9.1"

View File

@ -1,13 +0,0 @@
max_width = 100
hard_tabs = false
tab_spaces = 2
newline_style = "Auto"
use_small_heuristics = "Default"
reorder_imports = true
reorder_modules = true
remove_nested_parens = true
edition = "2018"
merge_derives = true
use_try_shorthand = false
use_field_init_shorthand = false
force_explicit_abi = true

View File

@ -15,6 +15,8 @@ pub mod file;
pub mod rpc;
pub mod version;
pub use tauri_utils::*;
use error_chain::error_chain;
error_chain! {

View File

@ -4,5 +4,8 @@ pub mod process;
use error_chain::error_chain;
error_chain! {
errors{}
foreign_links {
Io(::std::io::Error);
}
errors {}
}

View File

@ -1,3 +1,5 @@
use std::path::{PathBuf, MAIN_SEPARATOR};
/// Try to determine the current target triple.
///
/// Returns a target triple (e.g. `x86_64-unknown-linux-gnu` or `i686-pc-windows-msvc`) or an
@ -51,3 +53,35 @@ pub fn target_triple() -> Result<String, crate::Error> {
Ok(format!("{}-{}", arch, os))
}
pub fn resource_dir() -> crate::Result<PathBuf> {
let exe = std::env::current_exe()?;
let exe_dir = exe.parent().expect("failed to get exe directory");
let app_name = exe
.file_name()
.expect("failed to get exe filename")
.to_string_lossy();
let curr_dir = exe_dir.display().to_string();
if curr_dir.ends_with(format!("{S}target{S}debug", S = MAIN_SEPARATOR).as_str())
|| curr_dir.ends_with(format!("{S}target{S}release", S = MAIN_SEPARATOR).as_str())
|| cfg!(target_os = "windows")
{
// running from the out dir or windows
return Ok(exe_dir.to_path_buf());
}
if cfg!(target_os = "linux") {
if curr_dir.ends_with("/data/usr/bin") {
// running from the deb bundle dir
Ok(exe_dir.join(format!("../lib/{}", app_name)))
} else {
// running bundle
Ok(PathBuf::from(format!("/usr/lib/{}", app_name)))
}
} else if cfg!(target_os = "macos") {
Ok(exe_dir.join("../Resources"))
} else {
Err(crate::Error::from("Unknown target_os"))
}
}

View File

@ -1 +1 @@
include!(concat!(env!("OUT_DIR"), "/data.rs"));
include!(concat!(env!("OUT_DIR"), "/data.rs"));