diff --git a/cli/tauri-cli/Cargo.toml b/cli/tauri-cli/Cargo.toml index 9a2f2ed73..6106388e7 100644 --- a/cli/tauri-cli/Cargo.toml +++ b/cli/tauri-cli/Cargo.toml @@ -42,6 +42,7 @@ lazy_static = { version = "1.4" } zip = { version = "0.5" } sha2 = { version = "0.8" } hex = { version = "0.4" } +regex = { version = "1" } [dev-dependencies] tempfile = "3" diff --git a/cli/tauri-cli/src/bundle.rs b/cli/tauri-cli/src/bundle.rs index f3ec36f2b..f05dfd909 100644 --- a/cli/tauri-cli/src/bundle.rs +++ b/cli/tauri-cli/src/bundle.rs @@ -41,6 +41,16 @@ pub fn bundle_project(settings: Settings) -> crate::Result> { PackageType::Dmg => dmg_bundle::bundle_project(&settings)?, }); } + + // copy external binaries to out dir for testing + let out_dir = settings.project_out_directory(); + for src in settings.external_binaries() { + let src = src?; + let dest = out_dir.join(src.file_name().expect("failed to extract external binary filename")); + common::copy_file(&src, &dest) + .map_err(|_| format!("Failed to copy external binary {:?}", src))?; + } + Ok(paths) } diff --git a/cli/tauri-cli/src/bundle/deb_bundle.rs b/cli/tauri-cli/src/bundle/deb_bundle.rs index 7f6625b15..8e7f37a80 100644 --- a/cli/tauri-cli/src/bundle/deb_bundle.rs +++ b/cli/tauri-cli/src/bundle/deb_bundle.rs @@ -91,6 +91,7 @@ pub fn generate_folders(settings: &Settings, package_dir: &Path) -> crate::Resul common::copy_file(settings.binary_path(), &binary_dest) .chain_err(|| "Failed to copy binary file")?; transfer_resource_files(settings, &data_dir).chain_err(|| "Failed to copy resource files")?; + transfer_external_binaries(settings, &data_dir).chain_err(|| "Failed to copy external binaries")?; generate_icon_files(settings, &data_dir).chain_err(|| "Failed to create icon files")?; generate_desktop_file(settings, &data_dir).chain_err(|| "Failed to create desktop file")?; @@ -212,6 +213,19 @@ fn transfer_resource_files(settings: &Settings, data_dir: &Path) -> crate::Resul Ok(()) } +/// Copy the bundle's external binaries into an appropriate directory under the +/// `data_dir`. +fn transfer_external_binaries(settings: &Settings, data_dir: &Path) -> crate::Result<()> { + let bin_dir = data_dir.join("usr/bin"); + for src in settings.external_binaries() { + let src = src?; + let dest = bin_dir.join(src.file_name().expect("failed to extract external binary filename")); + common::copy_file(&src, &dest) + .chain_err(|| format!("Failed to copy external binary {:?}", src))?; + } + Ok(()) +} + /// Generate the icon files and store them under the `data_dir`. fn generate_icon_files(settings: &Settings, data_dir: &PathBuf) -> crate::Result<()> { let base_dir = data_dir.join("usr/share/icons/hicolor"); diff --git a/cli/tauri-cli/src/bundle/osx_bundle.rs b/cli/tauri-cli/src/bundle/osx_bundle.rs index 775a50651..657ca89e0 100644 --- a/cli/tauri-cli/src/bundle/osx_bundle.rs +++ b/cli/tauri-cli/src/bundle/osx_bundle.rs @@ -50,6 +50,7 @@ pub fn bundle_project(settings: &Settings) -> crate::Result> { })?; let resources_dir = bundle_directory.join("Resources"); + let bin_dir = bundle_directory.join("MacOS"); let bundle_icon_file: Option = { create_icns_file(&resources_dir, settings).chain_err(|| "Failed to create app icon")? }; @@ -67,6 +68,13 @@ pub fn bundle_project(settings: &Settings) -> crate::Result> { .chain_err(|| format!("Failed to copy resource file {:?}", src))?; } + for src in settings.external_binaries() { + let src = src?; + let dest = bin_dir.join(src.file_name().expect("failed to extract external binary filename")); + common::copy_file(&src, &dest) + .chain_err(|| format!("Failed to copy external binary {:?}", src))?; + } + copy_binary_to_bundle(&bundle_directory, settings) .chain_err(|| format!("Failed to copy binary from {:?}", settings.binary_path()))?; diff --git a/cli/tauri-cli/src/bundle/settings.rs b/cli/tauri-cli/src/bundle/settings.rs index 09b8dc284..2b8aaf2bb 100644 --- a/cli/tauri-cli/src/bundle/settings.rs +++ b/cli/tauri-cli/src/bundle/settings.rs @@ -9,6 +9,7 @@ use std::path::{Path, PathBuf}; use target_build_utils::TargetInfo; use toml; use walkdir; +use crate::platform::{target_triple}; #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum PackageType { @@ -104,6 +105,7 @@ struct BundleSettings { // Bundles for other binaries/examples: bin: Option>, example: Option>, + external_bin: Option>, } #[derive(Clone, Debug, Deserialize)] @@ -118,7 +120,7 @@ struct PackageSettings { description: String, homepage: Option, authors: Option>, - metadata: Option, + metadata: Option } #[derive(Clone, Debug, Deserialize)] @@ -207,7 +209,7 @@ impl Settings { } else { bail!("No [package.metadata.bundle] section in Cargo.toml"); }; - let (bundle_settings, binary_name) = match build_artifact { + let (mut bundle_settings, binary_name) = match build_artifact { BuildArtifact::Main => (bundle_settings, package.name.clone()), BuildArtifact::Bin(ref name) => ( bundle_settings_from_table(&bundle_settings.bin, "bin", name)?, @@ -224,6 +226,26 @@ impl Settings { binary_name }; let binary_path = target_dir.join(&binary_name); + + let target_triple = target_triple()?; + let mut win_paths = Vec::new(); + match bundle_settings.external_bin { + Some(paths) => { + for curr_path in paths.iter() { + win_paths.push( + format!( + "{}-{}{}", + curr_path, + target_triple, + if cfg!(windows) { ".exe" } else { "" } + ) + ); + } + bundle_settings.external_bin = Some(win_paths); + }, + None => { } + } + Ok(Settings { package, package_type, @@ -414,6 +436,15 @@ impl Settings { } } + /// Returns an iterator over the external binaries to be included in this + /// bundle. + pub fn external_binaries(&self) -> ResourcePaths<'_> { + match self.bundle_settings.external_bin { + Some(ref paths) => ResourcePaths::new(paths.as_slice(), true), + None => ResourcePaths::new(&[], true), + } + } + pub fn version_string(&self) -> &str { self .bundle_settings diff --git a/cli/tauri-cli/src/bundle/templates/main.wxs b/cli/tauri-cli/src/bundle/templates/main.wxs index df0aa492e..b1f14cffc 100644 --- a/cli/tauri-cli/src/bundle/templates/main.wxs +++ b/cli/tauri-cli/src/bundle/templates/main.wxs @@ -43,6 +43,11 @@ + {{#each external_binaries as |external_bin| ~}} + + + + {{/each~}} @@ -93,6 +98,9 @@ Level="1" Absent="allow"> + {{#each external_binaries as |external_bin| ~}} + + {{/each~}} diff --git a/cli/tauri-cli/src/bundle/wix.rs b/cli/tauri-cli/src/bundle/wix.rs index 8148a4175..aa5b807ce 100644 --- a/cli/tauri-cli/src/bundle/wix.rs +++ b/cli/tauri-cli/src/bundle/wix.rs @@ -2,9 +2,10 @@ use super::common; use super::path_utils::{copy, Options}; use super::settings::Settings; -use handlebars::Handlebars; +use handlebars::{Handlebars, to_json}; use lazy_static::lazy_static; use sha2::Digest; +use regex::Regex; use std::collections::BTreeMap; use std::fs::{create_dir_all, remove_dir_all, write, File}; @@ -153,8 +154,12 @@ fn extract_zip(data: &Vec, path: &Path) -> crate::Result<()> { // Generates the UUID for the Wix template. fn generate_package_guid(settings: &Settings) -> Uuid { + generate_guid(settings.bundle_identifier().as_bytes()) +} + +fn generate_guid(key: &[u8]) -> Uuid { let namespace = Uuid::from_bytes(UUID_NAMESPACE); - Uuid::new_v5(&namespace, settings.bundle_identifier().as_bytes()) + Uuid::new_v5(&namespace, key) } // Specifically goes and gets Wix and verifies the download via Sha256 @@ -340,37 +345,59 @@ pub fn build_wix_app_installer( let mut data = BTreeMap::new(); - data.insert("product_name", settings.bundle_name()); - data.insert("version", settings.version_string()); + data.insert("product_name", to_json(settings.bundle_name())); + data.insert("version", to_json(settings.version_string())); let manufacturer = settings.bundle_identifier().to_string(); - data.insert("manufacturer", manufacturer.as_str()); + data.insert("manufacturer", to_json(manufacturer.as_str())); let upgrade_code = Uuid::new_v5( &Uuid::NAMESPACE_DNS, format!("{}.app.x64", &settings.binary_name()).as_bytes(), ) .to_string(); - data.insert("upgrade_code", &upgrade_code.as_str()); + data.insert("upgrade_code", to_json(&upgrade_code.as_str())); let path_guid = generate_package_guid(settings).to_string(); - data.insert("path_component_guid", &path_guid.as_str()); + data.insert("path_component_guid", to_json(&path_guid.as_str())); let shortcut_guid = generate_package_guid(settings).to_string(); - data.insert("shortcut_guid", &shortcut_guid.as_str()); + data.insert("shortcut_guid", to_json(&shortcut_guid.as_str())); let app_exe_name = settings.binary_name().to_string(); - data.insert("app_exe_name", &app_exe_name); + data.insert("app_exe_name", to_json(&app_exe_name)); + + #[derive(Serialize)] + struct ExternalBinary { + guid: String, + id: String, + path: String + } + let mut external_binaries = Vec::new(); + let regex = Regex::new("[^A-Za-z0-9\\._]").unwrap(); + let cwd = std::env::current_dir()?; + for src in settings.external_binaries() { + let src = src?; + let filename = src.file_name().expect("failed to extract external binary filename").to_os_string().into_string().expect("failed to convert external binary filename to string"); + let guid = generate_guid(filename.as_bytes()).to_string(); + external_binaries.push(ExternalBinary { + guid: guid, + path: cwd.join(src).into_os_string().into_string().expect("failed to read external binary path"), + id: regex.replace_all(&filename, "").to_string() + }); + } + let external_binaries_json = to_json(&external_binaries); + data.insert("external_binaries", external_binaries_json); let app_exe_source = settings.binary_path().display().to_string(); - data.insert("app_exe_source", &app_exe_source); + data.insert("app_exe_source", to_json(&app_exe_source)); // copy icons from icons folder to resource folder near msi let image_path = copy_icons(&settings)?; let path = image_path.join("icon.ico").display().to_string(); - data.insert("icon_path", path.as_str()); + data.insert("icon_path", to_json(path.as_str())); let temp = HANDLEBARS .render("main.wxs", &data) diff --git a/cli/tauri-cli/src/main.rs b/cli/tauri-cli/src/main.rs index 7313ab082..f8e28ad4b 100644 --- a/cli/tauri-cli/src/main.rs +++ b/cli/tauri-cli/src/main.rs @@ -11,6 +11,7 @@ extern crate serde_derive; extern crate tempfile; mod bundle; +mod platform; use crate::bundle::{bundle_project, check_icons, BuildArtifact, PackageType, Settings}; use clap::{App, AppSettings, Arg, SubCommand}; diff --git a/cli/tauri-cli/src/platform.rs b/cli/tauri-cli/src/platform.rs new file mode 100644 index 000000000..b221c897c --- /dev/null +++ b/cli/tauri-cli/src/platform.rs @@ -0,0 +1,51 @@ +/// 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 +/// `Error::Config` if the current config cannot be determined or is not some combination of the +/// following values: +/// `linux, mac, windows` -- `i686, x86, armv7` -- `gnu, musl, msvc` +/// +/// * Errors: +/// * Unexpected system config +pub(crate) fn target_triple() -> Result { + let arch = if cfg!(target_arch = "x86") { + "i686" + } else if cfg!(target_arch = "x86_64") { + "x86_64" + } else if cfg!(target_arch = "arm") { + "armv7" + } else { + return Err(crate::Error::from("Unable to determine target-architecture")); + }; + + let os = if cfg!(target_os = "linux") { + "unknown-linux" + } else if cfg!(target_os = "macos") { + "apple-darwin" + } else if cfg!(target_os = "windows") { + "pc-windows" + } else if cfg!(target_os = "freebsd") { + "unknown-freebsd" + } else { + return Err(crate::Error::from("Unable to determine target-os")); + }; + + let s; + let os = if cfg!(target_os = "macos") || cfg!(target_os = "freebsd") { + os + } else { + let env = if cfg!(target_env = "gnu") { + "gnu" + } else if cfg!(target_env = "musl") { + "musl" + } else if cfg!(target_env = "msvc") { + "msvc" + } else { + return Err(crate::Error::from("Unable to determine target-environment")); + }; + s = format!("{}-{}", os, env); + &s + }; + + Ok(format!("{}-{}", arch, os)) +} \ No newline at end of file diff --git a/examples/vue/quasar-app/packaged-node.js b/examples/vue/quasar-app/packaged-node.js new file mode 100644 index 000000000..76139cc89 --- /dev/null +++ b/examples/vue/quasar-app/packaged-node.js @@ -0,0 +1,3 @@ +setInterval(() => { + console.log(Math.random()) +}, 500) diff --git a/examples/vue/quasar-app/src-tauri/Cargo.toml b/examples/vue/quasar-app/src-tauri/Cargo.toml index cfbc52308..d8964d845 100644 --- a/examples/vue/quasar-app/src-tauri/Cargo.toml +++ b/examples/vue/quasar-app/src-tauri/Cargo.toml @@ -19,6 +19,7 @@ icon = [ "icons/icon.icns", "icons/icon.ico" ] +external_bin = [ "bin/packaged-node" ] [dependencies] serde_json = "1.0.44" diff --git a/examples/vue/quasar-app/src-tauri/bin/packaged-node b/examples/vue/quasar-app/src-tauri/bin/packaged-node new file mode 100755 index 000000000..d293e50c8 Binary files /dev/null and b/examples/vue/quasar-app/src-tauri/bin/packaged-node differ diff --git a/examples/vue/quasar-app/src-tauri/bin/packaged-node-x86_64-pc-windows-msvc.exe b/examples/vue/quasar-app/src-tauri/bin/packaged-node-x86_64-pc-windows-msvc.exe new file mode 100644 index 000000000..9cf1c417f Binary files /dev/null and b/examples/vue/quasar-app/src-tauri/bin/packaged-node-x86_64-pc-windows-msvc.exe differ diff --git a/examples/vue/quasar-app/src-tauri/src/main.rs b/examples/vue/quasar-app/src-tauri/src/main.rs index 41c854f71..322de9eea 100644 --- a/examples/vue/quasar-app/src-tauri/src/main.rs +++ b/examples/vue/quasar-app/src-tauri/src/main.rs @@ -9,10 +9,31 @@ mod cmd; extern crate serde_derive; extern crate serde_json; +use std::io::BufRead; + fn main() { tauri::AppBuilder::new() .setup(|_webview| { - let handle = _webview.handle(); + 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(), + ) + .expect("Failed to spawn packaged node") + .stdout.expect("Failed to get packaged node stdout"); + let reader = std::io::BufReader::new(stdout); + + reader + .lines() + .filter_map(|line| line.ok()) + .for_each(|line| { + tauri::event::emit(&handle1, String::from("node"), format!("'{}'", line)) + }); + }); + + let handle2 = _webview.handle(); tauri::event::listen(String::from("hello"), move |msg| { #[derive(Serialize)] pub struct Reply { @@ -26,7 +47,7 @@ fn main() { }; tauri::event::emit( - &handle, + &handle2, String::from("reply"), serde_json::to_string(&reply).unwrap(), ); diff --git a/examples/vue/quasar-app/src/pages/Index.vue b/examples/vue/quasar-app/src/pages/Index.vue index 8cd73d605..4637ccc10 100644 --- a/examples/vue/quasar-app/src/pages/Index.vue +++ b/examples/vue/quasar-app/src/pages/Index.vue @@ -1,5 +1,6 @@