mirror of
https://github.com/tauri-apps/tauri.git
synced 2026-04-01 10:01:07 +02:00
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:
committed by
nothingismagick
parent
bed75c1099
commit
6225e5d30a
@@ -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"
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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()))?;
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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};
|
||||
|
||||
51
cli/tauri-cli/src/platform.rs
Normal file
51
cli/tauri-cli/src/platform.rs
Normal 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))
|
||||
}
|
||||
3
examples/vue/quasar-app/packaged-node.js
Normal file
3
examples/vue/quasar-app/packaged-node.js
Normal file
@@ -0,0 +1,3 @@
|
||||
setInterval(() => {
|
||||
console.log(Math.random())
|
||||
}, 500)
|
||||
@@ -19,6 +19,7 @@ icon = [
|
||||
"icons/icon.icns",
|
||||
"icons/icon.ico"
|
||||
]
|
||||
external_bin = [ "bin/packaged-node" ]
|
||||
|
||||
[dependencies]
|
||||
serde_json = "1.0.44"
|
||||
|
||||
BIN
examples/vue/quasar-app/src-tauri/bin/packaged-node
Executable file
BIN
examples/vue/quasar-app/src-tauri/bin/packaged-node
Executable file
Binary file not shown.
Binary file not shown.
@@ -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(),
|
||||
);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
51
tauri-api/src/platform.rs
Normal 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))
|
||||
}
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user