|
3 | 3 | // SPDX-License-Identifier: MIT |
4 | 4 |
|
5 | 5 | use crate::{ |
| 6 | + bundle::BundleFormat, |
6 | 7 | helpers::{ |
7 | | - app_paths::{app_dir, tauri_dir}, |
8 | | - command_env, |
9 | | - config::{get as get_config, ConfigHandle, ConfigMetadata, FrontendDist, HookCommand}, |
10 | | - updater_signature::{secret_key as updater_secret_key, sign_file}, |
| 8 | + self, |
| 9 | + app_paths::tauri_dir, |
| 10 | + config::{get as get_config, ConfigHandle, FrontendDist}, |
11 | 11 | }, |
12 | 12 | interface::{AppInterface, AppSettings, Interface}, |
13 | | - CommandExt, ConfigValue, Result, |
14 | | -}; |
15 | | -use anyhow::{bail, Context}; |
16 | | -use base64::Engine; |
17 | | -use clap::{builder::PossibleValue, ArgAction, Parser, ValueEnum}; |
18 | | -use std::{ |
19 | | - env::{set_current_dir, var}, |
20 | | - path::{Path, PathBuf}, |
21 | | - process::Command, |
22 | | - str::FromStr, |
23 | | - sync::OnceLock, |
24 | | -}; |
25 | | -use tauri_bundler::{ |
26 | | - bundle::{bundle_project, PackageType}, |
27 | | - Bundle, |
| 13 | + ConfigValue, Result, |
28 | 14 | }; |
| 15 | +use anyhow::Context; |
| 16 | +use clap::{ArgAction, Parser}; |
| 17 | +use std::env::set_current_dir; |
29 | 18 | use tauri_utils::platform::Target; |
30 | 19 |
|
31 | | -#[derive(Debug, Clone)] |
32 | | -pub struct BundleFormat(PackageType); |
33 | | - |
34 | | -impl FromStr for BundleFormat { |
35 | | - type Err = anyhow::Error; |
36 | | - fn from_str(s: &str) -> Result<Self> { |
37 | | - PackageType::from_short_name(s) |
38 | | - .map(Self) |
39 | | - .ok_or_else(|| anyhow::anyhow!("unknown bundle format {s}")) |
40 | | - } |
41 | | -} |
42 | | - |
43 | | -impl ValueEnum for BundleFormat { |
44 | | - fn value_variants<'a>() -> &'a [Self] { |
45 | | - static VARIANTS: OnceLock<Vec<BundleFormat>> = OnceLock::new(); |
46 | | - VARIANTS.get_or_init(|| PackageType::all().iter().map(|t| Self(*t)).collect()) |
47 | | - } |
48 | | - |
49 | | - fn to_possible_value(&self) -> Option<PossibleValue> { |
50 | | - Some(PossibleValue::new(self.0.short_name())) |
51 | | - } |
52 | | -} |
53 | | - |
54 | 20 | #[derive(Debug, Clone, Parser)] |
55 | 21 | #[clap( |
56 | 22 | about = "Build your app in release mode and generate bundles and installers", |
@@ -120,17 +86,21 @@ pub fn command(mut options: Options, verbosity: u8) -> Result<()> { |
120 | 86 |
|
121 | 87 | interface.build(interface_options)?; |
122 | 88 |
|
| 89 | + log::info!(action ="Built"; "application at: {}", tauri_utils::display_path(&bin_path)); |
| 90 | + |
123 | 91 | let app_settings = interface.app_settings(); |
124 | 92 |
|
125 | | - bundle( |
126 | | - &options, |
127 | | - verbosity, |
128 | | - ci, |
129 | | - &interface, |
130 | | - &app_settings, |
131 | | - config_, |
132 | | - out_dir, |
133 | | - )?; |
| 93 | + if !options.no_bundle && (config_.bundle.active || options.bundles.is_some()) { |
| 94 | + crate::bundle::bundle( |
| 95 | + &options.into(), |
| 96 | + verbosity, |
| 97 | + ci, |
| 98 | + &interface, |
| 99 | + &app_settings, |
| 100 | + config_, |
| 101 | + out_dir, |
| 102 | + )?; |
| 103 | + } |
134 | 104 |
|
135 | 105 | Ok(()) |
136 | 106 | } |
@@ -173,7 +143,7 @@ pub fn setup( |
173 | 143 | } |
174 | 144 |
|
175 | 145 | if let Some(before_build) = config_.build.before_build_command.clone() { |
176 | | - run_hook("beforeBuildCommand", before_build, interface, options.debug)?; |
| 146 | + helpers::run_hook("beforeBuildCommand", before_build, interface, options.debug)?; |
177 | 147 | } |
178 | 148 |
|
179 | 149 | if let Some(FrontendDist::Directory(web_asset_path)) = &config_.build.frontend_dist { |
@@ -222,223 +192,3 @@ pub fn setup( |
222 | 192 |
|
223 | 193 | Ok(()) |
224 | 194 | } |
225 | | - |
226 | | -fn bundle<A: AppSettings>( |
227 | | - options: &Options, |
228 | | - verbosity: u8, |
229 | | - ci: bool, |
230 | | - interface: &AppInterface, |
231 | | - app_settings: &std::sync::Arc<A>, |
232 | | - config: &ConfigMetadata, |
233 | | - out_dir: &Path, |
234 | | -) -> crate::Result<()> { |
235 | | - if options.no_bundle || (options.bundles.is_none() && !config.bundle.active) { |
236 | | - return Ok(()); |
237 | | - } |
238 | | - |
239 | | - let package_types: Vec<PackageType> = if let Some(bundles) = &options.bundles { |
240 | | - bundles.iter().map(|bundle| bundle.0).collect::<Vec<_>>() |
241 | | - } else { |
242 | | - config |
243 | | - .bundle |
244 | | - .targets |
245 | | - .to_vec() |
246 | | - .into_iter() |
247 | | - .map(Into::into) |
248 | | - .collect() |
249 | | - }; |
250 | | - |
251 | | - if package_types.is_empty() { |
252 | | - return Ok(()); |
253 | | - } |
254 | | - |
255 | | - // if we have a package to bundle, let's run the `before_bundle_command`. |
256 | | - if !package_types.is_empty() { |
257 | | - if let Some(before_bundle) = config.build.before_bundle_command.clone() { |
258 | | - run_hook( |
259 | | - "beforeBundleCommand", |
260 | | - before_bundle, |
261 | | - interface, |
262 | | - options.debug, |
263 | | - )?; |
264 | | - } |
265 | | - } |
266 | | - |
267 | | - let mut settings = app_settings |
268 | | - .get_bundler_settings(options.clone().into(), config, out_dir, package_types) |
269 | | - .with_context(|| "failed to build bundler settings")?; |
270 | | - |
271 | | - settings.set_log_level(match verbosity { |
272 | | - 0 => log::Level::Error, |
273 | | - 1 => log::Level::Info, |
274 | | - _ => log::Level::Trace, |
275 | | - }); |
276 | | - |
277 | | - // set env vars used by the bundler |
278 | | - #[cfg(target_os = "linux")] |
279 | | - { |
280 | | - if config.bundle.linux.appimage.bundle_media_framework { |
281 | | - std::env::set_var("APPIMAGE_BUNDLE_GSTREAMER", "1"); |
282 | | - } |
283 | | - |
284 | | - if let Some(open) = config.plugins.0.get("shell").and_then(|v| v.get("open")) { |
285 | | - if open.as_bool().is_some_and(|x| x) || open.is_string() { |
286 | | - std::env::set_var("APPIMAGE_BUNDLE_XDG_OPEN", "1"); |
287 | | - } |
288 | | - } |
289 | | - |
290 | | - if settings.deep_link_protocols().is_some() { |
291 | | - std::env::set_var("APPIMAGE_BUNDLE_XDG_MIME", "1"); |
292 | | - } |
293 | | - } |
294 | | - |
295 | | - let bundles = bundle_project(settings) |
296 | | - .map_err(|e| anyhow::anyhow!("{:#}", e)) |
297 | | - .with_context(|| "failed to bundle project")?; |
298 | | - |
299 | | - let update_enabled_bundles: Vec<&Bundle> = bundles |
300 | | - .iter() |
301 | | - .filter(|bundle| { |
302 | | - matches!( |
303 | | - bundle.package_type, |
304 | | - PackageType::Updater | PackageType::Nsis | PackageType::WindowsMsi | PackageType::AppImage |
305 | | - ) |
306 | | - }) |
307 | | - .collect(); |
308 | | - |
309 | | - // Skip if no updater is active |
310 | | - if !update_enabled_bundles.is_empty() { |
311 | | - let updater_pub_key = config |
312 | | - .plugins |
313 | | - .0 |
314 | | - .get("updater") |
315 | | - .and_then(|k| k.get("pubkey")) |
316 | | - .and_then(|v| v.as_str()) |
317 | | - .map(|v| v.to_string()); |
318 | | - |
319 | | - if let Some(pubkey) = updater_pub_key { |
320 | | - // get the public key |
321 | | - // check if pubkey points to a file... |
322 | | - let maybe_path = Path::new(&pubkey); |
323 | | - let pubkey = if maybe_path.exists() { |
324 | | - std::fs::read_to_string(maybe_path)? |
325 | | - } else { |
326 | | - pubkey |
327 | | - }; |
328 | | - |
329 | | - // if no password provided we use an empty string |
330 | | - let password = var("TAURI_SIGNING_PRIVATE_KEY_PASSWORD").ok().or_else(|| { |
331 | | - if ci { |
332 | | - Some("".into()) |
333 | | - } else { |
334 | | - None |
335 | | - } |
336 | | - }); |
337 | | - |
338 | | - // get the private key |
339 | | - let secret_key = match var("TAURI_SIGNING_PRIVATE_KEY") { |
340 | | - Ok(private_key) => { |
341 | | - // check if private_key points to a file... |
342 | | - let maybe_path = Path::new(&private_key); |
343 | | - let private_key = if maybe_path.exists() { |
344 | | - std::fs::read_to_string(maybe_path)? |
345 | | - } else { |
346 | | - private_key |
347 | | - }; |
348 | | - updater_secret_key(private_key, password) |
349 | | - } |
350 | | - _ => Err(anyhow::anyhow!("A public key has been found, but no private key. Make sure to set `TAURI_SIGNING_PRIVATE_KEY` environment variable.")), |
351 | | - }?; |
352 | | - |
353 | | - let pubkey = base64::engine::general_purpose::STANDARD.decode(pubkey)?; |
354 | | - let pub_key_decoded = String::from_utf8_lossy(&pubkey); |
355 | | - let public_key = minisign::PublicKeyBox::from_string(&pub_key_decoded)?.into_public_key()?; |
356 | | - |
357 | | - // make sure we have our package built |
358 | | - let mut signed_paths = Vec::new(); |
359 | | - for bundle in update_enabled_bundles { |
360 | | - // we expect to have only one path in the vec but we iter if we add |
361 | | - // another type of updater package who require multiple file signature |
362 | | - for path in bundle.bundle_paths.iter() { |
363 | | - // sign our path from environment variables |
364 | | - let (signature_path, signature) = sign_file(&secret_key, path)?; |
365 | | - if signature.keynum() != public_key.keynum() { |
366 | | - log::warn!("The updater secret key from `TAURI_PRIVATE_KEY` does not match the public key from `plugins > updater > pubkey`. If you are not rotating keys, this means your configuration is wrong and won't be accepted at runtime when performing update."); |
367 | | - } |
368 | | - signed_paths.push(signature_path); |
369 | | - } |
370 | | - } |
371 | | - |
372 | | - print_signed_updater_archive(&signed_paths)?; |
373 | | - } |
374 | | - } |
375 | | - |
376 | | - Ok(()) |
377 | | -} |
378 | | - |
379 | | -fn run_hook(name: &str, hook: HookCommand, interface: &AppInterface, debug: bool) -> Result<()> { |
380 | | - let (script, script_cwd) = match hook { |
381 | | - HookCommand::Script(s) if s.is_empty() => (None, None), |
382 | | - HookCommand::Script(s) => (Some(s), None), |
383 | | - HookCommand::ScriptWithOptions { script, cwd } => (Some(script), cwd.map(Into::into)), |
384 | | - }; |
385 | | - let cwd = script_cwd.unwrap_or_else(|| app_dir().clone()); |
386 | | - if let Some(script) = script { |
387 | | - log::info!(action = "Running"; "{} `{}`", name, script); |
388 | | - |
389 | | - let mut env = command_env(debug); |
390 | | - env.extend(interface.env()); |
391 | | - |
392 | | - log::debug!("Setting environment for hook {:?}", env); |
393 | | - |
394 | | - #[cfg(target_os = "windows")] |
395 | | - let status = Command::new("cmd") |
396 | | - .arg("/S") |
397 | | - .arg("/C") |
398 | | - .arg(&script) |
399 | | - .current_dir(cwd) |
400 | | - .envs(env) |
401 | | - .piped() |
402 | | - .with_context(|| format!("failed to run `{}` with `cmd /C`", script))?; |
403 | | - #[cfg(not(target_os = "windows"))] |
404 | | - let status = Command::new("sh") |
405 | | - .arg("-c") |
406 | | - .arg(&script) |
407 | | - .current_dir(cwd) |
408 | | - .envs(env) |
409 | | - .piped() |
410 | | - .with_context(|| format!("failed to run `{script}` with `sh -c`"))?; |
411 | | - |
412 | | - if !status.success() { |
413 | | - bail!( |
414 | | - "{} `{}` failed with exit code {}", |
415 | | - name, |
416 | | - script, |
417 | | - status.code().unwrap_or_default() |
418 | | - ); |
419 | | - } |
420 | | - } |
421 | | - |
422 | | - Ok(()) |
423 | | -} |
424 | | - |
425 | | -fn print_signed_updater_archive(output_paths: &[PathBuf]) -> crate::Result<()> { |
426 | | - use std::fmt::Write; |
427 | | - if !output_paths.is_empty() { |
428 | | - let pluralised = if output_paths.len() == 1 { |
429 | | - "updater signature" |
430 | | - } else { |
431 | | - "updater signatures" |
432 | | - }; |
433 | | - let mut printable_paths = String::new(); |
434 | | - for path in output_paths { |
435 | | - writeln!( |
436 | | - printable_paths, |
437 | | - " {}", |
438 | | - tauri_utils::display_path(path) |
439 | | - )?; |
440 | | - } |
441 | | - log::info!( action = "Finished"; "{} {} at:\n{}", output_paths.len(), pluralised, printable_paths); |
442 | | - } |
443 | | - Ok(()) |
444 | | -} |
0 commit comments