浏览代码

feat: Further improve workspace inheritance, closes #6122, #5070 (#6144)

Co-authored-by: Lucas Nogueira <lucas@tauri.studio>
Fabian-Lars 2 年之前
父节点
当前提交
d20a728892

+ 2 - 1
.changes/version-inheritance.md

@@ -1,5 +1,6 @@
 ---
 'cli.rs': 'minor'
+'tauri-build': 'minor'
 ---
 
-Add support for Cargo's workspace inheritance for the package version.
+Added support for Cargo's workspace inheritance for package information. The cli now also detects inherited `tauri` and `tauri-build` dependencies and disables manifest rewrites accordingly.

+ 1 - 0
core/tauri-build/Cargo.toml

@@ -22,6 +22,7 @@ quote = { version = "1", optional = true }
 tauri-codegen = { version = "1.2.1", path = "../tauri-codegen", optional = true }
 tauri-utils = { version = "1.2.1", path = "../tauri-utils", features = [ "build", "resources" ] }
 cargo_toml = "0.14"
+serde = "1"
 serde_json = "1"
 heck = "0.4"
 json-patch = "0.3"

+ 114 - 46
core/tauri-build/src/lib.rs

@@ -5,9 +5,13 @@
 #![cfg_attr(doc_cfg, feature(doc_cfg))]
 
 pub use anyhow::Result;
+use cargo_toml::{Dependency, Manifest};
 use heck::AsShoutySnakeCase;
 
-use tauri_utils::resources::{external_binaries, resource_relpath, ResourcePaths};
+use tauri_utils::{
+  config::Config,
+  resources::{external_binaries, resource_relpath, ResourcePaths},
+};
 
 use std::path::{Path, PathBuf};
 
@@ -242,8 +246,6 @@ pub fn build() {
 #[allow(unused_variables)]
 pub fn try_build(attributes: Attributes) -> Result<()> {
   use anyhow::anyhow;
-  use cargo_toml::{Dependency, Manifest};
-  use tauri_utils::config::{Config, TauriConfig};
 
   println!("cargo:rerun-if-env-changed=TAURI_CONFIG");
   println!("cargo:rerun-if-changed=tauri.conf.json");
@@ -268,50 +270,33 @@ pub fn try_build(attributes: Attributes) -> Result<()> {
 
   cfg_alias("dev", !has_feature("custom-protocol"));
 
-  let mut manifest = Manifest::from_path("Cargo.toml")?;
-  if let Some(tauri) = manifest.dependencies.remove("tauri") {
-    let features = match tauri {
-      Dependency::Simple(_) => Vec::new(),
-      Dependency::Detailed(dep) => dep.features,
-      Dependency::Inherited(dep) => dep.features,
-    };
-
-    let all_cli_managed_features = TauriConfig::all_features();
-    let diff = features_diff(
-      &features
-        .into_iter()
-        .filter(|f| all_cli_managed_features.contains(&f.as_str()))
-        .collect::<Vec<String>>(),
-      &config
-        .tauri
-        .features()
-        .into_iter()
-        .map(|f| f.to_string())
-        .collect::<Vec<String>>(),
-    );
-
-    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"
-      });
+  let ws_path = get_workspace_dir()?;
+  let mut manifest =
+    Manifest::<cargo_toml::Value>::from_slice_with_metadata(&std::fs::read("Cargo.toml")?)?;
+
+  if let Ok(ws_manifest) = Manifest::from_path(ws_path.join("Cargo.toml")) {
+    Manifest::complete_from_path_and_workspace(
+      &mut manifest,
+      Path::new("Cargo.toml"),
+      Some((&ws_manifest, ws_path.as_path())),
+    )?;
+  } else {
+    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!("
@@ -485,6 +470,89 @@ fn features_diff(current: &[String], expected: &[String]) -> Diff {
   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,
+}
+
+fn get_workspace_dir() -> Result<PathBuf> {
+  let output = std::process::Command::new("cargo")
+    .args(["metadata", "--no-deps", "--format-version", "1"])
+    .output()?;
+
+  if !output.status.success() {
+    return Err(anyhow::anyhow!(
+      "cargo metadata command exited with a non zero exit code: {}",
+      String::from_utf8(output.stderr)?
+    ));
+  }
+
+  Ok(serde_json::from_slice::<CargoMetadata>(&output.stdout)?.workspace_root)
+}
+
 #[cfg(test)]
 mod tests {
   use super::Diff;

+ 13 - 11
examples/workspace/Cargo.lock

@@ -2334,7 +2334,7 @@ dependencies = [
 
 [[package]]
 name = "tauri"
-version = "1.2.3"
+version = "1.2.4"
 dependencies = [
  "anyhow",
  "cocoa",
@@ -2384,9 +2384,10 @@ dependencies = [
  "heck 0.4.0",
  "json-patch",
  "semver 1.0.16",
+ "serde",
  "serde_json",
  "tauri-utils",
- "winres",
+ "tauri-winres",
 ]
 
 [[package]]
@@ -2487,6 +2488,16 @@ dependencies = [
  "windows",
 ]
 
+[[package]]
+name = "tauri-winres"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1b7a78dc04f75fb5ab815e66ac561c81e92a968a40f29e7c21afd152d694fad8"
+dependencies = [
+ "toml",
+ "version_check",
+]
+
 [[package]]
 name = "tempfile"
 version = "3.3.0"
@@ -3045,15 +3056,6 @@ version = "0.42.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd"
 
-[[package]]
-name = "winres"
-version = "0.1.12"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b68db261ef59e9e52806f688020631e987592bd83619edccda9c47d42cde4f6c"
-dependencies = [
- "toml",
-]
-
 [[package]]
 name = "wry"
 version = "0.24.1"

+ 4 - 0
examples/workspace/Cargo.toml

@@ -6,3 +6,7 @@ members = [
 
 [workspace.package]
 version = "1.0.0"
+
+[workspace.dependencies]
+tauri-build = { path = "../../core/tauri-build", features = [] }
+tauri = { path = "../../core/tauri", features = [] }

+ 2 - 2
examples/workspace/src-tauri/Cargo.toml

@@ -10,12 +10,12 @@ edition = "2021"
 rust-version = "1.59"
 
 [build-dependencies]
-tauri-build = { path = "../../../core/tauri-build", features = [] }
+tauri-build = { workspace = true }
 
 [dependencies]
 serde_json = "1.0"
 serde = { version = "1.0", features = ["derive"] }
-tauri = { path = "../../../core/tauri", features = [] }
+tauri = { workspace = true }
 core-api = { path = "../core" }
 
 [features]

+ 1 - 1
tooling/cli/Cargo.lock

@@ -3185,7 +3185,6 @@ dependencies = [
  "tempfile",
  "thiserror",
  "time",
- "toml",
  "uuid 1.1.2",
  "walkdir",
  "winreg",
@@ -3211,6 +3210,7 @@ dependencies = [
  "ignore",
  "image",
  "include_dir",
+ "itertools",
  "json-patch 0.2.6",
  "jsonschema",
  "kuchiki",

+ 1 - 0
tooling/cli/Cargo.toml

@@ -80,6 +80,7 @@ kuchiki = "0.8"
 tokio = { version = "1", features = [ "macros", "sync" ] }
 common-path = "1"
 serde-value = "0.7.0"
+itertools = "0.10"
 
 [target."cfg(windows)".dependencies]
 winapi = { version = "0.3", features = [ "handleapi", "processenv", "winbase", "wincon", "winnt" ] }

+ 51 - 29
tooling/cli/src/interface/rust.rs

@@ -567,7 +567,9 @@ struct WorkspaceSettings {
 
 #[derive(Clone, Debug, Deserialize)]
 struct WorkspacePackageSettings {
-  /// the workspace members.
+  authors: Option<Vec<String>>,
+  description: Option<String>,
+  homepage: Option<String>,
   version: Option<String>,
 }
 
@@ -585,11 +587,11 @@ pub struct CargoPackageSettings {
   /// the package's version.
   pub version: Option<MaybeWorkspace<String>>,
   /// the package's description.
-  pub description: Option<String>,
+  pub description: Option<MaybeWorkspace<String>>,
   /// the package's homepage.
-  pub homepage: Option<String>,
+  pub homepage: Option<MaybeWorkspace<String>>,
   /// the package's authors.
-  pub authors: Option<Vec<String>>,
+  pub authors: Option<MaybeWorkspace<Vec<String>>>,
   /// the default binary to run.
   pub default_run: Option<String>,
 }
@@ -794,6 +796,11 @@ impl RustAppSettings {
       }
     };
 
+    let ws_package_settings = CargoSettings::load(&get_workspace_dir()?)
+      .with_context(|| "failed to load cargo settings from workspace root")?
+      .workspace
+      .and_then(|v| v.package);
+
     let package_settings = PackageSettings {
       product_name: config.package.product_name.clone().unwrap_or_else(|| {
         cargo_package_settings
@@ -807,15 +814,51 @@ impl RustAppSettings {
           .clone()
           .expect("Cargo manifest must have the `package.version` field")
           .resolve("version", || {
-            get_workspace_version()?.context("Workspace Cargo manifest must have the `workspace.package.version` field if a member tries to inherit the version")
-          }).expect("Cargo project does not have a version")
+            ws_package_settings
+              .as_ref()
+              .and_then(|p| p.version.clone())
+              .ok_or_else(|| anyhow::anyhow!("Couldn't inherit value for `version` from workspace"))
+          })
+          .expect("Cargo project does not have a version")
       }),
       description: cargo_package_settings
         .description
         .clone()
+        .map(|description| {
+          description
+            .resolve("description", || {
+              ws_package_settings
+                .as_ref()
+                .and_then(|v| v.description.clone())
+                .ok_or_else(|| {
+                  anyhow::anyhow!("Couldn't inherit value for `description` from workspace")
+                })
+            })
+            .unwrap()
+        })
         .unwrap_or_default(),
-      homepage: cargo_package_settings.homepage.clone(),
-      authors: cargo_package_settings.authors.clone(),
+      homepage: cargo_package_settings.homepage.clone().map(|homepage| {
+        homepage
+          .resolve("homepage", || {
+            ws_package_settings
+              .as_ref()
+              .and_then(|v| v.homepage.clone())
+              .ok_or_else(|| {
+                anyhow::anyhow!("Couldn't inherit value for `homepage` from workspace")
+              })
+          })
+          .unwrap()
+      }),
+      authors: cargo_package_settings.authors.clone().map(|authors| {
+        authors
+          .resolve("authors", || {
+            ws_package_settings
+              .as_ref()
+              .and_then(|v| v.authors.clone())
+              .ok_or_else(|| anyhow::anyhow!("Couldn't inherit value for `authors` from workspace"))
+          })
+          .unwrap()
+      }),
       default_run: cargo_package_settings.default_run.clone(),
     };
 
@@ -913,27 +956,6 @@ pub fn get_workspace_dir() -> crate::Result<PathBuf> {
   )
 }
 
-pub fn get_workspace_version() -> crate::Result<Option<String>> {
-  // This will already fail because `cargo metadata` fails if there is no version in the workspace root when a package tries to inherit it.
-  let toml_path = get_workspace_dir()
-    .map(|p| p.join("Cargo.toml"))
-    .with_context(|| "failed to get workspace Cargo.toml file")?;
-
-  let mut toml_str = String::new();
-  let mut toml_file = File::open(toml_path).with_context(|| "failed to open Cargo.toml")?;
-  toml_file
-    .read_to_string(&mut toml_str)
-    .with_context(|| "failed to read Cargo.toml")?;
-
-  Ok(
-    toml::from_str::<CargoSettings>(&toml_str)
-      .with_context(|| "failed to parse Cargo.toml")?
-      .workspace
-      .and_then(|ws| ws.package)
-      .and_then(|p| p.version),
-  )
-}
-
 #[allow(unused_variables)]
 fn tauri_config_to_bundle_settings(
   manifest: &Manifest,

+ 13 - 1
tooling/cli/src/interface/rust/manifest.rs

@@ -8,6 +8,8 @@ use crate::helpers::{
 };
 
 use anyhow::Context;
+use itertools::Itertools;
+use log::info;
 use toml_edit::{Array, Document, InlineTable, Item, Table, Value};
 
 use std::{
@@ -82,7 +84,7 @@ fn get_enabled_features(list: &HashMap<String, Vec<String>>, feature: &str) -> V
   f
 }
 
-fn read_manifest(manifest_path: &Path) -> crate::Result<Document> {
+pub fn read_manifest(manifest_path: &Path) -> crate::Result<Document> {
   let mut manifest_str = String::new();
 
   let mut manifest_file = File::open(manifest_path)
@@ -114,6 +116,16 @@ fn write_features(
 ) -> crate::Result<bool> {
   let item = dependencies.entry(dependency_name).or_insert(Item::None);
 
+  // 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);
+  }
+
   if let Some(dep) = item.as_table_mut() {
     let manifest_features = dep.entry("features").or_insert(Item::None);
     if let Item::Value(Value::Array(f)) = &manifest_features {