use crate::{
error::ErrorExt,
interface::rust::manifest::{read_manifest, serialize_manifest},
Result,
};
use tauri_utils::config_v1::Allowlist;
use toml_edit::{DocumentMut, Entry, Item, TableLike, Value};
use std::path::Path;
const CRATE_TYPES: [&str; 3] = ["lib", "staticlib", "cdylib"];
pub fn migrate(tauri_dir: &Path) -> Result<()> {
let manifest_path = tauri_dir.join("Cargo.toml");
let (mut manifest, _) = read_manifest(&manifest_path)?;
migrate_manifest(&mut manifest)?;
std::fs::write(&manifest_path, serialize_manifest(&manifest))
.fs_context("failed to rewrite Cargo manifest", &manifest_path)?;
Ok(())
}
fn migrate_manifest(manifest: &mut DocumentMut) -> Result<()> {
let version = dependency_version();
let remove_features = features_to_remove();
let rename_features = features_to_rename();
let rename_message = rename_features
.iter()
.map(|(from, to)| format!("{from} to {to}"))
.collect::<Vec<_>>()
.join(", ");
for (dependency, table) in [
("tauri", "dependencies"),
("tauri-utils", "dependencies"),
("tauri-runtime", "dependencies"),
("tauri-codegen", "dependencies"),
("tauri-macros", "dependencies"),
("tauri-runtime-wry", "dependencies"),
("tauri-plugin-authenticator", "dependencies"),
("tauri-plugin-autostart", "dependencies"),
("tauri-plugin-fs-extra", "dependencies"),
("tauri-plugin-fs-watch", "dependencies"),
("tauri-plugin-localhost", "dependencies"),
("tauri-plugin-log", "dependencies"),
("tauri-plugin-persisted-scope", "dependencies"),
("tauri-plugin-positioner", "dependencies"),
("tauri-plugin-single-instance", "dependencies"),
("tauri-plugin-sql", "dependencies"),
("tauri-plugin-store", "dependencies"),
("tauri-plugin-stronghold", "dependencies"),
("tauri-plugin-upload", "dependencies"),
("tauri-plugin-websocket", "dependencies"),
("tauri-plugin-window-state", "dependencies"),
("tauri", "dev-dependencies"),
("tauri-utils", "dev-dependencies"),
("tauri-runtime", "dev-dependencies"),
("tauri-codegen", "dev-dependencies"),
("tauri-macros", "dev-dependencies"),
("tauri-runtime-wry", "dev-dependencies"),
("tauri-build", "build-dependencies"),
] {
let items = find_dependency(manifest, dependency, table);
for item in items {
if item
.get("workspace")
.and_then(|v| v.as_bool())
.unwrap_or_default()
{
log::warn!("`{dependency}` dependency has workspace inheritance enabled. This migration must be manually migrated to v2 by changing its version to {version}, removing any of the {remove_features:?} and renaming [{}] Cargo features.", rename_message);
} else {
migrate_dependency(item, &version, &remove_features, &rename_features);
}
}
}
if let Some(lib) = manifest
.as_table_mut()
.get_mut("lib")
.and_then(|l| l.as_table_mut())
{
match lib.entry("crate-type") {
Entry::Occupied(mut e) => {
if let Item::Value(Value::Array(types)) = e.get_mut() {
let mut crate_types_to_add = CRATE_TYPES.to_vec();
for t in types.iter() {
if let Some(i) = crate_types_to_add
.iter()
.position(|ty| Some(ty) == t.as_str().as_ref())
{
crate_types_to_add.remove(i);
}
}
for t in crate_types_to_add {
types.push(t);
}
}
}
Entry::Vacant(e) => {
let mut arr = toml_edit::Array::new();
arr.extend(CRATE_TYPES.to_vec());
e.insert(Item::Value(arr.into()));
}
}
}
Ok(())
}
fn find_dependency<'a>(
manifest: &'a mut DocumentMut,
name: &'a str,
table: &'a str,
) -> Vec<&'a mut Item> {
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 features_to_rename() -> Vec<(&'static str, &'static str)> {
vec![
("window-data-url", "webview-data-url"),
("reqwest-native-tls-vendored", "native-tls-vendored"),
("system-tray", "tray-icon"),
("icon-ico", "image-ico"),
("icon-png", "image-png"),
]
}
fn features_to_remove() -> Vec<&'static str> {
let mut features_to_remove = tauri_utils::config_v1::AllowlistConfig::all_features();
features_to_remove.extend(&[
"reqwest-client",
"http-multipart",
"process-command-api",
"shell-open-api",
"os-api",
"global-shortcut",
"clipboard",
"dialog",
"notification",
"fs-extract-api",
"windows7-compat",
"updater",
"cli",
"linux-protocol-headers",
"dox",
]);
let index = features_to_remove
.iter()
.position(|x| x == &"protocol-asset")
.unwrap();
features_to_remove.remove(index);
features_to_remove
}
fn dependency_version() -> String {
let pre = env!("CARGO_PKG_VERSION_PRE");
if pre.is_empty() {
env!("CARGO_PKG_VERSION_MAJOR").to_string()
} else {
format!(
"{}.0.0-{}",
env!("CARGO_PKG_VERSION_MAJOR"),
pre.split('.').next().unwrap()
)
}
}
fn migrate_dependency(item: &mut Item, version: &str, remove: &[&str], rename: &[(&str, &str)]) {
if let Some(dep) = item.as_table_mut() {
migrate_dependency_table(dep, version, remove, rename);
} else if let Some(Value::InlineTable(table)) = item.as_value_mut() {
migrate_dependency_table(table, version, remove, rename);
} else if item.as_str().is_some() {
*item = Item::Value(version.into());
}
}
fn migrate_dependency_table<D: TableLike>(
dep: &mut D,
version: &str,
remove: &[&str],
rename: &[(&str, &str)],
) {
dep.remove("rev");
dep.remove("git");
dep.remove("branch");
dep.remove("tag");
*dep.entry("version").or_insert(Item::None) = Item::Value(version.into());
let manifest_features = dep.entry("features").or_insert(Item::None);
if let Some(features_array) = manifest_features.as_array_mut() {
let mut i = features_array.len();
let mut add_features = Vec::new();
while i != 0 {
let index = i - 1;
if let Some(f) = features_array.get(index).and_then(|f| f.as_str()) {
if remove.contains(&f) {
features_array.remove(index);
} else if let Some((_from, rename_to)) = rename.iter().find(|(from, _to)| *from == f) {
features_array.remove(index);
add_features.push(rename_to);
}
}
i -= 1;
}
for f in add_features {
features_array.push(f.to_string());
}
}
}
#[cfg(test)]
mod tests {
use itertools::Itertools;
fn migrate_deps<F: FnOnce(&[&str]) -> String>(get_toml: F) {
let keep_features = vec!["isolation", "protocol-asset"];
let mut features = super::features_to_remove();
features.extend(keep_features.clone());
let toml = get_toml(&features);
let mut manifest = toml
.parse::<toml_edit::DocumentMut>()
.expect("invalid toml");
super::migrate_manifest(&mut manifest).expect("failed to migrate manifest");
let dependencies = manifest
.as_table()
.get("dependencies")
.expect("missing manifest dependencies")
.as_table()
.expect("manifest dependencies isn't a table");
let tauri = dependencies
.get("tauri")
.expect("missing tauri dependency in manifest");
let tauri_table = if let Some(table) = tauri.as_table() {
table.clone()
} else if let Some(toml_edit::Value::InlineTable(table)) = tauri.as_value() {
table.clone().into_table()
} else if let Some(version) = tauri.as_str() {
let mut table = toml_edit::Table::new();
table.insert(
"version",
toml_edit::Item::Value(version.to_string().into()),
);
table.insert(
"features",
toml_edit::Item::Value(toml_edit::Value::Array(Default::default())),
);
table
} else {
panic!("unexpected tauri dependency format");
};
let version = tauri_table
.get("version")
.expect("missing version")
.as_str()
.expect("version must be a string");
assert_eq!(version, super::dependency_version());
let features = tauri_table
.get("features")
.expect("missing features")
.as_array()
.expect("features must be an array")
.clone();
if toml.contains("reqwest-native-tls-vendored") {
assert!(
features
.iter()
.any(|f| f.as_str().expect("feature must be a string") == "native-tls-vendored"),
"reqwest-native-tls-vendored was not replaced with native-tls-vendored"
);
}
if toml.contains("system-tray") {
assert!(
features
.iter()
.any(|f| f.as_str().expect("feature must be a string") == "tray-icon"),
"system-tray was not replaced with tray-icon"
);
}
for feature in features.iter() {
let feature = feature.as_str().expect("feature must be a string");
assert!(
keep_features.contains(&feature)
|| feature == "native-tls-vendored"
|| feature == "tray-icon",
"feature {feature} should have been removed"
);
}
}
#[test]
fn migrate_table() {
migrate_deps(|features| {
format!(
r#"
[dependencies]
tauri = {{ version = "1.0.0", features = [{}] }}
"#,
features.iter().map(|f| format!("{f:?}")).join(", ")
)
});
}
#[test]
fn migrate_inline_table() {
migrate_deps(|features| {
format!(
r#"
[dependencies.tauri]
version = "1.0.0"
features = [{}]
"#,
features.iter().map(|f| format!("{f:?}")).join(", ")
)
});
}
#[test]
fn migrate_str() {
migrate_deps(|_features| {
r#"
[dependencies]
tauri = "1.0.0"
"#
.into()
})
}
#[test]
fn migrate_add_crate_types() {
let toml = r#"
[lib]
crate-type = ["something"]"#;
let mut manifest = toml
.parse::<toml_edit::DocumentMut>()
.expect("invalid toml");
super::migrate_manifest(&mut manifest).expect("failed to migrate manifest");
if let Some(crate_types) = manifest
.as_table()
.get("lib")
.and_then(|l| l.get("crate-type"))
.and_then(|c| c.as_array())
{
let mut not_added_crate_types = super::CRATE_TYPES.to_vec();
for t in crate_types {
let t = t.as_str().expect("crate-type must be a string");
if let Some(i) = not_added_crate_types.iter().position(|ty| ty == &t) {
not_added_crate_types.remove(i);
}
}
assert!(
not_added_crate_types.is_empty(),
"missing crate-type: {not_added_crate_types:?}"
);
}
}
}