mirror of
https://github.com/tauri-apps/tauri.git
synced 2024-11-28 20:48:52 +03:00
feat(cli): enhance Cargo features injection, add tests (#7141)
This commit is contained in:
parent
b41b57ebb2
commit
52474e479d
5
.changes/build-enhance-features-check.md
Normal file
5
.changes/build-enhance-features-check.md
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
"tauri-build": patch
|
||||
---
|
||||
|
||||
Enhance Cargo features check.
|
6
.changes/enhance-allowlist-injection.md
Normal file
6
.changes/enhance-allowlist-injection.md
Normal file
@ -0,0 +1,6 @@
|
||||
---
|
||||
"tauri-cli": patch
|
||||
"@tauri-apps/cli": patch
|
||||
---
|
||||
|
||||
Enhance injection of Cargo features.
|
211
core/tauri-build/src/allowlist.rs
Normal file
211
core/tauri-build/src/allowlist.rs
Normal file
@ -0,0 +1,211 @@
|
||||
// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
use anyhow::{anyhow, Result};
|
||||
use cargo_toml::{Dependency, Manifest};
|
||||
use tauri_utils::config::{Config, PatternKind, TauriConfig};
|
||||
|
||||
#[derive(Debug, Default, PartialEq, Eq)]
|
||||
struct Diff {
|
||||
remove: Vec<String>,
|
||||
add: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
enum DependencyKind {
|
||||
Build,
|
||||
Normal,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct AllowlistedDependency {
|
||||
name: String,
|
||||
alias: Option<String>,
|
||||
kind: DependencyKind,
|
||||
all_cli_managed_features: Option<Vec<&'static str>>,
|
||||
expected_features: Vec<String>,
|
||||
}
|
||||
|
||||
pub fn check(config: &Config, manifest: &mut Manifest) -> Result<()> {
|
||||
let dependencies = vec![
|
||||
AllowlistedDependency {
|
||||
name: "tauri-build".into(),
|
||||
alias: None,
|
||||
kind: DependencyKind::Build,
|
||||
all_cli_managed_features: Some(vec!["isolation"]),
|
||||
expected_features: match config.tauri.pattern {
|
||||
PatternKind::Isolation { .. } => vec!["isolation".to_string()],
|
||||
_ => vec![],
|
||||
},
|
||||
},
|
||||
AllowlistedDependency {
|
||||
name: "tauri".into(),
|
||||
alias: None,
|
||||
kind: DependencyKind::Normal,
|
||||
all_cli_managed_features: Some(TauriConfig::all_features()),
|
||||
expected_features: config
|
||||
.tauri
|
||||
.features()
|
||||
.into_iter()
|
||||
.map(|f| f.to_string())
|
||||
.collect::<Vec<String>>(),
|
||||
},
|
||||
];
|
||||
|
||||
for metadata in dependencies {
|
||||
let mut name = metadata.name.clone();
|
||||
let mut deps = find_dependency(manifest, &metadata.name, metadata.kind);
|
||||
if deps.is_empty() {
|
||||
if let Some(alias) = &metadata.alias {
|
||||
deps = find_dependency(manifest, alias, metadata.kind);
|
||||
name = alias.clone();
|
||||
}
|
||||
}
|
||||
|
||||
for dep in deps {
|
||||
if let Err(error) = check_features(dep, &metadata) {
|
||||
return Err(anyhow!("
|
||||
The `{}` dependency features on the `Cargo.toml` file does not match the allowlist defined under `tauri.conf.json`.
|
||||
Please run `tauri dev` or `tauri build` or {}.
|
||||
", name, error));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn find_dependency(manifest: &mut Manifest, name: &str, kind: DependencyKind) -> Vec<Dependency> {
|
||||
let dep = match kind {
|
||||
DependencyKind::Build => manifest.build_dependencies.remove(name),
|
||||
DependencyKind::Normal => manifest.dependencies.remove(name),
|
||||
};
|
||||
|
||||
if let Some(dep) = dep {
|
||||
vec![dep]
|
||||
} else {
|
||||
let mut deps = Vec::new();
|
||||
for target in manifest.target.values_mut() {
|
||||
if let Some(dep) = match kind {
|
||||
DependencyKind::Build => target.build_dependencies.remove(name),
|
||||
DependencyKind::Normal => target.dependencies.remove(name),
|
||||
} {
|
||||
deps.push(dep);
|
||||
}
|
||||
}
|
||||
deps
|
||||
}
|
||||
}
|
||||
|
||||
fn features_diff(current: &[String], expected: &[String]) -> Diff {
|
||||
let mut remove = Vec::new();
|
||||
let mut add = Vec::new();
|
||||
for feature in current {
|
||||
if !expected.contains(feature) {
|
||||
remove.push(feature.clone());
|
||||
}
|
||||
}
|
||||
|
||||
for feature in expected {
|
||||
if !current.contains(feature) {
|
||||
add.push(feature.clone());
|
||||
}
|
||||
}
|
||||
|
||||
Diff { remove, add }
|
||||
}
|
||||
|
||||
fn check_features(dependency: Dependency, metadata: &AllowlistedDependency) -> Result<(), String> {
|
||||
let features = match dependency {
|
||||
Dependency::Simple(_) => Vec::new(),
|
||||
Dependency::Detailed(dep) => dep.features,
|
||||
Dependency::Inherited(dep) => dep.features,
|
||||
};
|
||||
|
||||
let diff = if let Some(all_cli_managed_features) = &metadata.all_cli_managed_features {
|
||||
features_diff(
|
||||
&features
|
||||
.into_iter()
|
||||
.filter(|f| all_cli_managed_features.contains(&f.as_str()))
|
||||
.collect::<Vec<String>>(),
|
||||
&metadata.expected_features,
|
||||
)
|
||||
} else {
|
||||
features_diff(
|
||||
&features
|
||||
.into_iter()
|
||||
.filter(|f| f.starts_with("allow-"))
|
||||
.collect::<Vec<String>>(),
|
||||
&metadata.expected_features,
|
||||
)
|
||||
};
|
||||
|
||||
let mut error_message = String::new();
|
||||
if !diff.remove.is_empty() {
|
||||
error_message.push_str("remove the `");
|
||||
error_message.push_str(&diff.remove.join(", "));
|
||||
error_message.push_str(if diff.remove.len() == 1 {
|
||||
"` feature"
|
||||
} else {
|
||||
"` features"
|
||||
});
|
||||
if !diff.add.is_empty() {
|
||||
error_message.push_str(" and ");
|
||||
}
|
||||
}
|
||||
if !diff.add.is_empty() {
|
||||
error_message.push_str("add the `");
|
||||
error_message.push_str(&diff.add.join(", "));
|
||||
error_message.push_str(if diff.add.len() == 1 {
|
||||
"` feature"
|
||||
} else {
|
||||
"` features"
|
||||
});
|
||||
}
|
||||
|
||||
if error_message.is_empty() {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(error_message)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Diff;
|
||||
|
||||
#[test]
|
||||
fn array_diff() {
|
||||
for (current, expected, result) in [
|
||||
(vec![], vec![], Default::default()),
|
||||
(
|
||||
vec!["a".into()],
|
||||
vec![],
|
||||
Diff {
|
||||
remove: vec!["a".into()],
|
||||
add: vec![],
|
||||
},
|
||||
),
|
||||
(vec!["a".into()], vec!["a".into()], Default::default()),
|
||||
(
|
||||
vec!["a".into(), "b".into()],
|
||||
vec!["a".into()],
|
||||
Diff {
|
||||
remove: vec!["b".into()],
|
||||
add: vec![],
|
||||
},
|
||||
),
|
||||
(
|
||||
vec!["a".into(), "b".into()],
|
||||
vec!["a".into(), "c".into()],
|
||||
Diff {
|
||||
remove: vec!["b".into()],
|
||||
add: vec!["c".into()],
|
||||
},
|
||||
),
|
||||
] {
|
||||
assert_eq!(super::features_diff(¤t, &expected), result);
|
||||
}
|
||||
}
|
||||
}
|
@ -5,7 +5,7 @@
|
||||
#![cfg_attr(doc_cfg, feature(doc_cfg))]
|
||||
|
||||
pub use anyhow::Result;
|
||||
use cargo_toml::{Dependency, Manifest};
|
||||
use cargo_toml::Manifest;
|
||||
use heck::AsShoutySnakeCase;
|
||||
|
||||
use tauri_utils::{
|
||||
@ -15,6 +15,7 @@ use tauri_utils::{
|
||||
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
mod allowlist;
|
||||
#[cfg(feature = "codegen")]
|
||||
mod codegen;
|
||||
mod static_vcruntime;
|
||||
@ -320,27 +321,7 @@ pub fn try_build(attributes: Attributes) -> Result<()> {
|
||||
Manifest::complete_from_path(&mut manifest, Path::new("Cargo.toml"))?;
|
||||
}
|
||||
|
||||
if let Some(tauri_build) = manifest.build_dependencies.remove("tauri-build") {
|
||||
let error_message = check_features(&config, tauri_build, true);
|
||||
|
||||
if !error_message.is_empty() {
|
||||
return Err(anyhow!("
|
||||
The `tauri-build` dependency features on the `Cargo.toml` file does not match the allowlist defined under `tauri.conf.json`.
|
||||
Please run `tauri dev` or `tauri build` or {}.
|
||||
", error_message));
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(tauri) = manifest.dependencies.remove("tauri") {
|
||||
let error_message = check_features(&config, tauri, false);
|
||||
|
||||
if !error_message.is_empty() {
|
||||
return Err(anyhow!("
|
||||
The `tauri` dependency features on the `Cargo.toml` file does not match the allowlist defined under `tauri.conf.json`.
|
||||
Please run `tauri dev` or `tauri build` or {}.
|
||||
", error_message));
|
||||
}
|
||||
}
|
||||
allowlist::check(&config, &mut manifest)?;
|
||||
|
||||
let target_triple = std::env::var("TARGET").unwrap();
|
||||
let out_dir = PathBuf::from(std::env::var("OUT_DIR").unwrap());
|
||||
@ -487,93 +468,6 @@ pub fn try_build(attributes: Attributes) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, PartialEq, Eq)]
|
||||
struct Diff {
|
||||
remove: Vec<String>,
|
||||
add: Vec<String>,
|
||||
}
|
||||
|
||||
fn features_diff(current: &[String], expected: &[String]) -> Diff {
|
||||
let mut remove = Vec::new();
|
||||
let mut add = Vec::new();
|
||||
for feature in current {
|
||||
if !expected.contains(feature) {
|
||||
remove.push(feature.clone());
|
||||
}
|
||||
}
|
||||
|
||||
for feature in expected {
|
||||
if !current.contains(feature) {
|
||||
add.push(feature.clone());
|
||||
}
|
||||
}
|
||||
|
||||
Diff { remove, add }
|
||||
}
|
||||
|
||||
fn check_features(config: &Config, dependency: Dependency, is_tauri_build: bool) -> String {
|
||||
use tauri_utils::config::{PatternKind, TauriConfig};
|
||||
|
||||
let features = match dependency {
|
||||
Dependency::Simple(_) => Vec::new(),
|
||||
Dependency::Detailed(dep) => dep.features,
|
||||
Dependency::Inherited(dep) => dep.features,
|
||||
};
|
||||
|
||||
let all_cli_managed_features = if is_tauri_build {
|
||||
vec!["isolation"]
|
||||
} else {
|
||||
TauriConfig::all_features()
|
||||
};
|
||||
|
||||
let expected = if is_tauri_build {
|
||||
match config.tauri.pattern {
|
||||
PatternKind::Isolation { .. } => vec!["isolation".to_string()],
|
||||
_ => vec![],
|
||||
}
|
||||
} else {
|
||||
config
|
||||
.tauri
|
||||
.features()
|
||||
.into_iter()
|
||||
.map(|f| f.to_string())
|
||||
.collect::<Vec<String>>()
|
||||
};
|
||||
|
||||
let diff = features_diff(
|
||||
&features
|
||||
.into_iter()
|
||||
.filter(|f| all_cli_managed_features.contains(&f.as_str()))
|
||||
.collect::<Vec<String>>(),
|
||||
&expected,
|
||||
);
|
||||
|
||||
let mut error_message = String::new();
|
||||
if !diff.remove.is_empty() {
|
||||
error_message.push_str("remove the `");
|
||||
error_message.push_str(&diff.remove.join(", "));
|
||||
error_message.push_str(if diff.remove.len() == 1 {
|
||||
"` feature"
|
||||
} else {
|
||||
"` features"
|
||||
});
|
||||
if !diff.add.is_empty() {
|
||||
error_message.push_str(" and ");
|
||||
}
|
||||
}
|
||||
if !diff.add.is_empty() {
|
||||
error_message.push_str("add the `");
|
||||
error_message.push_str(&diff.add.join(", "));
|
||||
error_message.push_str(if diff.add.len() == 1 {
|
||||
"` feature"
|
||||
} else {
|
||||
"` features"
|
||||
});
|
||||
}
|
||||
|
||||
error_message
|
||||
}
|
||||
|
||||
#[derive(serde::Deserialize)]
|
||||
struct CargoMetadata {
|
||||
workspace_root: PathBuf,
|
||||
@ -593,42 +487,3 @@ fn get_workspace_dir() -> Result<PathBuf> {
|
||||
|
||||
Ok(serde_json::from_slice::<CargoMetadata>(&output.stdout)?.workspace_root)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Diff;
|
||||
|
||||
#[test]
|
||||
fn array_diff() {
|
||||
for (current, expected, result) in [
|
||||
(vec![], vec![], Default::default()),
|
||||
(
|
||||
vec!["a".into()],
|
||||
vec![],
|
||||
Diff {
|
||||
remove: vec!["a".into()],
|
||||
add: vec![],
|
||||
},
|
||||
),
|
||||
(vec!["a".into()], vec!["a".into()], Default::default()),
|
||||
(
|
||||
vec!["a".into(), "b".into()],
|
||||
vec!["a".into()],
|
||||
Diff {
|
||||
remove: vec!["b".into()],
|
||||
add: vec![],
|
||||
},
|
||||
),
|
||||
(
|
||||
vec!["a".into(), "b".into()],
|
||||
vec!["a".into(), "c".into()],
|
||||
Diff {
|
||||
remove: vec!["b".into()],
|
||||
add: vec!["c".into()],
|
||||
},
|
||||
),
|
||||
] {
|
||||
assert_eq!(super::features_diff(¤t, &expected), result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2434,7 +2434,6 @@ pub struct TauriConfig {
|
||||
|
||||
impl TauriConfig {
|
||||
/// Returns all Cargo features.
|
||||
#[allow(dead_code)]
|
||||
pub fn all_features() -> Vec<&'static str> {
|
||||
let mut features = AllowlistConfig::all_features();
|
||||
features.extend(vec![
|
||||
@ -2448,7 +2447,6 @@ impl TauriConfig {
|
||||
}
|
||||
|
||||
/// Returns the enabled Cargo features.
|
||||
#[allow(dead_code)]
|
||||
pub fn features(&self) -> Vec<&str> {
|
||||
let mut features = self.allowlist.to_features();
|
||||
if self.cli.is_some() {
|
||||
|
@ -10,7 +10,7 @@ use crate::helpers::{
|
||||
use anyhow::Context;
|
||||
use itertools::Itertools;
|
||||
use log::info;
|
||||
use toml_edit::{Array, Document, InlineTable, Item, Table, TableLike, Value};
|
||||
use toml_edit::{Array, Document, InlineTable, Item, TableLike, Value};
|
||||
|
||||
use std::{
|
||||
collections::{HashMap, HashSet},
|
||||
@ -108,31 +108,55 @@ fn toml_array(features: &HashSet<String>) -> Array {
|
||||
f
|
||||
}
|
||||
|
||||
fn write_features(
|
||||
dependencies: &mut Table,
|
||||
dependency_name: &str,
|
||||
all_features: Vec<&str>,
|
||||
features: &mut HashSet<String>,
|
||||
) -> crate::Result<bool> {
|
||||
let item = dependencies.entry(dependency_name).or_insert(Item::None);
|
||||
fn find_dependency<'a>(
|
||||
manifest: &'a mut Document,
|
||||
name: &'a str,
|
||||
kind: DependencyKind,
|
||||
) -> Vec<&'a mut Item> {
|
||||
let table = match kind {
|
||||
DependencyKind::Build => "build-dependencies",
|
||||
DependencyKind::Normal => "dependencies",
|
||||
};
|
||||
|
||||
// do not rewrite if dependency uses workspace inheritance
|
||||
if item
|
||||
.get("workspace")
|
||||
.and_then(|v| v.as_bool())
|
||||
.unwrap_or_default()
|
||||
{
|
||||
info!("`{dependency_name}` dependency has workspace inheritance enabled. The features array won't be automatically rewritten. Expected features: [{}]", features.iter().join(", "));
|
||||
return Ok(false);
|
||||
let m = manifest.as_table_mut();
|
||||
for (k, v) in m.iter_mut() {
|
||||
if let Some(t) = v.as_table_mut() {
|
||||
if k == table {
|
||||
if let Some(item) = t.get_mut(name) {
|
||||
return vec![item];
|
||||
}
|
||||
} else if k == "target" {
|
||||
let mut matching_deps = Vec::new();
|
||||
for (_, target_value) in t.iter_mut() {
|
||||
if let Some(target_table) = target_value.as_table_mut() {
|
||||
if let Some(deps) = target_table.get_mut(table) {
|
||||
if let Some(item) = deps.as_table_mut().and_then(|t| t.get_mut(name)) {
|
||||
matching_deps.push(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return matching_deps;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Vec::new()
|
||||
}
|
||||
|
||||
fn write_features<F: Fn(&str) -> bool>(
|
||||
dependency_name: &str,
|
||||
item: &mut Item,
|
||||
is_managed_feature: F,
|
||||
features: &mut HashSet<String>,
|
||||
) -> crate::Result<bool> {
|
||||
if let Some(dep) = item.as_table_mut() {
|
||||
inject_features_table(dep, &all_features, features);
|
||||
inject_features_table(dep, is_managed_feature, features);
|
||||
Ok(true)
|
||||
} else if let Some(dep) = item.as_value_mut() {
|
||||
match dep {
|
||||
Value::InlineTable(table) => {
|
||||
inject_features_table(table, &all_features, features);
|
||||
inject_features_table(table, is_managed_feature, features);
|
||||
}
|
||||
Value::String(version) => {
|
||||
let mut def = InlineTable::default();
|
||||
@ -153,16 +177,30 @@ fn write_features(
|
||||
}
|
||||
}
|
||||
|
||||
fn inject_features_table<D: TableLike>(
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
enum DependencyKind {
|
||||
Build,
|
||||
Normal,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct DependencyAllowlist {
|
||||
name: String,
|
||||
kind: DependencyKind,
|
||||
all_cli_managed_features: Vec<&'static str>,
|
||||
features: HashSet<String>,
|
||||
}
|
||||
|
||||
fn inject_features_table<D: TableLike, F: Fn(&str) -> bool>(
|
||||
dep: &mut D,
|
||||
all_features: &[&str],
|
||||
is_managed_feature: F,
|
||||
features: &mut HashSet<String>,
|
||||
) {
|
||||
let manifest_features = dep.entry("features").or_insert(Item::None);
|
||||
if let Item::Value(Value::Array(f)) = &manifest_features {
|
||||
for feat in f.iter() {
|
||||
if let Value::String(feature) = feat {
|
||||
if !all_features.contains(&feature.value().as_str()) {
|
||||
if !is_managed_feature(feature.value().as_str()) {
|
||||
features.insert(feature.value().to_string());
|
||||
}
|
||||
}
|
||||
@ -192,71 +230,275 @@ fn inject_features_table<D: TableLike>(
|
||||
}
|
||||
}
|
||||
|
||||
fn inject_features(
|
||||
manifest: &mut Document,
|
||||
dependencies: &mut Vec<DependencyAllowlist>,
|
||||
) -> crate::Result<bool> {
|
||||
let mut persist = false;
|
||||
for dependency in dependencies {
|
||||
let name = dependency.name.clone();
|
||||
let items = find_dependency(manifest, &dependency.name, dependency.kind);
|
||||
|
||||
for item in items {
|
||||
// do not rewrite if dependency uses workspace inheritance
|
||||
if item
|
||||
.get("workspace")
|
||||
.and_then(|v| v.as_bool())
|
||||
.unwrap_or_default()
|
||||
{
|
||||
info!("`{name}` dependency has workspace inheritance enabled. The features array won't be automatically rewritten. Expected features: [{}]", dependency.features.iter().join(", "));
|
||||
} else {
|
||||
let all_cli_managed_features = dependency.all_cli_managed_features.clone();
|
||||
let is_managed_feature: Box<dyn Fn(&str) -> bool> =
|
||||
Box::new(move |feature| all_cli_managed_features.contains(&feature));
|
||||
|
||||
let should_write =
|
||||
write_features(&name, item, is_managed_feature, &mut dependency.features)?;
|
||||
|
||||
if !persist {
|
||||
persist = should_write;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(persist)
|
||||
}
|
||||
|
||||
pub fn rewrite_manifest(config: &Config) -> crate::Result<Manifest> {
|
||||
let manifest_path = tauri_dir().join("Cargo.toml");
|
||||
let mut manifest = read_manifest(&manifest_path)?;
|
||||
|
||||
let mut dependencies = Vec::new();
|
||||
|
||||
// tauri-build
|
||||
let mut tauri_build_features = HashSet::new();
|
||||
if let PatternKind::Isolation { .. } = config.tauri.pattern {
|
||||
tauri_build_features.insert("isolation".to_string());
|
||||
}
|
||||
let resp = write_features(
|
||||
manifest
|
||||
.as_table_mut()
|
||||
.entry("build-dependencies")
|
||||
.or_insert(Item::Table(Table::new()))
|
||||
.as_table_mut()
|
||||
.expect("manifest build-dependencies isn't a table"),
|
||||
"tauri-build",
|
||||
vec!["isolation"],
|
||||
&mut tauri_build_features,
|
||||
)?;
|
||||
dependencies.push(DependencyAllowlist {
|
||||
name: "tauri-build".into(),
|
||||
kind: DependencyKind::Build,
|
||||
all_cli_managed_features: vec!["isolation"],
|
||||
features: tauri_build_features,
|
||||
});
|
||||
|
||||
let mut tauri_features =
|
||||
// tauri
|
||||
let tauri_features =
|
||||
HashSet::from_iter(config.tauri.features().into_iter().map(|f| f.to_string()));
|
||||
let cli_managed_tauri_features = crate::helpers::config::TauriConfig::all_features();
|
||||
let res = match write_features(
|
||||
manifest
|
||||
.as_table_mut()
|
||||
.entry("dependencies")
|
||||
.or_insert(Item::Table(Table::new()))
|
||||
.as_table_mut()
|
||||
.expect("manifest dependencies isn't a table"),
|
||||
"tauri",
|
||||
cli_managed_tauri_features,
|
||||
&mut tauri_features,
|
||||
) {
|
||||
Err(e) => Err(e),
|
||||
Ok(t) if !resp => Ok(t),
|
||||
_ => Ok(true),
|
||||
};
|
||||
dependencies.push(DependencyAllowlist {
|
||||
name: "tauri".into(),
|
||||
kind: DependencyKind::Normal,
|
||||
all_cli_managed_features: crate::helpers::config::TauriConfig::all_features(),
|
||||
features: tauri_features,
|
||||
});
|
||||
|
||||
match res {
|
||||
Ok(true) => {
|
||||
let mut manifest_file =
|
||||
File::create(&manifest_path).with_context(|| "failed to open Cargo.toml for rewrite")?;
|
||||
manifest_file.write_all(
|
||||
manifest
|
||||
.to_string()
|
||||
// apply some formatting fixes
|
||||
.replace(r#"" ,features =["#, r#"", features = ["#)
|
||||
.replace(r#"" , features"#, r#"", features"#)
|
||||
.replace("]}", "] }")
|
||||
.replace("={", "= {")
|
||||
.replace("=[", "= [")
|
||||
.replace(r#"",""#, r#"", ""#)
|
||||
.as_bytes(),
|
||||
)?;
|
||||
manifest_file.flush()?;
|
||||
Ok(Manifest {
|
||||
inner: manifest,
|
||||
tauri_features,
|
||||
})
|
||||
}
|
||||
Ok(false) => Ok(Manifest {
|
||||
let persist = inject_features(&mut manifest, &mut dependencies)?;
|
||||
|
||||
let tauri_features = dependencies
|
||||
.into_iter()
|
||||
.find(|d| d.name == "tauri")
|
||||
.unwrap()
|
||||
.features;
|
||||
|
||||
if persist {
|
||||
let mut manifest_file =
|
||||
File::create(&manifest_path).with_context(|| "failed to open Cargo.toml for rewrite")?;
|
||||
manifest_file.write_all(
|
||||
manifest
|
||||
.to_string()
|
||||
// apply some formatting fixes
|
||||
.replace(r#"" ,features =["#, r#"", features = ["#)
|
||||
.replace(r#"" , features"#, r#"", features"#)
|
||||
.replace("]}", "] }")
|
||||
.replace("={", "= {")
|
||||
.replace("=[", "= [")
|
||||
.replace(r#"",""#, r#"", ""#)
|
||||
.as_bytes(),
|
||||
)?;
|
||||
manifest_file.flush()?;
|
||||
Ok(Manifest {
|
||||
inner: manifest,
|
||||
tauri_features,
|
||||
}),
|
||||
Err(e) => Err(e),
|
||||
})
|
||||
} else {
|
||||
Ok(Manifest {
|
||||
inner: manifest,
|
||||
tauri_features,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{DependencyAllowlist, DependencyKind};
|
||||
use std::collections::{HashMap, HashSet};
|
||||
|
||||
fn inject_features(toml: &str, mut dependencies: Vec<DependencyAllowlist>) {
|
||||
let mut manifest = toml.parse::<toml_edit::Document>().expect("invalid toml");
|
||||
|
||||
let mut expected = HashMap::new();
|
||||
for dep in &dependencies {
|
||||
let mut features = dep.features.clone();
|
||||
for item in super::find_dependency(&mut manifest, &dep.name, dep.kind) {
|
||||
let item_table = if let Some(table) = item.as_table() {
|
||||
Some(table.clone())
|
||||
} else if let Some(toml_edit::Value::InlineTable(table)) = item.as_value() {
|
||||
Some(table.clone().into_table())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
if let Some(f) = item_table
|
||||
.and_then(|t| t.get("features").cloned())
|
||||
.and_then(|f| f.as_array().cloned())
|
||||
{
|
||||
for feature in f.iter() {
|
||||
let feature = feature.as_str().expect("feature is not a string");
|
||||
if !dep.all_cli_managed_features.contains(&feature) {
|
||||
features.insert(feature.into());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
expected.insert(dep.name.clone(), features);
|
||||
}
|
||||
|
||||
super::inject_features(&mut manifest, &mut dependencies).expect("failed to migrate manifest");
|
||||
|
||||
for dep in dependencies {
|
||||
let expected_features = expected.get(&dep.name).unwrap();
|
||||
for item in super::find_dependency(&mut manifest, &dep.name, dep.kind) {
|
||||
let item_table = if let Some(table) = item.as_table() {
|
||||
table.clone()
|
||||
} else if let Some(toml_edit::Value::InlineTable(table)) = item.as_value() {
|
||||
table.clone().into_table()
|
||||
} else {
|
||||
panic!("unexpected TOML item kind for {}", dep.name);
|
||||
};
|
||||
|
||||
let features_array = item_table
|
||||
.get("features")
|
||||
.expect("missing features")
|
||||
.as_array()
|
||||
.expect("features must be an array")
|
||||
.clone();
|
||||
|
||||
let mut features = Vec::new();
|
||||
for feature in features_array.iter() {
|
||||
let feature = feature.as_str().expect("feature must be a string");
|
||||
features.push(feature);
|
||||
}
|
||||
for expected in expected_features {
|
||||
assert!(
|
||||
features.contains(&expected.as_str()),
|
||||
"feature {expected} should have been injected"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn tauri_dependency(features: HashSet<String>) -> DependencyAllowlist {
|
||||
DependencyAllowlist {
|
||||
name: "tauri".into(),
|
||||
kind: DependencyKind::Normal,
|
||||
all_cli_managed_features: vec!["isolation"],
|
||||
features,
|
||||
}
|
||||
}
|
||||
|
||||
fn tauri_build_dependency(features: HashSet<String>) -> DependencyAllowlist {
|
||||
DependencyAllowlist {
|
||||
name: "tauri-build".into(),
|
||||
kind: DependencyKind::Build,
|
||||
all_cli_managed_features: crate::helpers::config::TauriConfig::all_features(),
|
||||
features,
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn inject_features_table() {
|
||||
inject_features(
|
||||
r#"
|
||||
[dependencies]
|
||||
tauri = { version = "1", features = ["dummy"] }
|
||||
|
||||
[build-dependencies]
|
||||
tauri-build = { version = "1" }
|
||||
"#,
|
||||
vec![
|
||||
tauri_dependency(HashSet::from_iter(
|
||||
crate::helpers::config::TauriConfig::all_features()
|
||||
.iter()
|
||||
.map(|f| f.to_string()),
|
||||
)),
|
||||
tauri_build_dependency(HashSet::from_iter(vec!["isolation".into()])),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn inject_features_target() {
|
||||
inject_features(
|
||||
r#"
|
||||
[target."cfg(windows)".dependencies]
|
||||
tauri = { version = "1", features = ["dummy"] }
|
||||
|
||||
[target."cfg(target_os = \"macos\")".build-dependencies]
|
||||
tauri-build = { version = "1" }
|
||||
|
||||
[target."cfg(target_os = \"linux\")".dependencies]
|
||||
tauri = { version = "1", features = ["isolation"] }
|
||||
|
||||
[target."cfg(windows)".build-dependencies]
|
||||
tauri-build = { version = "1" }
|
||||
"#,
|
||||
vec![
|
||||
tauri_dependency(Default::default()),
|
||||
tauri_build_dependency(HashSet::from_iter(vec!["isolation".into()])),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn inject_features_inline_table() {
|
||||
inject_features(
|
||||
r#"
|
||||
[dependencies.tauri]
|
||||
version = "1"
|
||||
features = ["test"]
|
||||
|
||||
[build-dependencies.tauri-build]
|
||||
version = "1"
|
||||
features = ["config-toml", "codegen", "isolation"]
|
||||
"#,
|
||||
vec![
|
||||
tauri_dependency(HashSet::from_iter(vec![
|
||||
"isolation".into(),
|
||||
"native-tls-vendored".into(),
|
||||
])),
|
||||
tauri_build_dependency(HashSet::from_iter(vec!["isolation".into()])),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn inject_features_string() {
|
||||
inject_features(
|
||||
r#"
|
||||
[dependencies]
|
||||
tauri = "1"
|
||||
|
||||
[build-dependencies]
|
||||
tauri-build = "1"
|
||||
"#,
|
||||
vec![
|
||||
tauri_dependency(HashSet::from_iter(vec![
|
||||
"isolation".into(),
|
||||
"native-tls-vendored".into(),
|
||||
])),
|
||||
tauri_build_dependency(HashSet::from_iter(vec!["isolation".into()])),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user