feat(bundler) bundle all binaries from the project (#726)

This commit is contained in:
Lucas Fernandes Nogueira 2020-06-30 19:06:39 -03:00 committed by GitHub
parent 522c86a956
commit 055d777a42
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 216 additions and 143 deletions

View File

@ -0,0 +1,6 @@
---
"tauri-bundler": minor
---
The bundler now bundles all binaries from your project ([[[bin]] target tables](https://doc.rust-lang.org/cargo/reference/cargo-targets.html#binaries)) and [src/bin folder](https://doc.rust-lang.org/cargo/guide/project-layout.html).
When multiple binaries are used, make sure to use the [default-run](https://doc.rust-lang.org/cargo/reference/manifest.html#the-default-run-field) config field.

View File

@ -15,8 +15,10 @@ mod tauri_config;
#[cfg(target_os = "windows")]
mod wix;
pub use self::common::{print_error, print_finished, print_info};
pub use self::settings::{BuildArtifact, PackageType, Settings};
#[cfg(target_os = "windows")]
pub use self::common::print_info;
pub use self::common::{print_error, print_finished};
pub use self::settings::{PackageType, Settings};
use std::path::PathBuf;

View File

@ -42,7 +42,7 @@ pub fn bundle_project(settings: &Settings) -> crate::Result<Vec<PathBuf>> {
};
let package_base_name = format!(
"{}_{}_{}",
settings.binary_name(),
settings.main_binary_name(),
settings.version_string(),
arch
);
@ -57,17 +57,17 @@ pub fn bundle_project(settings: &Settings) -> crate::Result<Vec<PathBuf>> {
remove_dir_all(&output_path)?;
}
std::fs::create_dir_all(output_path.clone())?;
let app_dir_path = output_path.join(format!("{}.AppDir", settings.binary_name()));
let appimage_path = output_path.join(format!("{}.AppImage", settings.binary_name()));
let app_dir_path = output_path.join(format!("{}.AppDir", settings.main_binary_name()));
let appimage_path = output_path.join(format!("{}.AppImage", settings.main_binary_name()));
path_utils::create(app_dir_path.clone(), true)?;
let upcase = settings.binary_name().to_uppercase();
let upcase_app_name = settings.main_binary_name().to_uppercase();
// setup data to insert into shell script
let mut sh_map = BTreeMap::new();
sh_map.insert("app_name", settings.binary_name());
sh_map.insert("app_name", settings.main_binary_name());
sh_map.insert("bundle_name", package_base_name.as_str());
sh_map.insert("app_name_uppercase", upcase.as_str());
sh_map.insert("app_name_uppercase", upcase_app_name.as_str());
// initialize shell script template.
let temp = HANDLEBARS.render("appimage", &sh_map)?;

View File

@ -57,7 +57,9 @@ fn symlink_file(src: &Path, dst: &Path) -> io::Result<()> {
/// Copies a regular file from one path to another, creating any parent
/// directories of the destination path as necessary. Fails if the source path
/// is a directory or doesn't exist.
pub fn copy_file(from: &Path, to: &Path) -> crate::Result<()> {
pub fn copy_file(from: impl AsRef<Path>, to: impl AsRef<Path>) -> crate::Result<()> {
let from = from.as_ref();
let to = to.as_ref();
if !from.exists() {
return Err(crate::Error::GenericError(format!(
"{:?} does not exist",
@ -212,6 +214,7 @@ pub fn print_warning(message: &str) -> crate::Result<()> {
}
/// Prints a Info message to stderr.
#[cfg(windows)]
pub fn print_info(message: &str) -> crate::Result<()> {
if let Some(mut output) = term::stderr() {
safe_term_attr(&mut output, term::Attr::Bold)?;

View File

@ -48,7 +48,7 @@ pub fn bundle_project(settings: &Settings) -> crate::Result<Vec<PathBuf>> {
};
let package_base_name = format!(
"{}_{}_{}",
settings.binary_name(),
settings.main_binary_name(),
settings.version_string(),
arch
);
@ -93,12 +93,14 @@ pub fn bundle_project(settings: &Settings) -> crate::Result<Vec<PathBuf>> {
pub fn generate_data(settings: &Settings, package_dir: &Path) -> crate::Result<PathBuf> {
// Generate data files.
let data_dir = package_dir.join("data");
let bin_name = settings.binary_name();
let binary_dest = data_dir.join("usr/bin").join(bin_name);
let bin_dir = data_dir.join("usr/bin");
common::copy_file(settings.binary_path(), &binary_dest)
.with_context(|| "Failed to copy binary file")?;
for bin in settings.binaries() {
let bin_path = settings.binary_path(bin);
common::copy_file(&bin_path, &bin_dir.join(bin.name()))
.with_context(|| format!("Failed to copy binary from {:?}", bin_path))?;
}
transfer_resource_files(settings, &data_dir).with_context(|| "Failed to copy resource files")?;
settings
@ -119,7 +121,7 @@ pub fn generate_data(settings: &Settings, package_dir: &Path) -> crate::Result<P
/// Generates the bootstrap script file.
fn generate_bootstrap_file(settings: &Settings, data_dir: &Path) -> crate::Result<()> {
let bin_name = settings.binary_name();
let bin_name = settings.main_binary_name();
let bin_dir = data_dir.join("usr/bin");
let bootstrap_file_name = format!("__{}-bootstrapper", bin_name);
@ -180,7 +182,7 @@ exit 0",
/// Generate the application desktop file and store it under the `data_dir`.
fn generate_desktop_file(settings: &Settings, data_dir: &Path) -> crate::Result<()> {
let bin_name = settings.binary_name();
let bin_name = settings.main_binary_name();
let desktop_file_name = format!("{}.desktop", bin_name);
let desktop_file_path = data_dir
.join("usr/share/applications")
@ -293,7 +295,7 @@ fn generate_md5sums(control_dir: &Path, data_dir: &Path) -> crate::Result<()> {
/// Copy the bundle's resource files into an appropriate directory under the
/// `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());
let resource_dir = data_dir.join("usr/lib").join(settings.main_binary_name());
settings.copy_resources(&resource_dir)
}
@ -306,7 +308,7 @@ fn generate_icon_files(settings: &Settings, data_dir: &PathBuf) -> crate::Result
width,
height,
if is_high_density { "@2x" } else { "" },
settings.binary_name()
settings.main_binary_name()
))
};
let mut sizes = BTreeSet::new();

View File

@ -51,9 +51,13 @@ pub fn bundle_project(settings: &Settings) -> crate::Result<Vec<PathBuf>> {
generate_icon_files(&bundle_dir, settings).with_context(|| "Failed to create app icons")?;
generate_info_plist(&bundle_dir, settings, &icon_filenames)
.with_context(|| "Failed to create Info.plist")?;
let bin_path = bundle_dir.join(&settings.bundle_name());
common::copy_file(settings.binary_path(), &bin_path)
.with_context(|| format!("Failed to copy binary from {:?}", settings.binary_path()))?;
for bin in settings.binaries() {
let bin_path = settings.binary_path(bin);
common::copy_file(&bin_path, &bundle_dir.join(bin.name()))
.with_context(|| format!("Failed to copy binary from {:?}", bin_path))?;
}
Ok(vec![bundle_dir])
}
@ -159,7 +163,7 @@ fn generate_info_plist(
write!(
file,
" <key>CFBundleExecutable</key>\n <string>{}</string>\n",
settings.binary_name()
settings.main_binary_name()
)?;
write!(
file,

View File

@ -1,4 +1,3 @@
use super::common;
use super::settings::Settings;
use super::wix;

View File

@ -73,8 +73,7 @@ pub fn bundle_project(settings: &Settings) -> crate::Result<Vec<PathBuf>> {
.copy_binaries(&bin_dir)
.with_context(|| "Failed to copy external binaries")?;
copy_binary_to_bundle(&bundle_directory, settings)
.with_context(|| format!("Failed to copy binary from {:?}", settings.binary_path()))?;
copy_binaries_to_bundle(&bundle_directory, settings)?;
let use_bootstrapper = settings.osx_use_bootstrapper();
if use_bootstrapper {
@ -84,13 +83,15 @@ pub fn bundle_project(settings: &Settings) -> crate::Result<Vec<PathBuf>> {
Ok(vec![app_bundle_path])
}
// Copies the app's binary to the bundle.
fn copy_binary_to_bundle(bundle_directory: &Path, settings: &Settings) -> crate::Result<()> {
// Copies the app's binaries to the bundle.
fn copy_binaries_to_bundle(bundle_directory: &Path, settings: &Settings) -> crate::Result<()> {
let dest_dir = bundle_directory.join("MacOS");
common::copy_file(
settings.binary_path(),
&dest_dir.join(settings.binary_name()),
)
for bin in settings.binaries() {
let bin_path = settings.binary_path(bin);
common::copy_file(&bin_path, &dest_dir.join(bin.name()))
.with_context(|| format!("Failed to copy binary from {:?}", bin_path))?;
}
Ok(())
}
// Creates the bootstrap script file.
@ -179,7 +180,7 @@ fn create_info_plist(
if use_bootstrapper {
"__bootstrapper"
} else {
settings.binary_name()
settings.main_binary_name()
}
)?;
if let Some(path) = bundle_icon_file {

View File

@ -84,17 +84,6 @@ const ALL_PACKAGE_TYPES: &[PackageType] = &[
PackageType::AppImage,
];
/// The build artifact we're bundling.
#[derive(Clone, Debug)]
pub enum BuildArtifact {
/// The main application.
Main,
/// A named binary which is inside of the [bin] section of your Cargo.toml.
Bin(String),
/// An example app of your crate.
Example(String),
}
/// The bundle settings of the BuildArtifact we're bundling.
#[derive(Clone, Debug, Deserialize, Default)]
struct BundleSettings {
@ -207,6 +196,8 @@ struct PackageSettings {
authors: Option<Vec<String>>,
/// the package's metadata.
metadata: Option<MetadataSettings>,
/// the default binary to run.
default_run: Option<String>,
}
/// The `workspace` section of the app configuration (read from Cargo.toml).
@ -216,6 +207,12 @@ struct WorkspaceSettings {
members: Option<Vec<String>>,
}
#[derive(Clone, Debug, Deserialize)]
struct BinarySettings {
name: String,
path: Option<String>,
}
/// The Cargo settings (Cargo.toml root descriptor).
#[derive(Clone, Debug, Deserialize)]
struct CargoSettings {
@ -227,6 +224,43 @@ struct CargoSettings {
///
/// it's present if the read Cargo.toml belongs to a workspace root.
workspace: Option<WorkspaceSettings>,
/// the binary targets configuration.
bin: Option<Vec<BinarySettings>>,
}
#[derive(Clone, Debug)]
pub struct BundleBinary {
name: String,
src_path: Option<String>,
main: bool,
}
impl BundleBinary {
pub fn new(name: String, main: bool) -> Self {
Self {
name: if cfg!(windows) {
format!("{}.exe", name)
} else {
name
},
src_path: None,
main,
}
}
pub fn set_src_path(mut self, src_path: Option<String>) -> Self {
self.src_path = src_path;
self
}
pub fn name(&self) -> &String {
&self.name
}
#[cfg(windows)]
pub fn main(&self) -> bool {
self.main
}
}
/// The Settings exposed by the module.
@ -244,16 +278,12 @@ pub struct Settings {
features: Option<Vec<String>>,
/// the directory where the bundles will be placed.
project_out_directory: PathBuf,
/// the type of build artifact we're bundling.
build_artifact: BuildArtifact,
/// whether we should build the app with release mode or not.
is_release: bool,
/// the path to the binary (project_out_directory + bin_name).
binary_path: PathBuf,
/// the binary name we're bundling.
binary_name: String,
/// the bundle settings.
bundle_settings: BundleSettings,
/// the binaries to bundle.
binaries: Vec<BundleBinary>,
}
impl CargoSettings {
@ -294,13 +324,6 @@ impl Settings {
}
None => None,
};
let build_artifact = if let Some(bin) = matches.value_of("bin") {
BuildArtifact::Bin(bin.to_string())
} else if let Some(example) = matches.value_of("example") {
BuildArtifact::Example(example.to_string())
} else {
BuildArtifact::Main
};
let is_release = matches.is_present("release");
let target = match matches.value_of("target") {
Some(triple) => Some((triple.to_string(), TargetInfo::from_str(triple)?)),
@ -329,7 +352,7 @@ impl Settings {
}
};
let workspace_dir = Settings::get_workspace_dir(&current_dir);
let target_dir = Settings::get_target_dir(&workspace_dir, &target, is_release, &build_artifact);
let target_dir = Settings::get_target_dir(&workspace_dir, &target, is_release);
let bundle_settings = match tauri_config {
Ok(config) => merge_settings(BundleSettings::default(), config.tauri.bundle),
Err(e) => {
@ -353,23 +376,49 @@ impl Settings {
}
}
};
let (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)?,
name.clone(),
),
BuildArtifact::Example(ref name) => (
bundle_settings_from_table(&bundle_settings.example, "example", name)?,
name.clone(),
),
};
let binary_name = if cfg!(windows) {
format!("{}.{}", &binary_name, "exe")
} else {
binary_name
};
let binary_path = target_dir.join(&binary_name);
let mut binaries: Vec<BundleBinary> = vec![];
if let Some(bin) = cargo_settings.bin {
let default_run = package.default_run.clone().unwrap_or("".to_string());
for binary in bin {
binaries.push(
BundleBinary::new(
binary.name.clone(),
binary.name.as_str() == package.name || binary.name.as_str() == default_run,
)
.set_src_path(binary.path),
)
}
}
let mut bins_path = PathBuf::from(current_dir);
bins_path.push("src/bin");
if let Ok(fs_bins) = std::fs::read_dir(bins_path) {
for entry in fs_bins {
let path = entry?.path();
if let Some(name) = path.file_stem() {
if !binaries.iter().any(|bin| {
bin.name.as_str() == name
|| path.ends_with(bin.src_path.as_ref().unwrap_or(&"".to_string()))
}) {
binaries.push(BundleBinary::new(name.to_string_lossy().to_string(), false))
}
}
}
}
if let Some(default_run) = package.default_run.as_ref() {
if !binaries.iter().any(|bin| bin.name.as_str() == default_run) {
binaries.push(BundleBinary::new(default_run.to_string(), true));
}
}
if binaries.len() == 1 {
binaries.get_mut(0).and_then(|bin| {
bin.main = true;
Some(bin)
});
}
let bundle_settings = parse_external_bin(bundle_settings)?;
@ -378,11 +427,9 @@ impl Settings {
package_types,
target,
features,
build_artifact,
is_release,
project_out_directory: target_dir,
binary_path,
binary_name,
binaries,
bundle_settings,
})
}
@ -393,16 +440,12 @@ impl Settings {
project_root_dir: &PathBuf,
target: &Option<(String, TargetInfo)>,
is_release: bool,
build_artifact: &BuildArtifact,
) -> PathBuf {
let mut path = project_root_dir.join("target");
if let &Some((ref triple, _)) = target {
path.push(triple);
}
path.push(if is_release { "release" } else { "debug" });
if let &BuildArtifact::Example(_) = build_artifact {
path.push("examples");
}
path
}
@ -454,13 +497,25 @@ impl Settings {
}
/// Returns the file name of the binary being bundled.
pub fn binary_name(&self) -> &str {
&self.binary_name
pub fn main_binary_name(&self) -> &str {
self
.binaries
.iter()
.find(|bin| bin.main)
.expect("failed to find main binary")
.name
.as_str()
}
/// Returns the path to the binary being bundled.
pub fn binary_path(&self) -> &Path {
&self.binary_path
/// Returns the path to the specified binary.
pub fn binary_path(&self, binary: &BundleBinary) -> PathBuf {
let mut path = self.project_out_directory.clone();
path.push(binary.name());
path
}
pub fn binaries(&self) -> &Vec<BundleBinary> {
&self.binaries
}
/// If a list of package types was specified by the command-line, returns
@ -524,11 +579,6 @@ impl Settings {
self.features.to_owned()
}
/// Returns the artifact that is being bundled.
pub fn build_artifact(&self) -> &BuildArtifact {
&self.build_artifact
}
/// Returns true if the bundle is being compiled in release mode, false if
/// it's being compiled in debug mode.
pub fn is_release_build(&self) -> bool {
@ -719,23 +769,6 @@ impl Settings {
}
}
/// Gets the bundle settings from a map.
/// It can be used to get the bundle settings from the [example] or [bin] section of Cargo.toml
fn bundle_settings_from_table(
opt_map: &Option<HashMap<String, BundleSettings>>,
map_name: &str,
bundle_name: &str,
) -> crate::Result<BundleSettings> {
if let Some(bundle_settings) = opt_map.as_ref().and_then(|map| map.get(bundle_name)) {
Ok(bundle_settings.clone())
} else {
return Err(crate::Error::GenericError(format!(
"No 'bundle:{}:{}' keys section in the tauri.conf.json file",
map_name, bundle_name
)));
}
}
/// Parses the external binaries to bundle, adding the target triple suffix to each of them.
fn parse_external_bin(bundle_settings: BundleSettings) -> crate::Result<BundleSettings> {
let target_triple = target_triple()?;

View File

@ -70,9 +70,9 @@
<Component Id="Path" Guid="{{{path_component_guid}}}" Win64="$(var.Win64)">
<File Id="Path" Source="{{{app_exe_source}}}" KeyPath="yes" Checksum="yes"/>
</Component>
{{#each external_binaries as |external_bin| ~}}
<Component Id="{{ external_bin.id }}" Guid="{{external_bin.guid}}" Win64="$(var.Win64)">
<File Id="Path_{{ external_bin.id }}" Source="{{external_bin.path}}" KeyPath="yes"/>
{{#each binaries as |bin| ~}}
<Component Id="{{ bin.id }}" Guid="{{bin.guid}}" Win64="$(var.Win64)">
<File Id="Path_{{ bin.id }}" Source="{{bin.path}}" KeyPath="yes"/>
</Component>
{{/each~}}
{{{resources}}}
@ -140,8 +140,8 @@
Level="1"
Absent="allow">
<ComponentRef Id="Path"/>
{{#each external_binaries as |external_bin| ~}}
<ComponentRef Id="{{ external_bin.id }}"/>
{{#each binaries as |bin| ~}}
<ComponentRef Id="{{ bin.id }}"/>
{{/each~}}
</Feature>
</Feature>

View File

@ -57,15 +57,16 @@ lazy_static! {
/// Mapper between a resource directory name and its ResourceDirectory descriptor.
type ResourceMap = BTreeMap<String, ResourceDirectory>;
/// An external binary to bundle with WIX.
/// A binary to bundle with WIX.
/// External binaries or additional project binaries are represented with this data structure.
/// This data structure is needed because WIX requires each path to have its own `id` and `guid`.
#[derive(Serialize)]
struct ExternalBinary {
struct Binary {
/// the GUID to use on the WIX XML.
guid: String,
/// the id to use on the WIX XML.
id: String,
/// the external binary path.
/// the binary path.
path: String,
}
@ -135,8 +136,7 @@ impl ResourceDirectory {
/// Copies the icons to the binary path, under the `resources` folder,
/// and returns the path to that directory.
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");
let base_dir = settings.project_out_directory();
let resource_dir = base_dir.join("resources");
@ -323,11 +323,20 @@ fn run_candle(
}
};
let main_binary = settings
.binaries()
.iter()
.find(|bin| bin.main())
.ok_or_else(|| anyhow::anyhow!("Failed to get main binary"))?;
let args = vec![
"-arch".to_string(),
arch.to_string(),
wxs_file_name.to_string(),
format!("-dSourceDir={}", settings.binary_path().display()),
format!(
"-dSourceDir={}",
settings.binary_path(main_binary).display()
),
];
let candle_exe = wix_toolset_path.join("candle.exe");
@ -434,7 +443,7 @@ pub fn build_wix_app_installer(
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(),
format!("{}.app.x64", &settings.main_binary_name()).as_bytes(),
)
.to_string();
@ -446,13 +455,13 @@ pub fn build_wix_app_installer(
let shortcut_guid = generate_package_guid(settings).to_string();
data.insert("shortcut_guid", to_json(&shortcut_guid.as_str()));
let app_exe_name = settings.binary_name().to_string();
let app_exe_name = settings.main_binary_name().to_string();
data.insert("app_exe_name", to_json(&app_exe_name));
let external_binaries = generate_external_binary_data(&settings)?;
let binaries = generate_binaries_data(&settings)?;
let external_binaries_json = to_json(&external_binaries);
data.insert("external_binaries", external_binaries_json);
let binaries_json = to_json(&binaries);
data.insert("binaries", binaries_json);
let resources = generate_resource_data(&settings)?;
let mut resources_wix_string = String::from("");
@ -468,7 +477,12 @@ pub fn build_wix_app_installer(
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();
let main_binary = settings
.binaries()
.iter()
.find(|bin| bin.main())
.ok_or_else(|| anyhow::anyhow!("Failed to get main binary"))?;
let app_exe_source = settings.binary_path(main_binary).display().to_string();
data.insert("app_exe_source", to_json(&app_exe_source));
@ -508,9 +522,9 @@ pub fn build_wix_app_installer(
Ok(target)
}
/// Generates the data required for the external binaries bundling.
fn generate_external_binary_data(settings: &Settings) -> crate::Result<Vec<ExternalBinary>> {
let mut external_binaries = Vec::new();
/// Generates the data required for the external binaries and extra binaries bundling.
fn generate_binaries_data(settings: &Settings) -> crate::Result<Vec<Binary>> {
let mut binaries = Vec::new();
let regex = Regex::new(r"[^\w\d\.]")?;
let cwd = std::env::current_dir()?;
for src in settings.external_binaries() {
@ -524,8 +538,8 @@ fn generate_external_binary_data(settings: &Settings) -> crate::Result<Vec<Exter
let guid = generate_guid(filename.as_bytes()).to_string();
external_binaries.push(ExternalBinary {
guid: guid,
binaries.push(Binary {
guid,
path: cwd
.join(src)
.into_os_string()
@ -535,7 +549,23 @@ fn generate_external_binary_data(settings: &Settings) -> crate::Result<Vec<Exter
});
}
Ok(external_binaries)
for bin in settings.binaries() {
let filename = bin.name();
let guid = generate_guid(filename.as_bytes()).to_string();
if !bin.main() {
binaries.push(Binary {
guid,
path: settings
.binary_path(bin)
.into_os_string()
.into_string()
.expect("failed to read binary path"),
id: regex.replace_all(&filename, "").to_string(),
})
}
}
Ok(binaries)
}
/// Generates the data required for the resource bundling on wix

View File

@ -2,9 +2,9 @@ mod bundle;
mod error;
pub use error::{Error, Result};
use crate::bundle::{
bundle_project, check_icons, print_info, BuildArtifact, PackageType, Settings,
};
#[cfg(windows)]
use crate::bundle::print_info;
use crate::bundle::{bundle_project, check_icons, PackageType, Settings};
use clap::{crate_version, App, AppSettings, Arg, SubCommand};
@ -16,18 +16,11 @@ use std::process;
// Runs `cargo build` to make sure the binary file is up-to-date.
fn build_project_if_unbuilt(settings: &Settings) -> crate::Result<()> {
let mut args = vec!["build".to_string()];
if let Some(triple) = settings.target_triple() {
args.push(format!("--target={}", triple));
}
match settings.build_artifact() {
&BuildArtifact::Main => {}
&BuildArtifact::Bin(ref name) => {
args.push(format!("--bin={}", name));
}
&BuildArtifact::Example(ref name) => {
args.push(format!("--example={}", name));
}
}
if settings.is_release_build() {
args.push("--release".to_string());
}