diff --git a/.changes/bundler-binaries.md b/.changes/bundler-binaries.md new file mode 100644 index 000000000..dc979e926 --- /dev/null +++ b/.changes/bundler-binaries.md @@ -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. diff --git a/cli/tauri-bundler/src/bundle.rs b/cli/tauri-bundler/src/bundle.rs index d427220ba..065af9061 100644 --- a/cli/tauri-bundler/src/bundle.rs +++ b/cli/tauri-bundler/src/bundle.rs @@ -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; diff --git a/cli/tauri-bundler/src/bundle/appimage_bundle.rs b/cli/tauri-bundler/src/bundle/appimage_bundle.rs index 103985540..2833347bf 100644 --- a/cli/tauri-bundler/src/bundle/appimage_bundle.rs +++ b/cli/tauri-bundler/src/bundle/appimage_bundle.rs @@ -42,7 +42,7 @@ pub fn bundle_project(settings: &Settings) -> crate::Result> { }; 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> { 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)?; diff --git a/cli/tauri-bundler/src/bundle/common.rs b/cli/tauri-bundler/src/bundle/common.rs index 282e905c3..91bb1cd39 100644 --- a/cli/tauri-bundler/src/bundle/common.rs +++ b/cli/tauri-bundler/src/bundle/common.rs @@ -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, to: impl AsRef) -> 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)?; diff --git a/cli/tauri-bundler/src/bundle/deb_bundle.rs b/cli/tauri-bundler/src/bundle/deb_bundle.rs index a7bb8e0f6..dd423967f 100644 --- a/cli/tauri-bundler/src/bundle/deb_bundle.rs +++ b/cli/tauri-bundler/src/bundle/deb_bundle.rs @@ -48,7 +48,7 @@ pub fn bundle_project(settings: &Settings) -> crate::Result> { }; 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> { pub fn generate_data(settings: &Settings, package_dir: &Path) -> crate::Result { // 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

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(); diff --git a/cli/tauri-bundler/src/bundle/ios_bundle.rs b/cli/tauri-bundler/src/bundle/ios_bundle.rs index e3accfde9..c2f3534a3 100644 --- a/cli/tauri-bundler/src/bundle/ios_bundle.rs +++ b/cli/tauri-bundler/src/bundle/ios_bundle.rs @@ -51,9 +51,13 @@ pub fn bundle_project(settings: &Settings) -> crate::Result> { 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, " CFBundleExecutable\n {}\n", - settings.binary_name() + settings.main_binary_name() )?; write!( file, diff --git a/cli/tauri-bundler/src/bundle/msi_bundle.rs b/cli/tauri-bundler/src/bundle/msi_bundle.rs index ffce9111b..af734e579 100644 --- a/cli/tauri-bundler/src/bundle/msi_bundle.rs +++ b/cli/tauri-bundler/src/bundle/msi_bundle.rs @@ -1,4 +1,3 @@ -use super::common; use super::settings::Settings; use super::wix; diff --git a/cli/tauri-bundler/src/bundle/osx_bundle.rs b/cli/tauri-bundler/src/bundle/osx_bundle.rs index b3ad0c54d..8252dec86 100644 --- a/cli/tauri-bundler/src/bundle/osx_bundle.rs +++ b/cli/tauri-bundler/src/bundle/osx_bundle.rs @@ -73,8 +73,7 @@ pub fn bundle_project(settings: &Settings) -> crate::Result> { .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> { 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 { diff --git a/cli/tauri-bundler/src/bundle/settings.rs b/cli/tauri-bundler/src/bundle/settings.rs index cc4355a57..ba9ca24e9 100644 --- a/cli/tauri-bundler/src/bundle/settings.rs +++ b/cli/tauri-bundler/src/bundle/settings.rs @@ -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>, /// the package's metadata. metadata: Option, + /// the default binary to run. + default_run: Option, } /// The `workspace` section of the app configuration (read from Cargo.toml). @@ -216,6 +207,12 @@ struct WorkspaceSettings { members: Option>, } +#[derive(Clone, Debug, Deserialize)] +struct BinarySettings { + name: String, + path: Option, +} + /// 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, + /// the binary targets configuration. + bin: Option>, +} + +#[derive(Clone, Debug)] +pub struct BundleBinary { + name: String, + src_path: Option, + 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) -> 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>, /// 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, } 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(¤t_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 = 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 { + &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>, - map_name: &str, - bundle_name: &str, -) -> crate::Result { - 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 { let target_triple = target_triple()?; diff --git a/cli/tauri-bundler/src/bundle/templates/main.wxs b/cli/tauri-bundler/src/bundle/templates/main.wxs index 5108c2d87..52127ec91 100644 --- a/cli/tauri-bundler/src/bundle/templates/main.wxs +++ b/cli/tauri-bundler/src/bundle/templates/main.wxs @@ -70,9 +70,9 @@ - {{#each external_binaries as |external_bin| ~}} - - + {{#each binaries as |bin| ~}} + + {{/each~}} {{{resources}}} @@ -140,8 +140,8 @@ Level="1" Absent="allow"> - {{#each external_binaries as |external_bin| ~}} - + {{#each binaries as |bin| ~}} + {{/each~}} diff --git a/cli/tauri-bundler/src/bundle/wix.rs b/cli/tauri-bundler/src/bundle/wix.rs index a03a7a181..c924edfcd 100644 --- a/cli/tauri-bundler/src/bundle/wix.rs +++ b/cli/tauri-bundler/src/bundle/wix.rs @@ -57,15 +57,16 @@ lazy_static! { /// Mapper between a resource directory name and its ResourceDirectory descriptor. type ResourceMap = BTreeMap; -/// 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 { - 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> { - 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> { + 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 crate::Result 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()); }