Skip to content

Commit

Permalink
feat: add support to TOML config file Tauri.toml, closes #4806 (#4813)
Browse files Browse the repository at this point in the history
  • Loading branch information
lucasfernog committed Aug 2, 2022
1 parent c04d034 commit ae83d00
Show file tree
Hide file tree
Showing 18 changed files with 350 additions and 136 deletions.
11 changes: 11 additions & 0 deletions .changes/config-toml.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
"tauri": minor
"tauri-utils": minor
"tauri-macros": minor
"tauri-codegen": minor
"tauri-build": minor
"cli.rs": minor
"cli.js": minor
---

Added support to configuration files in TOML format (Tauri.toml file).
5 changes: 5 additions & 0 deletions .changes/utils-parse-refactor.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"tauri-utils": minor
---

Refactored the `config::parse` module.
1 change: 1 addition & 0 deletions core/tauri-build/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,4 @@ semver = "1"
codegen = [ "tauri-codegen", "quote" ]
isolation = [ "tauri-codegen/isolation", "tauri-utils/isolation" ]
config-json5 = [ "tauri-utils/config-json5" ]
config-toml = [ "tauri-utils/config-toml" ]
2 changes: 2 additions & 0 deletions core/tauri-build/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,8 @@ pub fn try_build(attributes: Attributes) -> Result<()> {
println!("cargo:rerun-if-changed=tauri.conf.json");
#[cfg(feature = "config-json5")]
println!("cargo:rerun-if-changed=tauri.conf.json5");
#[cfg(feature = "config-toml")]
println!("cargo:rerun-if-changed=Tauri.toml");

let target_os = std::env::var("CARGO_CFG_TARGET_OS").unwrap();
let mobile = target_os == "ios" || target_os == "android";
Expand Down
1 change: 1 addition & 0 deletions core/tauri-codegen/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,4 @@ compression = [ "brotli", "tauri-utils/compression" ]
isolation = [ "tauri-utils/isolation" ]
shell-scope = [ "regex" ]
config-json5 = [ "tauri-utils/config-json5" ]
config-toml = [ "tauri-utils/config-toml" ]
1 change: 1 addition & 0 deletions core/tauri-macros/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,4 @@ compression = [ "tauri-codegen/compression" ]
isolation = [ "tauri-codegen/isolation" ]
shell-scope = [ "tauri-codegen/shell-scope" ]
config-json5 = [ "tauri-codegen/config-json5", "tauri-utils/config-json5" ]
config-toml = [ "tauri-codegen/config-toml", "tauri-utils/config-toml" ]
4 changes: 2 additions & 2 deletions core/tauri-macros/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use syn::{
LitStr, PathArguments, PathSegment, Token,
};
use tauri_codegen::{context_codegen, get_config, ContextData};
use tauri_utils::config::parse::does_supported_extension_exist;
use tauri_utils::config::parse::does_supported_file_name_exist;

pub(crate) struct ContextItems {
config_file: PathBuf,
Expand All @@ -36,7 +36,7 @@ impl Parse for ContextItems {
VarError::NotUnicode(_) => "CARGO_MANIFEST_DIR env var contained invalid utf8".into(),
})
.and_then(|path| {
if does_supported_extension_exist(&path) {
if does_supported_file_name_exist(&path) {
Ok(path)
} else {
Err(format!(
Expand Down
2 changes: 2 additions & 0 deletions core/tauri-utils/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ getrandom = { version = "0.2", optional = true, features = [ "std" ] }
serialize-to-javascript = { version = "=0.1.1", optional = true }
ctor = "0.1"
json5 = { version = "0.4", optional = true }
toml = { version = "0.5", optional = true }
json-patch = "0.2"
glob = { version = "0.3.0", optional = true }
walkdir = { version = "2", optional = true }
Expand Down Expand Up @@ -56,4 +57,5 @@ schema = [ "schemars" ]
isolation = [ "aes-gcm", "getrandom", "serialize-to-javascript" ]
process-relaunch-dangerous-allow-symlink-macos = [ ]
config-json5 = [ "json5" ]
config-toml = [ "toml" ]
resources = [ "glob", "walkdir" ]
180 changes: 121 additions & 59 deletions core/tauri-utils/src/config.rs

Large diffs are not rendered by default.

179 changes: 145 additions & 34 deletions core/tauri-utils/src/config/parse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,75 @@ use std::path::{Path, PathBuf};
use thiserror::Error;

/// All extensions that are possibly supported, but perhaps not enabled.
pub const EXTENSIONS_SUPPORTED: &[&str] = &["json", "json5"];
pub const EXTENSIONS_SUPPORTED: &[&str] = &["json", "json5", "toml"];

/// All extensions that are currently enabled.
pub const EXTENSIONS_ENABLED: &[&str] = &[
"json",
/// All configuration formats that are possibly supported, but perhaps not enabled.
pub const SUPPORTED_FORMATS: &[ConfigFormat] =
&[ConfigFormat::Json, ConfigFormat::Json5, ConfigFormat::Toml];

/// All configuration formats that are currently enabled.
pub const ENABLED_FORMATS: &[ConfigFormat] = &[
ConfigFormat::Json,
#[cfg(feature = "config-json5")]
"json5",
ConfigFormat::Json5,
#[cfg(feature = "config-toml")]
ConfigFormat::Toml,
];

/// The available configuration formats.
#[derive(Debug, Copy, Clone)]
pub enum ConfigFormat {
/// The default JSON (tauri.conf.json) format.
Json,
/// The JSON5 (tauri.conf.json5) format.
Json5,
/// The TOML (Tauri.toml file) format.
Toml,
}

impl ConfigFormat {
/// Maps the config format to its file name.
pub fn into_file_name(self) -> &'static str {
match self {
Self::Json => "tauri.conf.json",
Self::Json5 => "tauri.conf.json5",
Self::Toml => "Tauri.toml",
}
}

fn into_platform_file_name(self) -> &'static str {
match self {
Self::Json => {
if cfg!(target_os = "macos") {
"tauri.macos.conf.json"
} else if cfg!(windows) {
"tauri.windows.conf.json"
} else {
"tauri.linux.conf.json"
}
}
Self::Json5 => {
if cfg!(target_os = "macos") {
"tauri.macos.conf.json5"
} else if cfg!(windows) {
"tauri.windows.conf.json5"
} else {
"tauri.linux.conf.json5"
}
}
Self::Toml => {
if cfg!(target_os = "macos") {
"Tauri.macos.toml"
} else if cfg!(windows) {
"Tauri.windows.toml"
} else {
"Tauri.linux.toml"
}
}
}
}
}

/// Represents all the errors that can happen while reading the config.
#[derive(Debug, Error)]
#[non_exhaustive]
Expand All @@ -45,7 +105,18 @@ pub enum ConfigError {
error: ::json5::Error,
},

/// Unknown file extension encountered.
/// Failed to parse from TOML.
#[cfg(feature = "config-toml")]
#[error("unable to parse toml Tauri config file at {path} because {error}")]
FormatToml {
/// The path that failed to parse into TOML.
path: PathBuf,

/// The parsing [`toml::Error`].
error: ::toml::de::Error,
},

/// Unknown config file name encountered.
#[error("unsupported format encountered {0}")]
UnsupportedFormat(String),

Expand Down Expand Up @@ -81,32 +152,21 @@ pub enum ConfigError {
///
/// [JSON Merge Patch (RFC 7396)]: https://datatracker.ietf.org/doc/html/rfc7396.
pub fn read_from(root_dir: PathBuf) -> Result<Value, ConfigError> {
let mut config: Value = parse_value(root_dir.join("tauri.conf.json"))?;
if let Some(platform_config) = read_platform(root_dir)? {
let mut config: Value = parse_value(root_dir.join("tauri.conf.json"))?.0;
if let Some((platform_config, _)) = read_platform(root_dir)? {
merge(&mut config, &platform_config);
}
Ok(config)
}

/// Gets the platform configuration file name.
pub fn get_platform_config_filename() -> &'static str {
if cfg!(target_os = "macos") {
"tauri.macos.conf.json"
} else if cfg!(windows) {
"tauri.windows.conf.json"
} else {
"tauri.linux.conf.json"
}
}

/// Reads the platform-specific configuration file from the given root directory if it exists.
///
/// Check [`read_from`] for more information.
pub fn read_platform(root_dir: PathBuf) -> Result<Option<Value>, ConfigError> {
let platform_config_path = root_dir.join(get_platform_config_filename());
if does_supported_extension_exist(&platform_config_path) {
let platform_config: Value = parse_value(platform_config_path)?;
Ok(Some(platform_config))
pub fn read_platform(root_dir: PathBuf) -> Result<Option<(Value, PathBuf)>, ConfigError> {
let platform_config_path = root_dir.join(ConfigFormat::Json.into_platform_file_name());
if does_supported_file_name_exist(&platform_config_path) {
let (platform_config, path): (Value, PathBuf) = parse_value(platform_config_path)?;
Ok(Some((platform_config, path)))
} else {
Ok(None)
}
Expand All @@ -116,11 +176,21 @@ pub fn read_platform(root_dir: PathBuf) -> Result<Option<Value>, ConfigError> {
///
/// The passed path is expected to be the path to the "default" configuration format, in this case
/// JSON with `.json`.
pub fn does_supported_extension_exist(path: impl Into<PathBuf>) -> bool {
pub fn does_supported_file_name_exist(path: impl Into<PathBuf>) -> bool {
let path = path.into();
EXTENSIONS_ENABLED
let source_file_name = path.file_name().unwrap().to_str().unwrap();
let lookup_platform_config = ENABLED_FORMATS
.iter()
.any(|ext| path.with_extension(ext).exists())
.any(|format| source_file_name == format.into_platform_file_name());
ENABLED_FORMATS.iter().any(|format| {
path
.with_file_name(if lookup_platform_config {
format.into_platform_file_name()
} else {
format.into_file_name()
})
.exists()
})
}

/// Parse the config from path, including alternative formats.
Expand All @@ -133,18 +203,39 @@ pub fn does_supported_extension_exist(path: impl Into<PathBuf>) -> bool {
/// 2. Check if `tauri.conf.json5` exists
/// a. Parse it with `json5`
/// b. Return error if all above steps failed
/// 3. Return error if all above steps failed
pub fn parse(path: impl Into<PathBuf>) -> Result<Config, ConfigError> {
/// 3. Check if `Tauri.json` exists
/// a. Parse it with `toml`
/// b. Return error if all above steps failed
/// 4. Return error if all above steps failed
pub fn parse(path: impl Into<PathBuf>) -> Result<(Config, PathBuf), ConfigError> {
do_parse(path.into())
}

/// See [`parse`] for specifics, returns a JSON [`Value`] instead of [`Config`].
pub fn parse_value(path: impl Into<PathBuf>) -> Result<Value, ConfigError> {
pub fn parse_value(path: impl Into<PathBuf>) -> Result<(Value, PathBuf), ConfigError> {
do_parse(path.into())
}

fn do_parse<D: DeserializeOwned>(path: PathBuf) -> Result<D, ConfigError> {
let json5 = path.with_extension("json5");
fn do_parse<D: DeserializeOwned>(path: PathBuf) -> Result<(D, PathBuf), ConfigError> {
let file_name = path
.file_name()
.map(OsStr::to_string_lossy)
.unwrap_or_default();
let lookup_platform_config = ENABLED_FORMATS
.iter()
.any(|format| file_name == format.into_platform_file_name());

let json5 = path.with_file_name(if lookup_platform_config {
ConfigFormat::Json5.into_platform_file_name()
} else {
ConfigFormat::Json5.into_file_name()
});
let toml = path.with_file_name(if lookup_platform_config {
ConfigFormat::Toml.into_platform_file_name()
} else {
ConfigFormat::Toml.into_file_name()
});

let path_ext = path
.extension()
.map(OsStr::to_string_lossy)
Expand All @@ -171,19 +262,31 @@ fn do_parse<D: DeserializeOwned>(path: PathBuf) -> Result<D, ConfigError> {
}
};

json
json.map(|j| (j, path))
} else if json5.exists() {
#[cfg(feature = "config-json5")]
{
let raw = read_to_string(&json5)?;
do_parse_json5(&raw, &path)
do_parse_json5(&raw, &path).map(|config| (config, json5))
}

#[cfg(not(feature = "config-json5"))]
Err(ConfigError::DisabledFormat {
extension: ".json5".into(),
feature: "config-json5".into(),
})
} else if toml.exists() {
#[cfg(feature = "config-toml")]
{
let raw = read_to_string(&toml)?;
do_parse_toml(&raw, &path).map(|config| (config, toml))
}

#[cfg(not(feature = "config-toml"))]
Err(ConfigError::DisabledFormat {
extension: ".toml".into(),
feature: "config-toml".into(),
})
} else if !EXTENSIONS_SUPPORTED.contains(&path_ext.as_ref()) {
Err(ConfigError::UnsupportedFormat(path_ext.to_string()))
} else {
Expand Down Expand Up @@ -241,6 +344,14 @@ fn do_parse_json5<D: DeserializeOwned>(raw: &str, path: &Path) -> Result<D, Conf
})
}

#[cfg(feature = "config-toml")]
fn do_parse_toml<D: DeserializeOwned>(raw: &str, path: &Path) -> Result<D, ConfigError> {
::toml::from_str(raw).map_err(|error| ConfigError::FormatToml {
path: path.into(),
error,
})
}

/// Helper function to wrap IO errors from [`std::fs::read_to_string`] into a [`ConfigError`].
fn read_to_string(path: &Path) -> Result<String, ConfigError> {
std::fs::read_to_string(path).map_err(|error| ConfigError::Io {
Expand Down
1 change: 1 addition & 0 deletions core/tauri/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,7 @@ window-set-cursor-position = [ ]
window-start-dragging = [ ]
window-print = [ ]
config-json5 = [ "tauri-macros/config-json5" ]
config-toml = [ "tauri-macros/config-toml" ]
icon-ico = [ "infer", "ico" ]
icon-png = [ "infer", "png" ]

Expand Down
1 change: 1 addition & 0 deletions core/tauri/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
//! - **window-data-url**: Enables usage of data URLs on the webview.
//! - **compression** *(enabled by default): Enables asset compression. You should only disable this if you want faster compile times in release builds - it produces larger binaries.
//! - **config-json5**: Adds support to JSON5 format for `tauri.conf.json`.
//! - **config-toml**: Adds support to TOML format for the configuration `Tauri.toml`.
//! - **icon-ico**: Adds support to set `.ico` window icons. Enables [`Icon::File`] and [`Icon::Raw`] variants.
//! - **icon-png**: Adds support to set `.png` window icons. Enables [`Icon::File`] and [`Icon::Raw`] variants.
//!
Expand Down
1 change: 1 addition & 0 deletions tooling/cli/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion tooling/cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ notify = "4.0"
shared_child = "1.0"
toml_edit = "0.14"
json-patch = "0.2"
tauri-utils = { version = "1.0.3", path = "../../core/tauri-utils", features = [ "isolation", "schema", "config-json5" ] }
tauri-utils = { version = "1.0.3", path = "../../core/tauri-utils", features = [ "isolation", "schema", "config-json5", "config-toml" ] }
toml = "0.5"
valico = "3.6"
handlebars = "4.3"
Expand Down
4 changes: 2 additions & 2 deletions tooling/cli/schema.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "Config",
"description": "The tauri.conf.json is a file generated by the [`tauri init`](https://tauri.app/v1/api/cli#init) command that lives in your Tauri application source directory (src-tauri).\n\nOnce generated, you may modify it at will to customize your Tauri application.\n\n## Platform-Specific Configuration\n\nIn addition to the JSON defined on the `tauri.conf.json` file, Tauri can read a platform-specific configuration from `tauri.linux.conf.json`, `tauri.windows.conf.json`, and `tauri.macos.conf.json` and merges it with the main `tauri.conf.json` configuration.\n\n## Configuration Structure\n\n`tauri.conf.json` is composed of the following objects:\n\n- [`package`](#packageconfig): Package settings - [`tauri`](#tauriconfig): The Tauri config - [`build`](#buildconfig): The build configuration - [`plugins`](#pluginconfig): The plugins config\n\n```json title=\"Example tauri.config.json file\" { \"build\": { \"beforeBuildCommand\": \"\", \"beforeDevCommand\": \"\", \"devPath\": \"../dist\", \"distDir\": \"../dist\" }, \"package\": { \"productName\": \"tauri-app\", \"version\": \"0.1.0\" }, \"tauri\": { \"allowlist\": { \"all\": true }, \"bundle\": {}, \"security\": { \"csp\": null }, \"updater\": { \"active\": false }, \"windows\": [ { \"fullscreen\": false, \"height\": 600, \"resizable\": true, \"title\": \"Tauri App\", \"width\": 800 } ] } } ```",
"description": "The Tauri configuration object. It is read from a file where you can define your frontend assets, configure the bundler, enable the app updater, define a system tray, enable APIs via the allowlist and more.\n\nThe configuration file is generated by the [`tauri init`](https://tauri.app/v1/api/cli#init) command that lives in your Tauri application source directory (src-tauri).\n\nOnce generated, you may modify it at will to customize your Tauri application.\n\n## File Formats\n\nBy default, the configuration is defined as a JSON file named `tauri.conf.json`.\n\nTauri also supports JSON5 and TOML files via the `config-json5` and `config-toml` Cargo features, respectively. The JSON5 file name must be either `tauri.conf.json` or `tauri.conf.json5`. The TOML file name is `Tauri.toml`.\n\n## Platform-Specific Configuration\n\nIn addition to the default configuration file, Tauri can read a platform-specific configuration from `tauri.linux.conf.json`, `tauri.windows.conf.json`, and `tauri.macos.conf.json` (or `Tauri.linux.toml`, `Tauri.windows.toml` and `Tauri.macos.toml` if the `Tauri.toml` format is used), which gets merged with the main configuration object.\n\n## Configuration Structure\n\nThe configuration is composed of the following objects:\n\n- [`package`](#packageconfig): Package settings - [`tauri`](#tauriconfig): The Tauri config - [`build`](#buildconfig): The build configuration - [`plugins`](#pluginconfig): The plugins config\n\n```json title=\"Example tauri.config.json file\" { \"build\": { \"beforeBuildCommand\": \"\", \"beforeDevCommand\": \"\", \"devPath\": \"../dist\", \"distDir\": \"../dist\" }, \"package\": { \"productName\": \"tauri-app\", \"version\": \"0.1.0\" }, \"tauri\": { \"allowlist\": { \"all\": true }, \"bundle\": {}, \"security\": { \"csp\": null }, \"updater\": { \"active\": false }, \"windows\": [ { \"fullscreen\": false, \"height\": 600, \"resizable\": true, \"title\": \"Tauri App\", \"width\": 800 } ] } } ```",
"type": "object",
"properties": {
"$schema": {
Expand Down Expand Up @@ -587,7 +587,7 @@
"type": "boolean"
},
"transparent": {
"description": "Whether the window is transparent or not.\n\nNote that on `macOS` this requires the `macos-private-api` feature flag, enabled under `tauri.conf.json > tauri > macOSPrivateApi`. WARNING: Using private APIs on `macOS` prevents your application from being accepted to the `App Store`.",
"description": "Whether the window is transparent or not.\n\nNote that on `macOS` this requires the `macos-private-api` feature flag, enabled under `tauri > macOSPrivateApi`. WARNING: Using private APIs on `macOS` prevents your application from being accepted to the `App Store`.",
"default": false,
"type": "boolean"
},
Expand Down
6 changes: 2 additions & 4 deletions tooling/cli/src/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,10 +84,8 @@ pub fn command(mut options: Options) -> Result<()> {
let config_ = config_guard.as_ref().unwrap();

let bundle_identifier_source = match config_.find_bundle_identifier_overwriter() {
Some(source) if source == MERGE_CONFIG_EXTENSION_NAME => {
merge_config_path.unwrap_or_else(|| source.into())
}
Some(source) => source.into(),
Some(source) if source == MERGE_CONFIG_EXTENSION_NAME => merge_config_path.unwrap_or(source),
Some(source) => source,
None => "tauri.conf.json".into(),
};

Expand Down

0 comments on commit ae83d00

Please sign in to comment.