feat(bundler) add option to bundle external binaries (#312)

* feat(tauri-cli) add option to bundle external binaries

* feat(tauri-cli) bundle external binaries for OSX

* fix(cargo.toml) external bin path

* feat(bundler) bundle external binaries on MSI/WiX

* feat(tauri) append target triple to external binary name

* call cargo fmt.

Co-authored-by: Tensor-Programming <abeltensor@tensor-programming.com>
Co-authored-by: nothingismagick <drthompsonsmagickindustries@gmail.com>
This commit is contained in:
Lucas Fernandes Nogueira
2020-01-15 15:39:34 -03:00
committed by nothingismagick
parent bed75c1099
commit 6225e5d30a
19 changed files with 258 additions and 18 deletions

View File

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

View File

@@ -41,6 +41,16 @@ pub fn bundle_project(settings: Settings) -> crate::Result<Vec<PathBuf>> {
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)
}

View File

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

View File

@@ -50,6 +50,7 @@ pub fn bundle_project(settings: &Settings) -> crate::Result<Vec<PathBuf>> {
})?;
let resources_dir = bundle_directory.join("Resources");
let bin_dir = bundle_directory.join("MacOS");
let bundle_icon_file: Option<PathBuf> =
{ 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<Vec<PathBuf>> {
.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()))?;

View File

@@ -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<HashMap<String, BundleSettings>>,
example: Option<HashMap<String, BundleSettings>>,
external_bin: Option<Vec<String>>,
}
#[derive(Clone, Debug, Deserialize)]
@@ -118,7 +120,7 @@ struct PackageSettings {
description: String,
homepage: Option<String>,
authors: Option<Vec<String>>,
metadata: Option<MetadataSettings>,
metadata: Option<MetadataSettings>
}
#[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

View File

@@ -43,6 +43,11 @@
<Component Id="Path" Guid="{{{path_component_guid}}}" Win64="$(var.Win64)" KeyPath="yes">
<File Id="PathFile" Source="{{{app_exe_source}}}" />
</Component>
{{#each external_binaries as |external_bin| ~}}
<Component Id="{{ external_bin.id }}" Guid="{{external_bin.guid}}" Win64="$(var.Win64)" KeyPath="yes">
<File Id="PathFile_{{ external_bin.id }}" Source="{{external_bin.path}}" />
</Component>
{{/each~}}
</Directory>
</Directory>
</Directory>
@@ -93,6 +98,9 @@
Level="1"
Absent="allow">
<ComponentRef Id="Path"/>
{{#each external_binaries as |external_bin| ~}}
<ComponentRef Id="{{ external_bin.id }}"/>
{{/each~}}
</Feature>
</Feature>

View File

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

View File

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

View File

@@ -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<String, crate::Error> {
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))
}

View File

@@ -0,0 +1,3 @@
setInterval(() => {
console.log(Math.random())
}, 500)

View File

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

Binary file not shown.

View File

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

View File

@@ -1,5 +1,6 @@
<template>
<q-page class="flex flex-center column">
<h4 class="row text-center">Node: {{ nodeMsg }}</h4>
<h3 class="row text-center">{{ msg }}</h3>
<div class="row">
<q-btn @click="eventToRust()">SEND MSG</q-btn>
@@ -15,13 +16,17 @@ export default {
name: 'HelloWorld',
data () {
return {
msg: 'waiting for rust'
msg: 'waiting for rust',
nodeMsg: 'waiting'
}
},
mounted () {
tauri.listen('reply', res => {
this.msg = res.payload.msg
})
tauri.listen('node', res => {
this.nodeMsg = res.payload
})
},
methods: {
// set up an event listener

View File

@@ -51,6 +51,14 @@ pub fn spawn_relative_command(
Ok(Command::new(cmd).args(args).stdout(stdout).spawn()?)
}
pub fn binary_command(binary_name: String) -> Result<String, String> {
return Ok(format!(
"{}-{}",
binary_name,
crate::platform::target_triple()?
));
}
// tests for the commands functions.
#[cfg(test)]
mod test {

View File

@@ -8,8 +8,8 @@ pub mod command;
pub mod dir;
pub mod file;
pub mod rpc;
pub mod version;
pub mod platform;
use error_chain::error_chain;

51
tauri-api/src/platform.rs Normal file
View File

@@ -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 fn target_triple() -> Result<String, String> {
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("Unable to determine target-architecture".to_string());
};
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("Unable to determine target-os".to_string());
};
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("Unable to determine target-environment".to_string());
};
s = format!("{}-{}", os, env);
&s
};
Ok(format!("{}-{}", arch, os))
}

View File

@@ -39,7 +39,7 @@ pub fn target_triple() -> Result<String, Error> {
} else {
let env = if cfg!(target_env = "gnu") {
"gnu"
} else if cfg!(target_env = "gnu") {
} else if cfg!(target_env = "musl") {
"musl"
} else if cfg!(target_env = "msvc") {
"msvc"