manifest.rs 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250
  1. // Copyright 2019-2022 Tauri Programme within The Commons Conservancy
  2. // SPDX-License-Identifier: Apache-2.0
  3. // SPDX-License-Identifier: MIT
  4. use crate::helpers::{
  5. app_paths::tauri_dir,
  6. config::{Config, PatternKind},
  7. };
  8. use anyhow::Context;
  9. use toml_edit::{Array, Document, InlineTable, Item, Table, Value};
  10. use std::{
  11. collections::{HashMap, HashSet},
  12. fs::File,
  13. io::{Read, Write},
  14. iter::FromIterator,
  15. path::Path,
  16. };
  17. #[derive(Default)]
  18. pub struct Manifest {
  19. pub inner: Document,
  20. pub tauri_features: HashSet<String>,
  21. }
  22. impl Manifest {
  23. pub fn features(&self) -> HashMap<String, Vec<String>> {
  24. let mut f = HashMap::new();
  25. if let Some(features) = self
  26. .inner
  27. .as_table()
  28. .get("features")
  29. .and_then(|f| f.as_table())
  30. {
  31. for (feature, enabled_features) in features.into_iter() {
  32. if let Item::Value(Value::Array(enabled_features)) = enabled_features {
  33. let mut enabled = Vec::new();
  34. for value in enabled_features {
  35. if let Value::String(s) = value {
  36. enabled.push(s.value().clone());
  37. }
  38. }
  39. f.insert(feature.to_string(), enabled);
  40. }
  41. }
  42. }
  43. f
  44. }
  45. pub fn all_enabled_features(&self, enabled_features: &[String]) -> Vec<String> {
  46. let mut all_enabled_features: Vec<String> = self
  47. .tauri_features
  48. .iter()
  49. .map(|f| format!("tauri/{}", f))
  50. .collect();
  51. let manifest_features = self.features();
  52. for f in enabled_features {
  53. all_enabled_features.extend(get_enabled_features(&manifest_features, f));
  54. }
  55. all_enabled_features
  56. }
  57. }
  58. fn get_enabled_features(list: &HashMap<String, Vec<String>>, feature: &str) -> Vec<String> {
  59. let mut f = Vec::new();
  60. if let Some(enabled_features) = list.get(feature) {
  61. for enabled in enabled_features {
  62. if list.contains_key(enabled) {
  63. f.extend(get_enabled_features(list, enabled));
  64. } else {
  65. f.push(enabled.clone());
  66. }
  67. }
  68. }
  69. f
  70. }
  71. fn read_manifest(manifest_path: &Path) -> crate::Result<Document> {
  72. let mut manifest_str = String::new();
  73. let mut manifest_file = File::open(manifest_path)
  74. .with_context(|| format!("failed to open `{:?}` file", manifest_path))?;
  75. manifest_file.read_to_string(&mut manifest_str)?;
  76. let manifest: Document = manifest_str
  77. .parse::<Document>()
  78. .with_context(|| "failed to parse Cargo.toml")?;
  79. Ok(manifest)
  80. }
  81. fn toml_array(features: &HashSet<String>) -> Array {
  82. let mut f = Array::default();
  83. let mut features: Vec<String> = features.iter().map(|f| f.to_string()).collect();
  84. features.sort();
  85. for feature in features {
  86. f.push(feature.as_str());
  87. }
  88. f
  89. }
  90. fn write_features(
  91. dependencies: &mut Table,
  92. dependency_name: &str,
  93. all_features: Vec<&str>,
  94. features: &mut HashSet<String>,
  95. ) -> crate::Result<bool> {
  96. let item = dependencies.entry(dependency_name).or_insert(Item::None);
  97. if let Some(dep) = item.as_table_mut() {
  98. let manifest_features = dep.entry("features").or_insert(Item::None);
  99. if let Item::Value(Value::Array(f)) = &manifest_features {
  100. for feat in f.iter() {
  101. if let Value::String(feature) = feat {
  102. if !all_features.contains(&feature.value().as_str()) {
  103. features.insert(feature.value().to_string());
  104. }
  105. }
  106. }
  107. }
  108. if let Some(features_array) = manifest_features.as_array_mut() {
  109. // add features that aren't in the manifest
  110. for feature in features.iter() {
  111. if !features_array.iter().any(|f| f.as_str() == Some(feature)) {
  112. features_array.insert(0, feature.as_str());
  113. }
  114. }
  115. // remove features that shouldn't be in the manifest anymore
  116. let mut i = 0;
  117. while i < features_array.len() {
  118. if let Some(f) = features_array.get(i).and_then(|f| f.as_str()) {
  119. if !features.contains(f) {
  120. features_array.remove(i);
  121. }
  122. }
  123. i += 1;
  124. }
  125. } else {
  126. *manifest_features = Item::Value(Value::Array(toml_array(features)));
  127. }
  128. Ok(true)
  129. } else if let Some(dep) = item.as_value_mut() {
  130. match dep {
  131. Value::InlineTable(table) => {
  132. let manifest_features = table.get_or_insert("features", Value::Array(Default::default()));
  133. if let Value::Array(f) = &manifest_features {
  134. for feat in f.iter() {
  135. if let Value::String(feature) = feat {
  136. if !all_features.contains(&feature.value().as_str()) {
  137. features.insert(feature.value().to_string());
  138. }
  139. }
  140. }
  141. }
  142. *manifest_features = Value::Array(toml_array(features));
  143. }
  144. Value::String(version) => {
  145. let mut def = InlineTable::default();
  146. def.get_or_insert("version", version.to_string().replace(['\"', ' '], ""));
  147. def.get_or_insert("features", Value::Array(toml_array(features)));
  148. *dep = Value::InlineTable(def);
  149. }
  150. _ => {
  151. return Err(anyhow::anyhow!(
  152. "Unsupported {} dependency format on Cargo.toml",
  153. dependency_name
  154. ))
  155. }
  156. }
  157. Ok(true)
  158. } else {
  159. Ok(false)
  160. }
  161. }
  162. pub fn rewrite_manifest(config: &Config) -> crate::Result<Manifest> {
  163. let manifest_path = tauri_dir().join("Cargo.toml");
  164. let mut manifest = read_manifest(&manifest_path)?;
  165. let mut tauri_build_features = HashSet::new();
  166. if let PatternKind::Isolation { .. } = config.tauri.pattern {
  167. tauri_build_features.insert("isolation".to_string());
  168. }
  169. let resp = write_features(
  170. manifest
  171. .as_table_mut()
  172. .entry("build-dependencies")
  173. .or_insert(Item::Table(Table::new()))
  174. .as_table_mut()
  175. .expect("manifest build-dependencies isn't a table"),
  176. "tauri-build",
  177. vec!["isolation"],
  178. &mut tauri_build_features,
  179. )?;
  180. let mut tauri_features =
  181. HashSet::from_iter(config.tauri.features().into_iter().map(|f| f.to_string()));
  182. let cli_managed_tauri_features = crate::helpers::config::TauriConfig::all_features();
  183. let res = match write_features(
  184. manifest
  185. .as_table_mut()
  186. .entry("dependencies")
  187. .or_insert(Item::Table(Table::new()))
  188. .as_table_mut()
  189. .expect("manifest dependencies isn't a table"),
  190. "tauri",
  191. cli_managed_tauri_features,
  192. &mut tauri_features,
  193. ) {
  194. Err(e) => Err(e),
  195. Ok(t) if !resp => Ok(t),
  196. _ => Ok(true),
  197. };
  198. match res {
  199. Ok(true) => {
  200. let mut manifest_file =
  201. File::create(&manifest_path).with_context(|| "failed to open Cargo.toml for rewrite")?;
  202. manifest_file.write_all(
  203. manifest
  204. .to_string()
  205. // apply some formatting fixes
  206. .replace(r#"" ,features =["#, r#"", features = ["#)
  207. .replace(r#"" , features"#, r#"", features"#)
  208. .replace("]}", "] }")
  209. .replace("={", "= {")
  210. .replace("=[", "= [")
  211. .as_bytes(),
  212. )?;
  213. manifest_file.flush()?;
  214. Ok(Manifest {
  215. inner: manifest,
  216. tauri_features,
  217. })
  218. }
  219. Ok(false) => Ok(Manifest {
  220. inner: manifest,
  221. tauri_features,
  222. }),
  223. Err(e) => Err(e),
  224. }
  225. }