mirror of
https://github.com/tauri-apps/tauri.git
synced 2026-02-06 13:37:09 +00:00
feat(cli): allow runner configuration to be an object with cwd and args (#13811)
* Update config.schema.json * Add RunnerConfig for customizable build runner Replaces runner String with RunnerConfig in CLI and config, allowing advanced runner configuration via string or object with cmd, cwd, and args. Updates schema and usage to support new format, and adds tests for serialization, deserialization, and API. Enables more flexible build and run command customization. * Create runner-object-config.md * Remove unused RunnerConfig import in tests Cleaned up the test module in config.rs by removing the unused RunnerConfig import from two test functions. * Fix tests failing Updates related tests in tauri-utils to improve readability and maintain consistency. Minor import reordering in tauri-cli for clarity. * Move RunnerConfig enum and impls above BuildConfig Relocated the RunnerConfig enum and its associated implementations to appear before the BuildConfig definition. This improves code organization and logical grouping of configuration-related types.
This commit is contained in:
parent
7bc77a038a
commit
33d079392a
6
.changes/runner-object-config.md
Normal file
6
.changes/runner-object-config.md
Normal file
@ -0,0 +1,6 @@
|
||||
---
|
||||
'tauri-cli': 'minor:feat'
|
||||
'tauri-utils': 'minor:feat'
|
||||
---
|
||||
|
||||
Allow runner configuration to be an object with cmd, cwd, and args properties. The runner can now be configured as `{ "cmd": "my_runner", "cwd": "/path", "args": ["--quiet"] }` while maintaining backwards compatibility with the existing string format.
|
||||
@ -1805,9 +1805,13 @@
|
||||
"properties": {
|
||||
"runner": {
|
||||
"description": "The binary used to build and run the application.",
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/RunnerConfig"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"devUrl": {
|
||||
@ -1880,6 +1884,45 @@
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"RunnerConfig": {
|
||||
"description": "The runner configuration.",
|
||||
"anyOf": [
|
||||
{
|
||||
"description": "A string specifying the binary to run.",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"description": "An object with advanced configuration options.",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"cmd"
|
||||
],
|
||||
"properties": {
|
||||
"cmd": {
|
||||
"description": "The binary to run.",
|
||||
"type": "string"
|
||||
},
|
||||
"cwd": {
|
||||
"description": "The current working directory to run the command from.",
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"args": {
|
||||
"description": "Arguments to pass to the command.",
|
||||
"type": [
|
||||
"array",
|
||||
"null"
|
||||
],
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"FrontendDist": {
|
||||
"description": "Defines the URL or assets to embed in the application.",
|
||||
"anyOf": [
|
||||
|
||||
@ -15,6 +15,7 @@ use crate::{
|
||||
use anyhow::Context;
|
||||
use clap::{ArgAction, Parser};
|
||||
use std::env::set_current_dir;
|
||||
use tauri_utils::config::RunnerConfig;
|
||||
use tauri_utils::platform::Target;
|
||||
|
||||
#[derive(Debug, Clone, Parser)]
|
||||
@ -25,7 +26,7 @@ use tauri_utils::platform::Target;
|
||||
pub struct Options {
|
||||
/// Binary to use to build the application, defaults to `cargo`
|
||||
#[clap(short, long)]
|
||||
pub runner: Option<String>,
|
||||
pub runner: Option<RunnerConfig>,
|
||||
/// Builds with the debug flag
|
||||
#[clap(short, long)]
|
||||
pub debug: bool,
|
||||
@ -210,7 +211,7 @@ pub fn setup(
|
||||
}
|
||||
|
||||
if options.runner.is_none() {
|
||||
options.runner.clone_from(&config_.build.runner);
|
||||
options.runner = config_.build.runner.clone();
|
||||
}
|
||||
|
||||
options
|
||||
|
||||
@ -17,7 +17,7 @@ use crate::{
|
||||
use anyhow::{bail, Context};
|
||||
use clap::{ArgAction, Parser};
|
||||
use shared_child::SharedChild;
|
||||
use tauri_utils::platform::Target;
|
||||
use tauri_utils::{config::RunnerConfig, platform::Target};
|
||||
|
||||
use std::{
|
||||
env::set_current_dir,
|
||||
@ -49,7 +49,7 @@ pub const TAURI_CLI_BUILTIN_WATCHER_IGNORE_FILE: &[u8] =
|
||||
pub struct Options {
|
||||
/// Binary to use to run the application
|
||||
#[clap(short, long)]
|
||||
pub runner: Option<String>,
|
||||
pub runner: Option<RunnerConfig>,
|
||||
/// Target triple to build against
|
||||
#[clap(short, long)]
|
||||
pub target: Option<String>,
|
||||
@ -224,9 +224,14 @@ pub fn setup(interface: &AppInterface, options: &mut Options, config: ConfigHand
|
||||
}
|
||||
|
||||
if options.runner.is_none() {
|
||||
options
|
||||
options.runner = config
|
||||
.lock()
|
||||
.unwrap()
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.build
|
||||
.runner
|
||||
.clone_from(&config.lock().unwrap().as_ref().unwrap().build.runner);
|
||||
.clone();
|
||||
}
|
||||
|
||||
let mut cargo_features = config
|
||||
|
||||
@ -25,7 +25,7 @@ use tauri_bundler::{
|
||||
IosSettings, MacOsSettings, PackageSettings, Position, RpmSettings, Size, UpdaterSettings,
|
||||
WindowsSettings,
|
||||
};
|
||||
use tauri_utils::config::{parse::is_configuration_file, DeepLinkProtocol, Updater};
|
||||
use tauri_utils::config::{parse::is_configuration_file, DeepLinkProtocol, RunnerConfig, Updater};
|
||||
|
||||
use super::{AppSettings, DevProcess, ExitReason, Interface};
|
||||
use crate::{
|
||||
@ -47,7 +47,7 @@ use manifest::{rewrite_manifest, Manifest};
|
||||
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct Options {
|
||||
pub runner: Option<String>,
|
||||
pub runner: Option<RunnerConfig>,
|
||||
pub debug: bool,
|
||||
pub target: Option<String>,
|
||||
pub features: Option<Vec<String>>,
|
||||
|
||||
@ -230,11 +230,21 @@ fn cargo_command(
|
||||
available_targets: &mut Option<Vec<RustupTarget>>,
|
||||
config_features: Vec<String>,
|
||||
) -> crate::Result<Command> {
|
||||
let runner = options.runner.unwrap_or_else(|| "cargo".into());
|
||||
let runner_config = options.runner.unwrap_or_else(|| "cargo".into());
|
||||
|
||||
let mut build_cmd = Command::new(runner);
|
||||
let mut build_cmd = Command::new(runner_config.cmd());
|
||||
build_cmd.arg(if dev { "run" } else { "build" });
|
||||
|
||||
// Set working directory if specified
|
||||
if let Some(cwd) = runner_config.cwd() {
|
||||
build_cmd.current_dir(cwd);
|
||||
}
|
||||
|
||||
// Add runner-specific arguments first
|
||||
if let Some(runner_args) = runner_config.args() {
|
||||
build_cmd.args(runner_args);
|
||||
}
|
||||
|
||||
if let Some(target) = &options.target {
|
||||
if available_targets.is_none() {
|
||||
*available_targets = fetch_available_targets();
|
||||
|
||||
@ -1805,9 +1805,13 @@
|
||||
"properties": {
|
||||
"runner": {
|
||||
"description": "The binary used to build and run the application.",
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/RunnerConfig"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"devUrl": {
|
||||
@ -1880,6 +1884,45 @@
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"RunnerConfig": {
|
||||
"description": "The runner configuration.",
|
||||
"anyOf": [
|
||||
{
|
||||
"description": "A string specifying the binary to run.",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"description": "An object with advanced configuration options.",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"cmd"
|
||||
],
|
||||
"properties": {
|
||||
"cmd": {
|
||||
"description": "The binary to run.",
|
||||
"type": "string"
|
||||
},
|
||||
"cwd": {
|
||||
"description": "The current working directory to run the command from.",
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"args": {
|
||||
"description": "Arguments to pass to the command.",
|
||||
"type": [
|
||||
"array",
|
||||
"null"
|
||||
],
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"FrontendDist": {
|
||||
"description": "Defines the URL or assets to embed in the application.",
|
||||
"anyOf": [
|
||||
|
||||
@ -2761,6 +2761,77 @@ pub enum HookCommand {
|
||||
},
|
||||
}
|
||||
|
||||
/// The runner configuration.
|
||||
#[skip_serializing_none]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "schema", derive(JsonSchema))]
|
||||
#[serde(untagged)]
|
||||
pub enum RunnerConfig {
|
||||
/// A string specifying the binary to run.
|
||||
String(String),
|
||||
/// An object with advanced configuration options.
|
||||
Object {
|
||||
/// The binary to run.
|
||||
cmd: String,
|
||||
/// The current working directory to run the command from.
|
||||
cwd: Option<String>,
|
||||
/// Arguments to pass to the command.
|
||||
args: Option<Vec<String>>,
|
||||
},
|
||||
}
|
||||
|
||||
impl Default for RunnerConfig {
|
||||
fn default() -> Self {
|
||||
RunnerConfig::String("cargo".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl RunnerConfig {
|
||||
/// Returns the command to run.
|
||||
pub fn cmd(&self) -> &str {
|
||||
match self {
|
||||
RunnerConfig::String(cmd) => cmd,
|
||||
RunnerConfig::Object { cmd, .. } => cmd,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the working directory.
|
||||
pub fn cwd(&self) -> Option<&str> {
|
||||
match self {
|
||||
RunnerConfig::String(_) => None,
|
||||
RunnerConfig::Object { cwd, .. } => cwd.as_deref(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the arguments.
|
||||
pub fn args(&self) -> Option<&[String]> {
|
||||
match self {
|
||||
RunnerConfig::String(_) => None,
|
||||
RunnerConfig::Object { args, .. } => args.as_deref(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::str::FromStr for RunnerConfig {
|
||||
type Err = std::convert::Infallible;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
Ok(RunnerConfig::String(s.to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&str> for RunnerConfig {
|
||||
fn from(s: &str) -> Self {
|
||||
RunnerConfig::String(s.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for RunnerConfig {
|
||||
fn from(s: String) -> Self {
|
||||
RunnerConfig::String(s)
|
||||
}
|
||||
}
|
||||
|
||||
/// The Build configuration object.
|
||||
///
|
||||
/// See more: <https://v2.tauri.app/reference/config/#buildconfig>
|
||||
@ -2770,7 +2841,7 @@ pub enum HookCommand {
|
||||
#[serde(rename_all = "camelCase", deny_unknown_fields)]
|
||||
pub struct BuildConfig {
|
||||
/// The binary used to build and run the application.
|
||||
pub runner: Option<String>,
|
||||
pub runner: Option<RunnerConfig>,
|
||||
/// The URL to load in development.
|
||||
///
|
||||
/// This is usually an URL to a dev server, which serves your application assets with hot-reload and HMR.
|
||||
@ -3436,11 +3507,34 @@ mod build {
|
||||
}
|
||||
}
|
||||
|
||||
impl ToTokens for RunnerConfig {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||
let prefix = quote! { ::tauri::utils::config::RunnerConfig };
|
||||
|
||||
tokens.append_all(match self {
|
||||
Self::String(cmd) => {
|
||||
let cmd = cmd.as_str();
|
||||
quote!(#prefix::String(#cmd.into()))
|
||||
}
|
||||
Self::Object { cmd, cwd, args } => {
|
||||
let cmd = cmd.as_str();
|
||||
let cwd = opt_str_lit(cwd.as_ref());
|
||||
let args = opt_lit(args.as_ref().map(|v| vec_lit(v, str_lit)).as_ref());
|
||||
quote!(#prefix::Object {
|
||||
cmd: #cmd.into(),
|
||||
cwd: #cwd,
|
||||
args: #args,
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl ToTokens for BuildConfig {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||
let dev_url = opt_lit(self.dev_url.as_ref().map(url_lit).as_ref());
|
||||
let frontend_dist = opt_lit(self.frontend_dist.as_ref());
|
||||
let runner = quote!(None);
|
||||
let runner = opt_lit(self.runner.as_ref());
|
||||
let before_dev_command = quote!(None);
|
||||
let before_build_command = quote!(None);
|
||||
let before_bundle_command = quote!(None);
|
||||
@ -3824,4 +3918,212 @@ mod test {
|
||||
assert_eq!(Color(0, 0, 0, 255), "#000000ff".parse().unwrap());
|
||||
assert_eq!(Color(0, 255, 0, 255), "#00ff00ff".parse().unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_runner_config_string_format() {
|
||||
use super::RunnerConfig;
|
||||
|
||||
// Test string format deserialization
|
||||
let json = r#""cargo""#;
|
||||
let runner: RunnerConfig = serde_json::from_str(json).unwrap();
|
||||
|
||||
assert_eq!(runner.cmd(), "cargo");
|
||||
assert_eq!(runner.cwd(), None);
|
||||
assert_eq!(runner.args(), None);
|
||||
|
||||
// Test string format serialization
|
||||
let serialized = serde_json::to_string(&runner).unwrap();
|
||||
assert_eq!(serialized, r#""cargo""#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_runner_config_object_format_full() {
|
||||
use super::RunnerConfig;
|
||||
|
||||
// Test object format with all fields
|
||||
let json = r#"{"cmd": "my_runner", "cwd": "/tmp/build", "args": ["--quiet", "--verbose"]}"#;
|
||||
let runner: RunnerConfig = serde_json::from_str(json).unwrap();
|
||||
|
||||
assert_eq!(runner.cmd(), "my_runner");
|
||||
assert_eq!(runner.cwd(), Some("/tmp/build"));
|
||||
assert_eq!(
|
||||
runner.args(),
|
||||
Some(&["--quiet".to_string(), "--verbose".to_string()][..])
|
||||
);
|
||||
|
||||
// Test object format serialization
|
||||
let serialized = serde_json::to_string(&runner).unwrap();
|
||||
let deserialized: RunnerConfig = serde_json::from_str(&serialized).unwrap();
|
||||
assert_eq!(runner, deserialized);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_runner_config_object_format_minimal() {
|
||||
use super::RunnerConfig;
|
||||
|
||||
// Test object format with only cmd field
|
||||
let json = r#"{"cmd": "cross"}"#;
|
||||
let runner: RunnerConfig = serde_json::from_str(json).unwrap();
|
||||
|
||||
assert_eq!(runner.cmd(), "cross");
|
||||
assert_eq!(runner.cwd(), None);
|
||||
assert_eq!(runner.args(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_runner_config_default() {
|
||||
use super::RunnerConfig;
|
||||
|
||||
let default_runner = RunnerConfig::default();
|
||||
assert_eq!(default_runner.cmd(), "cargo");
|
||||
assert_eq!(default_runner.cwd(), None);
|
||||
assert_eq!(default_runner.args(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_runner_config_from_str() {
|
||||
use super::RunnerConfig;
|
||||
|
||||
// Test From<&str> trait
|
||||
let runner: RunnerConfig = "my_runner".into();
|
||||
assert_eq!(runner.cmd(), "my_runner");
|
||||
assert_eq!(runner.cwd(), None);
|
||||
assert_eq!(runner.args(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_runner_config_from_string() {
|
||||
use super::RunnerConfig;
|
||||
|
||||
// Test From<String> trait
|
||||
let runner: RunnerConfig = "another_runner".to_string().into();
|
||||
assert_eq!(runner.cmd(), "another_runner");
|
||||
assert_eq!(runner.cwd(), None);
|
||||
assert_eq!(runner.args(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_runner_config_from_str_parse() {
|
||||
use super::RunnerConfig;
|
||||
use std::str::FromStr;
|
||||
|
||||
// Test FromStr trait
|
||||
let runner = RunnerConfig::from_str("parsed_runner").unwrap();
|
||||
assert_eq!(runner.cmd(), "parsed_runner");
|
||||
assert_eq!(runner.cwd(), None);
|
||||
assert_eq!(runner.args(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_runner_config_in_build_config() {
|
||||
use super::BuildConfig;
|
||||
|
||||
// Test string format in BuildConfig
|
||||
let json = r#"{"runner": "cargo"}"#;
|
||||
let build_config: BuildConfig = serde_json::from_str(json).unwrap();
|
||||
|
||||
let runner = build_config.runner.unwrap();
|
||||
assert_eq!(runner.cmd(), "cargo");
|
||||
assert_eq!(runner.cwd(), None);
|
||||
assert_eq!(runner.args(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_runner_config_in_build_config_object() {
|
||||
use super::BuildConfig;
|
||||
|
||||
// Test object format in BuildConfig
|
||||
let json = r#"{"runner": {"cmd": "cross", "cwd": "/workspace", "args": ["--target", "x86_64-unknown-linux-gnu"]}}"#;
|
||||
let build_config: BuildConfig = serde_json::from_str(json).unwrap();
|
||||
|
||||
let runner = build_config.runner.unwrap();
|
||||
assert_eq!(runner.cmd(), "cross");
|
||||
assert_eq!(runner.cwd(), Some("/workspace"));
|
||||
assert_eq!(
|
||||
runner.args(),
|
||||
Some(
|
||||
&[
|
||||
"--target".to_string(),
|
||||
"x86_64-unknown-linux-gnu".to_string()
|
||||
][..]
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_runner_config_in_full_config() {
|
||||
use super::Config;
|
||||
|
||||
// Test runner config in full Tauri config
|
||||
let json = r#"{
|
||||
"productName": "Test App",
|
||||
"version": "1.0.0",
|
||||
"identifier": "com.test.app",
|
||||
"build": {
|
||||
"runner": {
|
||||
"cmd": "my_custom_cargo",
|
||||
"cwd": "/tmp/build",
|
||||
"args": ["--quiet", "--verbose"]
|
||||
}
|
||||
}
|
||||
}"#;
|
||||
|
||||
let config: Config = serde_json::from_str(json).unwrap();
|
||||
let runner = config.build.runner.unwrap();
|
||||
|
||||
assert_eq!(runner.cmd(), "my_custom_cargo");
|
||||
assert_eq!(runner.cwd(), Some("/tmp/build"));
|
||||
assert_eq!(
|
||||
runner.args(),
|
||||
Some(&["--quiet".to_string(), "--verbose".to_string()][..])
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_runner_config_equality() {
|
||||
use super::RunnerConfig;
|
||||
|
||||
let runner1 = RunnerConfig::String("cargo".to_string());
|
||||
let runner2 = RunnerConfig::String("cargo".to_string());
|
||||
let runner3 = RunnerConfig::String("cross".to_string());
|
||||
|
||||
assert_eq!(runner1, runner2);
|
||||
assert_ne!(runner1, runner3);
|
||||
|
||||
let runner4 = RunnerConfig::Object {
|
||||
cmd: "cargo".to_string(),
|
||||
cwd: Some("/tmp".to_string()),
|
||||
args: Some(vec!["--quiet".to_string()]),
|
||||
};
|
||||
let runner5 = RunnerConfig::Object {
|
||||
cmd: "cargo".to_string(),
|
||||
cwd: Some("/tmp".to_string()),
|
||||
args: Some(vec!["--quiet".to_string()]),
|
||||
};
|
||||
|
||||
assert_eq!(runner4, runner5);
|
||||
assert_ne!(runner1, runner4);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_runner_config_untagged_serialization() {
|
||||
use super::RunnerConfig;
|
||||
|
||||
// Test that serde untagged works correctly - string should serialize as string, not object
|
||||
let string_runner = RunnerConfig::String("cargo".to_string());
|
||||
let string_json = serde_json::to_string(&string_runner).unwrap();
|
||||
assert_eq!(string_json, r#""cargo""#);
|
||||
|
||||
// Test that object serializes as object
|
||||
let object_runner = RunnerConfig::Object {
|
||||
cmd: "cross".to_string(),
|
||||
cwd: None,
|
||||
args: None,
|
||||
};
|
||||
let object_json = serde_json::to_string(&object_runner).unwrap();
|
||||
assert!(object_json.contains("\"cmd\":\"cross\""));
|
||||
// With skip_serializing_none, null values should not be included
|
||||
assert!(object_json.contains("\"cwd\":null") || !object_json.contains("cwd"));
|
||||
assert!(object_json.contains("\"args\":null") || !object_json.contains("args"));
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user