From a978ad3094c36e1995e6e506df25618770d3f3b7 Mon Sep 17 00:00:00 2001 From: Jonathan Cammisuli Date: Thu, 1 Jun 2023 14:00:27 -0400 Subject: [PATCH] feat(core): rust based watcher (#16915) --- .circleci/config.yml | 2 + Cargo.lock | 1575 ++++++++++++++++- Cargo.toml | 4 +- nx.json | 1 + packages/nx/Cargo.toml | 14 +- packages/nx/project.json | 5 + packages/nx/src/config/nx-json.ts | 4 + packages/nx/src/daemon/client/client.ts | 5 +- packages/nx/src/daemon/server/server.ts | 84 +- .../nx/src/daemon/server/shutdown-utils.ts | 32 + packages/nx/src/daemon/server/watcher.ts | 70 +- packages/nx/src/native/index.d.ts | 22 + packages/nx/src/native/index.js | 4 +- packages/nx/src/native/mod.rs | 1 + packages/nx/src/native/native_hasher.rs | 6 +- packages/nx/src/native/tests/native.spec.ts | 146 +- packages/nx/src/native/watch/mod.rs | 5 + packages/nx/src/native/watch/types.rs | 78 + packages/nx/src/native/watch/utils.rs | 47 + packages/nx/src/native/watch/watch_config.rs | 45 + .../nx/src/native/watch/watch_filterer.rs | 64 + packages/nx/src/native/watch/watcher.rs | 203 +++ packages/nx/src/utils/testing/temp-fs.ts | 24 +- 23 files changed, 2361 insertions(+), 80 deletions(-) create mode 100644 packages/nx/src/native/watch/mod.rs create mode 100644 packages/nx/src/native/watch/types.rs create mode 100644 packages/nx/src/native/watch/utils.rs create mode 100644 packages/nx/src/native/watch/watch_config.rs create mode 100644 packages/nx/src/native/watch/watch_filterer.rs create mode 100644 packages/nx/src/native/watch/watcher.rs diff --git a/.circleci/config.yml b/.circleci/config.yml index 51ec3e84c0..a82ddb8ae9 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -126,6 +126,7 @@ jobs: SELECTED_PM: << parameters.pm >> NX_E2E_RUN_CYPRESS: 'true' NX_VERBOSE_LOGGING: 'false' + NX_NATIVE_LOGGING: 'false' NX_PERF_LOGGING: 'false' steps: - run: @@ -154,6 +155,7 @@ jobs: NX_VERBOSE_LOGGING: 'false' NX_DAEMON: 'true' NX_PERF_LOGGING: 'false' + NX_NATIVE_LOGGING: 'false' steps: - run: name: Set dynamic nx run variable diff --git a/Cargo.lock b/Cargo.lock index 8814625787..ad0cd76aec 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11,6 +11,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "anyhow" +version = "1.0.71" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8" + [[package]] name = "assert_fs" version = "1.0.10" @@ -25,6 +31,49 @@ dependencies = [ "tempfile", ] +[[package]] +name = "async-priority-channel" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c21678992e1b21bebfe2bc53ab5f5f68c106eddab31b24e0bb06e9b715a86640" +dependencies = [ + "event-listener", +] + +[[package]] +name = "async-recursion" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e97ce7de6cf12de5d7226c73f5ba9811622f4db3a5b91b55c53e987e5f91cba" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.15", +] + +[[package]] +name = "async-trait" +version = "0.1.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9ccdd8f2a161be9bd5c023df56f1b2a0bd1d83872ae53b71a84a12c9bf6e842" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.15", +] + +[[package]] +name = "atomic-take" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8ab6b55fe97976e46f91ddbed8d147d966475dc29b2032757ba47e02376fbc3" + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + [[package]] name = "bitflags" version = "1.3.2" @@ -32,21 +81,75 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] -name = "bstr" -version = "1.1.0" +name = "bitflags" +version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b45ea9b00a7b3f2988e9a65ad3917e62123c38dba709b666506207be96d1790b" +checksum = "24a6904aef64d73cf10ab17ebace7befb918b82164785cb89907993be7f83813" + +[[package]] +name = "bstr" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d4260bcc2e8fc9df1eac4919a720effeb63a3f0952f5bf4944adfa18897f09" dependencies = [ "memchr", + "once_cell", + "regex-automata", "serde", ] +[[package]] +name = "btoi" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dd6407f73a9b8b6162d8a2ef999fe6afd7cc15902ebf42c5cd296addf17e0ad" +dependencies = [ + "num-traits", +] + +[[package]] +name = "bytes" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" + +[[package]] +name = "cc" +version = "1.0.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" + [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "clearscreen" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f3f22f1a586604e62efd23f78218f3ccdecf7a33c4500db2d37d85a24fe994" +dependencies = [ + "nix", + "terminfo", + "thiserror", + "which", + "winapi", +] + +[[package]] +name = "command-group" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5080df6b0f0ecb76cab30808f00d937ba725cebe266a3da8cd89dff92f2a9916" +dependencies = [ + "async-trait", + "nix", + "tokio", + "winapi", +] + [[package]] name = "convert_case" version = "0.6.0" @@ -66,6 +169,30 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "crossbeam-deque" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef" +dependencies = [ + "cfg-if", + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46bd5f3f85273295a9d14aedfb86f6aadbff6d8f5295c4a9edb08e819dcf5695" +dependencies = [ + "autocfg", + "cfg-if", + "crossbeam-utils", + "memoffset 0.8.0", + "scopeguard", +] + [[package]] name = "crossbeam-utils" version = "0.8.14" @@ -77,12 +204,12 @@ dependencies = [ [[package]] name = "ctor" -version = "0.1.26" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d2301688392eb071b0bf1a37be05c469d3cc4dbbd95df672fe28ab021e6a096" +checksum = "dd4056f63fce3b82d852c3da92b08ea59959890813a7f4ce9c0ff85b10cf301b" dependencies = [ "quote", - "syn", + "syn 2.0.15", ] [[package]] @@ -91,12 +218,38 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8" +[[package]] +name = "dirs" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + [[package]] name = "doc-comment" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" +[[package]] +name = "dunce" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56ce8c6da7551ec6c462cbaf3bfbc75131ebbfa1c944aeaa9dab51ca1c5f0c3b" + [[package]] name = "either" version = "1.8.0" @@ -104,20 +257,384 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797" [[package]] -name = "fastrand" -version = "1.8.0" +name = "endian-type" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a407cfaa3385c4ae6b23e84623d48c2798d06e3e6a1878f7f59f17b3f86499" +checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d" + +[[package]] +name = "errno" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" +dependencies = [ + "errno-dragonfly", + "libc", + "windows-sys 0.48.0", +] + +[[package]] +name = "errno-dragonfly" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "event-listener" +version = "2.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" + +[[package]] +name = "fastrand" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" dependencies = [ "instant", ] +[[package]] +name = "filetime" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cbc844cecaee9d4443931972e1289c8ff485cb4cc2767cb03ca139ed6885153" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall 0.2.16", + "windows-sys 0.48.0", +] + [[package]] name = "fnv" version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "fsevent-sys" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76ee7a02da4d231650c7cea31349b889be2f45ddb3ef3032d2ec8185f6313fd2" +dependencies = [ + "libc", +] + +[[package]] +name = "futures" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" + +[[package]] +name = "futures-executor" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" + +[[package]] +name = "futures-macro" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.15", +] + +[[package]] +name = "futures-sink" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" + +[[package]] +name = "futures-task" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" + +[[package]] +name = "futures-util" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "getrandom" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c85e1d9ab2eadba7e5040d4e09cbd6d072b76a557ad64e797c2cb9d4da21d7e4" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "gix-actor" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "848efa0f1210cea8638f95691c82a46f98a74b9e3524f01d4955ebc25a8f84f3" +dependencies = [ + "bstr", + "btoi", + "gix-date", + "itoa", + "nom", + "thiserror", +] + +[[package]] +name = "gix-config" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d252a0eddb6df74600d3d8872dc9fe98835a7da43110411d705b682f49d4ac1" +dependencies = [ + "bstr", + "gix-config-value", + "gix-features", + "gix-glob", + "gix-path", + "gix-ref", + "gix-sec", + "log", + "memchr", + "nom", + "once_cell", + "smallvec", + "thiserror", + "unicode-bom", +] + +[[package]] +name = "gix-config-value" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "786861e84a5793ad5f863d846de5eb064cd23b87e61ad708c8c402608202e7be" +dependencies = [ + "bitflags 2.2.1", + "bstr", + "gix-path", + "libc", + "thiserror", +] + +[[package]] +name = "gix-date" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99056f37270715f5c7584fd8b46899a2296af9cae92463bf58b8bd1f5a78e553" +dependencies = [ + "bstr", + "itoa", + "thiserror", + "time", +] + +[[package]] +name = "gix-features" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf69b0f5c701cc3ae22d3204b671907668f6437ca88862d355eaf9bc47a4f897" +dependencies = [ + "gix-hash", + "libc", + "sha1_smol", + "walkdir", +] + +[[package]] +name = "gix-fs" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b37a1832f691fdc09910bd267f9a2e413737c1f9ec68c6e31f9e802616278a9" +dependencies = [ + "gix-features", +] + +[[package]] +name = "gix-glob" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c07c98204529ac3f24b34754540a852593d2a4c7349008df389240266627a72a" +dependencies = [ + "bitflags 2.2.1", + "bstr", + "gix-features", + "gix-path", +] + +[[package]] +name = "gix-hash" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "078eec3ac2808cc03f0bddd2704cb661da5c5dc33b41a9d7947b141d499c7c42" +dependencies = [ + "hex", + "thiserror", +] + +[[package]] +name = "gix-lock" +version = "5.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c693d7f05730fa74a7c467150adc7cea393518410c65f0672f80226b8111555" +dependencies = [ + "gix-tempfile", + "gix-utils", + "thiserror", +] + +[[package]] +name = "gix-object" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9bb30ce0818d37096daa29efe361a4bc6dd0b51a5726598898be7e9a40a01e1" +dependencies = [ + "bstr", + "btoi", + "gix-actor", + "gix-features", + "gix-hash", + "gix-validate", + "hex", + "itoa", + "nom", + "smallvec", + "thiserror", +] + +[[package]] +name = "gix-path" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fc78f47095a0c15aea0e66103838f0748f4494bf7a9555dfe0f00425400396c" +dependencies = [ + "bstr", + "home", + "once_cell", + "thiserror", +] + +[[package]] +name = "gix-ref" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e03989e9d49954368e1b526578230fc7189d1634acdfbe79e9ba1de717e15d5" +dependencies = [ + "gix-actor", + "gix-features", + "gix-fs", + "gix-hash", + "gix-lock", + "gix-object", + "gix-path", + "gix-tempfile", + "gix-validate", + "memmap2", + "nom", + "thiserror", +] + +[[package]] +name = "gix-sec" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "794520043d5a024dfeac335c6e520cb616f6963e30dab995892382e998c12897" +dependencies = [ + "bitflags 2.2.1", + "gix-path", + "libc", + "windows", +] + +[[package]] +name = "gix-tempfile" +version = "5.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71a0d32f34e71e86586124225caefd78dabc605d0486de580d717653addf182" +dependencies = [ + "gix-fs", + "libc", + "once_cell", + "parking_lot", + "tempfile", +] + +[[package]] +name = "gix-utils" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c10b69beac219acb8df673187a1f07dde2d74092f974fb3f9eb385aeb667c909" +dependencies = [ + "fastrand", +] + +[[package]] +name = "gix-validate" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7bd629d3680773e1785e585d76fd4295b740b559cad9141517300d99a0c8c049" +dependencies = [ + "bstr", + "thiserror", +] + [[package]] name = "globset" version = "0.4.10" @@ -137,16 +654,46 @@ version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "93e3af942408868f6934a7b85134a3230832b9977cf66125df2f9edcfce4ddcc" dependencies = [ - "bitflags", + "bitflags 1.3.2", "ignore", "walkdir", ] [[package]] -name = "ignore" -version = "0.4.19" +name = "hermit-abi" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a05705bc64e0b66a806c3740bd6578ea66051b157ec42dc219c785cbf185aef3" +checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" +dependencies = [ + "libc", +] + +[[package]] +name = "hermit-abi" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "home" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5444c27eef6923071f7ebcc33e3444508466a76f7a2b93da00ed6e19f30c1ddb" +dependencies = [ + "windows-sys 0.48.0", +] + +[[package]] +name = "ignore" +version = "0.4.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbe7873dab538a9a44ad79ede1faf5f30d49f9a5c883ddbab48bce81b64b7492" dependencies = [ "globset", "lazy_static", @@ -159,6 +706,44 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "ignore-files" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad7ef2262641fef97d2e3230dc14b50d80418a2b7a22497457f23c533e8a75de" +dependencies = [ + "dunce", + "futures", + "gix-config", + "ignore", + "miette", + "project-origins", + "radix_trie", + "thiserror", + "tokio", + "tracing", +] + +[[package]] +name = "inotify" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8069d3ec154eb856955c1c0fbffefbf5f3c40a104ec912d4797314c1801abff" +dependencies = [ + "bitflags 1.3.2", + "inotify-sys", + "libc", +] + +[[package]] +name = "inotify-sys" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb" +dependencies = [ + "libc", +] + [[package]] name = "instant" version = "0.1.12" @@ -168,6 +753,17 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "io-lifetimes" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c66c74d2ae7e79a5a8f7ac924adbe38ee42a859c6539ad869eb51f0b52dc220" +dependencies = [ + "hermit-abi 0.3.1", + "libc", + "windows-sys 0.48.0", +] + [[package]] name = "itertools" version = "0.10.5" @@ -177,6 +773,32 @@ dependencies = [ "either", ] +[[package]] +name = "itoa" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" + +[[package]] +name = "kqueue" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c8fc60ba15bf51257aa9807a48a61013db043fcf3a78cb0d916e8e396dcad98" +dependencies = [ + "kqueue-sys", + "libc", +] + +[[package]] +name = "kqueue-sys" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8367585489f01bc55dd27404dcf56b95e6da061a256a666ab23be9ba96a2e587" +dependencies = [ + "bitflags 1.3.2", + "libc", +] + [[package]] name = "lazy_static" version = "1.4.0" @@ -199,6 +821,22 @@ dependencies = [ "winapi", ] +[[package]] +name = "linux-raw-sys" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ece97ea872ece730aed82664c424eb4c8291e1ff2480247ccf7409044bc6479f" + +[[package]] +name = "lock_api" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" +dependencies = [ + "autocfg", + "scopeguard", +] + [[package]] name = "log" version = "0.4.17" @@ -208,6 +846,15 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "matchers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +dependencies = [ + "regex-automata", +] + [[package]] name = "memchr" version = "2.5.0" @@ -215,16 +862,86 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" [[package]] -name = "napi" -version = "2.10.4" +name = "memmap2" +version = "0.5.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "838b5b414a008e75b97edb3c3e6f189034af789a0608686299b149d3b0e66c39" +checksum = "83faa42c0a078c393f6b29d5db232d8be22776a891f8f56e5284faee4a20b327" dependencies = [ - "bitflags", + "libc", +] + +[[package]] +name = "memoffset" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4" +dependencies = [ + "autocfg", +] + +[[package]] +name = "memoffset" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d61c719bcfbcf5d62b3a09efa6088de8c54bc0bfcd3ea7ae39fcc186108b8de1" +dependencies = [ + "autocfg", +] + +[[package]] +name = "miette" +version = "5.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92a992891d5579caa9efd8e601f82e30a1caa79a27a5db075dde30ecb9eab357" +dependencies = [ + "miette-derive", + "once_cell", + "thiserror", + "unicode-width", +] + +[[package]] +name = "miette-derive" +version = "5.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c65c625186a9bcce6699394bee511e1b1aec689aa7e3be1bf4e996e75834153" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.15", +] + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "mio" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b9d9a46eff5b4ff64b45a9e316a6d1e0bc719ef429cbec4dc630684212bfdf9" +dependencies = [ + "libc", + "log", + "wasi", + "windows-sys 0.45.0", +] + +[[package]] +name = "napi" +version = "2.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49ac8112fe5998579b22e29903c7b277fc7f91c7860c0236f35792caf8156e18" +dependencies = [ + "anyhow", + "bitflags 2.2.1", "ctor", + "napi-derive", "napi-sys", "once_cell", - "thread_local", + "tokio", ] [[package]] @@ -235,58 +952,244 @@ checksum = "882a73d9ef23e8dc2ebbffb6a6ae2ef467c0f18ac10711e4cc59c5485d41df0e" [[package]] name = "napi-derive" -version = "2.9.3" +version = "2.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af4e44e34e70aa61be9036ae652e27c20db5bca80e006be0f482419f6601352a" +checksum = "c47e0f395207c062e680a158f0624ec456c1dfb3c96a8cb888e0401506d50ae9" dependencies = [ + "cfg-if", "convert_case", "napi-derive-backend", "proc-macro2", "quote", - "syn", + "syn 1.0.107", ] [[package]] name = "napi-derive-backend" -version = "1.0.40" +version = "1.0.51" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17925fff04b6fa636f8e4b4608cc1a4f1360b64ac8ecbfdb7da1be1dc74f6843" +checksum = "0a83afae5b4ba6f98ed6e33a52da343fdeb66474f1162a38cde5a3d46eb054e7" dependencies = [ "convert_case", "once_cell", "proc-macro2", "quote", "regex", - "syn", + "semver", + "syn 1.0.107", ] [[package]] name = "napi-sys" -version = "2.2.2" +version = "2.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "529671ebfae679f2ce9630b62dd53c72c56b3eb8b2c852e7e2fa91704ff93d67" +checksum = "166b5ef52a3ab5575047a9fe8d4a030cdd0f63c96f071cd6907674453b07bae3" dependencies = [ "libloading", ] +[[package]] +name = "nibble_vec" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a5d83df9f36fe23f0c3648c6bbb8b0298bb5f1939c8f2704431371f4b84d43" +dependencies = [ + "smallvec", +] + +[[package]] +name = "nix" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfdda3d196821d6af13126e40375cdf7da646a96114af134d5f417a9a1dc8e1a" +dependencies = [ + "bitflags 1.3.2", + "cfg-if", + "libc", + "memoffset 0.7.1", + "pin-utils", + "static_assertions", +] + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "normalize-path" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf22e319b2e3cb517350572e3b70c6822e0a520abfb5c78f690e829a73e8d9f2" + +[[package]] +name = "notify" +version = "5.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58ea850aa68a06e48fdb069c0ec44d0d64c8dbffa49bf3b6f7f0a901fdea1ba9" +dependencies = [ + "bitflags 1.3.2", + "crossbeam-channel", + "filetime", + "fsevent-sys", + "inotify", + "kqueue", + "libc", + "mio", + "walkdir", + "windows-sys 0.42.0", +] + +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + +[[package]] +name = "num-traits" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_cpus" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" +dependencies = [ + "hermit-abi 0.2.6", + "libc", +] + +[[package]] +name = "num_threads" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44" +dependencies = [ + "libc", +] + [[package]] name = "nx" version = "0.1.0" dependencies = [ + "anyhow", "assert_fs", "crossbeam-channel", "ignore", + "ignore-files", + "itertools", "napi", "napi-build", "napi-derive", + "rayon", + "tracing", + "tracing-subscriber", + "watchexec", + "watchexec-events", + "watchexec-filterer-ignore", + "watchexec-signals", "xxhash-rust", ] [[package]] name = "once_cell" -version = "1.17.0" +version = "1.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66" +checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" + +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + +[[package]] +name = "parking_lot" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9069cbb9f99e3a5083476ccb29ceb1de18b9118cafa53e90c9551235de2b9521" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall 0.2.16", + "smallvec", + "windows-sys 0.45.0", +] + +[[package]] +name = "phf" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "928c6535de93548188ef63bb7c4036bd415cd8f36ad25af44b9789b2ee72a48c" +dependencies = [ + "phf_shared", +] + +[[package]] +name = "phf_codegen" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a56ac890c5e3ca598bbdeaa99964edb5b0258a583a9eb6ef4e89fc85d9224770" +dependencies = [ + "phf_generator", + "phf_shared", +] + +[[package]] +name = "phf_generator" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1181c94580fa345f50f19d738aaa39c0ed30a600d95cb2d3e23f94266f14fbf" +dependencies = [ + "phf_shared", + "rand", +] + +[[package]] +name = "phf_shared" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1fb5f6f826b772a8d4c0394209441e7d37cbbb967ae9c7e0e8134365c9ee676" +dependencies = [ + "siphasher", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "predicates" @@ -317,29 +1220,107 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.49" +version = "1.0.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57a8eca9f9c4ffde41714334dee777596264c7825420f521abc92b5b5deb63a5" +checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435" dependencies = [ "unicode-ident", ] [[package]] -name = "quote" -version = "1.0.23" +name = "project-origins" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b" +checksum = "629e0d57f265ca8238345cb616eea8847b8ecb86b5d97d155be2c8963a314379" +dependencies = [ + "futures", + "tokio", + "tokio-stream", +] + +[[package]] +name = "quote" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f4f29d145265ec1c483c7c654450edde0bfe043d3938d6972630663356d9500" dependencies = [ "proc-macro2", ] +[[package]] +name = "radix_trie" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c069c179fcdc6a2fe24d8d18305cf085fdbd4f922c041943e203685d6a1c58fd" +dependencies = [ + "endian-type", + "nibble_vec", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" + +[[package]] +name = "rayon" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d2df5196e37bcc87abebc0053e20787d73847bb33134a69841207dd0a47f03b" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b8f95bd6966f5c87776639160a66bd8ab9895d9d4ab01ddba9fc60661aebe8d" +dependencies = [ + "crossbeam-channel", + "crossbeam-deque", + "crossbeam-utils", + "num_cpus", +] + [[package]] name = "redox_syscall" version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" dependencies = [ - "bitflags", + "bitflags 1.3.2", +] + +[[package]] +name = "redox_syscall" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "redox_users" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" +dependencies = [ + "getrandom", + "redox_syscall 0.2.16", + "thiserror", ] [[package]] @@ -353,6 +1334,15 @@ dependencies = [ "regex-syntax", ] +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax", +] + [[package]] name = "regex-syntax" version = "0.6.28" @@ -360,12 +1350,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" [[package]] -name = "remove_dir_all" -version = "0.5.3" +name = "rustix" +version = "0.37.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" +checksum = "62b24138615de35e32031d041a09032ef3487a616d901ca4db224e7d557efae2" dependencies = [ - "winapi", + "bitflags 1.3.2", + "errno", + "io-lifetimes", + "libc", + "linux-raw-sys", + "windows-sys 0.45.0", ] [[package]] @@ -377,12 +1372,75 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[package]] +name = "semver" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed" + [[package]] name = "serde" version = "1.0.152" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb" +[[package]] +name = "sha1_smol" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae1a47186c03a32177042e55dbc5fd5aee900b8e0069a8d70fba96a9375cd012" + +[[package]] +name = "sharded-slab" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" +dependencies = [ + "libc", +] + +[[package]] +name = "siphasher" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7bd3e3206899af3f8b12af284fafc038cc1dc2b41d1b89dd17297221c5d225de" + +[[package]] +name = "slab" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d" +dependencies = [ + "autocfg", +] + +[[package]] +name = "smallvec" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + [[package]] name = "syn" version = "1.0.107" @@ -395,17 +1453,40 @@ dependencies = [ ] [[package]] -name = "tempfile" -version = "3.3.0" +name = "syn" +version = "2.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" +checksum = "a34fcf3e8b60f57e6a14301a2e916d323af98b0ea63c599441eec8558660c822" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tempfile" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9fbec84f381d5795b08656e4912bec604d162bff9291d6189a78f4c8ab87998" dependencies = [ "cfg-if", "fastrand", - "libc", - "redox_syscall", - "remove_dir_all", - "winapi", + "redox_syscall 0.3.5", + "rustix", + "windows-sys 0.45.0", +] + +[[package]] +name = "terminfo" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "666cd3a6681775d22b200409aad3b089c5b99fb11ecdd8a204d9d62f8148498f" +dependencies = [ + "dirs", + "fnv", + "nom", + "phf", + "phf_codegen", ] [[package]] @@ -414,6 +1495,26 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95059e91184749cb66be6dc994f67f182b6d897cb3df74a5bf66b5e709295fd8" +[[package]] +name = "thiserror" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.15", +] + [[package]] name = "thread_local" version = "1.1.4" @@ -423,6 +1524,143 @@ dependencies = [ "once_cell", ] +[[package]] +name = "time" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f3403384eaacbca9923fa06940178ac13e4edb725486d70e8e15881d0c836cc" +dependencies = [ + "itoa", + "libc", + "num_threads", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb" + +[[package]] +name = "time-macros" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "372950940a5f07bf38dbe211d7283c9e6d7327df53794992d293e534c733d09b" +dependencies = [ + "time-core", +] + +[[package]] +name = "tokio" +version = "1.28.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0aa32867d44e6f2ce3385e89dceb990188b8bb0fb25b0cf576647a6f98ac5105" +dependencies = [ + "autocfg", + "bytes", + "libc", + "mio", + "num_cpus", + "pin-project-lite", + "signal-hook-registry", + "tokio-macros", + "windows-sys 0.48.0", +] + +[[package]] +name = "tokio-macros" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.15", +] + +[[package]] +name = "tokio-stream" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "397c988d37662c7dda6d2208364a706264bf3d6138b11d436cbac0ad38832842" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tracing" +version = "0.1.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" +dependencies = [ + "cfg-if", + "log", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f57e3ca2a01450b1a921183a9c9cbfda207fd822cef4ccb00a65402cbba7a74" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.15", +] + +[[package]] +name = "tracing-core" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922" +dependencies = [ + "lazy_static", + "log", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30a651bc37f915e81f087d86e62a18eec5f79550c7faff886f7090b4ea757c77" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "unicode-bom" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98e90c70c9f0d4d1ee6d0a7d04aa06cb9bbd53d8cfbdd62a0269a7c2eb640552" + [[package]] name = "unicode-ident" version = "1.0.6" @@ -435,6 +1673,18 @@ version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fdbf052a0783de01e944a6ce7a8cb939e295b1e7be835a1112c3b9a7f047a5a" +[[package]] +name = "unicode-width" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" + +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + [[package]] name = "walkdir" version = "2.3.2" @@ -446,6 +1696,85 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "watchexec" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8b97d05a9305a9aa6a7bedef64cd012ebc9b6f1f5ed0368fb48f0fe58f96988" +dependencies = [ + "async-priority-channel", + "async-recursion", + "atomic-take", + "clearscreen", + "command-group", + "futures", + "ignore-files", + "miette", + "nix", + "normalize-path", + "notify", + "once_cell", + "project-origins", + "thiserror", + "tokio", + "tracing", + "watchexec-events", + "watchexec-signals", +] + +[[package]] +name = "watchexec-events" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01603bbe02fd75918f010dadad456d47eda14fb8fdcab276b0b4b8362f142ae3" +dependencies = [ + "nix", + "notify", + "watchexec-signals", +] + +[[package]] +name = "watchexec-filterer-ignore" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f345f020367ccdb7a19fcd59cafe5b8e99acdaf937b19d5847a6a1b81fd44ea3" +dependencies = [ + "dunce", + "ignore", + "ignore-files", + "tracing", + "watchexec", + "watchexec-signals", +] + +[[package]] +name = "watchexec-signals" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc2a5df96c388901c94ca04055fcd51d4196ca3e971c5e805bd4a4b61dd6a7e5" +dependencies = [ + "miette", + "nix", + "thiserror", +] + +[[package]] +name = "which" +version = "4.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2441c784c52b289a054b7201fc93253e288f094e2f4be9058343127c4226a269" +dependencies = [ + "either", + "libc", + "once_cell", +] + [[package]] name = "winapi" version = "0.3.9" @@ -477,6 +1806,162 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" +dependencies = [ + "windows-targets 0.48.0", +] + +[[package]] +name = "windows-sys" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.0", +] + +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + +[[package]] +name = "windows-targets" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" +dependencies = [ + "windows_aarch64_gnullvm 0.48.0", + "windows_aarch64_msvc 0.48.0", + "windows_i686_gnu 0.48.0", + "windows_i686_msvc 0.48.0", + "windows_x86_64_gnu 0.48.0", + "windows_x86_64_gnullvm 0.48.0", + "windows_x86_64_msvc 0.48.0", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" + [[package]] name = "xxhash-rust" version = "0.8.6" diff --git a/Cargo.toml b/Cargo.toml index 8e4a982785..c4f876bf1a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,8 @@ [workspace] -members = ['packages/nx'] +members = [ + 'packages/nx' +] [profile.release] lto = true diff --git a/nx.json b/nx.json index 945e6e0145..5fc6058d27 100644 --- a/nx.json +++ b/nx.json @@ -6,6 +6,7 @@ "tasksRunnerOptions": { "default": { "runner": "nx-cloud", + "nativeWatcher": true, "options": { "accessToken": "NDg1NTA3MTAtOGFmZC00YmIwLTk2Y2MtOTkzNzc4ZTczYTlkfHJlYWQtb25seQ==", "cacheableOperations": [ diff --git a/packages/nx/Cargo.toml b/packages/nx/Cargo.toml index 5b94c12553..d3b91f0a2e 100644 --- a/packages/nx/Cargo.toml +++ b/packages/nx/Cargo.toml @@ -5,11 +5,23 @@ edition = '2021' [dependencies] xxhash-rust = { version = '0.8.5', features = ['xxh3', 'xxh64'] } -napi = { version = '2.10.2', default-features = false, features = ['napi4'] } +napi = { version = '2.12.6', default-features = false, features = ['anyhow', 'napi4', 'tokio_rt'] } napi-derive = '2.9.3' ignore = '0.4' crossbeam-channel = '0.5' +ignore-files = "1.3.0" +watchexec = "2.3.0" +watchexec-filterer-ignore = "1.2.1" +watchexec-events = "1.0.0" +watchexec-signals = "1.0.0" + +tracing = "0.1.37" +tracing-subscriber = { version = "0.3.17", features = ["env-filter"]} +anyhow = "1.0.71" +itertools = "0.10.5" +rayon = "1.7.0" + [lib] crate-type = ['cdylib'] diff --git a/packages/nx/project.json b/packages/nx/project.json index b291e8c93d..9f9b3715f0 100644 --- a/packages/nx/project.json +++ b/packages/nx/project.json @@ -12,6 +12,11 @@ "dist": "packages/nx/src/native", "jsFile": "packages/nx/src/native/index.js", "release": true + }, + "configurations": { + "local": { + "release": false + } } }, "artifacts": { diff --git a/packages/nx/src/config/nx-json.ts b/packages/nx/src/config/nx-json.ts index e3d07b59ad..3ddd158b66 100644 --- a/packages/nx/src/config/nx-json.ts +++ b/packages/nx/src/config/nx-json.ts @@ -96,6 +96,10 @@ export interface NxJsonConfiguration { * Default options for the runner */ options?: any; + /** + * Enables the Rust watcher within the daemon + */ + nativeWatcher?: boolean; }; }; /** diff --git a/packages/nx/src/daemon/client/client.ts b/packages/nx/src/daemon/client/client.ts index 3b0db1038f..59800a3f0d 100644 --- a/packages/nx/src/daemon/client/client.ts +++ b/packages/nx/src/daemon/client/client.ts @@ -339,6 +339,10 @@ export class DaemonClient { this._out = await open(DAEMON_OUTPUT_LOG_FILE, 'a'); this._err = await open(DAEMON_OUTPUT_LOG_FILE, 'a'); + if (this.nxJson.tasksRunnerOptions.default?.nativeWatcher) { + DAEMON_ENV_SETTINGS['NX_NATIVE_WATCHER'] = true; + } + const backgroundProcess = spawn( process.execPath, [join(__dirname, '../server/start.js')], @@ -352,7 +356,6 @@ export class DaemonClient { } ); backgroundProcess.unref(); - // /** * Ensure the server is actually available to connect to via IPC before resolving diff --git a/packages/nx/src/daemon/server/server.ts b/packages/nx/src/daemon/server/server.ts index 44332d03fa..621562c26f 100644 --- a/packages/nx/src/daemon/server/server.ts +++ b/packages/nx/src/daemon/server/server.ts @@ -10,15 +10,19 @@ import { import { serverLogger } from './logger'; import { getOutputsWatcherSubscription, + getOutputWatcherInstance, getSourceWatcherSubscription, + getWatcherInstance, handleServerProcessTermination, resetInactivityTimeout, respondToClient, respondWithErrorAndExit, SERVER_INACTIVITY_TIMEOUT_MS, storeOutputsWatcherSubscription, + storeOutputWatcherInstance, storeProcessJsonSubscription, storeSourceWatcherSubscription, + storeWatcherInstance, } from './shutdown-utils'; import { convertChangeEventsToLogMessage, @@ -26,6 +30,8 @@ import { subscribeToWorkspaceChanges, FileWatcherCallback, subscribeToServerProcessJsonChanges, + watchWorkspace, + watchOutputFiles, } from './watcher'; import { addUpdatedAndDeletedFiles } from './project-graph-incremental-recomputation'; import { existsSync, statSync } from 'fs'; @@ -232,6 +238,10 @@ function registerProcessTerminationListeners() { } async function registerProcessServerJsonTracking() { + if (useNativeWatcher()) { + return; + } + storeProcessJsonSubscription( await subscribeToServerProcessJsonChanges(async () => { if (getDaemonProcessIdSync() !== process.pid) { @@ -312,12 +322,13 @@ const handleWorkspaceChanges: FileWatcherCallback = async ( } if (err || !changeEvents || !changeEvents.length) { + let error = typeof err === 'string' ? new Error(err) : err; serverLogger.watcherLog( 'Unexpected workspace watcher error', - err.message + error.message ); - console.error(err); - workspaceWatcherError = err; + console.error(error); + workspaceWatcherError = error; return; } @@ -361,15 +372,21 @@ const handleWorkspaceChanges: FileWatcherCallback = async ( const handleOutputsChanges: FileWatcherCallback = async (err, changeEvents) => { try { if (err || !changeEvents || !changeEvents.length) { - serverLogger.watcherLog('Unexpected outputs watcher error', err.message); - console.error(err); - outputsWatcherError = err; + let error = typeof err === 'string' ? new Error(err) : err; + serverLogger.watcherLog( + 'Unexpected outputs watcher error', + error.message + ); + console.error(error); + outputsWatcherError = error; disableOutputsTracking(); return; } if (outputsWatcherError) { return; } + + serverLogger.watcherLog('Processing file changes in outputs'); processFileChangesInOutputs(changeEvents); } catch (err) { serverLogger.watcherLog(`Unexpected outputs watcher error`, err.message); @@ -398,25 +415,46 @@ export async function startServer(): Promise { // this triggers the storage of the lock file hash daemonIsOutdated(); - if (!getSourceWatcherSubscription()) { - storeSourceWatcherSubscription( - await subscribeToWorkspaceChanges(server, handleWorkspaceChanges) - ); - serverLogger.watcherLog( - `Subscribed to changes within: ${workspaceRoot}` - ); - } + if (useNativeWatcher()) { + if (!getWatcherInstance()) { + storeWatcherInstance( + await watchWorkspace(server, handleWorkspaceChanges) + ); - // temporary disable outputs tracking on linux - const outputsTrackingIsEnabled = process.platform != 'linux'; - if (outputsTrackingIsEnabled) { - if (!getOutputsWatcherSubscription()) { - storeOutputsWatcherSubscription( - await subscribeToOutputsChanges(handleOutputsChanges) + serverLogger.watcherLog( + `Subscribed to changes within: ${workspaceRoot} (native)` + ); + } + + if (!getOutputWatcherInstance()) { + storeOutputWatcherInstance( + await watchOutputFiles(handleOutputsChanges) ); } } else { - disableOutputsTracking(); + if (!getSourceWatcherSubscription()) { + storeSourceWatcherSubscription( + await subscribeToWorkspaceChanges( + server, + handleWorkspaceChanges + ) + ); + serverLogger.watcherLog( + `Subscribed to changes within: ${workspaceRoot}` + ); + } + + // temporary disable outputs tracking on linux + const outputsTrackingIsEnabled = process.platform != 'linux'; + if (outputsTrackingIsEnabled) { + if (!getOutputsWatcherSubscription()) { + storeOutputsWatcherSubscription( + await subscribeToOutputsChanges(handleOutputsChanges) + ); + } + } else { + disableOutputsTracking(); + } } return resolve(server); @@ -429,3 +467,7 @@ export async function startServer(): Promise { } }); } + +function useNativeWatcher() { + return process.env.NX_NATIVE_WATCHER === 'true'; +} diff --git a/packages/nx/src/daemon/server/shutdown-utils.ts b/packages/nx/src/daemon/server/shutdown-utils.ts index 2ffb3a0106..9bbc05053a 100644 --- a/packages/nx/src/daemon/server/shutdown-utils.ts +++ b/packages/nx/src/daemon/server/shutdown-utils.ts @@ -4,6 +4,7 @@ import { serverLogger } from './logger'; import { serializeResult } from '../socket-utils'; import type { AsyncSubscription } from '@parcel/watcher'; import { deleteDaemonJsonProcessCache } from '../cache'; +import type { Watcher } from '../../native'; export const SERVER_INACTIVITY_TIMEOUT_MS = 10800000 as const; // 10800000 ms = 3 hours @@ -32,6 +33,22 @@ export function storeProcessJsonSubscription(s: AsyncSubscription) { processJsonSubscription = s; } +let watcherInstance: Watcher | undefined; +export function storeWatcherInstance(instance: Watcher) { + watcherInstance = instance; +} +export function getWatcherInstance() { + return watcherInstance; +} + +let outputWatcherInstance: Watcher | undefined; +export function storeOutputWatcherInstance(instance: Watcher) { + outputWatcherInstance = instance; +} +export function getOutputWatcherInstance() { + return outputWatcherInstance; +} + interface HandleServerProcessTerminationParams { server: Server; reason: string; @@ -62,6 +79,21 @@ export async function handleServerProcessTermination({ `Unsubscribed from changes within: ${workspaceRoot} (server-process.json)` ); } + + if (watcherInstance) { + await watcherInstance.stop(); + serverLogger.watcherLog( + `Stopping the watcher for ${workspaceRoot} (sources)` + ); + } + + if (outputWatcherInstance) { + await outputWatcherInstance.stop(); + serverLogger.watcherLog( + `Stopping the watcher for ${workspaceRoot} (outputs)` + ); + } + serverLogger.log(`Server stopped because: "${reason}"`); } finally { process.exit(0); diff --git a/packages/nx/src/daemon/server/watcher.ts b/packages/nx/src/daemon/server/watcher.ts index e5bea7e007..3c1ce15fca 100644 --- a/packages/nx/src/daemon/server/watcher.ts +++ b/packages/nx/src/daemon/server/watcher.ts @@ -18,13 +18,14 @@ import { getIgnoreObject, } from '../../utils/ignore'; import { platform } from 'os'; -import { serverProcessJsonPath } from '../cache'; +import { getDaemonProcessIdSync, serverProcessJsonPath } from '../cache'; +import type { WatchEvent } from '../../native'; const ALWAYS_IGNORE = [...getAlwaysIgnore(workspaceRoot), FULL_OS_SOCKET_PATH]; export type FileWatcherCallback = ( - err: Error | null, - changeEvents: Event[] | null + err: Error | string | null, + changeEvents: Event[] | WatchEvent[] | null ) => Promise; export async function subscribeToOutputsChanges( @@ -52,6 +53,69 @@ export async function subscribeToOutputsChanges( ); } +export async function watchWorkspace(server: Server, cb: FileWatcherCallback) { + const { Watcher } = await import('../../native'); + + let relativeServerProcess = normalizePath( + relative(workspaceRoot, serverProcessJsonPath) + ); + + let watcher = new Watcher(workspaceRoot, [`!${relativeServerProcess}`]); + watcher.watch((err, events) => { + if (err) { + return cb(err, null); + } + + for (const event of events) { + if ( + event.path == relativeServerProcess && + getDaemonProcessIdSync() !== process.pid + ) { + handleServerProcessTermination({ + server, + reason: 'this process is no longer the current daemon (native)', + }); + } + + if (event.path.endsWith('.gitignore') || event.path === '.nxignore') { + // If the ignore files themselves have changed we need to dynamically update our cached ignoreGlobs + handleServerProcessTermination({ + server, + reason: + 'Stopping the daemon the set of ignored files changed (native)', + }); + } + } + + cb(null, events); + }); + + return watcher; +} + +export async function watchOutputFiles(cb: FileWatcherCallback) { + const { Watcher } = await import('../../native'); + + let watcher = new Watcher(workspaceRoot, null, false); + watcher.watch((err, events) => { + if (err) { + return cb(err, null); + } + + for (const event of events) { + if ( + event.path.startsWith('.git') || + event.path.includes('node_modules') + ) { + return; + } + } + + cb(null, events); + }); + return watcher; +} + export async function subscribeToWorkspaceChanges( server: Server, cb: FileWatcherCallback diff --git a/packages/nx/src/native/index.d.ts b/packages/nx/src/native/index.d.ts index 5fce28e41f..de0e880d35 100644 --- a/packages/nx/src/native/index.d.ts +++ b/packages/nx/src/native/index.d.ts @@ -10,3 +10,25 @@ export interface FileData { export function hashArray(input: Array): string export function hashFile(file: string): FileData | null export function hashFiles(workspaceRoot: string): Record +/** + * Newly created files will have the `update` EventType as well. + * This simplifies logic between OS's, IDEs and git operations + */ +export const enum EventType { + delete = 'delete', + update = 'update' +} +export interface WatchEvent { + path: string + type: EventType +} +export class Watcher { + origin: string + /** + * Creates a new Watcher instance. + * If `useIgnore` is set to false, no ignores will be used, even when `additionalGlobs` is set + */ + constructor(origin: string, additionalGlobs?: Array | undefined | null, useIgnore?: boolean | undefined | null) + watch(callback: (err: string | null, events: WatchEvent[]) => void): void + stop(): Promise +} diff --git a/packages/nx/src/native/index.js b/packages/nx/src/native/index.js index ae285586b2..28567443c0 100644 --- a/packages/nx/src/native/index.js +++ b/packages/nx/src/native/index.js @@ -246,8 +246,10 @@ if (!nativeBinding) { throw new Error(`Failed to load native binding`) } -const { hashArray, hashFile, hashFiles } = nativeBinding +const { hashArray, hashFile, hashFiles, EventType, Watcher } = nativeBinding module.exports.hashArray = hashArray module.exports.hashFile = hashFile module.exports.hashFiles = hashFiles +module.exports.EventType = EventType +module.exports.Watcher = Watcher diff --git a/packages/nx/src/native/mod.rs b/packages/nx/src/native/mod.rs index 88c58ee628..e21ec9d95b 100644 --- a/packages/nx/src/native/mod.rs +++ b/packages/nx/src/native/mod.rs @@ -1 +1,2 @@ pub mod native_hasher; +pub mod watch; diff --git a/packages/nx/src/native/native_hasher.rs b/packages/nx/src/native/native_hasher.rs index 4734d1ce02..438262fcc4 100644 --- a/packages/nx/src/native/native_hasher.rs +++ b/packages/nx/src/native/native_hasher.rs @@ -19,7 +19,7 @@ pub struct FileData { fn hash_array(input: Vec) -> String { let joined = input.join(","); let content = joined.as_bytes(); - return xxh3::xxh3_64(content).to_string(); + xxh3::xxh3_64(content).to_string() } #[napi] @@ -40,7 +40,7 @@ fn hash_files(workspace_root: String) -> HashMap { let git_folder = workspace_root.join(".git"); let node_folder = workspace_root.join("node_modules"); - let mut walker = WalkBuilder::new(&workspace_root); + let mut walker = WalkBuilder::new(workspace_root); walker.hidden(false); walker.add_custom_ignore_filename(&nx_ignore); @@ -85,7 +85,7 @@ fn hash_files(workspace_root: String) -> HashMap { // convert back-slashes in Windows paths, since the js expects only forward-slash path separators #[cfg(target_os = "windows")] - let file_path = file_path.replace('\\', "/"); + let file_path = file_path.replace('\\', "/"); tx.send((file_path.to_string(), content)).ok(); diff --git a/packages/nx/src/native/tests/native.spec.ts b/packages/nx/src/native/tests/native.spec.ts index 42b0973120..6672179e0c 100644 --- a/packages/nx/src/native/tests/native.spec.ts +++ b/packages/nx/src/native/tests/native.spec.ts @@ -1,8 +1,9 @@ -import { hashFile, hashArray } from '../index'; +import { hashArray, hashFile, Watcher } from '../index'; import { tmpdir } from 'os'; -import { mkdtemp, writeFile } from 'fs-extra'; +import { mkdtemp, realpathSync, writeFile } from 'fs-extra'; import { join } from 'path'; +import { TempFs } from '../../utils/testing/temp-fs'; describe('native', () => { it('should hash files', async () => { @@ -18,7 +19,7 @@ describe('native', () => { it('should hash content', async () => { expect(hashArray).toBeDefined(); - expect(hashArray(["one", "two"])).toEqual("10960201262927338690") + expect(hashArray(['one', 'two'])).toEqual('10960201262927338690'); }); it('should create an instance of NativeHasher', () => { @@ -26,3 +27,142 @@ describe('native', () => { // expect(nativeHasher instanceof NativeFileHasher).toBe(true); }); }); + +describe('watcher', () => { + let temp: TempFs; + let watcher: Watcher; + beforeEach(() => { + temp = new TempFs('watch-dir'); + temp.createFilesSync({ + '.gitignore': 'node_modules/', + '.nxignore': 'app2/', + 'app1/main.js': '', + 'app1/main.css': '', + 'app2/main.js': '', + 'nested-ignore/.gitignore': '*', + 'nested-ignore/file.js': '', + 'node_modules/module/index.js': '', + }); + + console.log(`watching ${temp.tempDir}`); + }); + + afterEach(() => { + watcher.stop(); + temp.cleanup(); + }); + + it('should trigger the callback for files that are not ignored', (done) => { + watcher = new Watcher(realpathSync(temp.tempDir)); + watcher.watch((error, paths) => { + expect(paths).toMatchInlineSnapshot(` + [ + { + "path": "app1/main.html", + "type": "update", + }, + ] + `); + done(); + }); + + wait().then(() => { + temp.createFileSync('node_modules/my-file.json', JSON.stringify({})); + temp.createFileSync('app2/main.css', JSON.stringify({})); + temp.createFileSync('app1/main.html', JSON.stringify({})); + }); + }); + + it('should trigger the callback when files are updated', (done) => { + watcher = new Watcher(realpathSync(temp.tempDir)); + + watcher.watch((err, paths) => { + expect(paths).toMatchInlineSnapshot(` + [ + { + "path": "app1/main.js", + "type": "update", + }, + ] + `); + done(); + }); + + wait().then(() => { + // nxignored file should not trigger a callback + temp.appendFile('app2/main.js', 'update'); + temp.appendFile('app1/main.js', 'update'); + }); + }); + + it('should watch file renames', (done) => { + watcher = new Watcher(realpathSync(temp.tempDir)); + + watcher.watch((err, paths) => { + expect(paths.length).toBe(2); + expect(paths.find((p) => p.type === 'update')).toMatchObject({ + path: 'app1/rename.js', + type: 'update', + }); + expect(paths.find((p) => p.type === 'delete')).toMatchObject({ + path: 'app1/main.js', + type: 'delete', + }); + done(); + }); + + wait().then(() => { + temp.renameFile('app1/main.js', 'app1/rename.js'); + }); + }); + + it('should trigger on deletes', (done) => { + watcher = new Watcher(realpathSync(temp.tempDir)); + + watcher.watch((err, paths) => { + expect(paths).toMatchInlineSnapshot(` + [ + { + "path": "app1/main.js", + "type": "delete", + }, + ] + `); + done(); + }); + + wait().then(() => { + temp.removeFileSync('app1/main.js'); + }); + }); + + it('should ignore nested gitignores', (done) => { + watcher = new Watcher(realpathSync(temp.tempDir)); + + watcher.watch((err, paths) => { + expect(paths).toMatchInlineSnapshot(` + [ + { + "path": "boo.txt", + "type": "update", + }, + ] + `); + done(); + }); + + wait().then(() => { + // should not be triggered + temp.createFileSync('nested-ignore/hello1.txt', ''); + temp.createFileSync('boo.txt', ''); + }); + }); +}); + +function wait() { + return new Promise((res) => { + setTimeout(() => { + res(); + }, 500); + }); +} diff --git a/packages/nx/src/native/watch/mod.rs b/packages/nx/src/native/watch/mod.rs new file mode 100644 index 0000000000..75a4914bd8 --- /dev/null +++ b/packages/nx/src/native/watch/mod.rs @@ -0,0 +1,5 @@ +mod types; +mod utils; +mod watch_config; +mod watch_filterer; +mod watcher; diff --git a/packages/nx/src/native/watch/types.rs b/packages/nx/src/native/watch/types.rs new file mode 100644 index 0000000000..5fc3bd2cc6 --- /dev/null +++ b/packages/nx/src/native/watch/types.rs @@ -0,0 +1,78 @@ +use napi::bindgen_prelude::*; +use std::path::PathBuf; +use tracing::trace; +use watchexec_events::{Event, Tag}; + +#[napi(string_enum)] +#[derive(Debug)] +/// Newly created files will have the `update` EventType as well. +/// This simplifies logic between OS's, IDEs and git operations +pub enum EventType { + #[allow(non_camel_case_types)] + delete, + #[allow(non_camel_case_types)] + update, +} + +#[derive(Debug, Clone)] +#[napi(object)] +pub struct WatchEvent { + pub path: String, + pub r#type: EventType, +} + +impl From for WatchEvent { + fn from(value: WatchEventInternal) -> Self { + let path = value + .path + .strip_prefix(&value.origin.expect("origin is available")) + .unwrap_or(&value.path) + .display() + .to_string(); + + #[cfg(windows)] + let path = path.replace('\\', "/"); + + WatchEvent { + path, + r#type: value.r#type, + } + } +} + +#[derive(Debug, Clone)] +pub(super) struct WatchEventInternal { + pub path: PathBuf, + pub r#type: EventType, + pub origin: Option, +} + +impl From<&Event> for WatchEventInternal { + fn from(value: &Event) -> Self { + let path = value.paths().next().expect("there should always be a path"); + + let event_kind = value + .tags + .iter() + .find_map(|t| match t { + Tag::FileEventKind(event_kind) => Some(event_kind), + _ => None, + }) + .expect("there should always be a file event kind"); + + let path_ref = path.0; + let event_type = if matches!(path.1, None) && !path_ref.exists() { + EventType::delete + } else { + EventType::update + }; + + trace!(?path, ?event_kind, ?event_type, "event kind -> event type"); + + WatchEventInternal { + path: path.0.into(), + r#type: event_type, + origin: None, + } + } +} diff --git a/packages/nx/src/native/watch/utils.rs b/packages/nx/src/native/watch/utils.rs new file mode 100644 index 0000000000..e70e42d13d --- /dev/null +++ b/packages/nx/src/native/watch/utils.rs @@ -0,0 +1,47 @@ +use ignore::WalkBuilder; +use ignore_files::IgnoreFile; +use std::path::PathBuf; + +pub(super) fn get_ignore_files>(root: T) -> Vec { + let root = root.as_ref(); + + let mut walker = WalkBuilder::new(root); + walker.hidden(false); + walker.git_ignore(false); + + let node_folder = PathBuf::from(root).join("node_modules"); + walker.filter_entry(move |entry| !entry.path().starts_with(&node_folder)); + walker + .build() + .flatten() + .filter(|result| { + result.path().ends_with(".nxignore") || result.path().ends_with(".gitignore") + }) + .map(|result| { + let path: PathBuf = result.path().into(); + let parent: PathBuf = path.parent().unwrap_or(&path).into(); + IgnoreFile { + path, + applies_in: Some(parent), + applies_to: None, + } + }) + .collect() +} + +// /// Get only the root level folders to watch. +// /// These will not include git ignored folders +// pub(super) fn get_watch_directories>(root: T) -> Vec { +// let root = root.as_ref(); +// +// let mut walker = WalkBuilder::new(root); +// walker.hidden(false); +// walker.max_depth(Some(1)); +// walker.filter_entry(|entry| entry.path().is_dir()); +// +// walker +// .build() +// .flatten() +// .map(|result| result.path().into()) +// .collect() +// } diff --git a/packages/nx/src/native/watch/watch_config.rs b/packages/nx/src/native/watch/watch_config.rs new file mode 100644 index 0000000000..5605eaca0e --- /dev/null +++ b/packages/nx/src/native/watch/watch_config.rs @@ -0,0 +1,45 @@ +use crate::native::watch::utils::get_ignore_files; +use crate::native::watch::watch_filterer::WatchFilterer; +use ignore_files::IgnoreFilter; +use std::sync::Arc; +use std::time::Duration; +use tracing::trace; +use watchexec::config::RuntimeConfig; +use watchexec_filterer_ignore::IgnoreFilterer; + +pub(super) async fn create_runtime( + origin: &str, + additional_globs: &[&str], + use_ignore: bool, +) -> napi::Result { + let ignore_files = if use_ignore { + get_ignore_files(origin) + } else { + vec![] + }; + + trace!( + ?use_ignore, + ?additional_globs, + ?ignore_files, + "Using these ignore files for the watcher" + ); + let mut filter = IgnoreFilter::new(origin, &ignore_files) + .await + .map_err(anyhow::Error::from)?; + + filter + .add_globs(&additional_globs, Some(&origin.into())) + .map_err(anyhow::Error::from)?; + + let mut runtime = RuntimeConfig::default(); + runtime.filterer(Arc::new(WatchFilterer { + inner: IgnoreFilterer(filter), + })); + runtime.action_throttle(Duration::from_millis(500)); + + // let watch_directories = get_watch_directories(origin); + // trace!(directories = ?watch_directories, "watching"); + runtime.pathset([&origin]); + Ok(runtime) +} diff --git a/packages/nx/src/native/watch/watch_filterer.rs b/packages/nx/src/native/watch/watch_filterer.rs new file mode 100644 index 0000000000..a94941d8c4 --- /dev/null +++ b/packages/nx/src/native/watch/watch_filterer.rs @@ -0,0 +1,64 @@ +use tracing::trace; +use watchexec::error::RuntimeError; +use watchexec::filter::Filterer; +use watchexec_events::filekind::{CreateKind, FileEventKind, ModifyKind, RemoveKind}; + +use watchexec_events::{Event, FileType, Priority, Source, Tag}; +use watchexec_filterer_ignore::IgnoreFilterer; + +#[derive(Debug)] +pub struct WatchFilterer { + pub inner: IgnoreFilterer, +} + +/// Used to filter out events that that come from watchexec +impl Filterer for WatchFilterer { + fn check_event(&self, event: &Event, priority: Priority) -> Result { + if !self.inner.check_event(event, priority)? { + return Ok(false); + } + + trace!(?event, "checking if event is valid"); + + // + // Tags will be a Vec that contains multiple types of information for a given event + // We are only interested if: + // 1) A `FileEventKind` is modified, created, removed, or renamed + // 2) A Path that is a FileType::File + // 3) Deleted files do not have a FileType::File (because they're deleted..), check if a path is valid + // 4) Only FileSystem sources are valid + // If there's a tag that doesnt confine to this criteria, we `return` early, otherwise we `continue`. + for tag in &event.tags { + match tag { + // Tag::Source(Source::Keyboard) => continue, + // Tag::Keyboard(Keyboard::Eof) => continue, + Tag::FileEventKind(file_event) => match file_event { + FileEventKind::Modify(ModifyKind::Name(_)) => continue, + FileEventKind::Modify(ModifyKind::Data(_)) => continue, + FileEventKind::Create(CreateKind::File) => continue, + FileEventKind::Remove(RemoveKind::File) => continue, + + #[cfg(windows)] + FileEventKind::Modify(ModifyKind::Any) => continue, + #[cfg(windows)] + FileEventKind::Create(CreateKind::Any) => continue, + #[cfg(windows)] + FileEventKind::Remove(RemoveKind::Any) => continue, + + _ => return Ok(false), + }, + // Deleted files do not have a file_type + we don't want directory changes + we dont want files that end with `~` + Tag::Path { + path, + file_type: Some(FileType::File) | None, + } if !path.display().to_string().ends_with('~') => continue, + Tag::Source(Source::Filesystem) => continue, + _ => return Ok(false), + } + } + + trace!(?event, "event passed all checks"); + + Ok(true) + } +} diff --git a/packages/nx/src/native/watch/watcher.rs b/packages/nx/src/native/watch/watcher.rs new file mode 100644 index 0000000000..2c1f63f2c0 --- /dev/null +++ b/packages/nx/src/native/watch/watcher.rs @@ -0,0 +1,203 @@ +use std::collections::HashMap; +use std::convert::Infallible; +use std::path::MAIN_SEPARATOR; +use std::sync::Arc; + +use crate::native::watch::types::{WatchEvent, WatchEventInternal}; +use itertools::Itertools; +use napi::bindgen_prelude::*; +use napi::threadsafe_function::{ + ThreadSafeCallContext, ThreadsafeFunction, ThreadsafeFunctionCallMode, +}; +use napi::{Env, JsFunction, JsObject}; +use rayon::prelude::*; +use tracing::trace; +use tracing_subscriber::EnvFilter; +use watchexec::action::{Action, Outcome}; +use watchexec::config::{InitConfig, RuntimeConfig}; +use watchexec::event::Tag; +use watchexec::Watchexec; +use watchexec_events::{Event, Keyboard, Priority}; +use watchexec_signals::Signal; + +use crate::native::watch::watch_config; + +#[napi] +pub struct Watcher { + pub origin: String, + watch_exec: Arc, + additional_globs: Vec, + use_ignore: bool, +} + +#[napi] +impl Watcher { + /// Creates a new Watcher instance. + /// If `useIgnore` is set to false, no ignores will be used, even when `additionalGlobs` is set + #[napi(constructor)] + pub fn new( + origin: String, + additional_globs: Option>, + use_ignore: Option, + ) -> Result { + let watch_exec = Watchexec::new(InitConfig::default(), RuntimeConfig::default()) + .map_err(anyhow::Error::from)?; + + let mut globs = if let Some(globs) = additional_globs { + globs + } else { + vec![] + }; + + // always ignore the .git and node_modules folder + globs.push(".git/".into()); + globs.push("node_modules/".into()); + + Ok(Watcher { + origin, + watch_exec, + additional_globs: globs, + use_ignore: use_ignore.unwrap_or(true), + }) + } + + #[napi] + pub fn watch( + &mut self, + env: Env, + #[napi(ts_arg_type = "(err: string | null, events: WatchEvent[]) => void")] + callback: JsFunction, + ) -> Result<()> { + _ = tracing_subscriber::fmt() + .with_env_filter(EnvFilter::from_env("NX_NATIVE_LOGGING")) + .try_init(); + + let mut callback_tsfn: ThreadsafeFunction>> = + callback.create_threadsafe_function( + 0, + |ctx: ThreadSafeCallContext>>| { + let mut watch_events: Vec = vec![]; + trace!(?ctx.value, "Base collection that will be sent"); + + for (_, value) in ctx.value { + let event = value + .first() + .expect("should always have at least 1 element") + .to_owned(); + + watch_events.push(event.into()); + } + + trace!(?watch_events, "sending to node"); + + Ok(vec![watch_events]) + }, + )?; + + callback_tsfn.unref(&env)?; + + let origin = self.origin.clone(); + let watch_exec = self.watch_exec.clone(); + let additional_globs = self.additional_globs.clone(); + let use_ignore = self.use_ignore.clone(); + let start = async move { + let mut runtime = watch_config::create_runtime( + &origin, + &additional_globs + .iter() + .map(String::as_ref) + .collect::>(), + use_ignore, + ) + .await?; + + runtime.on_action(move |action: Action| { + let ok_future = async { Ok::<(), Infallible>(()) }; + let signals: Vec = action.events.iter().flat_map(Event::signals).collect(); + + if signals.contains(&Signal::Terminate) { + trace!("terminate - ending watch"); + action.outcome(Outcome::both(Outcome::Stop, Outcome::Exit)); + return ok_future; + } + + if signals.contains(&Signal::Interrupt) { + trace!("interrupt - ending watch"); + action.outcome(Outcome::both(Outcome::Stop, Outcome::Exit)); + return ok_future; + } + + let is_keyboard_eof = action + .events + .iter() + .any(|e| e.tags.contains(&Tag::Keyboard(Keyboard::Eof))); + + if is_keyboard_eof { + trace!("ending watch"); + action.outcome(Outcome::both(Outcome::Stop, Outcome::Exit)); + return ok_future; + } + + let mut origin_path = origin.clone(); + if !origin_path.ends_with(MAIN_SEPARATOR) { + origin_path.push(MAIN_SEPARATOR); + } + trace!(?origin_path); + + let events = action + .events + .par_iter() + .map(|ev| { + let mut watch_event: WatchEventInternal = ev.into(); + watch_event.origin = Some(origin_path.clone()); + watch_event + }) + .collect::>(); + + let group_events = events + .into_iter() + .into_group_map_by(|g| g.path.display().to_string()); + + callback_tsfn.call(Ok(group_events), ThreadsafeFunctionCallMode::NonBlocking); + + action.outcome(Outcome::Start); + ok_future + }); + + trace!("configuring watch exec"); + watch_exec + .reconfigure(runtime) + .map_err(anyhow::Error::from)?; + + trace!("starting watch exec"); + watch_exec.main().await.map_err(anyhow::Error::from)?.ok(); + Ok(()) + }; + + env.spawn_future(start)?; + trace!("started watch exec"); + Ok(()) + } + + #[napi(ts_return_type = "Promise")] + pub fn stop(&mut self, env: Env) -> Result { + trace!("stopping the watch process"); + let watch_exec = self.watch_exec.clone(); + let send_terminate = async move { + watch_exec + .send_event( + Event { + tags: vec![Tag::Signal(Signal::Terminate)], + metadata: HashMap::new(), + }, + Priority::Urgent, + ) + .await + .map_err(anyhow::Error::from)?; + + Ok(()) + }; + + env.spawn_future(send_terminate) + } +} diff --git a/packages/nx/src/utils/testing/temp-fs.ts b/packages/nx/src/utils/testing/temp-fs.ts index 76926fa15b..0bfbd5637b 100644 --- a/packages/nx/src/utils/testing/temp-fs.ts +++ b/packages/nx/src/utils/testing/temp-fs.ts @@ -6,9 +6,11 @@ import { outputFile, rmSync, emptyDirSync, + outputFileSync, + unlinkSync, } from 'fs-extra'; import { joinPathFragments } from '../path'; -import { appendFileSync, writeFileSync } from 'fs'; +import { appendFileSync, writeFileSync, renameSync } from 'fs'; type NestedFiles = { [fileName: string]: string; @@ -31,14 +33,28 @@ export class TempFs { ); } + createFilesSync(fileObject: NestedFiles) { + for (let path of Object.keys(fileObject)) { + this.createFileSync(path, fileObject[path]); + } + } + async createFile(filePath: string, content: string) { await outputFile(joinPathFragments(this.tempDir, filePath), content); } + createFileSync(filePath: string, content: string) { + outputFileSync(joinPathFragments(this.tempDir, filePath), content); + } + async readFile(filePath: string): Promise { return await readFile(filePath, 'utf-8'); } + removeFileSync(filePath: string): void { + unlinkSync(joinPathFragments(this.tempDir, filePath)); + } + appendFile(filePath: string, content: string) { appendFileSync(joinPathFragments(this.tempDir, filePath), content); } @@ -46,6 +62,12 @@ export class TempFs { writeFile(filePath: string, content: string) { writeFileSync(joinPathFragments(this.tempDir, filePath), content); } + renameFile(oldPath: string, newPath: string) { + renameSync( + joinPathFragments(this.tempDir, oldPath), + joinPathFragments(this.tempDir, newPath) + ); + } cleanup() { rmSync(this.tempDir, { recursive: true });