Skip to content
6 changes: 6 additions & 0 deletions .changes/only-watch-dependencies.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@tauri-apps/cli": patch:bug
"tauri-cli": patch:bug
---

Only watch dependent workspace members when running `tauri dev` instead of watching on all members
123 changes: 83 additions & 40 deletions crates/tauri-cli/src/interface/rust.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ use std::{
};

use dunce::canonicalize;
use glob::glob;
use ignore::gitignore::{Gitignore, GitignoreBuilder};
use notify::RecursiveMode;
use notify_debouncer_full::new_debouncer;
Expand Down Expand Up @@ -449,25 +448,15 @@ fn dev_options(
}
}

// Copied from https://github.com/rust-lang/cargo/blob/69255bb10de7f74511b5cef900a9d102247b6029/src/cargo/core/workspace.rs#L665
fn expand_member_path(path: &Path) -> crate::Result<Vec<PathBuf>> {
let path = path.to_str().context("path is not UTF-8 compatible")?;
let res = glob(path).with_context(|| format!("failed to expand glob pattern for {path}"))?;
let res = res
.map(|p| p.with_context(|| format!("failed to expand glob pattern for {path}")))
.collect::<Result<Vec<_>, _>>()?;
Ok(res)
}

fn get_watch_folders(
additional_watch_folders: &[PathBuf],
tauri_dir: &Path,
) -> crate::Result<Vec<PathBuf>> {
let workspace_path = get_workspace_dir(tauri_dir)?;

// We always want to watch the main tauri folder.
let mut watch_folders = vec![tauri_dir.to_path_buf()];

watch_folders.extend(get_in_workspace_dependency_paths(tauri_dir)?);

// Add the additional watch folders, resolving the path from the tauri path if it is relative
watch_folders.extend(additional_watch_folders.iter().filter_map(|dir| {
let path = if dir.is_absolute() {
Expand All @@ -486,30 +475,6 @@ fn get_watch_folders(
canonicalized
}));

// We also try to watch workspace members, no matter if the tauri cargo project is the workspace root or a workspace member
let cargo_settings = CargoSettings::load(&workspace_path)?;
if let Some(members) = cargo_settings.workspace.and_then(|w| w.members) {
for p in members {
let p = workspace_path.join(p);
match expand_member_path(&p) {
// Sometimes expand_member_path returns an empty vec, for example if the path contains `[]` as in `C:/[abc]/project/`.
// Cargo won't complain unless theres a workspace.members config with glob patterns so we should support it too.
Ok(expanded_paths) => {
if expanded_paths.is_empty() {
watch_folders.push(p);
} else {
watch_folders.extend(expanded_paths);
}
}
Err(err) => {
// If this fails cargo itself should fail too. But we still try to keep going with the unexpanded path.
log::error!("Error watching {}: {}", p.display(), err);
watch_folders.push(p);
}
};
}
}

Ok(watch_folders)
}

Expand Down Expand Up @@ -690,7 +655,7 @@ pub struct TomlWorkspaceField {
#[derive(Clone, Debug, Deserialize)]
struct WorkspaceSettings {
/// the workspace members.
members: Option<Vec<String>>,
// members: Option<Vec<String>>,
package: Option<WorkspacePackageSettings>,
}

Expand Down Expand Up @@ -1210,7 +1175,7 @@ pub(crate) struct CargoMetadata {
pub(crate) workspace_root: PathBuf,
}

pub(crate) fn get_cargo_metadata(tauri_dir: &Path) -> crate::Result<CargoMetadata> {
fn run_cargo_metadata(tauri_dir: &Path) -> crate::Result<Vec<u8>> {
let output = Command::new("cargo")
.args(["metadata", "--no-deps", "--format-version", "1"])
.current_dir(tauri_dir)
Expand All @@ -1227,7 +1192,85 @@ pub(crate) fn get_cargo_metadata(tauri_dir: &Path) -> crate::Result<CargoMetadat
});
}

serde_json::from_slice(&output.stdout).context("failed to parse cargo metadata")
Ok(output.stdout)
}

pub(crate) fn get_cargo_metadata(tauri_dir: &Path) -> crate::Result<CargoMetadata> {
let output = run_cargo_metadata(tauri_dir)?;
serde_json::from_slice(&output).context("failed to parse cargo metadata")
}

#[derive(Deserialize)]
pub struct CargoMetadataExpended {
workspace_members: Vec<String>,
packages: Vec<Package>,
}

#[derive(Deserialize)]
struct Package {
name: String,
id: String,
manifest_path: PathBuf,
dependencies: Vec<Dependency>,
}

#[derive(Deserialize)]
struct Dependency {
name: String,
/// Local package
path: Option<PathBuf>,
}

/// Get the tauri project crate's dependencies that are inside the workspace
fn get_in_workspace_dependency_paths(tauri_dir: &Path) -> crate::Result<Vec<PathBuf>> {
let output = run_cargo_metadata(tauri_dir)?;
let metadata: CargoMetadataExpended =
serde_json::from_slice(&output).context("failed to parse cargo metadata")?;
let tauri_project_manifest_path = tauri_dir.join("Cargo.toml");
let tauri_project_package = metadata
.packages
.iter()
.find(|package| package.manifest_path == tauri_project_manifest_path)
.context("tauri project package doesn't exist in cargo metadata output `packages`")?;

let workspace_packages = metadata
.workspace_members
.iter()
.map(|member_package_id| {
metadata
.packages
.iter()
.find(|package| package.id == *member_package_id)
.context("workspace member doesn't exist in cargo metadata output `packages`")
})
.collect::<crate::Result<Vec<_>>>()?;

let mut found_dependency_paths = Vec::new();
find_dependencies(
tauri_project_package,
&workspace_packages,
&mut found_dependency_paths,
);
Ok(found_dependency_paths)
}

fn find_dependencies(
package: &Package,
workspace_packages: &Vec<&Package>,
found_dependency_paths: &mut Vec<PathBuf>,
) {
for dependency in &package.dependencies {
if let Some(path) = &dependency.path {
if let Some(package) = workspace_packages.iter().find(|workspace_package| {
workspace_package.name == dependency.name
&& path.join("Cargo.toml") == workspace_package.manifest_path
&& !found_dependency_paths.contains(path)
}) {
found_dependency_paths.push(path.to_owned());
find_dependencies(package, workspace_packages, found_dependency_paths);
}
}
}
}

/// Get the cargo target directory based on the provided arguments.
Expand Down
Loading