فهرست منبع

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>
Lucas Fernandes Nogueira 5 سال پیش
والد
کامیت
b7a6bc0f42

+ 1 - 0
cli/tauri-cli/src/bundle.rs

@@ -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)

+ 1 - 7
cli/tauri-cli/src/bundle/deb_bundle.rs

@@ -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`.

+ 5 - 3
cli/tauri-cli/src/bundle/dmg_bundle.rs

@@ -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
 

+ 1 - 6
cli/tauri-cli/src/bundle/ios_bundle.rs

@@ -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")?;

+ 1 - 6
cli/tauri-cli/src/bundle/osx_bundle.rs

@@ -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)

+ 11 - 0
cli/tauri-cli/src/bundle/settings.rs

@@ -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

+ 5 - 0
cli/tauri-cli/src/bundle/templates/main.wxs

@@ -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">

+ 157 - 0
cli/tauri-cli/src/bundle/wix.rs

@@ -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)
+}

+ 1 - 1
examples/vue/quasar-app/src-tauri/Cargo.toml

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

BIN
examples/vue/quasar-app/src-tauri/bin/packaged-node


BIN
examples/vue/quasar-app/src-tauri/bin/packaged-node-x86_64-pc-windows-msvc.exe


+ 0 - 0
examples/vue/quasar-app/packaged-node.js → examples/vue/quasar-app/src-tauri/resources/packaged-node.js


+ 6 - 5
examples/vue/quasar-app/src-tauri/src/main.rs

@@ -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);

+ 0 - 2
tauri-api/Cargo.toml

@@ -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"

+ 0 - 13
tauri-api/rustfmt.toml

@@ -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

+ 2 - 0
tauri-api/src/lib.rs

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

+ 4 - 1
tauri-utils/src/lib.rs

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

+ 34 - 0
tauri-utils/src/platform.rs

@@ -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"))
+  }
+}

+ 1 - 1
tauri/src/assets.rs

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