diff --git a/Cargo.lock b/Cargo.lock index 14a0d93..fb395f5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -136,9 +136,21 @@ checksum = "631ae5198c9be5e753e5cc215e1bd73c2b466a3565173db433f52bb9d3e66dba" [[package]] name = "camino" -version = "1.0.5" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52d74260d9bf6944e2208aa46841b4b8f0d7ffc0849a06837b2f510337f86b2b" +checksum = "88ad0e1e3e88dd237a156ab9f571021b8a158caa0ae44b1968a241efb5144c1e" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo-platform" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbdb825da8a5df079a43676dbe042702f1707b1109f713a01420fbb4cc71fa27" +dependencies = [ + "serde", +] [[package]] name = "cargo-watch" @@ -146,6 +158,7 @@ version = "8.1.2" dependencies = [ "assert_cmd", "camino", + "cargo_metadata", "clap", "insta", "log", @@ -157,6 +170,20 @@ dependencies = [ "watchexec", ] +[[package]] +name = "cargo_metadata" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "406c859255d568f4f742b3146d51851f3bfd49f734a2c289d9107c4395ee0062" +dependencies = [ + "camino", + "cargo-platform", + "semver", + "serde", + "serde_json", + "thiserror", +] + [[package]] name = "cc" version = "1.0.72" @@ -749,9 +776,9 @@ dependencies = [ [[package]] name = "itoa" -version = "0.4.8" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" +checksum = "4217ad341ebadf8d8e724e264f13e593e0648f5b3e94b3896a5df283be015ecc" [[package]] name = "kernel32-sys" @@ -1162,11 +1189,11 @@ checksum = "bc881b2c22681370c6a780e47af9840ef841837bc98118431d4e1868bd0c1086" [[package]] name = "proc-macro2" -version = "1.0.32" +version = "1.0.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba508cc11742c0dc5c1659771673afbab7a0efab23aa17e854cbab0837ed0b43" +checksum = "5ea3d908b0e36316caf9e9e2c4625cdde190a7e6f440d794667ed17a1855e725" dependencies = [ - "unicode-xid", + "unicode-ident", ] [[package]] @@ -1321,20 +1348,29 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2" +[[package]] +name = "semver" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e25dfac463d778e353db5be2449d1cce89bd6fd23c9f1ea21310ce6e5a1b29c4" +dependencies = [ + "serde", +] + [[package]] name = "serde" -version = "1.0.130" +version = "1.0.147" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f12d06de37cf59146fbdecab66aa99f9fe4f78722e3607577a5375d66bd0c913" +checksum = "d193d69bae983fc11a79df82342761dfbf28a99fc8d203dca4c3c1b590948965" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.130" +version = "1.0.147" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7bc1a1ab1961464eae040d96713baa5a724a8152c1222492465b54322ec508b" +checksum = "4f1d362ca8fc9c3e3a7484440752472d68a6caa98f1ab81d99b5dfe517cec852" dependencies = [ "proc-macro2", "quote", @@ -1343,9 +1379,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.70" +version = "1.0.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e277c495ac6cd1a01a58d0a0c574568b4d1ddf14f59965c6a58b8d96400b54f3" +checksum = "6ce777b7b150d76b9cf60d28b55f5847135a003f7d7350c6be7a773508ce7d45" dependencies = [ "itoa", "ryu", @@ -1463,13 +1499,13 @@ dependencies = [ [[package]] name = "syn" -version = "1.0.81" +version = "1.0.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2afee18b8beb5a596ecb4a2dce128c719b4ba399d34126b9e4396e3f9860966" +checksum = "a864042229133ada95abf3b54fdc62ef5ccabe9515b64717bcb9a1919e59445d" dependencies = [ "proc-macro2", "quote", - "unicode-xid", + "unicode-ident", ] [[package]] @@ -1521,18 +1557,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.30" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417" +checksum = "10deb33631e3c9018b9baf9dcbbc4f737320d2b576bac10f6aefa048fa407e3e" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.30" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b" +checksum = "982d17546b47146b28f7c22e3d08465f6b8903d0ea13c1660d9d84a6e7adcdbb" dependencies = [ "proc-macro2", "quote", @@ -1567,6 +1603,12 @@ dependencies = [ "serde", ] +[[package]] +name = "unicode-ident" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3" + [[package]] name = "unicode-segmentation" version = "1.8.0" @@ -1579,12 +1621,6 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" -[[package]] -name = "unicode-xid" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" - [[package]] name = "uuid" version = "0.8.2" diff --git a/Cargo.toml b/Cargo.toml index 069bfb8..89a8d87 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,6 +22,7 @@ name = "cargo-watch" [dependencies] camino = "1.0.4" +cargo_metadata = "0.15.1" clap = "2.33.1" log = "0.4.14" shell-escape = "0.1.5" diff --git a/README.md b/README.md index 77e93d6..c657ae3 100644 --- a/README.md +++ b/README.md @@ -94,49 +94,39 @@ FLAGS: --no-gitignore Don’t use .gitignore files --no-ignore Don’t use .ignore files --no-restart Don’t restart command while it’s still running + -N, --notify Send a desktop notification when watchexec notices a change + (experimental, behaviour may change) --poll Force use of polling for file changes --postpone Postpone first run until a file changes + --skip-local-deps Don't try to find local dependencies of the current crate and watch + their working directories. Only watch the current directory. -V, --version Display version information - --watch-when-idle Ignore events emitted while the commands run. - Will become default behaviour in 8.0. + --watch-when-idle Ignore events emitted while the commands run. Will become default + behaviour in 8.0. OPTIONS: - -x, --exec ... - Cargo command(s) to execute on changes [default: check] - + -x, --exec ... Cargo command(s) to execute on changes [default: check] -s, --shell ... Shell command(s) to execute on changes - - -d, --delay - File updates debounce delay in seconds [default: 0.5] - - --features - List of features passed to cargo invocations - + -d, --delay File updates debounce delay in seconds [default: 0.5] + --features List of features passed to cargo invocations -i, --ignore ... Ignore a glob/gitignore-style pattern - - -B - Inject RUST_BACKTRACE=VALUE (generally you want to set it to 1) - into the environment - - --use-shell - Use a different shell. E.g. --use-shell=bash. On Windows, try - --use-shell=powershell, which will become the default in 8.0. - - -w, --watch ... - Watch specific file(s) or folder(s) [default: .] - - -C, --workdir - Change working directory before running command [default: crate root] + -B Inject RUST_BACKTRACE=VALUE (generally you want to set it to 1) + into the environment + --use-shell Use a different shell. E.g. --use-shell=bash + -w, --watch ... Watch specific file(s) or folder(s). Disables finding and + watching local dependencies. + -C, --workdir Change working directory before running command [default: crate + root] ARGS: ... Full command to run. -x and -s will be ignored! -Cargo commands (-x) are always executed before shell commands (-s). You can use -the `-- command` style instead, note you'll need to use full commands, it won't -prefix `cargo` for you. +Cargo commands (-x) are always executed before shell commands (-s). You can use the `-- command` +style instead, note you'll need to use full commands, it won't prefix `cargo` for you. -By default, your entire project is watched, except for the target/ and .git/ -folders, and your .ignore and .gitignore files are used to filter paths. +By default, the workspace directories of your project and all local dependencies are watched, +except for the target/ and .git/ folders. Your .ignore and .gitignore files are used to filter +paths. On Windows, patterns given to -i have forward slashes (/) automatically converted to backward ones (\) to ease command portability. diff --git a/cargo-watch.1 b/cargo-watch.1 index 53c7ca2..4980466 100644 --- a/cargo-watch.1 +++ b/cargo-watch.1 @@ -57,11 +57,15 @@ Show paths that changed\. Suppress output from cargo\-watch itself\. . .TP +\fB\-\-skip\-local\-deps\fR +Don't try to find local dependencies of the current crate and watch their working directories\. Only watch the current directory\. +. +.TP \fB\-w\fR, \fB\-\-watch\fR \fIwatch\fR\.\.\. -Watch specific file(s) or folder(s)\. +Watch specific file(s) or folder(s)\. Disables finding and watching local dependencies\. . .P -By default, your entire project is watched, except for the target/ and \.git/ folders, and your \.ignore and \.gitignore files are used to filter paths\. +By default, the workspace directories of your project and all local dependencies are watched, except for the target/ and \.git/ folders\. Your \.ignore and \.gitignore files are used to filter paths\. . .TP \fB\-i\fR, \fB\-\-ignore\fR \fIpattern\fR\.\.\. diff --git a/cargo-watch.1.ronn b/cargo-watch.1.ronn index 4753cad..77fe833 100644 --- a/cargo-watch.1.ronn +++ b/cargo-watch.1.ronn @@ -42,10 +42,13 @@ Show paths that changed. * `-q`, `--quiet`: Suppress output from cargo-watch itself. +* `--skip-local-deps`: +Don't try to find local dependencies of the current crate and watch their working directories. Only watch the current directory. + * `-w`, `--watch` ...: -Watch specific file(s) or folder(s). +Watch specific file(s) or folder(s). Disables finding and watching local dependencies. -By default, your entire project is watched, except for the target/ and .git/ folders, and your .ignore and .gitignore files are used to filter paths. +By default, the workspace directories of your project and all local dependencies are watched, except for the target/ and .git/ folders. Your .ignore and .gitignore files are used to filter paths. * `-i`, `--ignore` ...: Ignore a glob/gitignore-style pattern. diff --git a/src/args.rs b/src/args.rs index 24347f2..8c20f28 100644 --- a/src/args.rs +++ b/src/args.rs @@ -2,7 +2,7 @@ use clap::{App, AppSettings, Arg, ArgMatches, ErrorKind, SubCommand}; use std::{env, process}; pub fn parse() -> ArgMatches<'static> { - let footnote = "Cargo commands (-x) are always executed before shell commands (-s). You can use the `-- command` style instead, note you'll need to use full commands, it won't prefix `cargo` for you.\n\nBy default, your entire project is watched, except for the target/ and .git/ folders, and your .ignore and .gitignore files are used to filter paths.".to_owned(); + let footnote = "Cargo commands (-x) are always executed before shell commands (-s). You can use the `-- command` style instead, note you'll need to use full commands, it won't prefix `cargo` for you.\n\nBy default, the workspace directories of your project and all local dependencies are watched, except for the target/ and .git/ folders. Your .ignore and .gitignore files are used to filter paths.".to_owned(); #[cfg(windows)] let footnote = format!("{}\n\nOn Windows, patterns given to -i have forward slashes (/) automatically converted to backward ones (\\) to ease command portability.", footnote); @@ -166,8 +166,7 @@ pub fn parse() -> ArgMatches<'static> { .empty_values(false) .min_values(1) .number_of_values(1) - .default_value(".") - .help("Watch specific file(s) or folder(s)"), + .help("Watch specific file(s) or folder(s). Disables finding and watching local dependencies."), ) .arg( Arg::with_name("use-shell") @@ -204,6 +203,11 @@ pub fn parse() -> ArgMatches<'static> { .raw(true) .help("Full command to run. -x and -s will be ignored!"), ) + .arg( + Arg::with_name("skip-local-deps") + .help("Don't try to find local dependencies of the current crate and watch their working directories. Only watch the current directory.") + .long("skip-local-deps") + ) .after_help(footnote.as_str()), ); diff --git a/src/options.rs b/src/options.rs index bec6204..2074cbc 100644 --- a/src/options.rs +++ b/src/options.rs @@ -1,5 +1,11 @@ -use std::{path::MAIN_SEPARATOR, time::Duration}; +use std::{ + collections::{HashMap, HashSet}, + iter::FromIterator, + path::{PathBuf, MAIN_SEPARATOR}, + time::Duration, +}; +use cargo_metadata::{MetadataCommand, Node, Package, PackageId}; use clap::{value_t, values_t, ArgMatches}; use log::{debug, warn}; use watchexec::{ @@ -148,6 +154,64 @@ pub fn set_debounce(builder: &mut ConfigBuilder, matches: &ArgMatches) { } } +fn find_local_deps() -> Result, String> { + let metadata = MetadataCommand::new() + .exec() + .map_err(|e| format!("Failed to execute `cargo metadata`: {}", e))?; + + let resolve = match metadata.resolve { + None => return Ok(Vec::new()), + Some(resolve) => resolve, + }; + let id_to_node = + HashMap::::from_iter(resolve.nodes.iter().map(|n| (n.id.clone(), n))); + let id_to_package = HashMap::::from_iter( + metadata.packages.iter().map(|p| (p.id.clone(), p)), + ); + + let mut pkgids_seen = HashSet::new(); + let mut pkgids_to_check = Vec::new(); + match resolve.root { + Some(root) => pkgids_to_check.push(root), + None => pkgids_to_check.extend_from_slice(&metadata.workspace_members), + }; + + // The set of directories of all packages we are interested in. + let mut local_deps = HashSet::new(); + + while !pkgids_to_check.is_empty() { + let current_pkgid = pkgids_to_check.pop().unwrap(); + if !pkgids_seen.insert(current_pkgid.clone()) { + continue; + } + let pkg = match id_to_package.get(¤t_pkgid) { + None => continue, + Some(&pkg) => pkg, + }; + // This means this is a remote package. Skip! + if pkg.source.is_some() { + continue; + } + // This is a path to Cargo.toml. + let mut path = pkg.manifest_path.clone(); + // We want the parent directory. + path.pop(); + local_deps.insert(path.into_std_path_buf()); + + // And find dependencies. + match id_to_node.get(¤t_pkgid) { + Some(node) => { + for dep in &node.deps { + pkgids_to_check.push(dep.pkg.clone()); + } + } + None => {} + } + } + + Ok(local_deps.into_iter().collect::>()) +} + pub fn set_watches(builder: &mut ConfigBuilder, matches: &ArgMatches) { let mut opts = Vec::new(); if matches.is_present("watch") { @@ -156,6 +220,22 @@ pub fn set_watches(builder: &mut ConfigBuilder, matches: &ArgMatches) { } } + if opts.is_empty() && !matches.is_present("skip-local-deps") { + match find_local_deps() { + Ok(workspaces) => { + if workspaces.is_empty() { + debug!("Found no local deps"); + } else { + opts = workspaces; + } + } + Err(err) => { + // If this fails just fall back to watching the current directory. + eprintln!("Finding local deps failed: {}", err); + } + } + } + if opts.is_empty() { opts.push(".".into()); }