refactor(cli): add --no-bundle flag, skip bundling on invalid formats (#8990)

* refactor(cli): add `--no-bundle` flag, skip bundling on invalid formats

* enhance bundle format parsing

* lint [skip ci]

---------

Co-authored-by: Lucas Nogueira <lucas@tauri.studio>
This commit is contained in:
Amr Bashir 2024-02-29 15:49:42 +02:00 committed by GitHub
parent 4f78941763
commit c68218b362
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 254 additions and 196 deletions

View File

@ -0,0 +1,6 @@
---
'tauri-cli': 'patch:enhance'
'@tauri-apps/cli': 'patch:enhance'
---
Add `--no-bundle` flag for `tauri build` command to skip bundling. Previously `none` was used to skip bundling, it will now be treated as invalid format and a warning will be emitted instead.

View File

@ -0,0 +1,5 @@
---
'tauri-utils': 'patch:bug'
---
Fix `BundleTarget::to_vec` returning an empty vec for `BundleTarget::All` variant.

View File

@ -0,0 +1,5 @@
---
'tauri-utils': 'patch:bug'
---
Add `BundleType::all` method to return all possible `BundleType` variants.

View File

@ -121,6 +121,22 @@ pub enum BundleType {
Updater,
}
impl BundleType {
/// All bundle types.
fn all() -> &'static [Self] {
&[
BundleType::Deb,
BundleType::Rpm,
BundleType::AppImage,
BundleType::Msi,
BundleType::Nsis,
BundleType::App,
BundleType::Dmg,
BundleType::Updater,
]
}
}
impl Display for BundleType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
@ -274,7 +290,7 @@ impl BundleTarget {
#[allow(dead_code)]
pub fn to_vec(&self) -> Vec<BundleType> {
match self {
Self::All => vec![],
Self::All => BundleType::all().to_vec(),
Self::List(list) => list.clone(),
Self::One(i) => vec![i.clone()],
}

View File

@ -3151,7 +3151,7 @@ checksum = "e1fc403891a21bcfb7c37834ba66a547a8f402146eba7265b5a6d88059c9ff2f"
[[package]]
name = "tauri"
version = "2.0.0-beta.7"
version = "2.0.0-beta.8"
dependencies = [
"anyhow",
"bytes",
@ -3202,7 +3202,7 @@ dependencies = [
[[package]]
name = "tauri-build"
version = "2.0.0-beta.5"
version = "2.0.0-beta.6"
dependencies = [
"anyhow",
"cargo_toml",
@ -3224,7 +3224,7 @@ dependencies = [
[[package]]
name = "tauri-codegen"
version = "2.0.0-beta.5"
version = "2.0.0-beta.6"
dependencies = [
"base64",
"brotli",
@ -3249,7 +3249,7 @@ dependencies = [
[[package]]
name = "tauri-macros"
version = "2.0.0-beta.5"
version = "2.0.0-beta.6"
dependencies = [
"heck",
"proc-macro2",
@ -3261,7 +3261,7 @@ dependencies = [
[[package]]
name = "tauri-plugin"
version = "2.0.0-beta.5"
version = "2.0.0-beta.6"
dependencies = [
"anyhow",
"glob",
@ -3287,7 +3287,7 @@ dependencies = [
[[package]]
name = "tauri-runtime"
version = "2.0.0-beta.5"
version = "2.0.0-beta.6"
dependencies = [
"gtk",
"http",
@ -3303,7 +3303,7 @@ dependencies = [
[[package]]
name = "tauri-runtime-wry"
version = "2.0.0-beta.5"
version = "2.0.0-beta.6"
dependencies = [
"cocoa",
"gtk",
@ -3324,7 +3324,7 @@ dependencies = [
[[package]]
name = "tauri-utils"
version = "2.0.0-beta.5"
version = "2.0.0-beta.6"
dependencies = [
"aes-gcm",
"brotli",

View File

@ -6,7 +6,7 @@ use crate::{
helpers::{
app_paths::{app_dir, tauri_dir},
command_env,
config::{get as get_config, ConfigHandle, FrontendDist, HookCommand},
config::{get as get_config, ConfigHandle, ConfigMetadata, FrontendDist, HookCommand},
updater_signature::{secret_key as updater_secret_key, sign_file},
},
interface::{AppInterface, AppSettings, Interface},
@ -14,16 +14,40 @@ use crate::{
};
use anyhow::{bail, Context};
use base64::Engine;
use clap::{ArgAction, Parser};
use log::{debug, error, info, warn};
use clap::{builder::PossibleValue, ArgAction, Parser, ValueEnum};
use std::{
env::{set_current_dir, var},
path::{Path, PathBuf},
process::Command,
str::FromStr,
sync::OnceLock,
};
use tauri_bundler::bundle::{bundle_project, Bundle, PackageType};
use tauri_utils::platform::Target;
#[derive(Debug, Clone)]
pub struct BundleFormat(PackageType);
impl FromStr for BundleFormat {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self> {
PackageType::from_short_name(s)
.map(Self)
.ok_or_else(|| anyhow::anyhow!("unknown bundle format {s}"))
}
}
impl ValueEnum for BundleFormat {
fn value_variants<'a>() -> &'a [Self] {
static VARIANTS: OnceLock<Vec<BundleFormat>> = OnceLock::new();
VARIANTS.get_or_init(|| PackageType::all().iter().map(|t| Self(*t)).collect())
}
fn to_possible_value(&self) -> Option<PossibleValue> {
Some(PossibleValue::new(self.0.short_name()))
}
}
#[derive(Debug, Clone, Parser)]
#[clap(
about = "Build your app in release mode and generate bundles and installers",
@ -48,12 +72,12 @@ pub struct Options {
pub features: Option<Vec<String>>,
/// Space or comma separated list of bundles to package.
///
/// Each bundle must be one of `deb`, `rpm`, `appimage`, `msi`, `app` or `dmg` on MacOS and `updater` on all platforms.
/// If `none` is specified, the bundler will be skipped.
///
/// Note that the `updater` bundle is not automatically added so you must specify it if the updater is enabled.
#[clap(short, long, action = ArgAction::Append, num_args(0..), value_delimiter = ',')]
pub bundles: Option<Vec<String>>,
pub bundles: Option<Vec<BundleFormat>>,
/// Skip the bundling step even if `bundle > active` is `true` in tauri config.
#[clap(long)]
pub no_bundle: bool,
/// JSON string or path to JSON file to merge with tauri.conf.json
#[clap(short, long)]
pub config: Option<ConfigValue>,
@ -95,74 +119,162 @@ pub fn command(mut options: Options, verbosity: u8) -> Result<()> {
let app_settings = interface.app_settings();
if config_.bundle.active {
let package_types = if let Some(names) = &options.bundles {
let mut types = vec![];
let mut skip = false;
for name in names {
if name == "none" {
skip = true;
break;
bundle(
&options,
verbosity,
ci,
&interface,
&app_settings,
config_,
out_dir,
)?;
Ok(())
}
pub fn setup(
interface: &AppInterface,
options: &mut Options,
config: ConfigHandle,
mobile: bool,
) -> Result<()> {
let tauri_path = tauri_dir();
set_current_dir(tauri_path).with_context(|| "failed to change current working directory")?;
let config_guard = config.lock().unwrap();
let config_ = config_guard.as_ref().unwrap();
let bundle_identifier_source = config_
.find_bundle_identifier_overwriter()
.unwrap_or_else(|| "tauri.conf.json".into());
if config_.identifier == "com.tauri.dev" {
log::error!(
"You must change the bundle identifier in `{} identifier`. The default value `com.tauri.dev` is not allowed as it must be unique across applications.",
bundle_identifier_source
);
std::process::exit(1);
}
match PackageType::from_short_name(name) {
Some(package_type) => {
types.push(package_type);
if config_
.identifier
.chars()
.any(|ch| !(ch.is_alphanumeric() || ch == '-' || ch == '.'))
{
log::error!(
"The bundle identifier \"{}\" set in `{} identifier`. The bundle identifier string must contain only alphanumeric characters (A-Z, a-z, and 0-9), hyphens (-), and periods (.).",
config_.identifier,
bundle_identifier_source
);
std::process::exit(1);
}
None => {
return Err(anyhow::anyhow!(format!(
"Unsupported bundle format: {name}"
)));
if let Some(before_build) = config_.build.before_build_command.clone() {
run_hook("beforeBuildCommand", before_build, interface, options.debug)?;
}
if let Some(FrontendDist::Directory(web_asset_path)) = &config_.build.frontend_dist {
if !web_asset_path.exists() {
return Err(anyhow::anyhow!(
"Unable to find your web assets, did you forget to build your web app? Your frontendDist is set to \"{:?}\".",
web_asset_path
));
}
if web_asset_path.canonicalize()?.file_name() == Some(std::ffi::OsStr::new("src-tauri")) {
return Err(anyhow::anyhow!(
"The configured frontendDist is the `src-tauri` folder. Please isolate your web assets on a separate folder and update `tauri.conf.json > build > frontendDist`.",
));
}
let mut out_folders = Vec::new();
for folder in &["node_modules", "src-tauri", "target"] {
if web_asset_path.join(folder).is_dir() {
out_folders.push(folder.to_string());
}
}
if !out_folders.is_empty() {
return Err(anyhow::anyhow!(
"The configured frontendDist includes the `{:?}` {}. Please isolate your web assets on a separate folder and update `tauri.conf.json > build > frontendDist`.",
out_folders,
if out_folders.len() == 1 { "folder" }else { "folders" }
)
);
}
}
if skip {
None
} else {
Some(types)
if options.runner.is_none() {
options.runner = config_.build.runner.clone();
}
} else {
let targets = config_.bundle.targets.to_vec();
if targets.is_empty() {
None
} else {
Some(targets.into_iter().map(Into::into).collect())
options
.features
.get_or_insert(Vec::new())
.extend(config_.build.features.clone().unwrap_or_default());
interface.build_options(&mut options.args, &mut options.features, mobile);
Ok(())
}
fn bundle<A: AppSettings>(
options: &Options,
verbosity: u8,
ci: bool,
interface: &AppInterface,
app_settings: &std::sync::Arc<A>,
config: &ConfigMetadata,
out_dir: &Path,
) -> crate::Result<()> {
if options.no_bundle || (options.bundles.is_none() && !config.bundle.active) {
return Ok(());
}
let package_types: Vec<PackageType> = if let Some(bundles) = &options.bundles {
bundles.iter().map(|bundle| bundle.0).collect::<Vec<_>>()
} else {
config
.bundle
.targets
.to_vec()
.into_iter()
.map(Into::into)
.collect()
};
let updater_pub_key = config_
if package_types.is_empty() {
return Ok(());
}
let updater_pub_key = config
.plugins
.0
.get("updater")
.and_then(|k| k.get("pubkey"))
.and_then(|v| v.as_str())
.map(|v| v.to_string());
if let Some(types) = &package_types {
if updater_pub_key
.as_ref()
.map(|v| !v.is_empty())
.unwrap_or(false)
&& !types.contains(&PackageType::Updater)
&& !package_types.contains(&PackageType::Updater)
{
warn!("`plugins > updater > pubkey` is set, but the bundle target list does not contain `updater`, so the updater artifacts won't be generated.");
}
log::warn!("`plugins > updater > pubkey` is set, but the bundle target list does not contain `updater`, so the updater artifacts won't be generated.");
}
// if we have a package to bundle, let's run the `before_bundle_command`.
if package_types.as_ref().map_or(true, |p| !p.is_empty()) {
if let Some(before_bundle) = config_.build.before_bundle_command.clone() {
if !package_types.is_empty() {
if let Some(before_bundle) = config.build.before_bundle_command.clone() {
run_hook(
"beforeBundleCommand",
before_bundle,
&interface,
interface,
options.debug,
)?;
}
}
let mut settings = app_settings
.get_bundler_settings(&options.into(), config_, out_dir, package_types)
.get_bundler_settings(options.clone().into(), config, out_dir, package_types)
.with_context(|| "failed to build bundler settings")?;
settings.set_log_level(match verbosity {
@ -174,11 +286,11 @@ pub fn command(mut options: Options, verbosity: u8) -> Result<()> {
// set env vars used by the bundler
#[cfg(target_os = "linux")]
{
if config_.bundle.linux.appimage.bundle_media_framework {
if config.bundle.linux.appimage.bundle_media_framework {
std::env::set_var("APPIMAGE_BUNDLE_GSTREAMER", "1");
}
if let Some(open) = config_.plugins.0.get("shell").and_then(|v| v.get("open")) {
if let Some(open) = config.plugins.0.get("shell").and_then(|v| v.get("open")) {
if open.as_bool().is_some_and(|x| x) || open.is_string() {
std::env::set_var("APPIMAGE_BUNDLE_XDG_OPEN", "1");
}
@ -236,8 +348,7 @@ pub fn command(mut options: Options, verbosity: u8) -> Result<()> {
let pubkey = base64::engine::general_purpose::STANDARD.decode(pubkey)?;
let pub_key_decoded = String::from_utf8_lossy(&pubkey);
let public_key =
minisign::PublicKeyBox::from_string(&pub_key_decoded)?.into_public_key()?;
let public_key = minisign::PublicKeyBox::from_string(&pub_key_decoded)?.into_public_key()?;
// make sure we have our package built
let mut signed_paths = Vec::new();
@ -259,90 +370,6 @@ pub fn command(mut options: Options, verbosity: u8) -> Result<()> {
print_signed_updater_archive(&signed_paths)?;
}
}
}
Ok(())
}
pub fn setup(
interface: &AppInterface,
options: &mut Options,
config: ConfigHandle,
mobile: bool,
) -> Result<()> {
let tauri_path = tauri_dir();
set_current_dir(tauri_path).with_context(|| "failed to change current working directory")?;
let config_guard = config.lock().unwrap();
let config_ = config_guard.as_ref().unwrap();
let bundle_identifier_source = config_
.find_bundle_identifier_overwriter()
.unwrap_or_else(|| "tauri.conf.json".into());
if config_.identifier == "com.tauri.dev" {
error!(
"You must change the bundle identifier in `{} identifier`. The default value `com.tauri.dev` is not allowed as it must be unique across applications.",
bundle_identifier_source
);
std::process::exit(1);
}
if config_
.identifier
.chars()
.any(|ch| !(ch.is_alphanumeric() || ch == '-' || ch == '.'))
{
error!(
"The bundle identifier \"{}\" set in `{} identifier`. The bundle identifier string must contain only alphanumeric characters (A-Z, a-z, and 0-9), hyphens (-), and periods (.).",
config_.identifier,
bundle_identifier_source
);
std::process::exit(1);
}
if let Some(before_build) = config_.build.before_build_command.clone() {
run_hook("beforeBuildCommand", before_build, interface, options.debug)?;
}
if let Some(FrontendDist::Directory(web_asset_path)) = &config_.build.frontend_dist {
if !web_asset_path.exists() {
return Err(anyhow::anyhow!(
"Unable to find your web assets, did you forget to build your web app? Your frontendDist is set to \"{:?}\".",
web_asset_path
));
}
if web_asset_path.canonicalize()?.file_name() == Some(std::ffi::OsStr::new("src-tauri")) {
return Err(anyhow::anyhow!(
"The configured frontendDist is the `src-tauri` folder. Please isolate your web assets on a separate folder and update `tauri.conf.json > build > frontendDist`.",
));
}
let mut out_folders = Vec::new();
for folder in &["node_modules", "src-tauri", "target"] {
if web_asset_path.join(folder).is_dir() {
out_folders.push(folder.to_string());
}
}
if !out_folders.is_empty() {
return Err(anyhow::anyhow!(
"The configured frontendDist includes the `{:?}` {}. Please isolate your web assets on a separate folder and update `tauri.conf.json > build > frontendDist`.",
out_folders,
if out_folders.len() == 1 { "folder" }else { "folders" }
)
);
}
}
if options.runner.is_none() {
options.runner = config_.build.runner.clone();
}
options
.features
.get_or_insert(Vec::new())
.extend(config_.build.features.clone().unwrap_or_default());
interface.build_options(&mut options.args, &mut options.features, mobile);
Ok(())
}
@ -355,12 +382,12 @@ fn run_hook(name: &str, hook: HookCommand, interface: &AppInterface, debug: bool
};
let cwd = script_cwd.unwrap_or_else(|| app_dir().clone());
if let Some(script) = script {
info!(action = "Running"; "{} `{}`", name, script);
log::info!(action = "Running"; "{} `{}`", name, script);
let mut env = command_env(debug);
env.extend(interface.env());
debug!("Setting environment for hook {:?}", env);
log::debug!("Setting environment for hook {:?}", env);
#[cfg(target_os = "windows")]
let status = Command::new("cmd")
@ -409,7 +436,7 @@ fn print_signed_updater_archive(output_paths: &[PathBuf]) -> crate::Result<()> {
tauri_utils::display_path(path)
)?;
}
info!( action = "Finished"; "{} {} at:\n{}", output_paths.len(), pluralised, printable_paths);
log::info!( action = "Finished"; "{} {} at:\n{}", output_paths.len(), pluralised, printable_paths);
}
Ok(())
}

View File

@ -41,10 +41,10 @@ pub trait AppSettings {
fn get_bundler_settings(
&self,
options: &Options,
options: Options,
config: &Config,
out_dir: &Path,
package_types: Option<Vec<PackageType>>,
package_types: Vec<PackageType>,
) -> crate::Result<Settings> {
let no_default_features = options.args.contains(&"--no-default-features".into());
let mut enabled_features = options.features.clone().unwrap_or_default();
@ -58,18 +58,15 @@ pub trait AppSettings {
tauri_utils::platform::target_triple()?
};
let mut settings_builder = SettingsBuilder::new()
SettingsBuilder::new()
.package_settings(self.get_package_settings())
.bundle_settings(self.get_bundle_settings(config, &enabled_features)?)
.binaries(self.get_binaries(config, &target)?)
.project_out_directory(out_dir)
.target(target);
if let Some(types) = package_types {
settings_builder = settings_builder.package_types(types);
}
settings_builder.build().map_err(Into::into)
.target(target)
.package_types(package_types)
.build()
.map_err(Into::into)
}
}

View File

@ -77,6 +77,7 @@ impl From<Options> for BuildOptions {
target: None,
features: options.features,
bundles: None,
no_bundle: false,
config: options.config,
args: Vec::new(),
ci: options.ci,

View File

@ -73,6 +73,7 @@ impl From<Options> for BuildOptions {
target: None,
features: options.features,
bundles: None,
no_bundle: false,
config: options.config,
args: Vec::new(),
ci: options.ci,