From d6f7d3cfe8a7ec8d68c8341016c4e0a3103da587 Mon Sep 17 00:00:00 2001 From: Lucas Fernandes Nogueira Date: Tue, 2 Aug 2022 18:54:50 -0300 Subject: [PATCH] Add cwd option to `before` commands, add wait option to dev #4740 #3551 (#4834) --- .changes/before-command-cwd.md | 7 +++ .changes/before-dev-command-wait.md | 7 +++ core/tauri-utils/src/config.rs | 39 +++++++++++- tooling/cli/schema.json | 83 ++++++++++++++++++++++-- tooling/cli/src/build.rs | 22 ++++--- tooling/cli/src/dev.rs | 97 ++++++++++++++++++----------- 6 files changed, 205 insertions(+), 50 deletions(-) create mode 100644 .changes/before-command-cwd.md create mode 100644 .changes/before-dev-command-wait.md diff --git a/.changes/before-command-cwd.md b/.changes/before-command-cwd.md new file mode 100644 index 000000000..a33f5d46a --- /dev/null +++ b/.changes/before-command-cwd.md @@ -0,0 +1,7 @@ +--- +"tauri-utils": minor +"cli.rs": minor +"cli.js": minor +--- + +Change `before_dev_command` and `before_build_command` config value to allow configuring the current working directory. diff --git a/.changes/before-dev-command-wait.md b/.changes/before-dev-command-wait.md new file mode 100644 index 000000000..bb0b07d34 --- /dev/null +++ b/.changes/before-dev-command-wait.md @@ -0,0 +1,7 @@ +--- +"tauri-utils": minor +"cli.rs": minor +"cli.js": minor +--- + +Allow configuring the `before_dev_command` to force the CLI to wait for the command to finish before proceeding. diff --git a/core/tauri-utils/src/config.rs b/core/tauri-utils/src/config.rs index a1cc88aea..bd935f05a 100644 --- a/core/tauri-utils/src/config.rs +++ b/core/tauri-utils/src/config.rs @@ -2377,6 +2377,41 @@ impl std::fmt::Display for AppUrl { } } +/// Describes the shell command to run before `tauri dev`. +#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)] +#[cfg_attr(feature = "schema", derive(JsonSchema))] +#[serde(rename_all = "camelCase", untagged)] +pub enum BeforeDevCommand { + /// Run the given script with the default options. + Script(String), + /// Run the given script with custom options. + ScriptWithOptions { + /// The script to execute. + script: String, + /// The current working directory. + cwd: Option, + /// Whether `tauri dev` should wait for the command to finish or not. Defaults to `false`. + #[serde(default)] + wait: bool, + }, +} + +/// Describes the shell command to run before `tauri build`. +#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)] +#[cfg_attr(feature = "schema", derive(JsonSchema))] +#[serde(rename_all = "camelCase", untagged)] +pub enum BeforeBuildCommand { + /// Run the given script with the default options. + Script(String), + /// Run the given script with custom options. + ScriptWithOptions { + /// The script to execute. + script: String, + /// The current working directory. + cwd: Option, + }, +} + /// The Build configuration object. #[skip_serializing_none] #[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)] @@ -2411,12 +2446,12 @@ pub struct BuildConfig { /// /// The TAURI_PLATFORM, TAURI_ARCH, TAURI_FAMILY, TAURI_PLATFORM_VERSION, TAURI_PLATFORM_TYPE and TAURI_DEBUG environment variables are set if you perform conditional compilation. #[serde(alias = "before-dev-command")] - pub before_dev_command: Option, + pub before_dev_command: Option, /// A shell command to run before `tauri build` kicks in. /// /// The TAURI_PLATFORM, TAURI_ARCH, TAURI_FAMILY, TAURI_PLATFORM_VERSION, TAURI_PLATFORM_TYPE and TAURI_DEBUG environment variables are set if you perform conditional compilation. #[serde(alias = "before-build-command")] - pub before_build_command: Option, + pub before_build_command: Option, /// Features passed to `cargo` commands. pub features: Option>, /// Whether we should inject the Tauri API on `window.__TAURI__` or not. diff --git a/tooling/cli/schema.json b/tooling/cli/schema.json index 13856f2f6..1f30317f0 100644 --- a/tooling/cli/schema.json +++ b/tooling/cli/schema.json @@ -2449,16 +2449,24 @@ }, "beforeDevCommand": { "description": "A shell command to run before `tauri dev` kicks in.\n\nThe TAURI_PLATFORM, TAURI_ARCH, TAURI_FAMILY, TAURI_PLATFORM_VERSION, TAURI_PLATFORM_TYPE and TAURI_DEBUG environment variables are set if you perform conditional compilation.", - "type": [ - "string", - "null" + "anyOf": [ + { + "$ref": "#/definitions/BeforeDevCommand" + }, + { + "type": "null" + } ] }, "beforeBuildCommand": { "description": "A shell command to run before `tauri build` kicks in.\n\nThe TAURI_PLATFORM, TAURI_ARCH, TAURI_FAMILY, TAURI_PLATFORM_VERSION, TAURI_PLATFORM_TYPE and TAURI_DEBUG environment variables are set if you perform conditional compilation.", - "type": [ - "string", - "null" + "anyOf": [ + { + "$ref": "#/definitions/BeforeBuildCommand" + }, + { + "type": "null" + } ] }, "features": { @@ -2499,6 +2507,69 @@ } ] }, + "BeforeDevCommand": { + "description": "Describes the shell command to run before `tauri dev`.", + "anyOf": [ + { + "description": "Run the given script with the default options.", + "type": "string" + }, + { + "description": "Run the given script with custom options.", + "type": "object", + "required": [ + "script" + ], + "properties": { + "script": { + "description": "The script to execute.", + "type": "string" + }, + "cwd": { + "description": "The current working directory.", + "type": [ + "string", + "null" + ] + }, + "wait": { + "description": "Whether `tauri dev` should wait for the command to finish or not. Defaults to `false`.", + "default": false, + "type": "boolean" + } + } + } + ] + }, + "BeforeBuildCommand": { + "description": "Describes the shell command to run before `tauri build`.", + "anyOf": [ + { + "description": "Run the given script with the default options.", + "type": "string" + }, + { + "description": "Run the given script with custom options.", + "type": "object", + "required": [ + "script" + ], + "properties": { + "script": { + "description": "The script to execute.", + "type": "string" + }, + "cwd": { + "description": "The current working directory.", + "type": [ + "string", + "null" + ] + } + } + } + ] + }, "PluginConfig": { "description": "The plugin configs holds a HashMap mapping a plugin name to its configuration object.", "type": "object", diff --git a/tooling/cli/src/build.rs b/tooling/cli/src/build.rs index d36cd918d..8be505e55 100644 --- a/tooling/cli/src/build.rs +++ b/tooling/cli/src/build.rs @@ -6,7 +6,9 @@ use crate::{ helpers::{ app_paths::{app_dir, tauri_dir}, command_env, - config::{get as get_config, AppUrl, WindowUrl, MERGE_CONFIG_EXTENSION_NAME}, + config::{ + get as get_config, AppUrl, BeforeBuildCommand, WindowUrl, MERGE_CONFIG_EXTENSION_NAME, + }, updater_signature::{read_key_from_file, secret_key as updater_secret_key, sign_file}, }, interface::{AppInterface, AppSettings, Interface}, @@ -112,23 +114,29 @@ pub fn command(mut options: Options) -> Result<()> { std::process::exit(1); } - if let Some(before_build) = &config_.build.before_build_command { - if !before_build.is_empty() { + if let Some(before_build) = config_.build.before_build_command.clone() { + let (script, script_cwd) = match before_build { + BeforeBuildCommand::Script(s) if s.is_empty() => (None, None), + BeforeBuildCommand::Script(s) => (Some(s), None), + BeforeBuildCommand::ScriptWithOptions { script, cwd } => (Some(script), cwd.map(Into::into)), + }; + let cwd = script_cwd.unwrap_or_else(|| app_dir().clone()); + if let Some(before_build) = script { info!(action = "Running"; "beforeBuildCommand `{}`", before_build); #[cfg(target_os = "windows")] let status = Command::new("cmd") .arg("/S") .arg("/C") - .arg(before_build) - .current_dir(app_dir()) + .arg(&before_build) + .current_dir(cwd) .envs(command_env(options.debug)) .piped() .with_context(|| format!("failed to run `{}` with `cmd /C`", before_build))?; #[cfg(not(target_os = "windows"))] let status = Command::new("sh") .arg("-c") - .arg(before_build) - .current_dir(app_dir()) + .arg(&before_build) + .current_dir(cwd) .envs(command_env(options.debug)) .piped() .with_context(|| format!("failed to run `{}` with `sh -c`", before_build))?; diff --git a/tooling/cli/src/dev.rs b/tooling/cli/src/dev.rs index 7b84d5d20..ff84a4115 100644 --- a/tooling/cli/src/dev.rs +++ b/tooling/cli/src/dev.rs @@ -6,14 +6,14 @@ use crate::{ helpers::{ app_paths::{app_dir, tauri_dir}, command_env, - config::{get as get_config, AppUrl, WindowUrl}, + config::{get as get_config, AppUrl, BeforeDevCommand, WindowUrl}, }, interface::{AppInterface, ExitReason, Interface}, - Result, + CommandExt, Result, }; use clap::Parser; -use anyhow::Context; +use anyhow::{bail, Context}; use log::{error, info, warn}; use once_cell::sync::OnceCell; use shared_child::SharedChild; @@ -89,65 +89,92 @@ fn command_internal(mut options: Options) -> Result<()> { let config = get_config(options.config.as_deref())?; - if let Some(before_dev) = &config + if let Some(before_dev) = config .lock() .unwrap() .as_ref() .unwrap() .build .before_dev_command + .clone() { - if !before_dev.is_empty() { + let (script, script_cwd, wait) = match before_dev { + BeforeDevCommand::Script(s) if s.is_empty() => (None, None, false), + BeforeDevCommand::Script(s) => (Some(s), None, false), + BeforeDevCommand::ScriptWithOptions { script, cwd, wait } => { + (Some(script), cwd.map(Into::into), wait) + } + }; + let cwd = script_cwd.unwrap_or_else(|| app_dir().clone()); + if let Some(before_dev) = script { info!(action = "Running"; "BeforeDevCommand (`{}`)", before_dev); - #[cfg(target_os = "windows")] + #[cfg(windows)] let mut command = { let mut command = Command::new("cmd"); command .arg("/S") .arg("/C") - .arg(before_dev) - .current_dir(app_dir()) + .arg(&before_dev) + .current_dir(cwd) .envs(command_env(true)); command }; - #[cfg(not(target_os = "windows"))] + #[cfg(not(windows))] let mut command = { let mut command = Command::new("sh"); command .arg("-c") - .arg(before_dev) - .current_dir(app_dir()) + .arg(&before_dev) + .current_dir(cwd) .envs(command_env(true)); command }; - command.stdin(Stdio::piped()); - command.stdout(os_pipe::dup_stdout()?); - command.stderr(os_pipe::dup_stderr()?); - let child = SharedChild::spawn(&mut command) - .unwrap_or_else(|_| panic!("failed to run `{}`", before_dev)); - let child = Arc::new(child); - let child_ = child.clone(); - - std::thread::spawn(move || { - let status = child_ - .wait() - .expect("failed to wait on \"beforeDevCommand\""); - if !(status.success() || KILL_BEFORE_DEV_FLAG.get().unwrap().load(Ordering::Relaxed)) { - error!("The \"beforeDevCommand\" terminated with a non-zero status code."); - exit(status.code().unwrap_or(1)); + if wait { + let status = command.piped().with_context(|| { + format!( + "failed to run `{}` with `{}`", + before_dev, + if cfg!(windows) { "cmd /S /C" } else { "sh -c" } + ) + })?; + if !status.success() { + bail!( + "beforeDevCommand `{}` failed with exit code {}", + before_dev, + status.code().unwrap_or_default() + ); } - }); + } else { + command.stdin(Stdio::piped()); + command.stdout(os_pipe::dup_stdout()?); + command.stderr(os_pipe::dup_stderr()?); - BEFORE_DEV.set(Mutex::new(child)).unwrap(); - KILL_BEFORE_DEV_FLAG.set(AtomicBool::default()).unwrap(); + let child = SharedChild::spawn(&mut command) + .unwrap_or_else(|_| panic!("failed to run `{}`", before_dev)); + let child = Arc::new(child); + let child_ = child.clone(); - let _ = ctrlc::set_handler(move || { - kill_before_dev_process(); - #[cfg(not(debug_assertions))] - let _ = check_for_updates(); - exit(130); - }); + std::thread::spawn(move || { + let status = child_ + .wait() + .expect("failed to wait on \"beforeDevCommand\""); + if !(status.success() || KILL_BEFORE_DEV_FLAG.get().unwrap().load(Ordering::Relaxed)) { + error!("The \"beforeDevCommand\" terminated with a non-zero status code."); + exit(status.code().unwrap_or(1)); + } + }); + + BEFORE_DEV.set(Mutex::new(child)).unwrap(); + KILL_BEFORE_DEV_FLAG.set(AtomicBool::default()).unwrap(); + + let _ = ctrlc::set_handler(move || { + kill_before_dev_process(); + #[cfg(not(debug_assertions))] + let _ = check_for_updates(); + exit(130); + }); + } } }