Explorar o código

Add cwd option to `before` commands, add wait option to dev #4740 #3551 (#4834)

Lucas Fernandes Nogueira %!s(int64=3) %!d(string=hai) anos
pai
achega
d6f7d3cfe8

+ 7 - 0
.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.

+ 7 - 0
.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.

+ 37 - 2
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<String>,
+    /// 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<String>,
+  },
+}
+
 /// 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<String>,
+  pub before_dev_command: Option<BeforeDevCommand>,
   /// 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<String>,
+  pub before_build_command: Option<BeforeBuildCommand>,
   /// Features passed to `cargo` commands.
   pub features: Option<Vec<String>>,
   /// Whether we should inject the Tauri API on `window.__TAURI__` or not.

+ 77 - 6
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",

+ 15 - 7
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))?;

+ 62 - 35
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()?);
+
+        let child = SharedChild::spawn(&mut command)
+          .unwrap_or_else(|_| panic!("failed to run `{}`", before_dev));
+        let child = Arc::new(child);
+        let child_ = child.clone();
 
-      BEFORE_DEV.set(Mutex::new(child)).unwrap();
-      KILL_BEFORE_DEV_FLAG.set(AtomicBool::default()).unwrap();
+        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));
+          }
+        });
 
-      let _ = ctrlc::set_handler(move || {
-        kill_before_dev_process();
-        #[cfg(not(debug_assertions))]
-        let _ = check_for_updates();
-        exit(130);
-      });
+        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);
+        });
+      }
     }
   }