From a13bc429eb190313ff5f95efafd92916b999337a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Saparelli?= Date: Sun, 26 Nov 2023 09:33:44 +1300 Subject: [PATCH] Watchexec lib v3 (#601) Co-authored-by: emilHof <95590295+emilHof@users.noreply.github.com> --- Cargo.lock | 1700 ++++++++--------- Cargo.toml | 1 + completions/fish | 10 +- completions/nu | 2 +- crates/bosion/Cargo.toml | 4 +- crates/bosion/examples/clap/Cargo.lock | 1114 ++++------- crates/bosion/examples/default/Cargo.lock | 1044 +++------- crates/bosion/examples/default/Cargo.toml | 4 +- crates/bosion/examples/no-git/Cargo.lock | 294 ++- crates/bosion/examples/no-git/Cargo.toml | 4 +- crates/bosion/examples/no-std/Cargo.lock | 294 ++- crates/bosion/examples/no-std/Cargo.toml | 4 +- crates/bosion/src/info.rs | 5 +- crates/bosion/src/lib.rs | 2 +- crates/cli/Cargo.toml | 55 +- crates/cli/src/args.rs | 6 +- crates/cli/src/config.rs | 482 ++++- crates/cli/src/config/init.rs | 52 - crates/cli/src/config/runtime.rs | 373 ---- crates/cli/src/filterer/globset.rs | 11 +- crates/cli/src/lib.rs | 17 +- crates/cli/src/main.rs | 21 +- crates/events/CHANGELOG.md | 2 + crates/events/Cargo.toml | 10 +- crates/events/src/event.rs | 2 +- crates/events/src/process.rs | 42 + crates/filterer/globset/Cargo.toml | 8 +- crates/filterer/globset/src/lib.rs | 7 +- crates/filterer/globset/tests/filtering.rs | 12 +- crates/filterer/globset/tests/helpers/mod.rs | 9 +- crates/filterer/ignore/Cargo.toml | 8 +- crates/filterer/ignore/src/lib.rs | 8 +- crates/filterer/ignore/tests/filtering.rs | 2 +- crates/filterer/ignore/tests/helpers/mod.rs | 9 +- crates/filterer/tagged/Cargo.toml | 8 +- crates/filterer/tagged/src/error.rs | 8 - crates/filterer/tagged/src/filter.rs | 2 +- crates/filterer/tagged/src/filterer.rs | 7 +- crates/filterer/tagged/tests/filter_files.rs | 2 +- crates/filterer/tagged/tests/helpers/mod.rs | 9 +- crates/filterer/tagged/tests/non_paths.rs | 2 +- crates/ignore-files/Cargo.toml | 17 +- crates/ignore-files/src/discover.rs | 2 +- crates/ignore-files/src/error.rs | 3 - crates/lib/CHANGELOG.md | 142 ++ crates/lib/Cargo.toml | 28 +- crates/lib/README.md | 198 +- crates/lib/examples/demo.rs | 74 - crates/lib/examples/fs.rs | 52 - crates/lib/examples/only_commands.rs | 55 + crates/lib/examples/only_events.rs | 30 + crates/lib/examples/readme.rs | 166 +- .../restart_run_on_successful_build.rs | 84 + crates/lib/examples/signal.rs | 42 - crates/lib/src/action.rs | 15 +- crates/lib/src/action/handler.rs | 161 ++ crates/lib/src/action/outcome.rs | 156 -- crates/lib/src/action/outcome_worker.rs | 209 -- crates/lib/src/action/process_holder.rs | 71 - crates/lib/src/action/quit.rs | 17 + crates/lib/src/action/return.rs | 18 + crates/lib/src/action/worker.rs | 176 +- crates/lib/src/action/workingdata.rs | 217 --- crates/lib/src/changeable.rs | 122 ++ crates/lib/src/command.rs | 157 -- crates/lib/src/command/process.rs | 148 -- crates/lib/src/command/supervisor.rs | 369 ---- crates/lib/src/command/tests.rs | 128 -- crates/lib/src/config.rs | 420 ++-- crates/lib/src/error/critical.rs | 34 +- crates/lib/src/error/runtime.rs | 100 +- crates/lib/src/error/specialised.rs | 44 +- crates/lib/src/filter.rs | 49 +- crates/lib/src/handler.rs | 264 --- crates/lib/src/id.rs | 67 + crates/lib/src/late_join_set.rs | 105 + crates/lib/src/lib.rs | 125 +- crates/lib/src/paths.rs | 2 +- crates/lib/src/sources.rs | 5 + crates/lib/src/{ => sources}/fs.rs | 204 +- crates/lib/src/{ => sources}/keyboard.rs | 62 +- crates/lib/src/{ => sources}/signal.rs | 64 +- crates/lib/src/watchexec.rs | 325 ++-- crates/lib/tests/env_reporting.rs | 6 +- crates/lib/tests/error_handler.rs | 14 +- crates/project-origins/Cargo.toml | 4 +- crates/signals/Cargo.toml | 6 +- crates/supervisor/CHANGELOG.md | 5 + crates/supervisor/Cargo.toml | 45 + crates/supervisor/README.md | 15 + crates/supervisor/release.toml | 10 + crates/supervisor/src/command.rs | 72 + crates/supervisor/src/command/conversions.rs | 100 + crates/supervisor/src/command/program.rs | 37 + crates/supervisor/src/command/shell.rs | 40 + crates/supervisor/src/errors.rs | 16 + crates/supervisor/src/flag.rs | 71 + crates/supervisor/src/job.rs | 31 + crates/supervisor/src/job/job.rs | 351 ++++ crates/supervisor/src/job/messages.rs | 148 ++ crates/supervisor/src/job/priority.rs | 146 ++ crates/supervisor/src/job/state.rs | 142 ++ crates/supervisor/src/job/task.rs | 354 ++++ crates/supervisor/src/job/test.rs | 849 ++++++++ crates/supervisor/src/job/testchild.rs | 150 ++ crates/supervisor/src/lib.rs | 147 ++ crates/supervisor/tests/programs.rs | 129 ++ doc/watchexec.1 | 4 + doc/watchexec.1.md | 6 + doc/watchexec.1.pdf | Bin 42901 -> 43261 bytes 110 files changed, 7085 insertions(+), 6196 deletions(-) delete mode 100644 crates/cli/src/config/init.rs delete mode 100644 crates/cli/src/config/runtime.rs delete mode 100644 crates/lib/examples/demo.rs delete mode 100644 crates/lib/examples/fs.rs create mode 100644 crates/lib/examples/only_commands.rs create mode 100644 crates/lib/examples/only_events.rs create mode 100644 crates/lib/examples/restart_run_on_successful_build.rs delete mode 100644 crates/lib/examples/signal.rs create mode 100644 crates/lib/src/action/handler.rs delete mode 100644 crates/lib/src/action/outcome.rs delete mode 100644 crates/lib/src/action/outcome_worker.rs delete mode 100644 crates/lib/src/action/process_holder.rs create mode 100644 crates/lib/src/action/quit.rs create mode 100644 crates/lib/src/action/return.rs delete mode 100644 crates/lib/src/action/workingdata.rs create mode 100644 crates/lib/src/changeable.rs delete mode 100644 crates/lib/src/command.rs delete mode 100644 crates/lib/src/command/process.rs delete mode 100644 crates/lib/src/command/supervisor.rs delete mode 100644 crates/lib/src/command/tests.rs delete mode 100644 crates/lib/src/handler.rs create mode 100644 crates/lib/src/id.rs create mode 100644 crates/lib/src/late_join_set.rs create mode 100644 crates/lib/src/sources.rs rename crates/lib/src/{ => sources}/fs.rs (61%) rename crates/lib/src/{ => sources}/keyboard.rs (61%) rename crates/lib/src/{ => sources}/signal.rs (69%) create mode 100644 crates/supervisor/CHANGELOG.md create mode 100644 crates/supervisor/Cargo.toml create mode 100644 crates/supervisor/README.md create mode 100644 crates/supervisor/release.toml create mode 100644 crates/supervisor/src/command.rs create mode 100644 crates/supervisor/src/command/conversions.rs create mode 100644 crates/supervisor/src/command/program.rs create mode 100644 crates/supervisor/src/command/shell.rs create mode 100644 crates/supervisor/src/errors.rs create mode 100644 crates/supervisor/src/flag.rs create mode 100644 crates/supervisor/src/job.rs create mode 100644 crates/supervisor/src/job/job.rs create mode 100644 crates/supervisor/src/job/messages.rs create mode 100644 crates/supervisor/src/job/priority.rs create mode 100644 crates/supervisor/src/job/state.rs create mode 100644 crates/supervisor/src/job/task.rs create mode 100644 crates/supervisor/src/job/test.rs create mode 100644 crates/supervisor/src/job/testchild.rs create mode 100644 crates/supervisor/src/lib.rs create mode 100644 crates/supervisor/tests/programs.rs diff --git a/Cargo.lock b/Cargo.lock index 78cf5cc..fe817db 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,23 +17,11 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" -[[package]] -name = "ahash" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f" -dependencies = [ - "cfg-if", - "getrandom", - "once_cell", - "version_check", -] - [[package]] name = "aho-corasick" -version = "1.0.5" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c378d78423fdad8089616f827526ee33c19f2fddbd5de1629152c9593ba4783" +checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" dependencies = [ "memchr", ] @@ -55,9 +43,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.5.0" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1f58811cfac344940f1a400b6e6231ce35171f614f26439e80f8c1465c5cc0c" +checksum = "2ab91ebe16eb252986481c5b62f6098f3b698a45e34b5b98200cf20dd2484a44" dependencies = [ "anstyle", "anstyle-parse", @@ -69,15 +57,15 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.2" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15c4c2c83f81532e5845a733998b6971faca23490340a418e9b72a3ec9de12ea" +checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87" [[package]] name = "anstyle-parse" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "938874ff5980b03a87c5524b3ae5b59cf99b1d6bc836848df7bc5ada9643c333" +checksum = "317b9a89c1868f5ea6ff1d9539a69f45dffc21ce321ac1fd1160dfa48c8e2140" dependencies = [ "utf8parse", ] @@ -93,9 +81,9 @@ dependencies = [ [[package]] name = "anstyle-wincon" -version = "2.1.0" +version = "3.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58f54d10c6dfa51283a066ceab3ec1ab78d13fae00aa49243a45e4571fb79dfd" +checksum = "f0699d10d2f4d628a98ee7b57b289abbc98ff3bad977cb3152709d4bf2330628" dependencies = [ "anstyle", "windows-sys 0.48.0", @@ -115,10 +103,11 @@ checksum = "bddcadddf5e9015d310179a59bb28c4d4b9920ad0f11e8e14dbadf654890c9a6" [[package]] name = "argfile" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "265f5108974489a217d5098cd81666b60480c8dd67302acbbe7cbdd8aa09d638" +checksum = "1287c4f82a41c5085e65ee337c7934d71ab43d5187740a81fb69129013f6a5f6" dependencies = [ + "fs-err", "os_str_bytes", ] @@ -128,32 +117,34 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c48ccdbf6ca6b121e0f586cbc0e73ae440e56c67c30fa0873b4e110d9c26d2b" dependencies = [ - "event-listener", + "event-listener 2.5.3", "futures-core", ] [[package]] name = "async-channel" -version = "1.9.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35" +checksum = "1ca33f4bc4ed1babef42cad36cc1f51fa88be00420404e5b1e80ab1b18f7678c" dependencies = [ "concurrent-queue", - "event-listener", + "event-listener 4.0.0", + "event-listener-strategy", "futures-core", + "pin-project-lite", ] [[package]] name = "async-executor" -version = "1.5.1" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fa3dc5f2a8564f07759c008b9109dc0d39de92a88d5588b8a5036d286383afb" +checksum = "17ae5ebefcc48e7452b4987947920dac9450be1110cadf34d1b8c116bdbaf97c" dependencies = [ - "async-lock", + "async-lock 3.1.2", "async-task", "concurrent-queue", - "fastrand 1.9.0", - "futures-lite", + "fastrand 2.0.1", + "futures-lite 2.0.1", "slab", ] @@ -163,10 +154,10 @@ version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "279cf904654eeebfa37ac9bb1598880884924aab82e290aa65c9e77a0e142e06" dependencies = [ - "async-lock", + "async-lock 2.8.0", "autocfg", "blocking", - "futures-lite", + "futures-lite 1.13.0", ] [[package]] @@ -175,27 +166,57 @@ version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fc5b45d93ef0529756f812ca52e44c221b35341892d3dcc34132ac02f3dd2af" dependencies = [ - "async-lock", + "async-lock 2.8.0", "autocfg", "cfg-if", "concurrent-queue", - "futures-lite", + "futures-lite 1.13.0", "log", "parking", - "polling", - "rustix 0.37.23", + "polling 2.8.0", + "rustix 0.37.27", "slab", - "socket2 0.4.9", + "socket2 0.4.10", "waker-fn", ] +[[package]] +name = "async-io" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6d3b15875ba253d1110c740755e246537483f152fa334f91abd7fe84c88b3ff" +dependencies = [ + "async-lock 3.1.2", + "cfg-if", + "concurrent-queue", + "futures-io", + "futures-lite 2.0.1", + "parking", + "polling 3.3.1", + "rustix 0.38.25", + "slab", + "tracing", + "windows-sys 0.52.0", +] + [[package]] name = "async-lock" version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "287272293e9d8c41773cec55e365490fe034813a2f172f502d6ddcf75b2f582b" dependencies = [ - "event-listener", + "event-listener 2.5.3", +] + +[[package]] +name = "async-lock" +version = "3.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dea8b3453dd7cc96711834b75400d671b73e3656975fa68d9f277163b7f7e316" +dependencies = [ + "event-listener 4.0.0", + "event-listener-strategy", + "pin-project-lite", ] [[package]] @@ -204,53 +225,92 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c21678992e1b21bebfe2bc53ab5f5f68c106eddab31b24e0bb06e9b715a86640" dependencies = [ - "event-listener", + "event-listener 2.5.3", ] [[package]] name = "async-process" -version = "1.7.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a9d28b1d97e08915212e2e45310d47854eafa69600756fc735fb788f75199c9" +checksum = "ea6438ba0a08d81529c69b36700fa2f95837bfe3e776ab39cde9c14d9149da88" dependencies = [ - "async-io", - "async-lock", - "autocfg", + "async-io 1.13.0", + "async-lock 2.8.0", + "async-signal", "blocking", "cfg-if", - "event-listener", - "futures-lite", - "rustix 0.37.23", - "signal-hook", + "event-listener 3.1.0", + "futures-lite 1.13.0", + "rustix 0.38.25", "windows-sys 0.48.0", ] [[package]] name = "async-recursion" -version = "1.0.4" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e97ce7de6cf12de5d7226c73f5ba9811622f4db3a5b91b55c53e987e5f91cba" +checksum = "5fd55a5ba1179988837d24ab4c7cc8ed6efdeff578ede0416b4225a5fca35bd0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.29", + "syn 2.0.39", +] + +[[package]] +name = "async-signal" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e47d90f65a225c4527103a8d747001fc56e375203592b25ad103e1ca13124c5" +dependencies = [ + "async-io 2.2.1", + "async-lock 2.8.0", + "atomic-waker", + "cfg-if", + "futures-core", + "futures-io", + "rustix 0.38.25", + "signal-hook-registry", + "slab", + "windows-sys 0.48.0", +] + +[[package]] +name = "async-stream" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd56dd203fef61ac097dd65721a419ddccb106b2d2b70ba60a6b529f03961a51" +dependencies = [ + "async-stream-impl", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-stream-impl" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.39", ] [[package]] name = "async-task" -version = "4.4.0" +version = "4.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecc7ab41815b3c653ccd2978ec3255c81349336702dfdf62ee6f7069b12a3aae" +checksum = "b4eb2cdb97421e01129ccb49169d8279ed21e829929144f4a22a6e54ac549ca1" [[package]] name = "async-trait" -version = "0.1.73" +version = "0.1.74" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc00ceb34980c03614e35a3a4e218276a0a824e911d07651cd0d858a51e8c0f0" +checksum = "a66537f1bb974b254c98ed142ff995236e81b9d0fe4db0575f46612cb15eb0f9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.29", + "syn 2.0.39", ] [[package]] @@ -261,9 +321,9 @@ checksum = "a8ab6b55fe97976e46f91ddbed8d147d966475dc29b2032757ba47e02376fbc3" [[package]] name = "atomic-waker" -version = "1.1.1" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1181e1e0d1fce796a03db1ae795d67167da795f9cf4a39c37589e85ef57f26d3" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] name = "autocfg" @@ -342,15 +402,9 @@ dependencies = [ [[package]] name = "base64" -version = "0.13.1" +version = "0.21.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" - -[[package]] -name = "base64" -version = "0.21.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "414dcefbc63d77c526a76b3afcf6fbb9b5e2791c19c3aa2297733208750c6e53" +checksum = "35636a1494ede3b646cc98f74f8e62c773a38a659ebc777a2cf26b9b74171df9" [[package]] name = "bitflags" @@ -360,9 +414,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.4.0" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" +checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" [[package]] name = "block" @@ -381,17 +435,18 @@ dependencies = [ [[package]] name = "blocking" -version = "1.3.1" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77231a1c8f801696fc0123ec6150ce92cffb8e164a02afb9c8ddee0e9b65ad65" +checksum = "6a37913e8dc4ddcc604f0c6d3bf2887c995153af3611de9e23c352b44c1b9118" dependencies = [ "async-channel", - "async-lock", + "async-lock 3.1.2", "async-task", - "atomic-waker", - "fastrand 1.9.0", - "futures-lite", - "log", + "fastrand 2.0.1", + "futures-io", + "futures-lite 2.0.1", + "piper", + "tracing", ] [[package]] @@ -399,17 +454,23 @@ name = "bosion" version = "1.0.1" dependencies = [ "gix", - "time 0.3.28", + "time", ] [[package]] -name = "bstr" -version = "1.6.1" +name = "boxcar" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8042c26c77e5bd6897a7358e0abb3ec412ed126d826988135653fc669263899d" +checksum = "e516014975db8769c0dcd1aba681c21079475fcddf77118bf54f9e2e013f6b29" + +[[package]] +name = "bstr" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "542f33a8835a0884b006a0c3df3dadd99c0c3f296ed26c2fdc8028e01ad6230c" dependencies = [ "memchr", - "regex-automata 0.3.7", + "regex-automata 0.4.3", "serde", ] @@ -424,21 +485,21 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.13.0" +version = "3.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1" +checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" [[package]] name = "byteorder" -version = "1.4.3" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" +checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" [[package]] name = "cc" @@ -446,7 +507,6 @@ version = "1.0.83" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" dependencies = [ - "jobserver", "libc", ] @@ -458,58 +518,55 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.27" +version = "0.4.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f56b4c72906975ca04becb8a30e102dfecddd0c06181e3e95ddc444be28881f8" +checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38" dependencies = [ "android-tzdata", "iana-time-zone", "js-sys", "num-traits", - "time 0.1.45", "wasm-bindgen", "windows-targets 0.48.5", ] [[package]] name = "clap" -version = "4.4.1" +version = "4.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c8d502cbaec4595d2e7d5f61e318f05417bd2b66fdc3809498f0d3fdf0bea27" +checksum = "2275f18819641850fa26c89acc84d465c1bf91ce57bc2748b28c420473352f64" dependencies = [ "clap_builder", "clap_derive", - "once_cell", ] [[package]] name = "clap_builder" -version = "4.4.1" +version = "4.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5891c7bc0edb3e1c2204fc5e94009affabeb1821c9e5fdc3959536c5c0bb984d" +checksum = "07cdf1b148b25c1e1f7a42225e30a0d99a615cd4637eae7365548dd4529b95bc" dependencies = [ "anstream", "anstyle", "clap_lex", - "once_cell", "strsim", - "terminal_size 0.2.6", + "terminal_size 0.3.0", ] [[package]] name = "clap_complete" -version = "4.4.0" +version = "4.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "586a385f7ef2f8b4d86bddaa0c094794e7ccbfe5ffef1f434fe928143fc783a5" +checksum = "bffe91f06a11b4b9420f62103854e90867812cd5d01557f853c5ee8e791b12ae" dependencies = [ "clap", ] [[package]] name = "clap_complete_nushell" -version = "4.4.0" +version = "4.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "787093c7ce9278e9f7ae55cbdba76a2d6610fe809e54db4c6d61a65bc0258d15" +checksum = "948bf70d7e1f179635d3ef819ce8baa2d3074d0d57816ac37387cd6f9eed0c31" dependencies = [ "clap", "clap_complete", @@ -517,27 +574,27 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.4.0" +version = "4.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9fd1a5729c4548118d7d70ff234a44868d00489a4b6597b0b020918a0e91a1a" +checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442" dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.29", + "syn 2.0.39", ] [[package]] name = "clap_lex" -version = "0.5.1" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd7cc57abe963c6d3b9d8be5b06ba7c8957a930305ca90304f24ef040aa6f961" +checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1" [[package]] name = "clap_mangen" -version = "0.2.13" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf8e5f34d85d9e0bbe2491d100a7a7c1007bb2467b518080bfe311e8947197a9" +checksum = "d3be86020147691e1d2ef58f75346a3d4d94807bfc473e377d52f09f0f7d77f7" dependencies = [ "clap", "roff", @@ -549,10 +606,10 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72f3f22f1a586604e62efd23f78218f3ccdecf7a33c4500db2d37d85a24fe994" dependencies = [ - "nix", + "nix 0.26.4", "terminfo", "thiserror", - "which", + "which 4.4.2", "winapi", ] @@ -570,31 +627,32 @@ checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" [[package]] name = "command-group" -version = "2.1.0" +version = "5.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5080df6b0f0ecb76cab30808f00d937ba725cebe266a3da8cd89dff92f2a9916" +checksum = "a68fa787550392a9d58f44c21a3022cfb3ea3e2458b7f85d3b399d0ceeccf409" dependencies = [ "async-trait", - "nix", + "nix 0.27.1", "tokio", "winapi", ] [[package]] name = "concurrent-queue" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62ec6771ecfa0762d24683ee5a32ad78487a3d3afdc0fb8cae19d2c5deb50b7c" +checksum = "f057a694a54f12365049b0958a1685bb52d567f5593b355fbf685838e873d400" dependencies = [ "crossbeam-utils", ] [[package]] name = "console-api" -version = "0.5.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2895653b4d9f1538a83970077cb01dfc77a4810524e51a110944688e916b18e" +checksum = "fd326812b3fd01da5bb1af7d340d0d555fd3d4b641e7f1dfcf5962a902952787" dependencies = [ + "futures-core", "prost", "prost-types", "tonic", @@ -603,14 +661,14 @@ dependencies = [ [[package]] name = "console-subscriber" -version = "0.1.10" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4cf42660ac07fcebed809cfe561dd8730bcd35b075215e6479c516bcd0d11cb" +checksum = "7481d4c57092cd1c19dd541b92bdce883de840df30aa5d03fd48a3935c01842e" dependencies = [ "console-api", "crossbeam-channel", "crossbeam-utils", - "futures", + "futures-task", "hdrhistogram", "humantime", "prost-types", @@ -625,32 +683,6 @@ dependencies = [ "tracing-subscriber", ] -[[package]] -name = "const_fn" -version = "0.4.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbdcdcb6d86f71c5e97409ad45898af11cbc995b4ee8112d59095a28d376c935" - -[[package]] -name = "const_format" -version = "0.2.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c990efc7a285731f9a4378d81aff2f0e85a2c8781a05ef0f8baa8dac54d0ff48" -dependencies = [ - "const_format_proc_macros", -] - -[[package]] -name = "const_format_proc_macros" -version = "0.2.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e026b6ce194a874cb9cf32cd5772d1ef9767cc8fcb5765948d74f37a9d8b2bf6" -dependencies = [ - "proc-macro2", - "quote", - "unicode-xid", -] - [[package]] name = "core-foundation-sys" version = "0.8.4" @@ -659,9 +691,9 @@ checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" [[package]] name = "cpufeatures" -version = "0.2.9" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a17b76ff3a4162b0b27f354a0c87015ddad39d35f9c0c36607a3bdd175dde1f1" +checksum = "ce420fe07aecd3e67c5f910618fe65e94158f6dcc0adf44e00d69ce2bdfe0fd0" dependencies = [ "libc", ] @@ -706,9 +738,12 @@ dependencies = [ [[package]] name = "deranged" -version = "0.3.8" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2696e8a945f658fd14dc3b87242e6b80cd0f36ff04ea560fa39082368847946" +checksum = "0f32d04922c60427da6f9fef14d042d9edddef64cb9d4ce0d64d0685fbeb1fd3" +dependencies = [ + "powerfmt", +] [[package]] name = "derivative" @@ -807,9 +842,9 @@ checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" [[package]] name = "embed-resource" -version = "2.3.0" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd0a2c9b742a980060d22545a7a83b573acd6b73045b9de6370c9530ce652f27" +checksum = "f54cc3e827ee1c3812239a9a41dede7b4d7d5d5464faa32d71bd7cba28ce2cb2" dependencies = [ "cc", "rustc_version", @@ -826,9 +861,9 @@ checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d" [[package]] name = "enumflags2" -version = "0.7.7" +version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c041f5090df68b32bcd905365fd51769c8b9d553fe87fde0b683534f10c01bd2" +checksum = "5998b4f30320c9d93aed72f63af821bfdac50465b75428fce77b48ec482c3939" dependencies = [ "enumflags2_derive", "serde", @@ -836,13 +871,13 @@ dependencies = [ [[package]] name = "enumflags2_derive" -version = "0.7.7" +version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e9a1f9f7d83e59740248a6e14ecf93929ade55027844dfcea78beafccc15745" +checksum = "f95e2801cd355d4a1a3e3953ce6ee5ae9603a5c833455343a8bfe3f44d418246" dependencies = [ "proc-macro2", "quote", - "syn 2.0.29", + "syn 2.0.39", ] [[package]] @@ -853,31 +888,61 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" -version = "0.3.3" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "136526188508e25c6fef639d7927dfb3e0e3084488bf202267829cf7fc23dbdd" +checksum = "f258a7194e7f7c2a7837a8913aeab7fd8c383457034fa20ce4dd3dcb813e8eb8" 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 = "event-listener" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d93877bcde0eb80ca09131a08d23f0a5c18a620b01db137dba666d18cd9b30c2" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "770d968249b5d99410d61f5bf89057f3199a077a04d087092f58e7d10692baae" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "958e4d70b6d5e81971bebec42271ec641e7ff4e170a6fa605f2b8a8b65cb97d3" +dependencies = [ + "event-listener 4.0.0", + "pin-project-lite", +] + +[[package]] +name = "faster-hex" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "239f7bfb930f820ab16a9cd95afc26f88264cf6905c960b340a615384aa3338a" +dependencies = [ + "serde", +] + [[package]] name = "fastrand" version = "1.9.0" @@ -889,9 +954,9 @@ dependencies = [ [[package]] name = "fastrand" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6999dc1837253364c2ebb0704ba97994bd874e8f195d665c50b7548f6ea92764" +checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" [[package]] name = "filetime" @@ -907,9 +972,9 @@ dependencies = [ [[package]] name = "flate2" -version = "1.0.27" +version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6c98ee8095e9d1dcbf2fcc6d95acccb90d1c81db1e44725c6a984b1dbdfb010" +checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e" dependencies = [ "crc32fast", "miniz_oxide", @@ -923,13 +988,22 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "form_urlencoded" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" dependencies = [ "percent-encoding", ] +[[package]] +name = "fs-err" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88a41f105fe1d5b6b34b2055e3dc59bb79b46b48b2040b9e6c7b4b5de097aa41" +dependencies = [ + "autocfg", +] + [[package]] name = "fsevent-sys" version = "4.1.0" @@ -941,9 +1015,9 @@ dependencies = [ [[package]] name = "futures" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40" +checksum = "da0290714b38af9b4a7b094b8a37086d1b4e61f2df9122c3cad2577669145335" dependencies = [ "futures-channel", "futures-core", @@ -956,9 +1030,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" +checksum = "ff4dd66668b557604244583e3e1e1eada8c5c2e96a6d0d6653ede395b78bbacb" dependencies = [ "futures-core", "futures-sink", @@ -966,15 +1040,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" +checksum = "eb1d22c66e66d9d72e1758f0bd7d4fd0bee04cad842ee34587d68c07e45d088c" [[package]] name = "futures-executor" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0" +checksum = "0f4fb8693db0cf099eadcca0efe2a5a22e4550f98ed16aba6c48700da29597bc" dependencies = [ "futures-core", "futures-task", @@ -983,9 +1057,9 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" +checksum = "8bf34a163b5c4c52d0478a4d757da8fb65cabef42ba90515efee0f6f9fa45aaa" [[package]] name = "futures-lite" @@ -1003,33 +1077,47 @@ dependencies = [ ] [[package]] -name = "futures-macro" -version = "0.3.28" +name = "futures-lite" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" +checksum = "d3831c2651acb5177cbd83943f3d9c8912c5ad03c76afcc0e9511ba568ec5ebb" +dependencies = [ + "fastrand 2.0.1", + "futures-core", + "futures-io", + "memchr", + "parking", + "pin-project-lite", +] + +[[package]] +name = "futures-macro" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53b153fd91e4b0147f4aced87be237c98248656bb01050b96bf3ee89220a8ddb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.29", + "syn 2.0.39", ] [[package]] name = "futures-sink" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" +checksum = "e36d3378ee38c2a36ad710c5d30c2911d752cb941c00c72dbabfb786a7970817" [[package]] name = "futures-task" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" +checksum = "efd193069b0ddadc69c46389b740bbccdd97203899b48d09c5f7969591d6bae2" [[package]] name = "futures-util" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" +checksum = "a19526d624e703a3179b3d322efec918b6246ea0fa51d41124525f00f1cc8104" dependencies = [ "futures-channel", "futures-core", @@ -1055,66 +1143,48 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.10" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" +checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f" dependencies = [ "cfg-if", "libc", - "wasi 0.11.0+wasi-snapshot-preview1", + "wasi", ] [[package]] name = "gimli" -version = "0.28.0" +version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" - -[[package]] -name = "git2" -version = "0.17.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b989d6a7ca95a362cf2cfc5ad688b3a467be1f87e480b8dad07fee8c79b0044" -dependencies = [ - "bitflags 1.3.2", - "libc", - "libgit2-sys", - "log", - "url", -] +checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" [[package]] name = "gix" -version = "0.48.0" +version = "0.55.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1e74cea676de7f53a79f3c0365812b11f6814b81e671b8ee4abae6ca09c7881" +checksum = "002667cd1ebb789313d0d0afe3d23b2821cf3b0e91605095f0e6d8751f0ceeea" dependencies = [ "gix-actor", - "gix-attributes", "gix-commitgraph", "gix-config", - "gix-credentials", "gix-date", "gix-diff", "gix-discover", - "gix-features 0.31.1", - "gix-fs 0.3.0", + "gix-features", + "gix-fs", "gix-glob", "gix-hash", "gix-hashtable", - "gix-ignore", - "gix-index", "gix-lock", - "gix-mailmap", - "gix-negotiate", + "gix-macros", "gix-object", "gix-odb", "gix-pack", "gix-path", - "gix-prompt", "gix-ref", "gix-refspec", "gix-revision", + "gix-revwalk", "gix-sec", "gix-tempfile", "gix-trace", @@ -1122,10 +1192,8 @@ dependencies = [ "gix-url", "gix-utils", "gix-validate", - "gix-worktree", - "log", "once_cell", - "signal-hook", + "parking_lot", "smallvec", "thiserror", "unicode-normalization", @@ -1133,42 +1201,16 @@ dependencies = [ [[package]] name = "gix-actor" -version = "0.23.0" +version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1969b77b9ee4cc1755c841987ec6f7622aaca95e952bcafb76973ae59d1b8716" +checksum = "948a5f9e43559d16faf583694f1c742eb401ce24ce8e6f2238caedea7486433c" dependencies = [ "bstr", "btoi", "gix-date", "itoa", - "nom", - "thiserror", -] - -[[package]] -name = "gix-attributes" -version = "0.14.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3772b0129dcd1fc73e985bbd08a1482d082097d2915cb1ee31ce8092b8e4434" -dependencies = [ - "bstr", - "gix-glob", - "gix-path", - "gix-quote", - "kstring", - "log", - "smallvec", - "thiserror", - "unicode-bom", -] - -[[package]] -name = "gix-bitmap" -version = "0.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ccab4bc576844ddb51b78d81b4a42d73e6229660fa614dfc3d3999c874d1959" -dependencies = [ "thiserror", + "winnow", ] [[package]] @@ -1180,24 +1222,15 @@ dependencies = [ "thiserror", ] -[[package]] -name = "gix-command" -version = "0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f28f654184b5f725c5737c7e4f466cbd8f0102ac352d5257eeab19647ee4256" -dependencies = [ - "bstr", -] - [[package]] name = "gix-commitgraph" -version = "0.17.1" +version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed42baa50075d41c1a0931074ce1a97c5797c7c6fe7591d9f1f2dcd448532c26" +checksum = "7e8bc78b1a6328fa6d8b3a53b6c73997af37fd6bfc1d6c49f149e63bda5cbb36" dependencies = [ "bstr", "gix-chunk", - "gix-features 0.31.1", + "gix-features", "gix-hash", "memmap2", "thiserror", @@ -1205,84 +1238,66 @@ dependencies = [ [[package]] name = "gix-config" -version = "0.25.1" +version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "817688c7005a716d9363e267913526adea402dabd947f4ba63842d10cc5132af" +checksum = "5cae98c6b4c66c09379bc35274b172587d6b0ac369a416c39128ad8c6454f9bb" dependencies = [ "bstr", "gix-config-value", - "gix-features 0.31.1", + "gix-features", "gix-glob", "gix-path", "gix-ref", "gix-sec", - "log", "memchr", - "nom", "once_cell", "smallvec", "thiserror", "unicode-bom", + "winnow", ] [[package]] name = "gix-config-value" -version = "0.12.5" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e874f41437441c02991dcea76990b9058fadfc54b02ab4dd06ab2218af43897" +checksum = "ea7505b97f4d8e7933e29735a568ba2f86d8de466669d9f0e8321384f9972f47" dependencies = [ - "bitflags 2.4.0", + "bitflags 2.4.1", "bstr", "gix-path", "libc", "thiserror", ] -[[package]] -name = "gix-credentials" -version = "0.16.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75a75565e0e6e7f80cfa4eb1b05cc448c6846ddd48dcf413a28875fbc11ee9af" -dependencies = [ - "bstr", - "gix-command", - "gix-config-value", - "gix-path", - "gix-prompt", - "gix-sec", - "gix-url", - "thiserror", -] - [[package]] name = "gix-date" -version = "0.7.3" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01e476b4e156f6044d35bf1ce2079d97b7207515cfb5a2bb6fcd489bb697d700" +checksum = "fc7df669639582dc7c02737642f76890b03b5544e141caba68a7d6b4eb551e0d" dependencies = [ "bstr", "itoa", "thiserror", - "time 0.3.28", + "time", ] [[package]] name = "gix-diff" -version = "0.32.0" +version = "0.37.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aaf5d9b9b521b284ebe53ee69eee33341835ec70edc314f36b2100ea81396121" +checksum = "931394f69fb8c9ed6afc0aae3487bd869e936339bcc13ed8884472af072e0554" dependencies = [ "gix-hash", "gix-object", - "imara-diff", "thiserror", ] [[package]] name = "gix-discover" -version = "0.21.1" +version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "272aad20dc63dedba76615373dd8885fb5aebe4795e5b5b0aa2a24e63c82085c" +checksum = "a45d5cf0321178883e38705ab2b098f625d609a7d4c391b33ac952eff2c490f2" dependencies = [ "bstr", "dunce", @@ -1295,9 +1310,9 @@ dependencies = [ [[package]] name = "gix-features" -version = "0.31.1" +version = "0.36.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06142d8cff5d17509399b04052b64d2f9b3a311d5cff0b1a32b220f62cd0d595" +checksum = "51f4365ba17c4f218d7fd9ec102b8d2d3cb0ca200a835e81151ace7778aec827" dependencies = [ "crc32fast", "flate2", @@ -1311,107 +1326,53 @@ dependencies = [ "walkdir", ] -[[package]] -name = "gix-features" -version = "0.32.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "882695cccf38da4c3cc7ee687bdb412cf25e37932d7f8f2c306112ea712449f1" -dependencies = [ - "gix-hash", - "gix-trace", - "libc", -] - [[package]] name = "gix-fs" -version = "0.3.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb15956bc0256594c62a2399fcf6958a02a11724217eddfdc2b49b21b6292496" +checksum = "8cd171c0cae97cd0dc57e7b4601cb1ebf596450e263ef3c02be9107272c877bd" dependencies = [ - "gix-features 0.31.1", -] - -[[package]] -name = "gix-fs" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d5b6e9d34a2c61ea4a02bbca94c409ab6dbbca1348cbb67298cd7fed8758761" -dependencies = [ - "gix-features 0.32.1", + "gix-features", ] [[package]] name = "gix-glob" -version = "0.9.1" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c18bdff83143d61e7d60da6183b87542a870d026b2a2d0b30170b8e9c0cd321a" +checksum = "8fac08925dbc14d414bd02eb45ffb4cecd912d1fce3883f867bd0103c192d3e4" dependencies = [ - "bitflags 2.4.0", + "bitflags 2.4.1", "bstr", - "gix-features 0.31.1", + "gix-features", "gix-path", ] [[package]] name = "gix-hash" -version = "0.11.4" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b422ff2ad9a0628baaad6da468cf05385bf3f5ab495ad5a33cce99b9f41092f" +checksum = "1884c7b41ea0875217c1be9ce91322f90bde433e91d374d0e1276073a51ccc60" dependencies = [ - "hex", + "faster-hex", "thiserror", ] [[package]] name = "gix-hashtable" -version = "0.2.4" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "385f4ce6ecf3692d313ca3aa9bd3b3d8490de53368d6d94bedff3af8b6d9c58d" +checksum = "409268480841ad008e81c17ca5a293393fbf9f2b6c2f85b8ab9de1f0c5176a16" dependencies = [ "gix-hash", - "hashbrown 0.14.0", + "hashbrown 0.14.2", "parking_lot", ] -[[package]] -name = "gix-ignore" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca801f2d0535210f77b33e2c067d565aedecacc82f1b3dbce26da1388ebc4634" -dependencies = [ - "bstr", - "gix-glob", - "gix-path", - "unicode-bom", -] - -[[package]] -name = "gix-index" -version = "0.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68099abdf6ee50ae3c897e8b05de96871cbe54d52a37cdf559101f911b883562" -dependencies = [ - "bitflags 2.4.0", - "bstr", - "btoi", - "filetime", - "gix-bitmap", - "gix-features 0.31.1", - "gix-hash", - "gix-lock", - "gix-object", - "gix-traverse", - "itoa", - "memmap2", - "smallvec", - "thiserror", -] - [[package]] name = "gix-lock" -version = "7.0.2" +version = "11.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e82ec23c8a281f91044bf3ed126063b91b59f9c9340bf0ae746f385cc85a6fa" +checksum = "f4feb1dcd304fe384ddc22edba9dd56a42b0800032de6537728cea2f033a4f37" dependencies = [ "gix-tempfile", "gix-utils", @@ -1419,62 +1380,44 @@ dependencies = [ ] [[package]] -name = "gix-mailmap" -version = "0.15.0" +name = "gix-macros" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1787e3c37fc43b1f7c0e3be6196c6837b3ba5f869190dfeaa444b816f0a7f34b" +checksum = "9d8acb5ee668d55f0f2d19a320a3f9ef67a6999ad483e11135abcc2464ed18b6" dependencies = [ - "bstr", - "gix-actor", - "gix-date", - "thiserror", -] - -[[package]] -name = "gix-negotiate" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e7bce64d4452dd609f44d04b14b29da2e0ad2c45fcdf4ce1472a5f5f8ec21c2" -dependencies = [ - "bitflags 2.4.0", - "gix-commitgraph", - "gix-date", - "gix-hash", - "gix-object", - "gix-revwalk", - "smallvec", - "thiserror", + "proc-macro2", + "quote", + "syn 2.0.39", ] [[package]] name = "gix-object" -version = "0.32.0" +version = "0.38.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a953f3d7ffad16734aa3ab1d05807972c80e339d1bd9dde03e0198716b99e2a6" +checksum = "740f2a44267f58770a1cb3a3d01d14e67b089c7136c48d4bddbb3cfd2bf86a51" dependencies = [ "bstr", "btoi", "gix-actor", "gix-date", - "gix-features 0.31.1", + "gix-features", "gix-hash", "gix-validate", - "hex", "itoa", - "nom", "smallvec", "thiserror", + "winnow", ] [[package]] name = "gix-odb" -version = "0.49.1" +version = "0.54.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6418cff00ecc2713b58c8e04bff30dda808fbba1a080e7248b299d069894a01" +checksum = "8630b56cb80d8fa684d383dad006a66401ee8314e12fbf0e566ddad8c115143b" dependencies = [ "arc-swap", "gix-date", - "gix-features 0.31.1", + "gix-features", "gix-hash", "gix-object", "gix-pack", @@ -1487,20 +1430,18 @@ dependencies = [ [[package]] name = "gix-pack" -version = "0.39.1" +version = "0.44.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "414935138d90043ea5898de7a93f02c2558e52652492719470e203ef26a8fd0a" +checksum = "1431ba2e30deff1405920693d54ab231c88d7c240dd6ccc936ee223d8f8697c3" dependencies = [ "clru", "gix-chunk", - "gix-diff", - "gix-features 0.31.1", + "gix-features", "gix-hash", "gix-hashtable", "gix-object", "gix-path", "gix-tempfile", - "gix-traverse", "memmap2", "parking_lot", "smallvec", @@ -1509,9 +1450,9 @@ dependencies = [ [[package]] name = "gix-path" -version = "0.8.4" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18609c8cbec8508ea97c64938c33cd305b75dfc04a78d0c3b78b8b3fd618a77c" +checksum = "6a1d370115171e3ae03c5c6d4f7d096f2981a40ddccb98dfd704c773530ba73b" dependencies = [ "bstr", "gix-trace", @@ -1520,19 +1461,6 @@ dependencies = [ "thiserror", ] -[[package]] -name = "gix-prompt" -version = "0.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c22decaf4a063ccae2b2108820c8630c01bd6756656df3fe464b32b8958a5ea" -dependencies = [ - "gix-command", - "gix-config-value", - "parking_lot", - "rustix 0.38.10", - "thiserror", -] - [[package]] name = "gix-quote" version = "0.4.7" @@ -1546,14 +1474,14 @@ dependencies = [ [[package]] name = "gix-ref" -version = "0.32.1" +version = "0.38.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39453f4e5f23cddc2e6e4cca2ba20adfdbec29379e3ca829714dfe98ae068ccd" +checksum = "0ec2f6d07ac88d2fb8007ee3fa3e801856fb9d82e7366ec0ca332eb2c9d74a52" dependencies = [ "gix-actor", "gix-date", - "gix-features 0.31.1", - "gix-fs 0.3.0", + "gix-features", + "gix-fs", "gix-hash", "gix-lock", "gix-object", @@ -1561,15 +1489,15 @@ dependencies = [ "gix-tempfile", "gix-validate", "memmap2", - "nom", "thiserror", + "winnow", ] [[package]] name = "gix-refspec" -version = "0.13.0" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8e76ff1f82fba295a121e31ab02f69642994e532c45c0c899aa393f4b740302" +checksum = "ccb0974cc41dbdb43a180c7f67aa481e1c1e160fcfa8f4a55291fd1126c1a6e7" dependencies = [ "bstr", "gix-hash", @@ -1581,9 +1509,9 @@ dependencies = [ [[package]] name = "gix-revision" -version = "0.17.0" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "237428a7d3978e8572964e1e45d984027c2acc94df47e594baa6c4b0da7c9922" +checksum = "2ca97ac73459a7f3766aa4a5638a6e37d56d4c7962bc1986fbaf4883d0772588" dependencies = [ "bstr", "gix-date", @@ -1591,14 +1519,15 @@ dependencies = [ "gix-hashtable", "gix-object", "gix-revwalk", + "gix-trace", "thiserror", ] [[package]] name = "gix-revwalk" -version = "0.3.0" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "028d50fcaf8326a8f79a359490d9ca9fb4e2b51ac9ac86503560d0bcc888d2eb" +checksum = "a16d8c892e4cd676d86f0265bf9d40cefd73d8d94f86b213b8b77d50e77efae0" dependencies = [ "gix-commitgraph", "gix-date", @@ -1611,11 +1540,11 @@ dependencies = [ [[package]] name = "gix-sec" -version = "0.8.4" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9615cbd6b456898aeb942cd75e5810c382fbfc48dbbff2fa23ebd2d33dcbe9c7" +checksum = "92b9542ac025a8c02ed5d17b3fc031a111a384e859d0be3532ec4d58c40a0f28" dependencies = [ - "bitflags 2.4.0", + "bitflags 2.4.1", "gix-path", "libc", "windows 0.48.0", @@ -1623,16 +1552,14 @@ dependencies = [ [[package]] name = "gix-tempfile" -version = "7.0.2" +version = "11.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa28d567848cec8fdd77d36ad4f5f78ecfaba7d78f647d4f63c8ae1a2cec7243" +checksum = "05cc2205cf10d99f70b96e04e16c55d4c7cf33efc151df1f793e29fd12a931f8" dependencies = [ - "gix-fs 0.4.1", + "gix-fs", "libc", "once_cell", "parking_lot", - "signal-hook", - "signal-hook-registry", "tempfile", ] @@ -1644,9 +1571,9 @@ checksum = "96b6d623a1152c3facb79067d6e2ecdae48130030cf27d6eb21109f13bd7b836" [[package]] name = "gix-traverse" -version = "0.29.0" +version = "0.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3cdfd54598db4fae57d5ae6f52958422b2d13382d2745796bfe5c8015ffa86e" +checksum = "14d050ec7d4e1bb76abf0636cf4104fb915b70e54e3ced9a4427c999100ff38a" dependencies = [ "gix-commitgraph", "gix-date", @@ -1660,12 +1587,12 @@ dependencies = [ [[package]] name = "gix-url" -version = "0.20.1" +version = "0.25.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "beaede6dbc83f408b19adfd95bb52f1dbf01fb8862c3faf6c6243e2e67fcdfa1" +checksum = "b1b9ac8ed32ad45f9fc6c5f8c0be2ed911e544a5a19afd62d95d524ebaa95671" dependencies = [ "bstr", - "gix-features 0.31.1", + "gix-features", "gix-path", "home", "thiserror", @@ -1678,40 +1605,19 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b85d89dc728613e26e0ed952a19583744e7f5240fcd4aa30d6c824ffd8b52f0f" dependencies = [ - "fastrand 2.0.0", + "fastrand 2.0.1", ] [[package]] name = "gix-validate" -version = "0.7.7" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba9b3737b2cef3dcd014633485f0034b0f1a931ee54aeb7d8f87f177f3c89040" +checksum = "e05cab2b03a45b866156e052aa38619f4ece4adcb2f79978bfc249bc3b21b8c5" dependencies = [ "bstr", "thiserror", ] -[[package]] -name = "gix-worktree" -version = "0.21.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1363b9aa66b9e14412ac04e1f759827203f491729d92172535a8ce6cde02efa" -dependencies = [ - "bstr", - "filetime", - "gix-attributes", - "gix-features 0.31.1", - "gix-fs 0.3.0", - "gix-glob", - "gix-hash", - "gix-ignore", - "gix-index", - "gix-object", - "gix-path", - "io-close", - "thiserror", -] - [[package]] name = "globset" version = "0.4.13" @@ -1727,9 +1633,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.3.21" +version = "0.3.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91fc23aa11be92976ef4729127f1a74adf36d8436f7816b185d18df956790833" +checksum = "4d6250322ef6e60f93f9a2162799302cd6f68f79f6e5d85c8c16f14d1d958178" dependencies = [ "bytes", "fnv", @@ -1737,7 +1643,7 @@ dependencies = [ "futures-sink", "futures-util", "http", - "indexmap 1.9.3", + "indexmap 2.1.0", "slab", "tokio", "tokio-util", @@ -1752,17 +1658,17 @@ checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" [[package]] name = "hashbrown" -version = "0.14.0" +version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" +checksum = "f93e7192158dbcda357bdec5fb5788eebf8bbac027f3f33e719d29135ae84156" [[package]] name = "hdrhistogram" -version = "7.5.2" +version = "7.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f19b9f54f7c7f55e31401bb647626ce0cf0f67b0004982ce815b3ee72a02aa8" +checksum = "765c9198f173dd59ce26ff9f95ef0aafd0a0fe01fb9d72841bc5066a4c06511d" dependencies = [ - "base64 0.13.1", + "base64", "byteorder", "flate2", "nom", @@ -1777,9 +1683,9 @@ checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" [[package]] name = "hermit-abi" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b" +checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" [[package]] name = "hex" @@ -1798,9 +1704,9 @@ dependencies = [ [[package]] name = "http" -version = "0.2.9" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482" +checksum = "8947b1a6fad4393052c7ba1f4cd97bed3e953a95c79c92ad9b051a04611d9fbb" dependencies = [ "bytes", "fnv", @@ -1853,7 +1759,7 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite", - "socket2 0.4.9", + "socket2 0.4.10", "tokio", "tower-service", "tracing", @@ -1874,16 +1780,16 @@ dependencies = [ [[package]] name = "iana-time-zone" -version = "0.1.57" +version = "0.1.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fad5b825842d2b38bd206f3e81d6957625fd7f0a361e345c30e01a0ae2dd613" +checksum = "8326b86b6cff230b97d0d312a6c40a60726df3332e721f72a1b035f451663b20" dependencies = [ "android_system_properties", "core-foundation-sys", "iana-time-zone-haiku", "js-sys", "wasm-bindgen", - "windows 0.48.0", + "windows-core", ] [[package]] @@ -1897,9 +1803,9 @@ dependencies = [ [[package]] name = "idna" -version = "0.4.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" dependencies = [ "unicode-bidi", "unicode-normalization", @@ -1938,16 +1844,6 @@ dependencies = [ "tracing", ] -[[package]] -name = "imara-diff" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e98c1d0ad70fc91b8b9654b1f33db55e59579d3b3de2bffdced0fdb810570cb8" -dependencies = [ - "ahash", - "hashbrown 0.12.3", -] - [[package]] name = "indexmap" version = "1.9.3" @@ -1960,12 +1856,12 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.0.0" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d" +checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f" dependencies = [ "equivalent", - "hashbrown 0.14.0", + "hashbrown 0.14.2", ] [[package]] @@ -1997,16 +1893,6 @@ dependencies = [ "cfg-if", ] -[[package]] -name = "io-close" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cadcf447f06744f8ce713d2d6239bb5bde2c357a452397a9ed90c625da390bc" -dependencies = [ - "libc", - "winapi", -] - [[package]] name = "io-lifetimes" version = "1.0.11" @@ -2025,7 +1911,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" dependencies = [ "hermit-abi", - "rustix 0.38.10", + "rustix 0.38.25", "windows-sys 0.48.0", ] @@ -2035,17 +1921,11 @@ version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "616cde7c720bb2bb5824a224687d8f77bfd38922027f01d825cd7453be5099fb" -[[package]] -name = "is_debug" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06d198e9919d9822d5f7083ba8530e04de87841eaf21ead9af8f2304efd57c89" - [[package]] name = "itertools" -version = "0.10.5" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" dependencies = [ "either", ] @@ -2056,20 +1936,11 @@ version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" -[[package]] -name = "jobserver" -version = "0.1.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "936cfd212a0155903bcbc060e316fb6cc7cbf2e1907329391ebadc1fe0ce77c2" -dependencies = [ - "libc", -] - [[package]] name = "js-sys" -version = "0.3.64" +version = "0.3.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a" +checksum = "54c0c35952f67de54bb584e9fd912b3023117cbafc0a77d8f3dee1fb5f572fe8" dependencies = [ "wasm-bindgen", ] @@ -2094,15 +1965,6 @@ dependencies = [ "libc", ] -[[package]] -name = "kstring" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec3066350882a1cd6d950d055997f379ac37fd39f81cd4d8ed186032eb3c5747" -dependencies = [ - "static_assertions", -] - [[package]] name = "lazy_static" version = "1.4.0" @@ -2111,42 +1973,29 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.147" +version = "0.2.150" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" - -[[package]] -name = "libgit2-sys" -version = "0.15.2+1.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a80df2e11fb4a61f4ba2ab42dbe7f74468da143f1a75c74e11dee7c813f694fa" -dependencies = [ - "cc", - "libc", - "libz-sys", - "pkg-config", -] +checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c" [[package]] name = "libmimalloc-sys" -version = "0.1.34" +version = "0.1.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25d058a81af0d1c22d7a1c948576bee6d673f7af3c0f35564abd6c81122f513d" +checksum = "3979b5c37ece694f1f5e51e7ecc871fdb0f517ed04ee45f88d15d6d553cb9664" dependencies = [ "cc", "libc", ] [[package]] -name = "libz-sys" -version = "1.1.12" +name = "libredox" +version = "0.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d97137b25e321a73eef1418d1d5d2eda4d77e12813f8e6dead84bc52c5870a7b" +checksum = "85c833ca1e66078851dba29046874e38f08b2c883700aa29a03ddd3b23814ee8" dependencies = [ - "cc", + "bitflags 2.4.1", "libc", - "pkg-config", - "vcpkg", + "redox_syscall 0.4.1", ] [[package]] @@ -2157,15 +2006,15 @@ checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" [[package]] name = "linux-raw-sys" -version = "0.4.5" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57bcfdad1b858c2db7c38303a6d2ad4dfaf5eb53dfeb0910128b2c26d6158503" +checksum = "969488b55f8ac402214f3f5fd243ebb7206cf82de60d3172994707a4bcc2b829" [[package]] name = "lock_api" -version = "0.4.10" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16" +checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" dependencies = [ "autocfg", "scopeguard", @@ -2187,7 +2036,7 @@ dependencies = [ "dirs-next", "objc-foundation", "objc_id", - "time 0.3.28", + "time", ] [[package]] @@ -2210,15 +2059,15 @@ dependencies = [ [[package]] name = "matchit" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed1202b2a6f884ae56f04cff409ab315c5ce26b5e58d7412e484f01fd52f52ef" +checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" [[package]] name = "memchr" -version = "2.6.1" +version = "2.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f478948fd84d9f8e86967bf432640e46adfb5a4bd4f14ef7e864ab38220534ae" +checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" [[package]] name = "memmap2" @@ -2267,14 +2116,14 @@ checksum = "49e7bc1560b95a3c4a25d03de42fe76ca718ab92d1a22a55b9b4cf67b3ae635c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.29", + "syn 2.0.39", ] [[package]] name = "mimalloc" -version = "0.1.38" +version = "0.1.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "972e5f23f6716f62665760b0f4cbf592576a80c7b879ba9beaafc0e558894127" +checksum = "fa01922b5ea280a911e323e4d2fd24b7fe5cc4042e0d2cda3c40775cdc4bdc9c" dependencies = [ "libmimalloc-sys", ] @@ -2302,13 +2151,13 @@ dependencies = [ [[package]] name = "mio" -version = "0.8.8" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2" +checksum = "3dce281c5e46beae905d4de1870d8b1509a9142b62eedf18b443b011ca8343d0" dependencies = [ "libc", "log", - "wasi 0.11.0+wasi-snapshot-preview1", + "wasi", "windows-sys 0.48.0", ] @@ -2331,7 +2180,17 @@ dependencies = [ "cfg-if", "libc", "memoffset", - "pin-utils", +] + +[[package]] +name = "nix" +version = "0.27.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053" +dependencies = [ + "bitflags 2.4.1", + "cfg-if", + "libc", ] [[package]] @@ -2358,28 +2217,29 @@ checksum = "f5438dd2b2ff4c6df6e1ce22d825ed2fa93ee2922235cc45186991717f0a892d" [[package]] name = "notify" -version = "5.2.0" +version = "6.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "729f63e1ca555a43fe3efa4f3efdf4801c479da85b432242a7b726f353c88486" +checksum = "6205bd8bb1e454ad2e27422015fb5e4f2bcc7e08fa8f27058670d208324a4d2d" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.4.1", "crossbeam-channel", "filetime", "fsevent-sys", "inotify", "kqueue", "libc", + "log", "mio", "serde", "walkdir", - "windows-sys 0.45.0", + "windows-sys 0.48.0", ] [[package]] name = "notify-rust" -version = "4.9.0" +version = "4.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d7b75c8958cb2eab3451538b32db8a7b74006abc33eb2e6a9a56d21e4775c2b" +checksum = "827c5edfa80235ded4ab3fe8e9dc619b4f866ef16fe9b1c6b8a7f8692c0f2226" dependencies = [ "log", "mac-notification-sys", @@ -2400,9 +2260,9 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f30b0abd723be7e2ffca1272140fac1a2f084c77ec3e123c192b66af1ee9e6c2" +checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" dependencies = [ "autocfg", ] @@ -2457,9 +2317,9 @@ dependencies = [ [[package]] name = "object" -version = "0.32.0" +version = "0.32.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77ac5bbd07aea88c60a577a1ce218075ffd59208b2d7ca97adf9bfc5aeb21ebe" +checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0" dependencies = [ "memchr", ] @@ -2488,9 +2348,9 @@ dependencies = [ [[package]] name = "os_str_bytes" -version = "6.5.1" +version = "6.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d5d9eb14b174ee9aa2ef96dc2b94637a2d4b6e7cb873c7e171f0c20c6cf3eac" +checksum = "e2355d85b9a3786f481747ced0e0ff2ba35213a1f9bd406ed906554d7af805a1" dependencies = [ "memchr", ] @@ -2509,9 +2369,9 @@ checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f" [[package]] name = "parking" -version = "2.1.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14f2252c834a40ed9bb5422029649578e63aa341ac401f74e719dd1afda8394e" +checksum = "bb813b8af86854136c6922af0598d719255ecb2179515e6e7730d468f05c9cae" [[package]] name = "parking_lot" @@ -2525,22 +2385,22 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.8" +version = "0.9.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447" +checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.3.5", + "redox_syscall 0.4.1", "smallvec", "windows-targets 0.48.5", ] [[package]] name = "percent-encoding" -version = "2.3.0" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "phf" @@ -2580,6 +2440,17 @@ dependencies = [ "siphasher", ] +[[package]] +name = "pid1" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d44f4ae25e184abe89b03033470ffe3b15fca221bf3feb090044fe16fff81474" +dependencies = [ + "nix 0.27.1", + "signal-hook", + "thiserror", +] + [[package]] name = "pin-project" version = "1.1.3" @@ -2597,7 +2468,7 @@ checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" dependencies = [ "proc-macro2", "quote", - "syn 2.0.29", + "syn 2.0.39", ] [[package]] @@ -2613,10 +2484,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] -name = "pkg-config" -version = "0.3.27" +name = "piper" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" +checksum = "668d31b1c4eba19242f2088b2bf3316b82ca31082a8335764db4e083db7485d4" +dependencies = [ + "atomic-waker", + "fastrand 2.0.1", + "futures-io", +] [[package]] name = "polling" @@ -2634,6 +2510,26 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "polling" +version = "3.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf63fa624ab313c11656b4cda960bfc46c410187ad493c41f6ba2d8c1e991c9e" +dependencies = [ + "cfg-if", + "concurrent-queue", + "pin-project-lite", + "rustix 0.38.25", + "tracing", + "windows-sys 0.52.0", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + [[package]] name = "ppv-lite86" version = "0.2.17" @@ -2647,23 +2543,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" dependencies = [ "once_cell", - "toml_edit", + "toml_edit 0.19.15", ] [[package]] name = "proc-macro2" -version = "1.0.66" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" +checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da" dependencies = [ "unicode-ident", ] [[package]] name = "prodash" -version = "25.0.2" +version = "26.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d67eb4220992a4a052a4bb03cf776e493ecb1a3a36bab551804153d63486af7" +checksum = "794b5bf8e2d19b53dcdcec3e4bba628e20f5b6062503ba89281fa7037dd7bbcf" [[package]] name = "project-origins" @@ -2678,9 +2574,9 @@ dependencies = [ [[package]] name = "prost" -version = "0.11.9" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b82eaa1d779e9a4bc1c3217db8ffbeabaae1dca241bf70183242128d48681cd" +checksum = "146c289cda302b98a28d40c8b3b90498d6e526dd24ac2ecea73e4e491685b94a" dependencies = [ "bytes", "prost-derive", @@ -2688,31 +2584,31 @@ dependencies = [ [[package]] name = "prost-derive" -version = "0.11.9" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5d2d8d10f3c6ded6da8b05b5fb3b8a5082514344d56c9f871412d29b4e075b4" +checksum = "efb6c9a1dd1def8e2124d17e83a20af56f1570d6c2d2bd9e266ccb768df3840e" dependencies = [ "anyhow", "itertools", "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.39", ] [[package]] name = "prost-types" -version = "0.11.9" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "213622a1460818959ac1181aaeb2dc9c7f63df720db7d788b3e24eacd1983e13" +checksum = "193898f59edcf43c26227dcd4c8427f00d99d61e95dcde58dabd49fa291d470e" dependencies = [ "prost", ] [[package]] name = "quick-xml" -version = "0.23.1" +version = "0.30.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11bafc859c6815fbaffbbbf4229ecb767ac913fecb27f9ad4343662e9ef099ea" +checksum = "eff6510e86862b57b210fd8cbe8ed3f0d7d600b9c2863cd4549a2e033c66e956" dependencies = [ "memchr", ] @@ -2766,15 +2662,6 @@ dependencies = [ "getrandom", ] -[[package]] -name = "redox_syscall" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" -dependencies = [ - "bitflags 1.3.2", -] - [[package]] name = "redox_syscall" version = "0.3.5" @@ -2785,26 +2672,35 @@ dependencies = [ ] [[package]] -name = "redox_users" -version = "0.4.3" +name = "redox_syscall" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "redox_users" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a18479200779601e498ada4e8c1e1f50e3ee19deb0259c25825a98b5603b2cb4" dependencies = [ "getrandom", - "redox_syscall 0.2.16", + "libredox", "thiserror", ] [[package]] name = "regex" -version = "1.9.4" +version = "1.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12de2eff854e5fa4b1295edd650e227e9d8fb0c9e90b12e7f36d6a6811791a29" +checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.3.7", - "regex-syntax 0.7.5", + "regex-automata 0.4.3", + "regex-syntax 0.8.2", ] [[package]] @@ -2818,13 +2714,13 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.3.7" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49530408a136e16e5b486e883fbb6ba058e8e4e8ae6621a77b048b314336e629" +checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.7.5", + "regex-syntax 0.8.2", ] [[package]] @@ -2835,9 +2731,9 @@ checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] name = "regex-syntax" -version = "0.7.5" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da" +checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" [[package]] name = "roff" @@ -2862,9 +2758,9 @@ dependencies = [ [[package]] name = "rustix" -version = "0.37.23" +version = "0.37.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d69718bf81c6127a49dc64e44a742e8bb9213c0ff8869a22c308f84c1d4ab06" +checksum = "fea8ca367a3a01fe35e6943c400addf443c0f57670e6ec51196f71a4b8762dd2" dependencies = [ "bitflags 1.3.2", "errno", @@ -2876,14 +2772,14 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.10" +version = "0.38.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed6248e1caa625eb708e266e06159f135e8c26f2bb7ceb72dc4b2766d0340964" +checksum = "dc99bc2d4f1fed22595588a013687477aedf3cdcfb26558c559edb67b4d9b22e" dependencies = [ - "bitflags 2.4.0", + "bitflags 2.4.1", "errno", "libc", - "linux-raw-sys 0.4.5", + "linux-raw-sys 0.4.11", "windows-sys 0.48.0", ] @@ -2916,35 +2812,35 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "semver" -version = "1.0.18" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0293b4b29daaf487284529cc2f5675b8e57c61f70167ba415a463651fd6a918" +checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090" [[package]] name = "serde" -version = "1.0.188" +version = "1.0.193" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e" +checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.188" +version = "1.0.193" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2" +checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.29", + "syn 2.0.39", ] [[package]] name = "serde_json" -version = "1.0.105" +version = "1.0.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "693151e1ac27563d6dbcec9dee9fbd5da8539b20fa14ad3752b2e6d363ace360" +checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b" dependencies = [ "itoa", "ryu", @@ -2953,29 +2849,29 @@ dependencies = [ [[package]] name = "serde_repr" -version = "0.1.16" +version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8725e1dfadb3a50f7e5ce0b1a540466f6ed3fe7a0fca2ac2b8b831d31316bd00" +checksum = "3081f5ffbb02284dda55132aa26daecedd7372a42417bbbab6f14ab7d6bb9145" dependencies = [ "proc-macro2", "quote", - "syn 2.0.29", + "syn 2.0.39", ] [[package]] name = "serde_spanned" -version = "0.6.3" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96426c9936fd7a0124915f9185ea1d20aa9445cc9821142f0a73bc9207a2e186" +checksum = "12022b835073e5b11e90a14f86838ceb1c8fb0325b72416845c487ac0fa95e80" dependencies = [ "serde", ] [[package]] name = "sha1" -version = "0.10.5" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" dependencies = [ "cfg-if", "cpufeatures", @@ -2988,24 +2884,11 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae1a47186c03a32177042e55dbc5fd5aee900b8e0069a8d70fba96a9375cd012" -[[package]] -name = "shadow-rs" -version = "0.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "157bef6b3029f72d6f4226acdfa466b84526aa62ae36a3bcf1e1801b403ecd74" -dependencies = [ - "const_format", - "git2", - "is_debug", - "time 0.3.28", - "tzdb", -] - [[package]] name = "sharded-slab" -version = "0.1.4" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" dependencies = [ "lazy_static", ] @@ -3031,9 +2914,9 @@ dependencies = [ [[package]] name = "similar" -version = "2.2.1" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "420acb44afdae038210c99e69aae24109f32f15500aa708e81d46c9f29d55fcf" +checksum = "2aeaf503862c419d66959f5d7ca015337d864e9c49485d771b732e2a20453597" [[package]] name = "siphasher" @@ -3052,21 +2935,21 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.11.0" +version = "1.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62bb4feee49fdd9f707ef802e22365a35de4b7b299de4763d44bfea899442ff9" +checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970" [[package]] name = "smawk" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f67ad224767faa3c7d8b6d91985b78e70a1324408abcb1cfcc2be4c06bc06043" +checksum = "b7c388c1b5e93756d0c740965c41e8822f866621d41acbdf6336a6a168f8840c" [[package]] name = "snapbox" -version = "0.4.12" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad90eb3a2e3a8031d636d45bd4832751aefd58a291b553f7305a2bacae21aff3" +checksum = "4b377c0b6e4715c116473d8e40d51e3fa5b0a2297ca9b2a931ba800667b259ed" dependencies = [ "anstream", "anstyle", @@ -3077,18 +2960,18 @@ dependencies = [ [[package]] name = "snapbox-macros" -version = "0.3.5" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95f4ffd811b87da98d0e48285134b7847954bd76e843bb794a893b47ca3ee325" +checksum = "ed1559baff8a696add3322b9be3e940d433e7bb4e38d79017205fd37ff28b28e" dependencies = [ "anstream", ] [[package]] name = "socket2" -version = "0.4.9" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" +checksum = "9f7916fc008ca5542385b89a3d3ce689953c143e9304a9bf8beec1de48994c0d" dependencies = [ "libc", "winapi", @@ -3096,9 +2979,9 @@ dependencies = [ [[package]] name = "socket2" -version = "0.5.3" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2538b18701741680e0322a2302176d3253a35388e2e62f172f64f4f16605f877" +checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9" dependencies = [ "libc", "windows-sys 0.48.0", @@ -3118,9 +3001,9 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "supports-color" -version = "2.0.0" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4950e7174bffabe99455511c39707310e7e9b440364a2fcb1cc21521be57b354" +checksum = "d6398cde53adc3c4557306a96ce67b302968513830a77a95b2b17305d9719a89" dependencies = [ "is-terminal", "is_ci", @@ -3157,9 +3040,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.29" +version = "2.0.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c324c494eba9d92503e6f1ef2e6df781e78f6a7705a0202d9801b198807d518a" +checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a" dependencies = [ "proc-macro2", "quote", @@ -3174,24 +3057,24 @@ checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" [[package]] name = "tauri-winrt-notification" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f5bff1d532fead7c43324a0fa33643b8621a47ce2944a633be4cb6c0240898f" +checksum = "006851c9ccefa3c38a7646b8cec804bb429def3da10497bfa977179869c3e8e2" dependencies = [ "quick-xml", - "windows 0.39.0", + "windows 0.51.1", ] [[package]] name = "tempfile" -version = "3.8.0" +version = "3.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb94d2f3cc536af71caac6b6fcebf65860b347e7ce0cc9ebe8f70d3e521054ef" +checksum = "7ef1adac450ad7f4b3c28589471ade84f25f731a7a0fe30d71dfa9f60fd808e5" dependencies = [ "cfg-if", - "fastrand 2.0.0", - "redox_syscall 0.3.5", - "rustix 0.38.10", + "fastrand 2.0.1", + "redox_syscall 0.4.1", + "rustix 0.38.25", "windows-sys 0.48.0", ] @@ -3207,11 +3090,11 @@ dependencies = [ [[package]] name = "terminal_size" -version = "0.2.6" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e6bf6f19e9f8ed8d4048dc22981458ebcf406d67e94cd422e5ecd73d63b3237" +checksum = "21bebf2b7c9e0a515f6e0f8c51dc0f8e4696391e6f1ff30379559f8365fb0df7" dependencies = [ - "rustix 0.37.23", + "rustix 0.38.25", "windows-sys 0.48.0", ] @@ -3241,22 +3124,22 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.47" +version = "1.0.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97a802ec30afc17eee47b2855fc72e0c4cd62be9b4efe6591edde0ec5bd68d8f" +checksum = "f9a7210f5c9a7156bb50aa36aed4c95afb51df0df00713949448cf9e97d382d2" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.47" +version = "1.0.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bb623b56e39ab7dcd4b1b98bb6c8f8d907ed255b18de254088016b27a8ee19b" +checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.29", + "syn 2.0.39", ] [[package]] @@ -3271,25 +3154,15 @@ dependencies = [ [[package]] name = "time" -version = "0.1.45" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a" -dependencies = [ - "libc", - "wasi 0.10.0+wasi-snapshot-preview1", - "winapi", -] - -[[package]] -name = "time" -version = "0.3.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17f6bb557fd245c28e6411aa56b6403c689ad95061f50e4be16c274e70a17e48" +checksum = "c4a34ab300f2dee6e562c10a046fc05e358b29f9bf92277f30c3c8d82275f6f5" dependencies = [ "deranged", "itoa", "libc", "num_threads", + "powerfmt", "serde", "time-core", "time-macros", @@ -3297,15 +3170,15 @@ dependencies = [ [[package]] name = "time-core" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "time-macros" -version = "0.2.14" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a942f44339478ef67935ab2bbaec2fb0322496cf3cbe84b261e06ac3814c572" +checksum = "4ad70d68dba9e1f8aceda7aa6711965dfec1cac869f311a51bd08b3a2ccbce20" dependencies = [ "time-core", ] @@ -3327,9 +3200,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.32.0" +version = "1.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17ed6077ed6cd6c74735e21f37eb16dc3935f96878b1fe961074089cc80893f9" +checksum = "d0c014766411e834f7af5b8f4cf46257aab4036ca95e9d2c144a10f59ad6f5b9" dependencies = [ "backtrace", "bytes", @@ -3338,7 +3211,7 @@ dependencies = [ "num_cpus", "pin-project-lite", "signal-hook-registry", - "socket2 0.5.3", + "socket2 0.5.5", "tokio-macros", "tracing", "windows-sys 0.48.0", @@ -3356,13 +3229,13 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "2.1.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" +checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.29", + "syn 2.0.39", ] [[package]] @@ -3378,9 +3251,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.8" +version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "806fe8c2c87eccc8b3267cbae29ed3ab2d0bd37fca70ab622e46aaa9375ddb7d" +checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15" dependencies = [ "bytes", "futures-core", @@ -3392,32 +3265,43 @@ dependencies = [ [[package]] name = "toml" -version = "0.7.6" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c17e963a819c331dcacd7ab957d80bc2b9a9c1e71c804826d2f283dd65306542" +checksum = "a1a195ec8c9da26928f773888e0742ca3ca1040c6cd859c919c9f59c1954ab35" dependencies = [ "serde", "serde_spanned", "toml_datetime", - "toml_edit", + "toml_edit 0.21.0", ] [[package]] name = "toml_datetime" -version = "0.6.3" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" +checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" dependencies = [ "serde", ] [[package]] name = "toml_edit" -version = "0.19.14" +version = "0.19.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8123f27e969974a3dfba720fdb560be359f57b44302d280ba72e76a74480e8a" +checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" dependencies = [ - "indexmap 2.0.0", + "indexmap 2.1.0", + "toml_datetime", + "winnow", +] + +[[package]] +name = "toml_edit" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d34d383cd00a163b4a5b85053df514d45bc330f6de7737edfe0a93311d1eaa03" +dependencies = [ + "indexmap 2.1.0", "serde", "serde_spanned", "toml_datetime", @@ -3426,16 +3310,15 @@ dependencies = [ [[package]] name = "tonic" -version = "0.9.2" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3082666a3a6433f7f511c7192923fa1fe07c69332d3c6a2e6bb040b569199d5a" +checksum = "d560933a0de61cf715926b9cac824d4c883c2c43142f787595e48280c40a1d0e" dependencies = [ + "async-stream", "async-trait", "axum", - "base64 0.21.3", + "base64", "bytes", - "futures-core", - "futures-util", "h2", "http", "http-body", @@ -3486,11 +3369,10 @@ checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" [[package]] name = "tracing" -version = "0.1.37" +version = "0.1.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" dependencies = [ - "cfg-if", "log", "pin-project-lite", "tracing-attributes", @@ -3499,20 +3381,20 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.26" +version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab" +checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.29", + "syn 2.0.39", ] [[package]] name = "tracing-core" -version = "0.1.31" +version = "0.1.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" dependencies = [ "once_cell", "valuable", @@ -3520,12 +3402,12 @@ dependencies = [ [[package]] name = "tracing-log" -version = "0.1.3" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" dependencies = [ - "lazy_static", "log", + "once_cell", "tracing-core", ] @@ -3541,9 +3423,9 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.3.17" +version = "0.3.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30a651bc37f915e81f087d86e62a18eec5f79550c7faff886f7090b4ea757c77" +checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" dependencies = [ "matchers", "nu-ansi-term", @@ -3568,28 +3450,9 @@ checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" [[package]] name = "typenum" -version = "1.16.0" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" - -[[package]] -name = "tz-rs" -version = "0.6.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33851b15c848fad2cf4b105c6bb66eb9512b6f6c44a4b13f57c53c73c707e2b4" -dependencies = [ - "const_fn", -] - -[[package]] -name = "tzdb" -version = "0.5.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec758958f2fb5069cd7fae385be95cc8eceb8cdfd270c7d14de6034f0108d99e" -dependencies = [ - "iana-time-zone", - "tz-rs", -] +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" [[package]] name = "uds_windows" @@ -3618,15 +3481,15 @@ checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" [[package]] name = "unicode-bom" -version = "2.0.2" +version = "2.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98e90c70c9f0d4d1ee6d0a7d04aa06cb9bbd53d8cfbdd62a0269a7c2eb640552" +checksum = "7eec5d1121208364f6793f7d2e222bf75a915c19557537745b195b253dd64217" [[package]] name = "unicode-ident" -version = "1.0.11" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] name = "unicode-linebreak" @@ -3645,21 +3508,15 @@ dependencies = [ [[package]] name = "unicode-width" -version = "0.1.10" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" - -[[package]] -name = "unicode-xid" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" +checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" [[package]] name = "url" -version = "2.4.1" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "143b538f18257fac9cad154828a57c6bf5157e1aa604d4816b5995bf6de87ae5" +checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" dependencies = [ "form_urlencoded", "idna", @@ -3678,12 +3535,6 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" -[[package]] -name = "vcpkg" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" - [[package]] name = "version_check" version = "0.9.4" @@ -3712,15 +3563,15 @@ dependencies = [ [[package]] name = "waker-fn" -version = "1.1.0" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca" +checksum = "f3c4517f54858c779bbcbf228f4fca63d121bf85fbecb2dc578cdf4a39395690" [[package]] name = "walkdir" -version = "2.3.3" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36df944cda56c7d8d8b7496af378e6b16de9284591917d307c9b4d313c44e698" +checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee" dependencies = [ "same-file", "winapi-util", @@ -3735,12 +3586,6 @@ dependencies = [ "try-lock", ] -[[package]] -name = "wasi" -version = "0.10.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" - [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -3749,9 +3594,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.87" +version = "0.2.88" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342" +checksum = "7daec296f25a1bae309c0cd5c29c4b260e510e6d813c286b19eaadf409d40fce" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -3759,24 +3604,24 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.87" +version = "0.2.88" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd" +checksum = "e397f4664c0e4e428e8313a469aaa58310d302159845980fd23b0f22a847f217" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn 2.0.29", + "syn 2.0.39", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.87" +version = "0.2.88" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d" +checksum = "5961017b3b08ad5f3fe39f1e79877f8ee7c23c5e5fd5eb80de95abc41f1f16b2" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -3784,22 +3629,22 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.87" +version = "0.2.88" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" +checksum = "c5353b8dab669f5e10f5bd76df26a9360c748f054f862ff5f3f8aae0c7fb3907" dependencies = [ "proc-macro2", "quote", - "syn 2.0.29", + "syn 2.0.39", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.87" +version = "0.2.88" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" +checksum = "0d046c5d029ba91a1ed14da14dca44b68bf2f124cfbaf741c54151fdb3e0750b" [[package]] name = "watchexec" @@ -3808,12 +3653,11 @@ dependencies = [ "async-priority-channel", "async-recursion", "atomic-take", - "clearscreen", "command-group", "futures", "ignore-files", "miette", - "nix", + "nix 0.27.1", "normalize-path", "notify", "once_cell", @@ -3824,6 +3668,7 @@ dependencies = [ "tracing-subscriber", "watchexec-events", "watchexec-signals", + "watchexec-supervisor", ] [[package]] @@ -3837,7 +3682,7 @@ dependencies = [ "clap_complete", "clap_complete_nushell", "clap_mangen", - "command-group", + "clearscreen", "console-subscriber", "dirs 5.0.1", "embed-resource", @@ -3848,9 +3693,9 @@ dependencies = [ "miette", "mimalloc", "notify-rust", + "pid1", "project-origins", "serde_json", - "shadow-rs", "tempfile", "tokio", "tracing", @@ -3859,14 +3704,14 @@ dependencies = [ "watchexec-events", "watchexec-filterer-globset", "watchexec-signals", - "which", + "which 5.0.0", ] [[package]] name = "watchexec-events" version = "1.0.0" dependencies = [ - "nix", + "nix 0.27.1", "notify", "serde", "serde_json", @@ -3886,6 +3731,7 @@ dependencies = [ "tracing", "tracing-subscriber", "watchexec", + "watchexec-events", "watchexec-filterer-ignore", ] @@ -3901,6 +3747,7 @@ dependencies = [ "tracing", "tracing-subscriber", "watchexec", + "watchexec-events", "watchexec-signals", ] @@ -3922,6 +3769,7 @@ dependencies = [ "tracing-subscriber", "unicase", "watchexec", + "watchexec-events", "watchexec-filterer-ignore", "watchexec-signals", ] @@ -3931,20 +3779,48 @@ name = "watchexec-signals" version = "1.0.0" dependencies = [ "miette", - "nix", + "nix 0.27.1", "serde", "thiserror", ] +[[package]] +name = "watchexec-supervisor" +version = "0.1.0" +dependencies = [ + "boxcar", + "command-group", + "futures", + "nix 0.27.1", + "tokio", + "tracing", + "watchexec-events", + "watchexec-signals", +] + [[package]] name = "which" -version = "4.4.0" +version = "4.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2441c784c52b289a054b7201fc93253e288f094e2f4be9058343127c4226a269" +checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" dependencies = [ "either", - "libc", + "home", "once_cell", + "rustix 0.38.25", +] + +[[package]] +name = "which" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bf3ea8596f3a0dd5980b46430f2058dfe2c36a27ccfbb1845d6fbfcd9ba6e14" +dependencies = [ + "either", + "home", + "once_cell", + "rustix 0.38.25", + "windows-sys 0.48.0", ] [[package]] @@ -3965,9 +3841,9 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" dependencies = [ "winapi", ] @@ -3978,19 +3854,6 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" -[[package]] -name = "windows" -version = "0.39.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1c4bd0a50ac6020f65184721f758dba47bb9fbc2133df715ec74a237b26794a" -dependencies = [ - "windows_aarch64_msvc 0.39.0", - "windows_i686_gnu 0.39.0", - "windows_i686_msvc 0.39.0", - "windows_x86_64_gnu 0.39.0", - "windows_x86_64_msvc 0.39.0", -] - [[package]] name = "windows" version = "0.48.0" @@ -4001,12 +3864,22 @@ dependencies = [ ] [[package]] -name = "windows-sys" -version = "0.45.0" +name = "windows" +version = "0.51.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +checksum = "ca229916c5ee38c2f2bc1e9d8f04df975b4bd93f9955dc69fabb5d91270045c9" dependencies = [ - "windows-targets 0.42.2", + "windows-core", + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-core" +version = "0.51.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1f8cf84f35d2db49a46868f947758c7a1138116f7fac3bc844f43ade1292e64" +dependencies = [ + "windows-targets 0.48.5", ] [[package]] @@ -4019,18 +3892,12 @@ dependencies = [ ] [[package]] -name = "windows-targets" -version = "0.42.2" +name = "windows-sys" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 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", + "windows-targets 0.52.0", ] [[package]] @@ -4049,10 +3916,19 @@ dependencies = [ ] [[package]] -name = "windows_aarch64_gnullvm" -version = "0.42.2" +name = "windows-targets" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" +checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +dependencies = [ + "windows_aarch64_gnullvm 0.52.0", + "windows_aarch64_msvc 0.52.0", + "windows_i686_gnu 0.52.0", + "windows_i686_msvc 0.52.0", + "windows_x86_64_gnu 0.52.0", + "windows_x86_64_gnullvm 0.52.0", + "windows_x86_64_msvc 0.52.0", +] [[package]] name = "windows_aarch64_gnullvm" @@ -4061,16 +3937,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] -name = "windows_aarch64_msvc" -version = "0.39.0" +name = "windows_aarch64_gnullvm" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec7711666096bd4096ffa835238905bb33fb87267910e154b18b44eaabb340f2" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" +checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" [[package]] name = "windows_aarch64_msvc" @@ -4079,16 +3949,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] -name = "windows_i686_gnu" -version = "0.39.0" +name = "windows_aarch64_msvc" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "763fc57100a5f7042e3057e7e8d9bdd7860d330070251a73d003563a3bb49e1b" - -[[package]] -name = "windows_i686_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" +checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" [[package]] name = "windows_i686_gnu" @@ -4097,16 +3961,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] -name = "windows_i686_msvc" -version = "0.39.0" +name = "windows_i686_gnu" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7bc7cbfe58828921e10a9f446fcaaf649204dcfe6c1ddd712c5eebae6bda1106" - -[[package]] -name = "windows_i686_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" +checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" [[package]] name = "windows_i686_msvc" @@ -4115,16 +3973,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] -name = "windows_x86_64_gnu" -version = "0.39.0" +name = "windows_i686_msvc" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6868c165637d653ae1e8dc4d82c25d4f97dd6605eaa8d784b5c6e0ab2a252b65" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" +checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" [[package]] name = "windows_x86_64_gnu" @@ -4133,10 +3985,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] -name = "windows_x86_64_gnullvm" -version = "0.42.2" +name = "windows_x86_64_gnu" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" +checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" [[package]] name = "windows_x86_64_gnullvm" @@ -4145,16 +3997,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] -name = "windows_x86_64_msvc" -version = "0.39.0" +name = "windows_x86_64_gnullvm" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e4d40883ae9cae962787ca76ba76390ffa29214667a111db9e0a1ad8377e809" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" +checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" [[package]] name = "windows_x86_64_msvc" @@ -4163,10 +4009,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] -name = "winnow" -version = "0.5.15" +name = "windows_x86_64_msvc" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c2e3184b9c4e92ad5167ca73039d0c42476302ab603e2fec4487511f38ccefc" +checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" + +[[package]] +name = "winnow" +version = "0.5.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "829846f3e3db426d4cee4510841b71a8e58aa2a76b1132579487ae430ccd9c7b" dependencies = [ "memchr", ] @@ -4187,7 +4039,7 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2769203cd13a0c6015d515be729c526d041e9cf2c0cc478d57faee85f40c6dcd" dependencies = [ - "nix", + "nix 0.26.4", "winapi", ] @@ -4200,8 +4052,8 @@ dependencies = [ "async-broadcast", "async-executor", "async-fs", - "async-io", - "async-lock", + "async-io 1.13.0", + "async-lock 2.8.0", "async-process", "async-recursion", "async-task", @@ -4210,12 +4062,12 @@ dependencies = [ "byteorder", "derivative", "enumflags2", - "event-listener", + "event-listener 2.5.3", "futures-core", "futures-sink", "futures-util", "hex", - "nix", + "nix 0.26.4", "once_cell", "ordered-stream", "rand", diff --git a/Cargo.toml b/Cargo.toml index 170a66d..ec85e91 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,6 +5,7 @@ members = [ "crates/cli", "crates/events", "crates/signals", + "crates/supervisor", "crates/filterer/globset", "crates/filterer/ignore", "crates/filterer/tagged", diff --git a/completions/fish b/completions/fish index 28f2460..a8579cb 100644 --- a/completions/fish +++ b/completions/fish @@ -1,6 +1,6 @@ complete -c watchexec -s w -l watch -d 'Watch a specific file or directory' -r -F -complete -c watchexec -s c -l clear -d 'Clear screen before running command' -r -f -a "{clear ,reset }" -complete -c watchexec -s o -l on-busy-update -d 'What to do when receiving events while the command is running' -r -f -a "{queue ,do-nothing ,restart ,signal }" +complete -c watchexec -s c -l clear -d 'Clear screen before running command' -r -f -a "{clear '',reset ''}" +complete -c watchexec -s o -l on-busy-update -d 'What to do when receiving events while the command is running' -r -f -a "{queue '',do-nothing '',restart '',signal ''}" complete -c watchexec -s s -l signal -d 'Send a signal to the process when it\'s still running' -r complete -c watchexec -l stop-signal -d 'Signal to send to stop the command' -r complete -c watchexec -l stop-timeout -d 'Time to wait for the command to exit gracefully' -r @@ -8,7 +8,7 @@ complete -c watchexec -s d -l debounce -d 'Time to wait for new events before ta complete -c watchexec -l delay-run -d 'Sleep before running the command' -r complete -c watchexec -l poll -d 'Poll for filesystem changes' -r complete -c watchexec -l shell -d 'Use a different shell' -r -complete -c watchexec -l emit-events-to -d 'Configure event emission' -r -f -a "{environment ,stdin ,file ,json-stdin ,json-file ,none }" +complete -c watchexec -l emit-events-to -d 'Configure event emission' -r -f -a "{environment '',stdin '',file '',json-stdin '',json-file '',none ''}" complete -c watchexec -s E -l env -d 'Add env vars to the command' -r complete -c watchexec -l project-origin -d 'Set the project origin' -r -f -a "(__fish_complete_directories)" complete -c watchexec -l workdir -d 'Set the working directory' -r -f -a "(__fish_complete_directories)" @@ -17,9 +17,9 @@ complete -c watchexec -s f -l filter -d 'Filename patterns to filter to' -r complete -c watchexec -l filter-file -d 'Files to load filters from' -r -F complete -c watchexec -s i -l ignore -d 'Filename patterns to filter out' -r complete -c watchexec -l ignore-file -d 'Files to load ignores from' -r -F -complete -c watchexec -l fs-events -d 'Filesystem events to filter to' -r -f -a "{access ,create ,remove ,rename ,modify ,metadata }" +complete -c watchexec -l fs-events -d 'Filesystem events to filter to' -r -f -a "{access '',create '',remove '',rename '',modify '',metadata ''}" complete -c watchexec -l log-file -d 'Write diagnostic logs to a file' -r -F -complete -c watchexec -l completions -d 'Generate a shell completions script' -r -f -a "{bash ,elvish ,fish ,nu ,powershell ,zsh }" +complete -c watchexec -l completions -d 'Generate a shell completions script' -r -f -a "{bash '',elvish '',fish '',nu '',powershell '',zsh ''}" complete -c watchexec -s W -l watch-when-idle -d 'Deprecated alias for \'--on-busy-update=do-nothing\'' complete -c watchexec -s r -l restart -d 'Restart the process if it\'s still running' complete -c watchexec -s k -l kill -d 'Hidden legacy shorthand for \'--signal=kill\'' diff --git a/completions/nu b/completions/nu index e1abfb9..72b92a1 100644 --- a/completions/nu +++ b/completions/nu @@ -71,4 +71,4 @@ module completions { } -use completions * +export use completions * diff --git a/crates/bosion/Cargo.toml b/crates/bosion/Cargo.toml index 65b2bda..6f66709 100644 --- a/crates/bosion/Cargo.toml +++ b/crates/bosion/Cargo.toml @@ -15,11 +15,11 @@ rust-version = "1.64.0" edition = "2021" [dependencies.time] -version = "0.3.20" +version = "0.3.30" features = ["macros", "formatting"] [dependencies.gix] -version = "0.48" +version = "0.55.2" optional = true default-features = false diff --git a/crates/bosion/examples/clap/Cargo.lock b/crates/bosion/examples/clap/Cargo.lock index 1de58f1..0e7a727 100644 --- a/crates/bosion/examples/clap/Cargo.lock +++ b/crates/bosion/examples/clap/Cargo.lock @@ -9,15 +9,51 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] -name = "ahash" -version = "0.8.3" +name = "anstream" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f" +checksum = "2ab91ebe16eb252986481c5b62f6098f3b698a45e34b5b98200cf20dd2484a44" dependencies = [ - "cfg-if", - "getrandom", - "once_cell", - "version_check", + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87" + +[[package]] +name = "anstyle-parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "317b9a89c1868f5ea6ff1d9539a69f45dffc21ce321ac1fd1160dfa48c8e2140" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0699d10d2f4d628a98ee7b57b289abbc98ff3bad977cb3152709d4bf2330628" +dependencies = [ + "anstyle", + "windows-sys", ] [[package]] @@ -26,12 +62,6 @@ version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bddcadddf5e9015d310179a59bb28c4d4b9920ad0f11e8e14dbadf654890c9a6" -[[package]] -name = "arrayvec" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6" - [[package]] name = "autocfg" version = "1.1.0" @@ -44,9 +74,15 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "bitflags" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" + [[package]] name = "bosion" -version = "1.0.0" +version = "1.0.1" dependencies = [ "gix", "time", @@ -62,12 +98,11 @@ dependencies = [ [[package]] name = "bstr" -version = "1.3.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ffdb39cb703212f3c11973452c2861b972f757b021158f3516ba10f2fa8b2c1" +checksum = "542f33a8835a0884b006a0c3df3dadd99c0c3f296ed26c2fdc8028e01ad6230c" dependencies = [ "memchr", - "once_cell", "regex-automata", "serde", ] @@ -81,18 +116,6 @@ dependencies = [ "num-traits", ] -[[package]] -name = "bytesize" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38fcc2979eff34a4b84e1cf9a1e3da42a7d44b3b690a40cdcb23e3d556cfb2e5" - -[[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" @@ -101,27 +124,33 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "clap" -version = "4.1.8" +version = "4.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3d7ae14b20b94cb02149ed21a86c423859cbe18dc7ed69845cace50e52b40a5" +checksum = "2275f18819641850fa26c89acc84d465c1bf91ce57bc2748b28c420473352f64" dependencies = [ - "bitflags", + "clap_builder", "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07cdf1b148b25c1e1f7a42225e30a0d99a615cd4637eae7365548dd4529b95bc" +dependencies = [ + "anstream", + "anstyle", "clap_lex", - "is-terminal", - "once_cell", "strsim", - "termcolor", ] [[package]] name = "clap_derive" -version = "4.1.8" +version = "4.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44bec8e5c9d09e439c4335b1af0abaab56dcf3b94999a936e1bb47b9134288f0" +checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442" dependencies = [ "heck", - "proc-macro-error", "proc-macro2", "quote", "syn", @@ -129,12 +158,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.3.2" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "350b9cf31731f9957399229e9b2adc51eeabdfbe9d71d9a0552275fd12710d09" -dependencies = [ - "os_str_bytes", -] +checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1" [[package]] name = "clru" @@ -142,6 +168,12 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8191fa7302e03607ff0e237d4246cc043ff5b3cb9409d995172ba3bea16b807" +[[package]] +name = "colorchoice" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" + [[package]] name = "crc32fast" version = "1.3.2" @@ -152,151 +184,50 @@ dependencies = [ ] [[package]] -name = "crossbeam" -version = "0.8.2" +name = "deranged" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2801af0d36612ae591caa9568261fddce32ce6e08a7275ea334a06a4ad021a2c" +checksum = "0f32d04922c60427da6f9fef14d042d9edddef64cb9d4ce0d64d0685fbeb1fd3" dependencies = [ - "cfg-if", - "crossbeam-channel", - "crossbeam-deque", - "crossbeam-epoch", - "crossbeam-queue", - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-channel" -version = "0.5.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf2b3e8478797446514c91ef04bafcb59faba183e621ad488df88983cc14128c" -dependencies = [ - "cfg-if", - "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", - "scopeguard", -] - -[[package]] -name = "crossbeam-queue" -version = "0.3.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1cfb3ea8a53f37c40dea2c7bedcbd88bdfae54f5e2175d6ecaff1c988353add" -dependencies = [ - "cfg-if", - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-utils" -version = "0.8.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c063cd8cc95f5c377ed0d4b49a4b21f632396ff690e8470c29b3359b346984b" -dependencies = [ - "cfg-if", -] - -[[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", + "powerfmt", ] [[package]] name = "dunce" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bd4b30a6560bbd9b4620f4de34c3f14f60848e58a9b7216801afcb4c7b31c3c" - -[[package]] -name = "either" -version = "1.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" +checksum = "56ce8c6da7551ec6c462cbaf3bfbc75131ebbfa1c944aeaa9dab51ca1c5f0c3b" [[package]] name = "errno" -version = "0.2.8" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1" +checksum = "f258a7194e7f7c2a7837a8913aeab7fd8c383457034fa20ce4dd3dcb813e8eb8" dependencies = [ - "errno-dragonfly", "libc", - "winapi", + "windows-sys", ] [[package]] -name = "errno-dragonfly" -version = "0.1.2" +name = "faster-hex" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +checksum = "239f7bfb930f820ab16a9cd95afc26f88264cf6905c960b340a615384aa3338a" dependencies = [ - "cc", - "libc", + "serde", ] [[package]] name = "fastrand" -version = "1.9.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" -dependencies = [ - "instant", -] - -[[package]] -name = "filetime" -version = "0.2.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a3de6e8d11b22ff9edc6d916f890800597d60f8b2da1caf2955c274638d6412" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall", - "windows-sys 0.45.0", -] +checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" [[package]] name = "flate2" -version = "1.0.25" +version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8a2db397cb1c8772f31494cb8917e48cd1e64f0fa7efac59fbd741a0a8ce841" +checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e" dependencies = [ "crc32fast", "miniz_oxide", @@ -304,61 +235,49 @@ dependencies = [ [[package]] name = "form_urlencoded" -version = "1.1.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" dependencies = [ "percent-encoding", ] -[[package]] -name = "getrandom" -version = "0.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" -dependencies = [ - "cfg-if", - "libc", - "wasi", -] - [[package]] name = "gix" -version = "0.38.0" +version = "0.55.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "297b7d406fdb5b818468946f1795c8bf5aca4b20744e7a45705b92201a0aa0b1" +checksum = "002667cd1ebb789313d0d0afe3d23b2821cf3b0e91605095f0e6d8751f0ceeea" dependencies = [ "gix-actor", - "gix-attributes", + "gix-commitgraph", "gix-config", - "gix-credentials", "gix-date", "gix-diff", "gix-discover", "gix-features", + "gix-fs", "gix-glob", "gix-hash", "gix-hashtable", - "gix-index", "gix-lock", - "gix-mailmap", + "gix-macros", "gix-object", "gix-odb", "gix-pack", "gix-path", - "gix-prompt", "gix-ref", "gix-refspec", "gix-revision", + "gix-revwalk", "gix-sec", "gix-tempfile", + "gix-trace", "gix-traverse", "gix-url", + "gix-utils", "gix-validate", - "gix-worktree", - "log", "once_cell", - "signal-hook", + "parking_lot", "smallvec", "thiserror", "unicode-normalization", @@ -366,65 +285,46 @@ dependencies = [ [[package]] name = "gix-actor" -version = "0.18.0" +version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cc0696e9d47d6c407f98cefc0f462d2dc1361aa62e166fe15c1c8d989583e8c" +checksum = "948a5f9e43559d16faf583694f1c742eb401ce24ce8e6f2238caedea7486433c" dependencies = [ "bstr", "btoi", "gix-date", "itoa", - "nom", - "thiserror", -] - -[[package]] -name = "gix-attributes" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "850914386c41114018a695dea0516c5bbea1144b0bf8d1683dd5290ad1b5c674" -dependencies = [ - "bstr", - "gix-features", - "gix-glob", - "gix-path", - "gix-quote", - "thiserror", - "unicode-bom", -] - -[[package]] -name = "gix-bitmap" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "024bca0c7187517bda5ea24ab148c9ca8208dd0c3e2bea88cdb2008f91791a6d" -dependencies = [ "thiserror", + "winnow", ] [[package]] name = "gix-chunk" -version = "0.4.1" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0d39583cab06464b8bf73b3f1707458270f0e7383cb24c3c9c1a16e6f792978" +checksum = "5b42ea64420f7994000130328f3c7a2038f639120518870436d31b8bde704493" dependencies = [ "thiserror", ] [[package]] -name = "gix-command" -version = "0.2.4" +name = "gix-commitgraph" +version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2c6f75c1e0f924de39e750880a6e21307194bb1ab773efe3c7d2d787277f8ab" +checksum = "7e8bc78b1a6328fa6d8b3a53b6c73997af37fd6bfc1d6c49f149e63bda5cbb36" dependencies = [ "bstr", + "gix-chunk", + "gix-features", + "gix-hash", + "memmap2", + "thiserror", ] [[package]] name = "gix-config" -version = "0.17.0" +version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30c7c8a572aa639df072016aa03b740606815cf7ab64e4fc7e2e1c48f748497c" +checksum = "5cae98c6b4c66c09379bc35274b172587d6b0ac369a416c39128ad8c6454f9bb" dependencies = [ "bstr", "gix-config-value", @@ -434,47 +334,31 @@ dependencies = [ "gix-ref", "gix-sec", "memchr", - "nom", "once_cell", "smallvec", "thiserror", "unicode-bom", + "winnow", ] [[package]] name = "gix-config-value" -version = "0.10.1" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "693d4a4ba0531e46fe558459557a5b29fb86c3e4b2666c1c0861d93c7c678331" +checksum = "ea7505b97f4d8e7933e29735a568ba2f86d8de466669d9f0e8321384f9972f47" dependencies = [ - "bitflags", + "bitflags 2.4.1", "bstr", "gix-path", "libc", "thiserror", ] -[[package]] -name = "gix-credentials" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa0bb5be75eb73793724585dc949a50804d29b4a6349561456febec2ea34afaf" -dependencies = [ - "bstr", - "gix-command", - "gix-config-value", - "gix-path", - "gix-prompt", - "gix-sec", - "gix-url", - "thiserror", -] - [[package]] name = "gix-date" -version = "0.4.3" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b96271912ce39822501616f177dea7218784e6c63be90d5f36322ff3a722aae2" +checksum = "fc7df669639582dc7c02737642f76890b03b5544e141caba68a7d6b4eb551e0d" dependencies = [ "bstr", "itoa", @@ -484,21 +368,20 @@ dependencies = [ [[package]] name = "gix-diff" -version = "0.27.0" +version = "0.37.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce7ea311543d215dc5558217760d0f9b7da5e6640421c8fe9b8e7222571fc6da" +checksum = "931394f69fb8c9ed6afc0aae3487bd869e936339bcc13ed8884472af072e0554" dependencies = [ "gix-hash", "gix-object", - "imara-diff", "thiserror", ] [[package]] name = "gix-discover" -version = "0.14.0" +version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dc06ab79a3f9a8be0c094a2fd5b4a852fd1362b95e4800a65bf7d119b2b6563" +checksum = "a45d5cf0321178883e38705ab2b098f625d609a7d4c391b33ac952eff2c490f2" dependencies = [ "bstr", "dunce", @@ -511,20 +394,16 @@ dependencies = [ [[package]] name = "gix-features" -version = "0.27.0" +version = "0.36.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e799c19245b6d19e371fc9c2981b8133e600611b59f6dc7df0293af2c7db50f" +checksum = "51f4365ba17c4f218d7fd9ec102b8d2d3cb0ca200a835e81151ace7778aec827" dependencies = [ - "bytesize", "crc32fast", - "crossbeam-channel", - "crossbeam-utils", "flate2", "gix-hash", - "jwalk", + "gix-trace", "libc", "once_cell", - "parking_lot", "prodash", "sha1_smol", "thiserror", @@ -532,106 +411,96 @@ dependencies = [ ] [[package]] -name = "gix-glob" -version = "0.5.5" +name = "gix-fs" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93e43efd776bc543f46f0fd0ca3d920c37af71a764a16f2aebd89765e9ff2993" +checksum = "8cd171c0cae97cd0dc57e7b4601cb1ebf596450e263ef3c02be9107272c877bd" dependencies = [ - "bitflags", + "gix-features", +] + +[[package]] +name = "gix-glob" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fac08925dbc14d414bd02eb45ffb4cecd912d1fce3883f867bd0103c192d3e4" +dependencies = [ + "bitflags 2.4.1", "bstr", + "gix-features", + "gix-path", ] [[package]] name = "gix-hash" -version = "0.10.3" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c0c5a9f4d621d4f4ea046bb331df5c746ca735b8cae5b234cc2be70ee4dbef0" +checksum = "1884c7b41ea0875217c1be9ce91322f90bde433e91d374d0e1276073a51ccc60" dependencies = [ - "hex", + "faster-hex", "thiserror", ] [[package]] name = "gix-hashtable" -version = "0.1.2" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9609c1b8f36f12968e6a6098f7cdb52004f7d42d570f47a2d6d7c16612f19acb" +checksum = "409268480841ad008e81c17ca5a293393fbf9f2b6c2f85b8ab9de1f0c5176a16" dependencies = [ "gix-hash", - "hashbrown 0.13.2", + "hashbrown", "parking_lot", ] -[[package]] -name = "gix-index" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d949c651d7612f8f73fb82b94c3d87a17291690b19d3bfae8baa7812ecb5514c" -dependencies = [ - "bitflags", - "bstr", - "btoi", - "filetime", - "gix-bitmap", - "gix-features", - "gix-hash", - "gix-lock", - "gix-object", - "gix-traverse", - "itoa", - "memmap2", - "smallvec", - "thiserror", -] - [[package]] name = "gix-lock" -version = "4.0.0" +version = "11.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66119ff8a4a395d0ea033fef718bc85f8b4f0855874f4ce1e005fc16cfe1f66e" +checksum = "f4feb1dcd304fe384ddc22edba9dd56a42b0800032de6537728cea2f033a4f37" dependencies = [ - "fastrand", "gix-tempfile", + "gix-utils", "thiserror", ] [[package]] -name = "gix-mailmap" -version = "0.10.0" +name = "gix-macros" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5c98cfd496e7c525f8289f13040d6379f5e943e6eaf6e6bc1a9ffcf5b3edbc4" +checksum = "9d8acb5ee668d55f0f2d19a320a3f9ef67a6999ad483e11135abcc2464ed18b6" dependencies = [ - "bstr", - "gix-actor", - "thiserror", + "proc-macro2", + "quote", + "syn", ] [[package]] name = "gix-object" -version = "0.27.0" +version = "0.38.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf7ac5d836f4f37073fe21ad4304a656a22e797394a55b3a589a180b293bbdcf" +checksum = "740f2a44267f58770a1cb3a3d01d14e67b089c7136c48d4bddbb3cfd2bf86a51" dependencies = [ "bstr", "btoi", "gix-actor", + "gix-date", "gix-features", "gix-hash", "gix-validate", - "hex", "itoa", - "nom", "smallvec", "thiserror", + "winnow", ] [[package]] name = "gix-odb" -version = "0.41.0" +version = "0.54.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfbf0748ab8fb3ad34d7dd8d02ed88a966c2e3f1c13a03008c00bbe0dbf15c7c" +checksum = "8630b56cb80d8fa684d383dad006a66401ee8314e12fbf0e566ddad8c115143b" dependencies = [ "arc-swap", + "gix-date", "gix-features", "gix-hash", "gix-object", @@ -645,55 +514,42 @@ dependencies = [ [[package]] name = "gix-pack" -version = "0.31.0" +version = "0.44.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d37919c003338c7211e9e0b5a49752ba536d8920f326d5d362c1e7a3b7a3d64" +checksum = "1431ba2e30deff1405920693d54ab231c88d7c240dd6ccc936ee223d8f8697c3" dependencies = [ "clru", "gix-chunk", - "gix-diff", "gix-features", "gix-hash", "gix-hashtable", "gix-object", "gix-path", "gix-tempfile", - "gix-traverse", "memmap2", "parking_lot", "smallvec", "thiserror", - "uluru", ] [[package]] name = "gix-path" -version = "0.7.2" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6c104a66dec149cb8f7aaafc6ab797654cf82d67f050fd0cb7e7294e328354b" +checksum = "6a1d370115171e3ae03c5c6d4f7d096f2981a40ddccb98dfd704c773530ba73b" dependencies = [ "bstr", - "thiserror", -] - -[[package]] -name = "gix-prompt" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a20cebf73229debaa82574c4fd20dcaf00fa8d4bfce823a862c4e990d7a0b5b4" -dependencies = [ - "gix-command", - "gix-config-value", - "nix", - "parking_lot", + "gix-trace", + "home", + "once_cell", "thiserror", ] [[package]] name = "gix-quote" -version = "0.4.3" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a282f5a8d9ee0b09ec47390ac727350c48f2f5c76d803cd8da6b3e7ad56e0bcb" +checksum = "475c86a97dd0127ba4465fbb239abac9ea10e68301470c9791a6dd5351cdc905" dependencies = [ "bstr", "btoi", @@ -702,12 +558,14 @@ dependencies = [ [[package]] name = "gix-ref" -version = "0.25.0" +version = "0.38.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99708f74e03bd329d385937b52b851d063fd238ccbd84d9812c15e4a32b92332" +checksum = "0ec2f6d07ac88d2fb8007ee3fa3e801856fb9d82e7366ec0ca332eb2c9d74a52" dependencies = [ "gix-actor", + "gix-date", "gix-features", + "gix-fs", "gix-hash", "gix-lock", "gix-object", @@ -715,15 +573,15 @@ dependencies = [ "gix-tempfile", "gix-validate", "memmap2", - "nom", "thiserror", + "winnow", ] [[package]] name = "gix-refspec" -version = "0.8.0" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0c1a5125520e07c98c5bc4574d6b3b286af0925b69392538bd57cac2022b733" +checksum = "ccb0974cc41dbdb43a180c7f67aa481e1c1e160fcfa8f4a55291fd1126c1a6e7" dependencies = [ "bstr", "gix-hash", @@ -735,26 +593,42 @@ dependencies = [ [[package]] name = "gix-revision" -version = "0.11.0" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "098760be523c5b0f09e370b64dfdd0819061f5ae505c99f50a374e0670a900b3" +checksum = "2ca97ac73459a7f3766aa4a5638a6e37d56d4c7962bc1986fbaf4883d0772588" dependencies = [ "bstr", "gix-date", "gix-hash", "gix-hashtable", "gix-object", + "gix-revwalk", + "gix-trace", + "thiserror", +] + +[[package]] +name = "gix-revwalk" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a16d8c892e4cd676d86f0265bf9d40cefd73d8d94f86b213b8b77d50e77efae0" +dependencies = [ + "gix-commitgraph", + "gix-date", + "gix-hash", + "gix-hashtable", + "gix-object", + "smallvec", "thiserror", ] [[package]] name = "gix-sec" -version = "0.6.2" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8ffa5bf0772f9b01de501c035b6b084cf9b8bb07dec41e3afc6a17336a65f47" +checksum = "92b9542ac025a8c02ed5d17b3fc031a111a384e859d0be3532ec4d58c40a0f28" dependencies = [ - "bitflags", - "dirs", + "bitflags 2.4.1", "gix-path", "libc", "windows", @@ -762,35 +636,44 @@ dependencies = [ [[package]] name = "gix-tempfile" -version = "4.1.0" +version = "11.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8e0227bd284cd16105e8479602bb8af6bddcb800427e881c1feee4806310a31" +checksum = "05cc2205cf10d99f70b96e04e16c55d4c7cf33efc151df1f793e29fd12a931f8" dependencies = [ + "gix-fs", "libc", "once_cell", "parking_lot", - "signal-hook", - "signal-hook-registry", "tempfile", ] [[package]] -name = "gix-traverse" -version = "0.23.0" +name = "gix-trace" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f791c926b4861ab9a512c5e8f58fc0f0c80db90ed26983b779ae3b188f40e873" +checksum = "96b6d623a1152c3facb79067d6e2ecdae48130030cf27d6eb21109f13bd7b836" + +[[package]] +name = "gix-traverse" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14d050ec7d4e1bb76abf0636cf4104fb915b70e54e3ced9a4427c999100ff38a" dependencies = [ + "gix-commitgraph", + "gix-date", "gix-hash", "gix-hashtable", "gix-object", + "gix-revwalk", + "smallvec", "thiserror", ] [[package]] name = "gix-url" -version = "0.14.0" +version = "0.25.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "402a16fdf106ad11781d857c7381f2b12c704538b5f321af78ab9feed0f44a5c" +checksum = "b1b9ac8ed32ad45f9fc6c5f8c0be2ed911e544a5a19afd62d95d524ebaa95671" dependencies = [ "bstr", "gix-features", @@ -800,45 +683,30 @@ dependencies = [ "url", ] +[[package]] +name = "gix-utils" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b85d89dc728613e26e0ed952a19583744e7f5240fcd4aa30d6c824ffd8b52f0f" +dependencies = [ + "fastrand", +] + [[package]] name = "gix-validate" -version = "0.7.3" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b69ddb780ea1465255e66818d75b7098371c58dbc9560da4488a44b9f5c7e443" +checksum = "e05cab2b03a45b866156e052aa38619f4ece4adcb2f79978bfc249bc3b21b8c5" dependencies = [ "bstr", "thiserror", ] -[[package]] -name = "gix-worktree" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06a1b5ce22eec13124a2c1744fb1fc340badd1fd9481a6a949cdfaf8c583a2ea" -dependencies = [ - "bstr", - "gix-attributes", - "gix-features", - "gix-glob", - "gix-hash", - "gix-index", - "gix-object", - "gix-path", - "io-close", - "thiserror", -] - [[package]] name = "hashbrown" -version = "0.12.3" +version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" - -[[package]] -name = "hashbrown" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" +checksum = "f93e7192158dbcda357bdec5fb5788eebf8bbac027f3f33e719d29135ae84156" [[package]] name = "heck" @@ -846,230 +714,86 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" -[[package]] -name = "hermit-abi" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -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.4" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "747309b4b440c06d57b0b25f2aee03ee9b5e5397d288c60e21fc709bb98a7408" +checksum = "5444c27eef6923071f7ebcc33e3444508466a76f7a2b93da00ed6e19f30c1ddb" dependencies = [ - "winapi", + "windows-sys", ] -[[package]] -name = "human_format" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86cce260d758a9aa3d7c4b99d55c815a540f8a37514ba6046ab6be402a157cb0" - [[package]] name = "idna" -version = "0.3.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" dependencies = [ "unicode-bidi", "unicode-normalization", ] -[[package]] -name = "imara-diff" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e98c1d0ad70fc91b8b9654b1f33db55e59579d3b3de2bffdced0fdb810570cb8" -dependencies = [ - "ahash", - "hashbrown 0.12.3", -] - -[[package]] -name = "instant" -version = "0.1.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "io-close" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cadcf447f06744f8ce713d2d6239bb5bde2c357a452397a9ed90c625da390bc" -dependencies = [ - "libc", - "winapi", -] - -[[package]] -name = "io-lifetimes" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1abeb7a0dd0f8181267ff8adc397075586500b81b28a73e8a0208b00fc170fb3" -dependencies = [ - "libc", - "windows-sys 0.45.0", -] - -[[package]] -name = "is-terminal" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21b6b32576413a8e69b90e952e4a026476040d81017b80445deda5f2d3921857" -dependencies = [ - "hermit-abi 0.3.1", - "io-lifetimes", - "rustix", - "windows-sys 0.45.0", -] - [[package]] name = "itoa" -version = "1.0.6" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" - -[[package]] -name = "jwalk" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2735847566356cd2179a2a38264839308f7079fa96e6bd5a42d740460e003c56" -dependencies = [ - "crossbeam", - "rayon", -] +checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" [[package]] name = "libc" -version = "0.2.139" +version = "0.2.150" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79" +checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c" [[package]] name = "linux-raw-sys" -version = "0.1.4" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4" +checksum = "969488b55f8ac402214f3f5fd243ebb7206cf82de60d3172994707a4bcc2b829" [[package]] name = "lock_api" -version = "0.4.9" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" +checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" dependencies = [ "autocfg", "scopeguard", ] -[[package]] -name = "log" -version = "0.4.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" -dependencies = [ - "cfg-if", -] - [[package]] name = "memchr" -version = "2.5.0" +version = "2.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" +checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" [[package]] name = "memmap2" -version = "0.5.10" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83faa42c0a078c393f6b29d5db232d8be22776a891f8f56e5284faee4a20b327" +checksum = "f49388d20533534cd19360ad3d6a7dadc885944aa802ba3995040c5ec11288c6" dependencies = [ "libc", ] -[[package]] -name = "memoffset" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d61c719bcfbcf5d62b3a09efa6088de8c54bc0bfcd3ea7ae39fcc186108b8de1" -dependencies = [ - "autocfg", -] - -[[package]] -name = "minimal-lexical" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" - [[package]] name = "miniz_oxide" -version = "0.6.2" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa" +checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" dependencies = [ "adler", ] -[[package]] -name = "nix" -version = "0.26.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfdda3d196821d6af13126e40375cdf7da646a96114af134d5f417a9a1dc8e1a" -dependencies = [ - "bitflags", - "cfg-if", - "libc", - "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 = "num-traits" -version = "0.2.15" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" 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" @@ -1081,15 +805,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.17.1" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" - -[[package]] -name = "os_str_bytes" -version = "6.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b7820b9daea5457c9f21c69448905d723fbd21136ccf521748f23fd49e723ee" +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" [[package]] name = "parking_lot" @@ -1103,135 +821,79 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.7" +version = "0.9.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9069cbb9f99e3a5083476ccb29ceb1de18b9118cafa53e90c9551235de2b9521" +checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" dependencies = [ "cfg-if", "libc", "redox_syscall", "smallvec", - "windows-sys 0.45.0", + "windows-targets", ] [[package]] name = "percent-encoding" -version = "2.2.0" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] -name = "proc-macro-error" -version = "1.0.4" +name = "powerfmt" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" -dependencies = [ - "proc-macro-error-attr", - "proc-macro2", - "quote", - "syn", - "version_check", -] - -[[package]] -name = "proc-macro-error-attr" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" -dependencies = [ - "proc-macro2", - "quote", - "version_check", -] +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] name = "proc-macro2" -version = "1.0.51" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d727cae5b39d21da60fa540906919ad737832fe0b1c165da3a34d6548c849d6" +checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da" dependencies = [ "unicode-ident", ] [[package]] name = "prodash" -version = "23.1.1" +version = "26.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d73c6b64cb5b99eb63ca97d378685712617ec0172ff5c04cd47a489d3e2c51f8" -dependencies = [ - "bytesize", - "human_format", -] +checksum = "794b5bf8e2d19b53dcdcec3e4bba628e20f5b6062503ba89281fa7037dd7bbcf" [[package]] name = "quote" -version = "1.0.23" +version = "1.0.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" dependencies = [ "proc-macro2", ] -[[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" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" dependencies = [ - "bitflags", -] - -[[package]] -name = "redox_users" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" -dependencies = [ - "getrandom", - "redox_syscall", - "thiserror", + "bitflags 1.3.2", ] [[package]] name = "regex-automata" -version = "0.1.10" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" [[package]] name = "rustix" -version = "0.36.9" +version = "0.38.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd5c6ff11fecd55b40746d1995a02f2eb375bf8c00d192d521ee09f42bef37bc" +checksum = "dc99bc2d4f1fed22595588a013687477aedf3cdcfb26558c559edb67b4d9b22e" dependencies = [ - "bitflags", + "bitflags 2.4.1", "errno", - "io-lifetimes", "libc", "linux-raw-sys", - "windows-sys 0.45.0", + "windows-sys", ] [[package]] @@ -1245,15 +907,29 @@ dependencies = [ [[package]] name = "scopeguard" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "serde" -version = "1.0.152" +version = "1.0.193" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb" +checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.193" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] [[package]] name = "sha1_smol" @@ -1261,36 +937,11 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae1a47186c03a32177042e55dbc5fd5aee900b8e0069a8d70fba96a9375cd012" -[[package]] -name = "signal-hook" -version = "0.3.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "732768f1176d21d09e076c23a93123d40bba92d50c4058da34d45c8de8e682b9" -dependencies = [ - "libc", - "signal-hook-registry", -] - -[[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 = "smallvec" -version = "1.10.0" +version = "1.11.2" 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" +checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970" [[package]] name = "strsim" @@ -1300,9 +951,9 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "syn" -version = "1.0.109" +version = "2.0.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a" dependencies = [ "proc-macro2", "quote", @@ -1311,40 +962,31 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.4.0" +version = "3.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af18f7ae1acd354b992402e9ec5864359d693cd8a79dcbef59f76891701c1e95" +checksum = "7ef1adac450ad7f4b3c28589471ade84f25f731a7a0fe30d71dfa9f60fd808e5" dependencies = [ "cfg-if", "fastrand", "redox_syscall", "rustix", - "windows-sys 0.42.0", -] - -[[package]] -name = "termcolor" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" -dependencies = [ - "winapi-util", + "windows-sys", ] [[package]] name = "thiserror" -version = "1.0.38" +version = "1.0.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a9cd18aa97d5c45c6603caea1da6628790b37f7a34b6ca89522331c5180fed0" +checksum = "f9a7210f5c9a7156bb50aa36aed4c95afb51df0df00713949448cf9e97d382d2" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.38" +version = "1.0.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f" +checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8" dependencies = [ "proc-macro2", "quote", @@ -1353,13 +995,15 @@ dependencies = [ [[package]] name = "time" -version = "0.3.20" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd0cbfecb4d19b5ea75bb31ad904eb5b9fa13f21079c3b92017ebdf4999a5890" +checksum = "c4a34ab300f2dee6e562c10a046fc05e358b29f9bf92277f30c3c8d82275f6f5" dependencies = [ + "deranged", "itoa", "libc", "num_threads", + "powerfmt", "serde", "time-core", "time-macros", @@ -1367,15 +1011,15 @@ dependencies = [ [[package]] name = "time-core" -version = "0.1.0" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "time-macros" -version = "0.2.8" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd80a657e71da814b8e5d60d3374fc6d35045062245d80224748ae522dd76f36" +checksum = "4ad70d68dba9e1f8aceda7aa6711965dfec1cac869f311a51bd08b3a2ccbce20" dependencies = [ "time-core", ] @@ -1395,32 +1039,23 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" -[[package]] -name = "uluru" -version = "3.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "794a32261a1f5eb6a4462c81b59cec87b5c27d5deea7dd1ac8fc781c41d226db" -dependencies = [ - "arrayvec", -] - [[package]] name = "unicode-bidi" -version = "0.3.10" +version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d54675592c1dbefd78cbd98db9bacd89886e1ca50692a0692baefffdeb92dd58" +checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" [[package]] name = "unicode-bom" -version = "1.1.4" +version = "2.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63ec69f541d875b783ca40184d655f2927c95f0bffd486faa83cd3ac3529ec32" +checksum = "7eec5d1121208364f6793f7d2e222bf75a915c19557537745b195b253dd64217" [[package]] name = "unicode-ident" -version = "1.0.7" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "775c11906edafc97bc378816b94585fbd9a054eabaf86fdd0ced94af449efab7" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] name = "unicode-normalization" @@ -1433,9 +1068,9 @@ dependencies = [ [[package]] name = "url" -version = "2.3.1" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643" +checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" dependencies = [ "form_urlencoded", "idna", @@ -1443,28 +1078,21 @@ dependencies = [ ] [[package]] -name = "version_check" -version = "0.9.4" +name = "utf8parse" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" [[package]] name = "walkdir" -version = "2.3.2" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56" +checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee" dependencies = [ "same-file", - "winapi", "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 = "winapi" version = "0.3.9" @@ -1483,9 +1111,9 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" dependencies = [ "winapi", ] @@ -1498,48 +1126,27 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows" -version = "0.43.0" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04662ed0e3e5630dfa9b26e4cb823b817f1a9addda855d973a9458c236556244" +checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows-targets", ] [[package]] name = "windows-sys" -version = "0.42.0" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" -dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", -] - -[[package]] -name = "windows-sys" -version = "0.45.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ "windows-targets", ] [[package]] name = "windows-targets" -version = "0.42.1" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e2522491fbfcd58cc84d47aeb2958948c4b8982e9a2d8a2a35bbaed431390e7" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" dependencies = [ "windows_aarch64_gnullvm", "windows_aarch64_msvc", @@ -1552,42 +1159,51 @@ dependencies = [ [[package]] name = "windows_aarch64_gnullvm" -version = "0.42.1" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c9864e83243fdec7fc9c5444389dcbbfd258f745e7853198f365e3c4968a608" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_msvc" -version = "0.42.1" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c8b1b673ffc16c47a9ff48570a9d85e25d265735c503681332589af6253c6c7" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_i686_gnu" -version = "0.42.1" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de3887528ad530ba7bdbb1faa8275ec7a1155a45ffa57c37993960277145d640" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_msvc" -version = "0.42.1" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf4d1122317eddd6ff351aa852118a2418ad4214e6613a50e0191f7004372605" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_x86_64_gnu" -version = "0.42.1" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1040f221285e17ebccbc2591ffdc2d44ee1f9186324dd3e84e99ac68d699c45" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnullvm" -version = "0.42.1" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "628bfdf232daa22b0d64fdb62b09fcc36bb01f05a3939e20ab73aaf9470d0463" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_msvc" -version = "0.42.1" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "winnow" +version = "0.5.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "829846f3e3db426d4cee4510841b71a8e58aa2a76b1132579487ae430ccd9c7b" +dependencies = [ + "memchr", +] diff --git a/crates/bosion/examples/default/Cargo.lock b/crates/bosion/examples/default/Cargo.lock index 12515a0..f1ff892 100644 --- a/crates/bosion/examples/default/Cargo.lock +++ b/crates/bosion/examples/default/Cargo.lock @@ -8,44 +8,31 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" -[[package]] -name = "ahash" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f" -dependencies = [ - "cfg-if", - "getrandom", - "once_cell", - "version_check", -] - [[package]] name = "anstream" -version = "0.3.2" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ca84f3628370c59db74ee214b3263d58f9aadd9b4fe7e711fd87dc452b7f163" +checksum = "2ab91ebe16eb252986481c5b62f6098f3b698a45e34b5b98200cf20dd2484a44" dependencies = [ "anstyle", "anstyle-parse", "anstyle-query", "anstyle-wincon", "colorchoice", - "is-terminal", "utf8parse", ] [[package]] name = "anstyle" -version = "1.0.0" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41ed9a86bf92ae6580e0a31281f65a1b1d867c0cc68d5346e2ae128dddfa6a7d" +checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87" [[package]] name = "anstyle-parse" -version = "0.2.0" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e765fd216e48e067936442276d1d57399e37bce53c264d6fefbe298080cb57ee" +checksum = "317b9a89c1868f5ea6ff1d9539a69f45dffc21ce321ac1fd1160dfa48c8e2140" dependencies = [ "utf8parse", ] @@ -56,17 +43,17 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" dependencies = [ - "windows-sys 0.48.0", + "windows-sys", ] [[package]] name = "anstyle-wincon" -version = "1.0.1" +version = "3.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "180abfa45703aebe0093f79badacc01b8fd4ea2e35118747e5811127f926e188" +checksum = "f0699d10d2f4d628a98ee7b57b289abbc98ff3bad977cb3152709d4bf2330628" dependencies = [ "anstyle", - "windows-sys 0.48.0", + "windows-sys", ] [[package]] @@ -75,12 +62,6 @@ version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bddcadddf5e9015d310179a59bb28c4d4b9920ad0f11e8e14dbadf654890c9a6" -[[package]] -name = "arrayvec" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6" - [[package]] name = "autocfg" version = "1.1.0" @@ -95,13 +76,13 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.2.1" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24a6904aef64d73cf10ab17ebace7befb918b82164785cb89907993be7f83813" +checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" [[package]] name = "bosion" -version = "1.0.0" +version = "1.0.1" dependencies = [ "gix", "time", @@ -119,12 +100,11 @@ dependencies = [ [[package]] name = "bstr" -version = "1.4.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3d4260bcc2e8fc9df1eac4919a720effeb63a3f0952f5bf4944adfa18897f09" +checksum = "542f33a8835a0884b006a0c3df3dadd99c0c3f296ed26c2fdc8028e01ad6230c" dependencies = [ "memchr", - "once_cell", "regex-automata", "serde", ] @@ -138,18 +118,6 @@ dependencies = [ "num-traits", ] -[[package]] -name = "bytesize" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38fcc2979eff34a4b84e1cf9a1e3da42a7d44b3b690a40cdcb23e3d556cfb2e5" - -[[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" @@ -178,70 +146,12 @@ dependencies = [ ] [[package]] -name = "crossbeam" -version = "0.8.2" +name = "deranged" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2801af0d36612ae591caa9568261fddce32ce6e08a7275ea334a06a4ad021a2c" +checksum = "0f32d04922c60427da6f9fef14d042d9edddef64cb9d4ce0d64d0685fbeb1fd3" dependencies = [ - "cfg-if", - "crossbeam-channel", - "crossbeam-deque", - "crossbeam-epoch", - "crossbeam-queue", - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-channel" -version = "0.5.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a33c2bf77f2df06183c3aa30d1e96c0695a313d4f9c453cc3762a6db39f99200" -dependencies = [ - "cfg-if", - "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", - "scopeguard", -] - -[[package]] -name = "crossbeam-queue" -version = "0.3.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1cfb3ea8a53f37c40dea2c7bedcbd88bdfae54f5e2175d6ecaff1c988353add" -dependencies = [ - "cfg-if", - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-utils" -version = "0.8.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c063cd8cc95f5c377ed0d4b49a4b21f632396ff690e8470c29b3359b346984b" -dependencies = [ - "cfg-if", + "powerfmt", ] [[package]] @@ -250,59 +160,36 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56ce8c6da7551ec6c462cbaf3bfbc75131ebbfa1c944aeaa9dab51ca1c5f0c3b" -[[package]] -name = "either" -version = "1.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" - [[package]] name = "errno" -version = "0.3.1" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" +checksum = "f258a7194e7f7c2a7837a8913aeab7fd8c383457034fa20ce4dd3dcb813e8eb8" dependencies = [ - "errno-dragonfly", "libc", - "windows-sys 0.48.0", + "windows-sys", ] [[package]] -name = "errno-dragonfly" -version = "0.1.2" +name = "faster-hex" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +checksum = "239f7bfb930f820ab16a9cd95afc26f88264cf6905c960b340a615384aa3338a" dependencies = [ - "cc", - "libc", + "serde", ] [[package]] name = "fastrand" -version = "1.9.0" +version = "2.0.1" 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", -] +checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" [[package]] name = "flate2" -version = "1.0.26" +version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b9429470923de8e8cbd4d2dc513535400b4b3fef0319fb5c4e1f520a7bef743" +checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e" dependencies = [ "crc32fast", "miniz_oxide", @@ -310,34 +197,22 @@ dependencies = [ [[package]] name = "form_urlencoded" -version = "1.1.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" dependencies = [ "percent-encoding", ] -[[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" -version = "0.44.1" +version = "0.55.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bf41b61f7df395284f7a579c0fa1a7e012c5aede655174d4e91299ef1cac643" +checksum = "002667cd1ebb789313d0d0afe3d23b2821cf3b0e91605095f0e6d8751f0ceeea" dependencies = [ "gix-actor", - "gix-attributes", + "gix-commitgraph", "gix-config", - "gix-credentials", "gix-date", "gix-diff", "gix-discover", @@ -346,28 +221,25 @@ dependencies = [ "gix-glob", "gix-hash", "gix-hashtable", - "gix-ignore", - "gix-index", "gix-lock", - "gix-mailmap", + "gix-macros", "gix-object", "gix-odb", "gix-pack", "gix-path", - "gix-prompt", "gix-ref", "gix-refspec", "gix-revision", + "gix-revwalk", "gix-sec", "gix-tempfile", + "gix-trace", "gix-traverse", "gix-url", "gix-utils", "gix-validate", - "gix-worktree", - "log", "once_cell", - "signal-hook", + "parking_lot", "smallvec", "thiserror", "unicode-normalization", @@ -375,67 +247,46 @@ dependencies = [ [[package]] name = "gix-actor" -version = "0.20.0" +version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "848efa0f1210cea8638f95691c82a46f98a74b9e3524f01d4955ebc25a8f84f3" +checksum = "948a5f9e43559d16faf583694f1c742eb401ce24ce8e6f2238caedea7486433c" dependencies = [ "bstr", "btoi", "gix-date", "itoa", - "nom", - "thiserror", -] - -[[package]] -name = "gix-attributes" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3015baa01ad2122fbcaab7863c857a603eb7b7ec12ac8141207c42c6439805e2" -dependencies = [ - "bstr", - "gix-glob", - "gix-path", - "gix-quote", - "kstring", - "log", - "smallvec", - "thiserror", - "unicode-bom", -] - -[[package]] -name = "gix-bitmap" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55a95f4942360766c3880bdb2b4b57f1ef73b190fc424755e7fdf480430af618" -dependencies = [ "thiserror", + "winnow", ] [[package]] name = "gix-chunk" -version = "0.4.1" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0d39583cab06464b8bf73b3f1707458270f0e7383cb24c3c9c1a16e6f792978" +checksum = "5b42ea64420f7994000130328f3c7a2038f639120518870436d31b8bde704493" dependencies = [ "thiserror", ] [[package]] -name = "gix-command" -version = "0.2.4" +name = "gix-commitgraph" +version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2c6f75c1e0f924de39e750880a6e21307194bb1ab773efe3c7d2d787277f8ab" +checksum = "7e8bc78b1a6328fa6d8b3a53b6c73997af37fd6bfc1d6c49f149e63bda5cbb36" dependencies = [ "bstr", + "gix-chunk", + "gix-features", + "gix-hash", + "memmap2", + "thiserror", ] [[package]] name = "gix-config" -version = "0.22.0" +version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d252a0eddb6df74600d3d8872dc9fe98835a7da43110411d705b682f49d4ac1" +checksum = "5cae98c6b4c66c09379bc35274b172587d6b0ac369a416c39128ad8c6454f9bb" dependencies = [ "bstr", "gix-config-value", @@ -444,49 +295,32 @@ dependencies = [ "gix-path", "gix-ref", "gix-sec", - "log", "memchr", - "nom", "once_cell", "smallvec", "thiserror", "unicode-bom", + "winnow", ] [[package]] name = "gix-config-value" -version = "0.12.0" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "786861e84a5793ad5f863d846de5eb064cd23b87e61ad708c8c402608202e7be" +checksum = "ea7505b97f4d8e7933e29735a568ba2f86d8de466669d9f0e8321384f9972f47" dependencies = [ - "bitflags 2.2.1", + "bitflags 2.4.1", "bstr", "gix-path", "libc", "thiserror", ] -[[package]] -name = "gix-credentials" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4874a4fc11ffa844a3c2b87a66957bda30a73b577ef1acf15ac34df5745de5ff" -dependencies = [ - "bstr", - "gix-command", - "gix-config-value", - "gix-path", - "gix-prompt", - "gix-sec", - "gix-url", - "thiserror", -] - [[package]] name = "gix-date" -version = "0.5.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99056f37270715f5c7584fd8b46899a2296af9cae92463bf58b8bd1f5a78e553" +checksum = "fc7df669639582dc7c02737642f76890b03b5544e141caba68a7d6b4eb551e0d" dependencies = [ "bstr", "itoa", @@ -496,21 +330,20 @@ dependencies = [ [[package]] name = "gix-diff" -version = "0.29.0" +version = "0.37.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "644a0f2768bc42d7a69289ada80c9e15c589caefc6a315d2307202df83ed1186" +checksum = "931394f69fb8c9ed6afc0aae3487bd869e936339bcc13ed8884472af072e0554" dependencies = [ "gix-hash", "gix-object", - "imara-diff", "thiserror", ] [[package]] name = "gix-discover" -version = "0.18.1" +version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a6b61363e63e7cdaa3e6f96acb0257ebdb3d8883e21eba5930c99f07f0a5fc0" +checksum = "a45d5cf0321178883e38705ab2b098f625d609a7d4c391b33ac952eff2c490f2" dependencies = [ "bstr", "dunce", @@ -523,19 +356,16 @@ dependencies = [ [[package]] name = "gix-features" -version = "0.29.0" +version = "0.36.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf69b0f5c701cc3ae22d3204b671907668f6437ca88862d355eaf9bc47a4f897" +checksum = "51f4365ba17c4f218d7fd9ec102b8d2d3cb0ca200a835e81151ace7778aec827" dependencies = [ - "bytesize", "crc32fast", - "crossbeam-channel", "flate2", "gix-hash", - "jwalk", + "gix-trace", "libc", "once_cell", - "parking_lot", "prodash", "sha1_smol", "thiserror", @@ -544,20 +374,20 @@ dependencies = [ [[package]] name = "gix-fs" -version = "0.1.1" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b37a1832f691fdc09910bd267f9a2e413737c1f9ec68c6e31f9e802616278a9" +checksum = "8cd171c0cae97cd0dc57e7b4601cb1ebf596450e263ef3c02be9107272c877bd" dependencies = [ "gix-features", ] [[package]] name = "gix-glob" -version = "0.7.0" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c07c98204529ac3f24b34754540a852593d2a4c7349008df389240266627a72a" +checksum = "8fac08925dbc14d414bd02eb45ffb4cecd912d1fce3883f867bd0103c192d3e4" dependencies = [ - "bitflags 2.2.1", + "bitflags 2.4.1", "bstr", "gix-features", "gix-path", @@ -565,64 +395,30 @@ dependencies = [ [[package]] name = "gix-hash" -version = "0.11.1" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "078eec3ac2808cc03f0bddd2704cb661da5c5dc33b41a9d7947b141d499c7c42" +checksum = "1884c7b41ea0875217c1be9ce91322f90bde433e91d374d0e1276073a51ccc60" dependencies = [ - "hex", + "faster-hex", "thiserror", ] [[package]] name = "gix-hashtable" -version = "0.2.0" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afebb85691c6a085b114e01a27f4a61364519298c5826cb87a45c304802299bc" +checksum = "409268480841ad008e81c17ca5a293393fbf9f2b6c2f85b8ab9de1f0c5176a16" dependencies = [ "gix-hash", - "hashbrown 0.13.2", + "hashbrown", "parking_lot", ] -[[package]] -name = "gix-ignore" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba205b6df563e2906768bb22834c82eb46c5fdfcd86ba2c347270bc8309a05b2" -dependencies = [ - "bstr", - "gix-glob", - "gix-path", - "unicode-bom", -] - -[[package]] -name = "gix-index" -version = "0.16.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f39c1ccc8f1912cbbd5191efc28dbc5f0d0598042aa56bc09427b7c34efab3ba" -dependencies = [ - "bitflags 2.2.1", - "bstr", - "btoi", - "filetime", - "gix-bitmap", - "gix-features", - "gix-hash", - "gix-lock", - "gix-object", - "gix-traverse", - "itoa", - "memmap2", - "smallvec", - "thiserror", -] - [[package]] name = "gix-lock" -version = "5.0.1" +version = "11.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c693d7f05730fa74a7c467150adc7cea393518410c65f0672f80226b8111555" +checksum = "f4feb1dcd304fe384ddc22edba9dd56a42b0800032de6537728cea2f033a4f37" dependencies = [ "gix-tempfile", "gix-utils", @@ -630,42 +426,43 @@ dependencies = [ ] [[package]] -name = "gix-mailmap" -version = "0.12.0" +name = "gix-macros" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8856cec3bdc3610c06970d28b6cb20a0c6621621cf9a8ec48cbd23f2630f362" +checksum = "9d8acb5ee668d55f0f2d19a320a3f9ef67a6999ad483e11135abcc2464ed18b6" dependencies = [ - "bstr", - "gix-actor", - "thiserror", + "proc-macro2", + "quote", + "syn", ] [[package]] name = "gix-object" -version = "0.29.1" +version = "0.38.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9bb30ce0818d37096daa29efe361a4bc6dd0b51a5726598898be7e9a40a01e1" +checksum = "740f2a44267f58770a1cb3a3d01d14e67b089c7136c48d4bddbb3cfd2bf86a51" dependencies = [ "bstr", "btoi", "gix-actor", + "gix-date", "gix-features", "gix-hash", "gix-validate", - "hex", "itoa", - "nom", "smallvec", "thiserror", + "winnow", ] [[package]] name = "gix-odb" -version = "0.45.0" +version = "0.54.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bca2f324aa67672b6d0f2c0fa93f96eb6a7029d260e4c1df5dce3c015f5e5add" +checksum = "8630b56cb80d8fa684d383dad006a66401ee8314e12fbf0e566ddad8c115143b" dependencies = [ "arc-swap", + "gix-date", "gix-features", "gix-hash", "gix-object", @@ -679,57 +476,42 @@ dependencies = [ [[package]] name = "gix-pack" -version = "0.35.0" +version = "0.44.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "164a515900a83257ae4aa80e741655bee7a2e39113fb535d7a5ac623b445ff20" +checksum = "1431ba2e30deff1405920693d54ab231c88d7c240dd6ccc936ee223d8f8697c3" dependencies = [ "clru", "gix-chunk", - "gix-diff", "gix-features", "gix-hash", "gix-hashtable", "gix-object", "gix-path", "gix-tempfile", - "gix-traverse", "memmap2", "parking_lot", "smallvec", "thiserror", - "uluru", ] [[package]] name = "gix-path" -version = "0.8.0" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fc78f47095a0c15aea0e66103838f0748f4494bf7a9555dfe0f00425400396c" +checksum = "6a1d370115171e3ae03c5c6d4f7d096f2981a40ddccb98dfd704c773530ba73b" dependencies = [ "bstr", + "gix-trace", "home", "once_cell", "thiserror", ] -[[package]] -name = "gix-prompt" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "330d11fdf88fff3366c2491efde2f3e454958efe7d5ddf60272e8fb1d944bb01" -dependencies = [ - "gix-command", - "gix-config-value", - "parking_lot", - "rustix", - "thiserror", -] - [[package]] name = "gix-quote" -version = "0.4.3" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a282f5a8d9ee0b09ec47390ac727350c48f2f5c76d803cd8da6b3e7ad56e0bcb" +checksum = "475c86a97dd0127ba4465fbb239abac9ea10e68301470c9791a6dd5351cdc905" dependencies = [ "bstr", "btoi", @@ -738,11 +520,12 @@ dependencies = [ [[package]] name = "gix-ref" -version = "0.29.1" +version = "0.38.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e03989e9d49954368e1b526578230fc7189d1634acdfbe79e9ba1de717e15d5" +checksum = "0ec2f6d07ac88d2fb8007ee3fa3e801856fb9d82e7366ec0ca332eb2c9d74a52" dependencies = [ "gix-actor", + "gix-date", "gix-features", "gix-fs", "gix-hash", @@ -752,15 +535,15 @@ dependencies = [ "gix-tempfile", "gix-validate", "memmap2", - "nom", "thiserror", + "winnow", ] [[package]] name = "gix-refspec" -version = "0.10.1" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a6ea733820df67e4cd7797deb12727905824d8f5b7c59d943c456d314475892" +checksum = "ccb0974cc41dbdb43a180c7f67aa481e1c1e160fcfa8f4a55291fd1126c1a6e7" dependencies = [ "bstr", "gix-hash", @@ -772,25 +555,42 @@ dependencies = [ [[package]] name = "gix-revision" -version = "0.13.0" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "810f35e9afeccca999d5d348b239f9c162353127d2e13ff3240e31b919e35476" +checksum = "2ca97ac73459a7f3766aa4a5638a6e37d56d4c7962bc1986fbaf4883d0772588" dependencies = [ "bstr", "gix-date", "gix-hash", "gix-hashtable", "gix-object", + "gix-revwalk", + "gix-trace", + "thiserror", +] + +[[package]] +name = "gix-revwalk" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a16d8c892e4cd676d86f0265bf9d40cefd73d8d94f86b213b8b77d50e77efae0" +dependencies = [ + "gix-commitgraph", + "gix-date", + "gix-hash", + "gix-hashtable", + "gix-object", + "smallvec", "thiserror", ] [[package]] name = "gix-sec" -version = "0.8.0" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "794520043d5a024dfeac335c6e520cb616f6963e30dab995892382e998c12897" +checksum = "92b9542ac025a8c02ed5d17b3fc031a111a384e859d0be3532ec4d58c40a0f28" dependencies = [ - "bitflags 2.2.1", + "bitflags 2.4.1", "gix-path", "libc", "windows", @@ -798,36 +598,44 @@ dependencies = [ [[package]] name = "gix-tempfile" -version = "5.0.3" +version = "11.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71a0d32f34e71e86586124225caefd78dabc605d0486de580d717653addf182" +checksum = "05cc2205cf10d99f70b96e04e16c55d4c7cf33efc151df1f793e29fd12a931f8" dependencies = [ "gix-fs", "libc", "once_cell", "parking_lot", - "signal-hook", - "signal-hook-registry", "tempfile", ] [[package]] -name = "gix-traverse" -version = "0.25.0" +name = "gix-trace" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5be1e807f288c33bb005075111886cceb43ed8a167b3182a0f62c186e2a0dd1" +checksum = "96b6d623a1152c3facb79067d6e2ecdae48130030cf27d6eb21109f13bd7b836" + +[[package]] +name = "gix-traverse" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14d050ec7d4e1bb76abf0636cf4104fb915b70e54e3ced9a4427c999100ff38a" dependencies = [ + "gix-commitgraph", + "gix-date", "gix-hash", "gix-hashtable", "gix-object", + "gix-revwalk", + "smallvec", "thiserror", ] [[package]] name = "gix-url" -version = "0.18.0" +version = "0.25.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfc77f89054297cc81491e31f1bab4027e554b5ef742a44bd7035db9a0f78b76" +checksum = "b1b9ac8ed32ad45f9fc6c5f8c0be2ed911e544a5a19afd62d95d524ebaa95671" dependencies = [ "bstr", "gix-features", @@ -839,76 +647,28 @@ dependencies = [ [[package]] name = "gix-utils" -version = "0.1.1" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c10b69beac219acb8df673187a1f07dde2d74092f974fb3f9eb385aeb667c909" +checksum = "b85d89dc728613e26e0ed952a19583744e7f5240fcd4aa30d6c824ffd8b52f0f" dependencies = [ "fastrand", ] [[package]] name = "gix-validate" -version = "0.7.4" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7bd629d3680773e1785e585d76fd4295b740b559cad9141517300d99a0c8c049" +checksum = "e05cab2b03a45b866156e052aa38619f4ece4adcb2f79978bfc249bc3b21b8c5" dependencies = [ "bstr", "thiserror", ] -[[package]] -name = "gix-worktree" -version = "0.17.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a69eaff0ae973a9d37c40f02ae5ae50fa726c8fc2fd3ab79d0a19eb61975aafa" -dependencies = [ - "bstr", - "filetime", - "gix-attributes", - "gix-features", - "gix-fs", - "gix-glob", - "gix-hash", - "gix-ignore", - "gix-index", - "gix-object", - "gix-path", - "io-close", - "thiserror", -] - [[package]] name = "hashbrown" -version = "0.12.3" +version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" - -[[package]] -name = "hashbrown" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" - -[[package]] -name = "hermit-abi" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -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" +checksum = "f93e7192158dbcda357bdec5fb5788eebf8bbac027f3f33e719d29135ae84156" [[package]] name = "home" @@ -916,172 +676,71 @@ version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5444c27eef6923071f7ebcc33e3444508466a76f7a2b93da00ed6e19f30c1ddb" dependencies = [ - "windows-sys 0.48.0", + "windows-sys", ] -[[package]] -name = "human_format" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86cce260d758a9aa3d7c4b99d55c815a540f8a37514ba6046ab6be402a157cb0" - [[package]] name = "idna" -version = "0.3.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" dependencies = [ "unicode-bidi", "unicode-normalization", ] -[[package]] -name = "imara-diff" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e98c1d0ad70fc91b8b9654b1f33db55e59579d3b3de2bffdced0fdb810570cb8" -dependencies = [ - "ahash", - "hashbrown 0.12.3", -] - -[[package]] -name = "instant" -version = "0.1.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "io-close" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cadcf447f06744f8ce713d2d6239bb5bde2c357a452397a9ed90c625da390bc" -dependencies = [ - "libc", - "winapi", -] - -[[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 = "is-terminal" -version = "0.4.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adcf93614601c8129ddf72e2d5633df827ba6551541c6d8c59520a371475be1f" -dependencies = [ - "hermit-abi 0.3.1", - "io-lifetimes", - "rustix", - "windows-sys 0.48.0", -] - [[package]] name = "itoa" -version = "1.0.6" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" - -[[package]] -name = "jwalk" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2735847566356cd2179a2a38264839308f7079fa96e6bd5a42d740460e003c56" -dependencies = [ - "crossbeam", - "rayon", -] - -[[package]] -name = "kstring" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec3066350882a1cd6d950d055997f379ac37fd39f81cd4d8ed186032eb3c5747" -dependencies = [ - "static_assertions", -] +checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" [[package]] name = "leon" -version = "0.0.1" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1afa3794684c32f91a5aa105e5109743bc6f2999a869c28fffa40aeffa30cfd0" +checksum = "52df920dfe9751d43501ff2ee12dd81c457d9e810d3f64b5200ee461fe73800b" dependencies = [ "thiserror", ] [[package]] name = "libc" -version = "0.2.144" +version = "0.2.150" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b00cc1c228a6782d0f076e7b232802e0c5689d41bb5df366f2a6b6621cfdfe1" +checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c" [[package]] name = "linux-raw-sys" -version = "0.3.7" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ece97ea872ece730aed82664c424eb4c8291e1ff2480247ccf7409044bc6479f" +checksum = "969488b55f8ac402214f3f5fd243ebb7206cf82de60d3172994707a4bcc2b829" [[package]] name = "lock_api" -version = "0.4.9" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" +checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" dependencies = [ "autocfg", "scopeguard", ] -[[package]] -name = "log" -version = "0.4.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" -dependencies = [ - "cfg-if", -] - [[package]] name = "memchr" -version = "2.5.0" +version = "2.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" +checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" [[package]] name = "memmap2" -version = "0.5.10" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83faa42c0a078c393f6b29d5db232d8be22776a891f8f56e5284faee4a20b327" +checksum = "f49388d20533534cd19360ad3d6a7dadc885944aa802ba3995040c5ec11288c6" dependencies = [ "libc", ] -[[package]] -name = "memoffset" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d61c719bcfbcf5d62b3a09efa6088de8c54bc0bfcd3ea7ae39fcc186108b8de1" -dependencies = [ - "autocfg", -] - -[[package]] -name = "minimal-lexical" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" - [[package]] name = "miniz_oxide" version = "0.7.1" @@ -1091,16 +750,6 @@ dependencies = [ "adler", ] -[[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-line-endings" version = "0.3.0" @@ -1109,23 +758,13 @@ checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be" [[package]] name = "num-traits" -version = "0.2.15" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" 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" @@ -1137,9 +776,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.17.1" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" [[package]] name = "parking_lot" @@ -1153,109 +792,79 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.7" +version = "0.9.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9069cbb9f99e3a5083476ccb29ceb1de18b9118cafa53e90c9551235de2b9521" +checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.2.16", + "redox_syscall", "smallvec", - "windows-sys 0.45.0", + "windows-targets", ] [[package]] name = "percent-encoding" -version = "2.2.0" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] name = "proc-macro2" -version = "1.0.56" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435" +checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da" dependencies = [ "unicode-ident", ] [[package]] name = "prodash" -version = "23.1.2" +version = "26.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9516b775656bc3e8985e19cd4b8c0c0de045095074e453d2c0a513b5f978392d" -dependencies = [ - "bytesize", - "human_format", -] +checksum = "794b5bf8e2d19b53dcdcec3e4bba628e20f5b6062503ba89281fa7037dd7bbcf" [[package]] name = "quote" -version = "1.0.27" +version = "1.0.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f4f29d145265ec1c483c7c654450edde0bfe043d3938d6972630663356d9500" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" dependencies = [ "proc-macro2", ] -[[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" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" -dependencies = [ - "bitflags 1.3.2", -] - -[[package]] -name = "redox_syscall" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" dependencies = [ "bitflags 1.3.2", ] [[package]] name = "regex-automata" -version = "0.1.10" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" [[package]] name = "rustix" -version = "0.37.19" +version = "0.38.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acf8729d8542766f1b2cf77eb034d52f40d375bb8b615d0b147089946e16613d" +checksum = "dc99bc2d4f1fed22595588a013687477aedf3cdcfb26558c559edb67b4d9b22e" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.4.1", "errno", - "io-lifetimes", "libc", "linux-raw-sys", - "windows-sys 0.48.0", + "windows-sys", ] [[package]] @@ -1269,15 +878,29 @@ dependencies = [ [[package]] name = "scopeguard" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "serde" -version = "1.0.162" +version = "1.0.193" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71b2f6e1ab5c2b98c05f0f35b236b22e8df7ead6ffbf51d7808da7f8817e7ab6" +checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.193" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] [[package]] name = "sha1_smol" @@ -1285,42 +908,23 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae1a47186c03a32177042e55dbc5fd5aee900b8e0069a8d70fba96a9375cd012" -[[package]] -name = "signal-hook" -version = "0.3.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "732768f1176d21d09e076c23a93123d40bba92d50c4058da34d45c8de8e682b9" -dependencies = [ - "libc", - "signal-hook-registry", -] - -[[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 = "similar" -version = "2.2.1" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "420acb44afdae038210c99e69aae24109f32f15500aa708e81d46c9f29d55fcf" +checksum = "2aeaf503862c419d66959f5d7ca015337d864e9c49485d771b732e2a20453597" [[package]] name = "smallvec" -version = "1.10.0" +version = "1.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" +checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970" [[package]] name = "snapbox" -version = "0.4.11" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6bccd62078347f89a914e3004d94582e13824d4e3d8a816317862884c423835" +checksum = "4b377c0b6e4715c116473d8e40d51e3fa5b0a2297ca9b2a931ba800667b259ed" dependencies = [ "anstream", "anstyle", @@ -1331,24 +935,18 @@ dependencies = [ [[package]] name = "snapbox-macros" -version = "0.3.4" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eaaf09df9f0eeae82be96290918520214530e738a7fe5a351b0f24cf77c0ca31" +checksum = "ed1559baff8a696add3322b9be3e940d433e7bb4e38d79017205fd37ff28b28e" dependencies = [ "anstream", ] -[[package]] -name = "static_assertions" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" - [[package]] name = "syn" -version = "2.0.15" +version = "2.0.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a34fcf3e8b60f57e6a14301a2e916d323af98b0ea63c599441eec8558660c822" +checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a" dependencies = [ "proc-macro2", "quote", @@ -1357,31 +955,31 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.5.0" +version = "3.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9fbec84f381d5795b08656e4912bec604d162bff9291d6189a78f4c8ab87998" +checksum = "7ef1adac450ad7f4b3c28589471ade84f25f731a7a0fe30d71dfa9f60fd808e5" dependencies = [ "cfg-if", "fastrand", - "redox_syscall 0.3.5", + "redox_syscall", "rustix", - "windows-sys 0.45.0", + "windows-sys", ] [[package]] name = "thiserror" -version = "1.0.40" +version = "1.0.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac" +checksum = "f9a7210f5c9a7156bb50aa36aed4c95afb51df0df00713949448cf9e97d382d2" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.40" +version = "1.0.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" +checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8" dependencies = [ "proc-macro2", "quote", @@ -1390,13 +988,15 @@ dependencies = [ [[package]] name = "time" -version = "0.3.21" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f3403384eaacbca9923fa06940178ac13e4edb725486d70e8e15881d0c836cc" +checksum = "c4a34ab300f2dee6e562c10a046fc05e358b29f9bf92277f30c3c8d82275f6f5" dependencies = [ + "deranged", "itoa", "libc", "num_threads", + "powerfmt", "serde", "time-core", "time-macros", @@ -1404,15 +1004,15 @@ dependencies = [ [[package]] name = "time-core" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "time-macros" -version = "0.2.9" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "372950940a5f07bf38dbe211d7283c9e6d7327df53794992d293e534c733d09b" +checksum = "4ad70d68dba9e1f8aceda7aa6711965dfec1cac869f311a51bd08b3a2ccbce20" dependencies = [ "time-core", ] @@ -1432,15 +1032,6 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" -[[package]] -name = "uluru" -version = "3.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "794a32261a1f5eb6a4462c81b59cec87b5c27d5deea7dd1ac8fc781c41d226db" -dependencies = [ - "arrayvec", -] - [[package]] name = "unicode-bidi" version = "0.3.13" @@ -1449,15 +1040,15 @@ checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" [[package]] name = "unicode-bom" -version = "2.0.2" +version = "2.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98e90c70c9f0d4d1ee6d0a7d04aa06cb9bbd53d8cfbdd62a0269a7c2eb640552" +checksum = "7eec5d1121208364f6793f7d2e222bf75a915c19557537745b195b253dd64217" [[package]] name = "unicode-ident" -version = "1.0.8" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] name = "unicode-normalization" @@ -1470,9 +1061,9 @@ dependencies = [ [[package]] name = "url" -version = "2.3.1" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643" +checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" dependencies = [ "form_urlencoded", "idna", @@ -1485,28 +1076,16 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" -[[package]] -name = "version_check" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" - [[package]] name = "walkdir" -version = "2.3.3" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36df944cda56c7d8d8b7496af378e6b16de9284591917d307c9b4d313c44e698" +checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee" dependencies = [ "same-file", "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 = "winapi" version = "0.3.9" @@ -1525,9 +1104,9 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" dependencies = [ "winapi", ] @@ -1544,16 +1123,7 @@ 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.45.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" -dependencies = [ - "windows-targets 0.42.2", + "windows-targets", ] [[package]] @@ -1562,119 +1132,71 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ - "windows-targets 0.48.0", + "windows-targets", ] [[package]] name = "windows-targets" -version = "0.42.2" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" 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", + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", ] [[package]] name = "windows_aarch64_gnullvm" -version = "0.42.2" +version = "0.48.5" 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" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_msvc" -version = "0.42.2" +version = "0.48.5" 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" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_i686_gnu" -version = "0.42.2" +version = "0.48.5" 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" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_msvc" -version = "0.42.2" +version = "0.48.5" 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" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_x86_64_gnu" -version = "0.42.2" +version = "0.48.5" 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" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnullvm" -version = "0.42.2" +version = "0.48.5" 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" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_msvc" -version = "0.42.2" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] -name = "windows_x86_64_msvc" -version = "0.48.0" +name = "winnow" +version = "0.5.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" +checksum = "829846f3e3db426d4cee4510841b71a8e58aa2a76b1132579487ae430ccd9c7b" +dependencies = [ + "memchr", +] diff --git a/crates/bosion/examples/default/Cargo.toml b/crates/bosion/examples/default/Cargo.toml index ea53229..ae82dfe 100644 --- a/crates/bosion/examples/default/Cargo.toml +++ b/crates/bosion/examples/default/Cargo.toml @@ -15,6 +15,6 @@ version = "*" path = "../.." [dependencies] -leon = { version = "0.0.1", default-features = false } +leon = { version = "2.0.1", default-features = false } snapbox = "0.4.8" -time = { version = "0.3.20", features = ["formatting", "macros"] } +time = { version = "0.3.30", features = ["formatting", "macros"] } diff --git a/crates/bosion/examples/no-git/Cargo.lock b/crates/bosion/examples/no-git/Cargo.lock index d5cec50..95df973 100644 --- a/crates/bosion/examples/no-git/Cargo.lock +++ b/crates/bosion/examples/no-git/Cargo.lock @@ -3,14 +3,56 @@ version = 3 [[package]] -name = "bitflags" -version = "1.3.2" +name = "anstream" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +checksum = "2ab91ebe16eb252986481c5b62f6098f3b698a45e34b5b98200cf20dd2484a44" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87" + +[[package]] +name = "anstyle-parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "317b9a89c1868f5ea6ff1d9539a69f45dffc21ce321ac1fd1160dfa48c8e2140" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0699d10d2f4d628a98ee7b57b289abbc98ff3bad977cb3152709d4bf2330628" +dependencies = [ + "anstyle", + "windows-sys", +] [[package]] name = "bosion" -version = "1.0.0" +version = "1.0.1" dependencies = [ "time", ] @@ -26,104 +68,35 @@ dependencies = [ ] [[package]] -name = "cc" -version = "1.0.79" +name = "colorchoice" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" +checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" [[package]] -name = "concolor" -version = "0.0.12" +name = "deranged" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7b3e3c41e9488eeda196b6806dbf487742107d61b2e16485bcca6c25ed5755b" +checksum = "0f32d04922c60427da6f9fef14d042d9edddef64cb9d4ce0d64d0685fbeb1fd3" dependencies = [ - "bitflags", - "concolor-query", - "is-terminal", -] - -[[package]] -name = "concolor-query" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82a90734b3d5dcf656e7624cca6bce9c3a90ee11f900e80141a7427ccfb3d317" - -[[package]] -name = "errno" -version = "0.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1" -dependencies = [ - "errno-dragonfly", - "libc", - "winapi", -] - -[[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 = "hermit-abi" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" - -[[package]] -name = "io-lifetimes" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1abeb7a0dd0f8181267ff8adc397075586500b81b28a73e8a0208b00fc170fb3" -dependencies = [ - "libc", - "windows-sys", -] - -[[package]] -name = "is-terminal" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21b6b32576413a8e69b90e952e4a026476040d81017b80445deda5f2d3921857" -dependencies = [ - "hermit-abi", - "io-lifetimes", - "rustix", - "windows-sys", + "powerfmt", ] [[package]] name = "itoa" -version = "1.0.6" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" +checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" [[package]] name = "leon" -version = "0.0.1" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1afa3794684c32f91a5aa105e5109743bc6f2999a869c28fffa40aeffa30cfd0" +checksum = "52df920dfe9751d43501ff2ee12dd81c457d9e810d3f64b5200ee461fe73800b" dependencies = [ "thiserror", ] -[[package]] -name = "libc" -version = "0.2.139" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79" - -[[package]] -name = "linux-raw-sys" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4" - [[package]] name = "normalize-line-endings" version = "0.3.0" @@ -131,73 +104,82 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be" [[package]] -name = "proc-macro2" -version = "1.0.51" +name = "powerfmt" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d727cae5b39d21da60fa540906919ad737832fe0b1c165da3a34d6548c849d6" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "proc-macro2" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.23" +version = "1.0.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" dependencies = [ "proc-macro2", ] [[package]] -name = "rustix" -version = "0.36.9" +name = "serde" +version = "1.0.193" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd5c6ff11fecd55b40746d1995a02f2eb375bf8c00d192d521ee09f42bef37bc" +checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89" dependencies = [ - "bitflags", - "errno", - "io-lifetimes", - "libc", - "linux-raw-sys", - "windows-sys", + "serde_derive", ] [[package]] -name = "serde" -version = "1.0.152" +name = "serde_derive" +version = "1.0.193" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb" +checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] [[package]] name = "similar" -version = "2.2.1" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "420acb44afdae038210c99e69aae24109f32f15500aa708e81d46c9f29d55fcf" +checksum = "2aeaf503862c419d66959f5d7ca015337d864e9c49485d771b732e2a20453597" [[package]] name = "snapbox" -version = "0.4.8" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4389a6395e9925166f19d67b64874e526ec28a4b8455f3321b686c912299c3ea" +checksum = "4b377c0b6e4715c116473d8e40d51e3fa5b0a2297ca9b2a931ba800667b259ed" dependencies = [ - "concolor", + "anstream", + "anstyle", "normalize-line-endings", "similar", "snapbox-macros", - "yansi", ] [[package]] name = "snapbox-macros" -version = "0.3.1" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "485e65c1203eb37244465e857d15a26d3a85a5410648ccb53b18bd44cb3a7336" +checksum = "ed1559baff8a696add3322b9be3e940d433e7bb4e38d79017205fd37ff28b28e" +dependencies = [ + "anstream", +] [[package]] name = "syn" -version = "1.0.109" +version = "2.0.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a" dependencies = [ "proc-macro2", "quote", @@ -206,18 +188,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.38" +version = "1.0.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a9cd18aa97d5c45c6603caea1da6628790b37f7a34b6ca89522331c5180fed0" +checksum = "f9a7210f5c9a7156bb50aa36aed4c95afb51df0df00713949448cf9e97d382d2" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.38" +version = "1.0.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f" +checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8" dependencies = [ "proc-macro2", "quote", @@ -226,11 +208,13 @@ dependencies = [ [[package]] name = "time" -version = "0.3.20" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd0cbfecb4d19b5ea75bb31ad904eb5b9fa13f21079c3b92017ebdf4999a5890" +checksum = "c4a34ab300f2dee6e562c10a046fc05e358b29f9bf92277f30c3c8d82275f6f5" dependencies = [ + "deranged", "itoa", + "powerfmt", "serde", "time-core", "time-macros", @@ -238,61 +222,45 @@ dependencies = [ [[package]] name = "time-core" -version = "0.1.0" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "time-macros" -version = "0.2.8" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd80a657e71da814b8e5d60d3374fc6d35045062245d80224748ae522dd76f36" +checksum = "4ad70d68dba9e1f8aceda7aa6711965dfec1cac869f311a51bd08b3a2ccbce20" dependencies = [ "time-core", ] [[package]] name = "unicode-ident" -version = "1.0.7" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "775c11906edafc97bc378816b94585fbd9a054eabaf86fdd0ced94af449efab7" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] -name = "winapi" -version = "0.3.9" +name = "utf8parse" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" [[package]] name = "windows-sys" -version = "0.45.0" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ "windows-targets", ] [[package]] name = "windows-targets" -version = "0.42.1" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e2522491fbfcd58cc84d47aeb2958948c4b8982e9a2d8a2a35bbaed431390e7" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" dependencies = [ "windows_aarch64_gnullvm", "windows_aarch64_msvc", @@ -305,48 +273,42 @@ dependencies = [ [[package]] name = "windows_aarch64_gnullvm" -version = "0.42.1" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c9864e83243fdec7fc9c5444389dcbbfd258f745e7853198f365e3c4968a608" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_msvc" -version = "0.42.1" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c8b1b673ffc16c47a9ff48570a9d85e25d265735c503681332589af6253c6c7" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_i686_gnu" -version = "0.42.1" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de3887528ad530ba7bdbb1faa8275ec7a1155a45ffa57c37993960277145d640" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_msvc" -version = "0.42.1" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf4d1122317eddd6ff351aa852118a2418ad4214e6613a50e0191f7004372605" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_x86_64_gnu" -version = "0.42.1" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1040f221285e17ebccbc2591ffdc2d44ee1f9186324dd3e84e99ac68d699c45" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnullvm" -version = "0.42.1" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "628bfdf232daa22b0d64fdb62b09fcc36bb01f05a3939e20ab73aaf9470d0463" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_msvc" -version = "0.42.1" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd" - -[[package]] -name = "yansi" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" diff --git a/crates/bosion/examples/no-git/Cargo.toml b/crates/bosion/examples/no-git/Cargo.toml index 7261134..cdce6bb 100644 --- a/crates/bosion/examples/no-git/Cargo.toml +++ b/crates/bosion/examples/no-git/Cargo.toml @@ -17,6 +17,6 @@ default-features = false features = ["std"] [dependencies] -leon = { version = "0.0.1", default-features = false } +leon = { version = "2.0.1", default-features = false } snapbox = "0.4.8" -time = { version = "0.3.20", features = ["formatting", "macros"] } +time = { version = "0.3.30", features = ["formatting", "macros"] } diff --git a/crates/bosion/examples/no-std/Cargo.lock b/crates/bosion/examples/no-std/Cargo.lock index 67f9e96..3167aa4 100644 --- a/crates/bosion/examples/no-std/Cargo.lock +++ b/crates/bosion/examples/no-std/Cargo.lock @@ -3,14 +3,56 @@ version = 3 [[package]] -name = "bitflags" -version = "1.3.2" +name = "anstream" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +checksum = "2ab91ebe16eb252986481c5b62f6098f3b698a45e34b5b98200cf20dd2484a44" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87" + +[[package]] +name = "anstyle-parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "317b9a89c1868f5ea6ff1d9539a69f45dffc21ce321ac1fd1160dfa48c8e2140" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0699d10d2f4d628a98ee7b57b289abbc98ff3bad977cb3152709d4bf2330628" +dependencies = [ + "anstyle", + "windows-sys", +] [[package]] name = "bosion" -version = "1.0.0" +version = "1.0.1" dependencies = [ "time", ] @@ -26,104 +68,35 @@ dependencies = [ ] [[package]] -name = "cc" -version = "1.0.79" +name = "colorchoice" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" +checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" [[package]] -name = "concolor" -version = "0.0.12" +name = "deranged" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7b3e3c41e9488eeda196b6806dbf487742107d61b2e16485bcca6c25ed5755b" +checksum = "0f32d04922c60427da6f9fef14d042d9edddef64cb9d4ce0d64d0685fbeb1fd3" dependencies = [ - "bitflags", - "concolor-query", - "is-terminal", -] - -[[package]] -name = "concolor-query" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82a90734b3d5dcf656e7624cca6bce9c3a90ee11f900e80141a7427ccfb3d317" - -[[package]] -name = "errno" -version = "0.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1" -dependencies = [ - "errno-dragonfly", - "libc", - "winapi", -] - -[[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 = "hermit-abi" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" - -[[package]] -name = "io-lifetimes" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1abeb7a0dd0f8181267ff8adc397075586500b81b28a73e8a0208b00fc170fb3" -dependencies = [ - "libc", - "windows-sys", -] - -[[package]] -name = "is-terminal" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21b6b32576413a8e69b90e952e4a026476040d81017b80445deda5f2d3921857" -dependencies = [ - "hermit-abi", - "io-lifetimes", - "rustix", - "windows-sys", + "powerfmt", ] [[package]] name = "itoa" -version = "1.0.6" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" +checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" [[package]] name = "leon" -version = "0.0.1" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1afa3794684c32f91a5aa105e5109743bc6f2999a869c28fffa40aeffa30cfd0" +checksum = "52df920dfe9751d43501ff2ee12dd81c457d9e810d3f64b5200ee461fe73800b" dependencies = [ "thiserror", ] -[[package]] -name = "libc" -version = "0.2.139" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79" - -[[package]] -name = "linux-raw-sys" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4" - [[package]] name = "normalize-line-endings" version = "0.3.0" @@ -131,73 +104,82 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be" [[package]] -name = "proc-macro2" -version = "1.0.51" +name = "powerfmt" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d727cae5b39d21da60fa540906919ad737832fe0b1c165da3a34d6548c849d6" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "proc-macro2" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.23" +version = "1.0.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" dependencies = [ "proc-macro2", ] [[package]] -name = "rustix" -version = "0.36.9" +name = "serde" +version = "1.0.193" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd5c6ff11fecd55b40746d1995a02f2eb375bf8c00d192d521ee09f42bef37bc" +checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89" dependencies = [ - "bitflags", - "errno", - "io-lifetimes", - "libc", - "linux-raw-sys", - "windows-sys", + "serde_derive", ] [[package]] -name = "serde" -version = "1.0.152" +name = "serde_derive" +version = "1.0.193" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb" +checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] [[package]] name = "similar" -version = "2.2.1" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "420acb44afdae038210c99e69aae24109f32f15500aa708e81d46c9f29d55fcf" +checksum = "2aeaf503862c419d66959f5d7ca015337d864e9c49485d771b732e2a20453597" [[package]] name = "snapbox" -version = "0.4.8" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4389a6395e9925166f19d67b64874e526ec28a4b8455f3321b686c912299c3ea" +checksum = "4b377c0b6e4715c116473d8e40d51e3fa5b0a2297ca9b2a931ba800667b259ed" dependencies = [ - "concolor", + "anstream", + "anstyle", "normalize-line-endings", "similar", "snapbox-macros", - "yansi", ] [[package]] name = "snapbox-macros" -version = "0.3.1" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "485e65c1203eb37244465e857d15a26d3a85a5410648ccb53b18bd44cb3a7336" +checksum = "ed1559baff8a696add3322b9be3e940d433e7bb4e38d79017205fd37ff28b28e" +dependencies = [ + "anstream", +] [[package]] name = "syn" -version = "1.0.109" +version = "2.0.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a" dependencies = [ "proc-macro2", "quote", @@ -206,18 +188,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.38" +version = "1.0.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a9cd18aa97d5c45c6603caea1da6628790b37f7a34b6ca89522331c5180fed0" +checksum = "f9a7210f5c9a7156bb50aa36aed4c95afb51df0df00713949448cf9e97d382d2" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.38" +version = "1.0.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f" +checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8" dependencies = [ "proc-macro2", "quote", @@ -226,11 +208,13 @@ dependencies = [ [[package]] name = "time" -version = "0.3.20" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd0cbfecb4d19b5ea75bb31ad904eb5b9fa13f21079c3b92017ebdf4999a5890" +checksum = "c4a34ab300f2dee6e562c10a046fc05e358b29f9bf92277f30c3c8d82275f6f5" dependencies = [ + "deranged", "itoa", + "powerfmt", "serde", "time-core", "time-macros", @@ -238,61 +222,45 @@ dependencies = [ [[package]] name = "time-core" -version = "0.1.0" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "time-macros" -version = "0.2.8" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd80a657e71da814b8e5d60d3374fc6d35045062245d80224748ae522dd76f36" +checksum = "4ad70d68dba9e1f8aceda7aa6711965dfec1cac869f311a51bd08b3a2ccbce20" dependencies = [ "time-core", ] [[package]] name = "unicode-ident" -version = "1.0.7" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "775c11906edafc97bc378816b94585fbd9a054eabaf86fdd0ced94af449efab7" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] -name = "winapi" -version = "0.3.9" +name = "utf8parse" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" [[package]] name = "windows-sys" -version = "0.45.0" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ "windows-targets", ] [[package]] name = "windows-targets" -version = "0.42.1" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e2522491fbfcd58cc84d47aeb2958948c4b8982e9a2d8a2a35bbaed431390e7" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" dependencies = [ "windows_aarch64_gnullvm", "windows_aarch64_msvc", @@ -305,48 +273,42 @@ dependencies = [ [[package]] name = "windows_aarch64_gnullvm" -version = "0.42.1" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c9864e83243fdec7fc9c5444389dcbbfd258f745e7853198f365e3c4968a608" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_msvc" -version = "0.42.1" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c8b1b673ffc16c47a9ff48570a9d85e25d265735c503681332589af6253c6c7" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_i686_gnu" -version = "0.42.1" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de3887528ad530ba7bdbb1faa8275ec7a1155a45ffa57c37993960277145d640" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_msvc" -version = "0.42.1" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf4d1122317eddd6ff351aa852118a2418ad4214e6613a50e0191f7004372605" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_x86_64_gnu" -version = "0.42.1" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1040f221285e17ebccbc2591ffdc2d44ee1f9186324dd3e84e99ac68d699c45" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnullvm" -version = "0.42.1" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "628bfdf232daa22b0d64fdb62b09fcc36bb01f05a3939e20ab73aaf9470d0463" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_msvc" -version = "0.42.1" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd" - -[[package]] -name = "yansi" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" diff --git a/crates/bosion/examples/no-std/Cargo.toml b/crates/bosion/examples/no-std/Cargo.toml index e04b65c..fed9497 100644 --- a/crates/bosion/examples/no-std/Cargo.toml +++ b/crates/bosion/examples/no-std/Cargo.toml @@ -22,6 +22,6 @@ path = "../.." default-features = false [dependencies] -leon = { version = "0.0.1", default-features = false } +leon = { version = "2.0.1", default-features = false } snapbox = "0.4.8" -time = { version = "0.3.20", features = ["formatting", "macros"] } +time = { version = "0.3.30", features = ["formatting", "macros"] } diff --git a/crates/bosion/src/info.rs b/crates/bosion/src/info.rs index 2d98bde..df3e7e8 100644 --- a/crates/bosion/src/info.rs +++ b/crates/bosion/src/info.rs @@ -77,7 +77,7 @@ impl Info { #[cfg(feature = "git")] git: GitInfo::gather() .map_err(|e| { - println!("cargo:warning=git info gathering failed: {}", e); + println!("cargo:warning=git info gathering failed: {e}"); }) .ok(), #[cfg(not(feature = "git"))] @@ -150,7 +150,8 @@ pub struct GitInfo { #[cfg(feature = "git")] impl GitInfo { fn gather() -> Result { - let (path, _) = gix::discover::upwards(".").err_string()?; + use std::path::Path; + let (path, _) = gix::discover::upwards(Path::new(".")).err_string()?; let repo = gix::discover(path).err_string()?; let head = repo.head_commit().err_string()?; let time = head.time().err_string()?; diff --git a/crates/bosion/src/lib.rs b/crates/bosion/src/lib.rs index 94c9d9c..8d999d0 100644 --- a/crates/bosion/src/lib.rs +++ b/crates/bosion/src/lib.rs @@ -107,7 +107,7 @@ pub fn gather_to(filename: &str, structname: &str, public: bool) { " ), format!("{crate_version} ({git_shorthash} {git_date}) {crate_feature_string}\ncommit-hash: {git_hash}\ncommit-date: {git_date}\nbuild-date: {build_date}\nrelease: {crate_version}\nfeatures: {crate_feature_list}")) } else { - ("".to_string(), format!("{crate_version} ({build_date}) {crate_feature_string}\nbuild-date: {build_date}\nrelease: {crate_version}\nfeatures: {crate_feature_list}")) + (String::new(), format!("{crate_version} ({build_date}) {crate_feature_string}\nbuild-date: {build_date}\nrelease: {crate_version}\nfeatures: {crate_feature_list}")) }; #[cfg(feature = "std")] diff --git a/crates/cli/Cargo.toml b/crates/cli/Cargo.toml index ab908d0..203df55 100644 --- a/crates/cli/Cargo.toml +++ b/crates/cli/Cargo.toml @@ -20,31 +20,28 @@ name = "watchexec" path = "src/main.rs" [dependencies] -argfile = "0.1.5" -chrono = "0.4.23" -clap_complete = "4.1.4" -clap_complete_nushell = "4.3.1" -clap_mangen = "0.2.9" +argfile = "0.1.6" +chrono = "0.4.31" +clap_complete = "4.4.4" +clap_complete_nushell = "4.4.2" +clap_mangen = "0.2.15" +clearscreen = "2.0.1" dirs = "5.0.0" -futures = "0.3.17" +futures = "0.3.29" humantime = "2.1.0" is-terminal = "0.4.4" -notify-rust = "4.5.2" -serde_json = "1.0.94" -tempfile = "3.4.0" -tracing = "0.1.26" -which = "4.4.0" - -[dependencies.command-group] -version = "2.1.0" -features = ["with-tokio"] +notify-rust = "4.9.0" +serde_json = "1.0.107" +tempfile = "3.8.1" +tracing = "0.1.40" +which = "5.0.0" [dependencies.console-subscriber] -version = "0.1.0" +version = "0.2.0" optional = true [dependencies.clap] -version = "4.1.8" +version = "4.4.7" features = ["cargo", "derive", "env", "wrap_help"] [dependencies.ignore-files] @@ -55,6 +52,10 @@ path = "../ignore-files" version = "5.3.0" features = ["fancy"] +[dependencies.pid1] +version = "0.1.1" +optional = true + [dependencies.project-origins] version = "1.2.0" path = "../project-origins" @@ -77,7 +78,7 @@ version = "1.2.0" path = "../filterer/globset" [dependencies.tokio] -version = "1.24.2" +version = "1.33.0" features = [ "fs", "io-std", @@ -98,22 +99,24 @@ features = [ ] [target.'cfg(target_env = "musl")'.dependencies] -mimalloc = "0.1.26" - -[target.'cfg(target_os = "linux")'.dependencies] -shadow-rs = "0.22.0" - -[target.'cfg(target_os = "linux")'.build-dependencies] -shadow-rs = "0.22.0" +mimalloc = "0.1.39" [build-dependencies] -embed-resource = "2.1.1" +embed-resource = "2.4.0" [build-dependencies.bosion] version = "1.0.1" path = "../bosion" [features] +default = ["pid1"] + +## Enables PID1 handling. +pid1 = ["dep:pid1"] + +## Enables logging for PID1 handling. +pid1-withlog = ["pid1"] + ## For debugging only: enables the Tokio Console. dev-console = ["console-subscriber"] diff --git a/crates/cli/src/args.rs b/crates/cli/src/args.rs index 2efa5f2..2ea5d15 100644 --- a/crates/cli/src/args.rs +++ b/crates/cli/src/args.rs @@ -46,7 +46,8 @@ include!(env!("BOSION_PATH")); author, version, long_version = Bosion::LONG_VERSION, - after_help = "Use @argfile as first argument to load arguments from the file 'argfile' (one argument per line) which will be inserted in place of the @argfile (further arguments on the CLI will override or add onto those in the file).", + after_help = "Want more detail? Try the long '--help' flag!", + after_long_help = "Use @argfile as first argument to load arguments from the file 'argfile' (one argument per line) which will be inserted in place of the @argfile (further arguments on the CLI will override or add onto those in the file).\n\nDidn't expect this much output? Use the short '-h' flag to get short help.", hide_possible_values = true, )] #[cfg_attr(debug_assertions, command(before_help = "⚠ DEBUG BUILD ⚠"))] @@ -103,6 +104,9 @@ pub struct Args { /// for '--project-origin' for more information. /// /// This option can be specified multiple times to watch multiple files or directories. + /// + /// The special value '/dev/null', provided as the only path watched, will cause Watchexec to + /// not watch any paths. Other event sources (like signals or key events) may still be used. #[arg( short = 'w', long = "watch", diff --git a/crates/cli/src/config.rs b/crates/cli/src/config.rs index 200a573..9f59ca8 100644 --- a/crates/cli/src/config.rs +++ b/crates/cli/src/config.rs @@ -1,5 +1,479 @@ -mod init; -mod runtime; +use std::{ + borrow::Cow, + collections::HashMap, + env::current_dir, + ffi::{OsStr, OsString}, + fs::File, + path::Path, + process::Stdio, + sync::Arc, +}; -pub use init::init; -pub use runtime::runtime; +use clearscreen::ClearScreen; +use miette::{miette, IntoDiagnostic, Report, Result}; +use notify_rust::Notification; +use tokio::{process::Command as TokioCommand, time::sleep}; +use tracing::{debug, debug_span, error}; +use watchexec::{ + command::{Command, Program, Shell, SpawnOptions}, + error::RuntimeError, + job::{CommandState, Job}, + sources::fs::Watcher, + Config, ErrorHook, Id, +}; +use watchexec_events::{Event, Keyboard, ProcessEnd, Tag}; +use watchexec_signals::Signal; + +use crate::state::State; +use crate::{ + args::{Args, ClearMode, EmitEvents, OnBusyUpdate}, + state::RotatingTempFile, +}; + +pub fn make_config(args: &Args, state: &State) -> Result { + let _span = debug_span!("args-runtime").entered(); + let config = Config::default(); + config.on_error(|err: ErrorHook| { + if let RuntimeError::IoError { + about: "waiting on process group", + .. + } = err.error + { + // "No child processes" and such + // these are often spurious, so condemn them to -v only + error!("{}", err.error); + return; + } + + if cfg!(debug_assertions) { + eprintln!("[[{:?}]]", err.error); + } + + eprintln!("[[Error (not fatal)]]\n{}", Report::new(err.error)); + }); + + config.pathset(if args.paths.is_empty() { + vec![current_dir().into_diagnostic()?] + } else if args.paths.len() == 1 + && args + .paths + .first() + .map_or(false, |p| p == Path::new("/dev/null")) + { + // special case: /dev/null means "don't start the fs event source" + Vec::new() + } else { + args.paths.clone() + }); + + config.throttle(args.debounce.0); + config.keyboard_events(args.stdin_quit); + + if let Some(interval) = args.poll { + config.file_watcher(Watcher::Poll(interval.0)); + } + + let clear = args.screen_clear; + let delay_run = args.delay_run.map(|ts| ts.0); + let on_busy = args.on_busy_update; + + let signal = args.signal; + let stop_signal = args.stop_signal; + let stop_timeout = args.stop_timeout.0; + + let once = args.once; + let notif = args.notify; + let print_events = args.print_events; + + let emit_events_to = args.emit_events_to; + let emit_file = state.emit_file.clone(); + let workdir = Arc::new(args.workdir.clone()); + + let mut add_envs = HashMap::new(); + for pair in &args.env { + if let Some((k, v)) = pair.split_once('=') { + add_envs.insert(k.to_owned(), OsString::from(v)); + } else { + return Err(miette!("{pair} is not in key=value format")); + } + } + debug!( + ?add_envs, + "additional environment variables to add to command" + ); + + let id = Id::default(); + let command = interpret_command_args(args)?; + + config.on_action_async(move |mut action| { + let add_envs = add_envs.clone(); + let command = command.clone(); + let emit_file = emit_file.clone(); + let workdir = workdir.clone(); + Box::new(async move { + let add_envs = add_envs.clone(); + let command = command.clone(); + let emit_file = emit_file.clone(); + let workdir = workdir.clone(); + + let job = action.get_or_create_job(id, move || command.clone()); + let events = action.events.clone(); + job.set_spawn_hook(move |command, _| { + let add_envs = add_envs.clone(); + let emit_file = emit_file.clone(); + let events = events.clone(); + + if let Some(ref workdir) = workdir.as_ref() { + debug!(?workdir, "set command workdir"); + command.current_dir(workdir); + } + + emit_events_to_command(command, events, emit_file, emit_events_to, add_envs); + }); + + let show_events = || { + if print_events { + for (n, event) in action.events.iter().enumerate() { + eprintln!("[EVENT {n}] {event}"); + } + } + }; + + if once { + show_events(); + + if let Some(delay) = delay_run { + job.run_async(move |_| { + Box::new(async move { + sleep(delay).await; + }) + }); + } + + // this blocks the event loop, but also this is a debug feature so i don't care + job.start().await; + job.to_wait().await; + action.quit(); + return action; + } + + let is_keyboard_eof = action + .events + .iter() + .any(|e| e.tags.contains(&Tag::Keyboard(Keyboard::Eof))); + if is_keyboard_eof { + show_events(); + action.quit(); + return action; + } + + let signals: Vec = action.signals().collect(); + + // if we got a terminate or interrupt signal, quit + if signals.contains(&Signal::Terminate) || signals.contains(&Signal::Interrupt) { + show_events(); + action.quit(); + return action; + } + + // pass all other signals on + for signal in signals { + job.signal(signal); + } + + // clear the screen before printing events + if let Some(mode) = clear { + match mode { + ClearMode::Clear => { + clearscreen::clear().ok(); + } + ClearMode::Reset => { + for cs in [ + ClearScreen::WindowsCooked, + ClearScreen::WindowsVt, + ClearScreen::VtLeaveAlt, + ClearScreen::VtWellDone, + ClearScreen::default(), + ] { + cs.clear().ok(); + } + } + } + } + + show_events(); + + if let Some(delay) = delay_run { + job.run_async(move |_| { + Box::new(async move { + sleep(delay).await; + }) + }); + } + + job.run_async({ + let job = job.clone(); + move |context| { + let job = job.clone(); + let is_running = matches!(context.current, CommandState::Running { .. }); + Box::new(async move { + let innerjob = job.clone(); + if is_running { + match on_busy { + OnBusyUpdate::DoNothing => {} + OnBusyUpdate::Signal => { + job.signal(if cfg!(windows) { + Signal::ForceStop + } else { + stop_signal.or(signal).unwrap_or(Signal::Terminate) + }); + } + OnBusyUpdate::Restart if cfg!(windows) => { + job.restart(); + job.run(move |context| { + setup_process( + innerjob.clone(), + context.command.clone(), + notif, + ) + }); + } + OnBusyUpdate::Restart => { + job.restart_with_signal( + stop_signal.unwrap_or(Signal::Terminate), + stop_timeout, + ); + job.run(move |context| { + setup_process( + innerjob.clone(), + context.command.clone(), + notif, + ) + }); + } + OnBusyUpdate::Queue => { + let job = job.clone(); + tokio::spawn(async move { + job.to_wait().await; + job.start(); + job.run(move |context| { + setup_process( + innerjob.clone(), + context.command.clone(), + notif, + ) + }); + }); + } + } + } else { + job.start(); + job.run(move |context| { + setup_process(innerjob.clone(), context.command.clone(), notif) + }); + } + }) + } + }); + + action + }) + }); + + Ok(config) +} + +fn interpret_command_args(args: &Args) -> Result> { + let mut cmd = args.command.clone(); + if cmd.is_empty() { + panic!("(clap) Bug: command is not present"); + } + + let shell = match if args.no_shell || args.no_shell_long { + None + } else { + args.shell.as_deref().or(Some("default")) + } { + Some("") => return Err(RuntimeError::CommandShellEmptyShell).into_diagnostic(), + + Some("none") | None => None, + + #[cfg(windows)] + Some("default") | Some("cmd") | Some("cmd.exe") | Some("CMD") | Some("CMD.EXE") => { + Some(Shell::cmd()) + } + + #[cfg(not(windows))] + Some("default") => Some(Shell::new("sh")), + + #[cfg(windows)] + Some("powershell") => Some(Shell::new(available_powershell())), + + Some(other) => { + let sh = other.split_ascii_whitespace().collect::>(); + + // UNWRAP: checked by Some("") + #[allow(clippy::unwrap_used)] + let (shprog, shopts) = sh.split_first().unwrap(); + + Some(Shell { + prog: shprog.into(), + options: shopts.iter().map(|s| (*s).to_string()).collect(), + program_option: Some(Cow::Borrowed(OsStr::new("-c"))), + }) + } + }; + + let program = if let Some(shell) = shell { + Program::Shell { + shell, + command: cmd.join(" "), + args: Vec::new(), + } + } else { + Program::Exec { + prog: cmd.remove(0).into(), + args: cmd, + } + }; + + Ok(Arc::new(Command { + program, + options: SpawnOptions { + grouped: !args.no_process_group, + ..Default::default() + }, + })) +} + +#[cfg(windows)] +fn available_powershell() -> String { + todo!("figure out if powershell.exe is available, and use that, otherwise use pwsh.exe") +} + +fn setup_process(job: Job, command: Arc, notif: bool) { + if notif { + Notification::new() + .summary("Watchexec: change detected") + .body(&format!("Running {command}")) + .show() + .map_or_else( + |err| { + eprintln!("[[Failed to send desktop notification: {err}]]"); + }, + drop, + ); + } + + tokio::spawn(async move { + job.to_wait().await; + job.run(move |context| end_of_process(context.current, notif)); + }); +} + +fn end_of_process(state: &CommandState, notif: bool) { + let CommandState::Finished { + status, + started, + finished, + } = state + else { + return; + }; + + let duration = *finished - *started; + let msg = match status { + ProcessEnd::ExitError(code) => { + format!("Command exited with {code}, lasted {duration:?}") + } + ProcessEnd::ExitSignal(sig) => { + format!("Command killed by {sig:?}, lasted {duration:?}") + } + ProcessEnd::ExitStop(sig) => { + format!("Command stopped by {sig:?}, lasted {duration:?}") + } + ProcessEnd::Continued => format!("Command continued, lasted {duration:?}"), + ProcessEnd::Exception(ex) => { + format!("Command ended by exception {ex:#x}, lasted {duration:?}") + } + ProcessEnd::Success => format!("Command was successful, lasted {duration:?}"), + }; + + if notif { + Notification::new() + .summary("Watchexec: command ended") + .body(&msg) + .show() + .map_or_else( + |err| { + eprintln!("[[Failed to send desktop notification: {err}]]"); + }, + drop, + ); + } +} + +fn emit_events_to_command( + command: &mut TokioCommand, + events: Arc<[Event]>, + emit_file: RotatingTempFile, + emit_events_to: EmitEvents, + mut add_envs: HashMap, +) { + use crate::emits::*; + + let mut stdin = None; + + match emit_events_to { + EmitEvents::Environment => { + add_envs.extend(emits_to_environment(&events)); + } + EmitEvents::Stdin => match emits_to_file(&emit_file, &events) + .and_then(|path| File::open(path).into_diagnostic()) + { + Ok(file) => { + stdin.replace(Stdio::from(file)); + } + Err(err) => { + error!("Failed to write events to stdin, continuing without it: {err}"); + } + }, + EmitEvents::File => match emits_to_file(&emit_file, &events) { + Ok(path) => { + add_envs.insert("WATCHEXEC_EVENTS_FILE".into(), path.into()); + } + Err(err) => { + error!("Failed to write WATCHEXEC_EVENTS_FILE, continuing without it: {err}"); + } + }, + EmitEvents::JsonStdin => match emits_to_json_file(&emit_file, &events) + .and_then(|path| File::open(path).into_diagnostic()) + { + Ok(file) => { + stdin.replace(Stdio::from(file)); + } + Err(err) => { + error!("Failed to write events to stdin, continuing without it: {err}"); + } + }, + EmitEvents::JsonFile => match emits_to_json_file(&emit_file, &events) { + Ok(path) => { + add_envs.insert("WATCHEXEC_EVENTS_FILE".into(), path.into()); + } + Err(err) => { + error!("Failed to write WATCHEXEC_EVENTS_FILE, continuing without it: {err}"); + } + }, + EmitEvents::None => {} + } + + for (k, v) in add_envs { + debug!(?k, ?v, "inserting environment variable"); + command.env(k, v); + } + + if let Some(stdin) = stdin { + debug!("set command stdin"); + command.stdin(stdin); + } +} diff --git a/crates/cli/src/config/init.rs b/crates/cli/src/config/init.rs deleted file mode 100644 index 50851a4..0000000 --- a/crates/cli/src/config/init.rs +++ /dev/null @@ -1,52 +0,0 @@ -use std::convert::Infallible; - -use miette::Report; -use tracing::error; -use watchexec::{ - config::InitConfig, - error::{FsWatcherError, RuntimeError}, - handler::SyncFnHandler, - ErrorHook, -}; - -use crate::args::Args; - -pub fn init(_args: &Args) -> InitConfig { - let mut config = InitConfig::default(); - config.on_error(SyncFnHandler::from( - |err: ErrorHook| -> std::result::Result<(), Infallible> { - if let RuntimeError::IoError { - about: "waiting on process group", - .. - } = err.error - { - // "No child processes" and such - // these are often spurious, so condemn them to -v only - error!("{}", err.error); - return Ok(()); - } - - if let RuntimeError::FsWatcher { - err: - FsWatcherError::Create { .. } - | FsWatcherError::TooManyWatches { .. } - | FsWatcherError::TooManyHandles { .. }, - .. - } = err.error - { - err.elevate(); - return Ok(()); - } - - if cfg!(debug_assertions) { - eprintln!("[[{:?}]]", err.error); - } - - eprintln!("[[Error (not fatal)]]\n{}", Report::new(err.error)); - - Ok(()) - }, - )); - - config -} diff --git a/crates/cli/src/config/runtime.rs b/crates/cli/src/config/runtime.rs deleted file mode 100644 index d199fe9..0000000 --- a/crates/cli/src/config/runtime.rs +++ /dev/null @@ -1,373 +0,0 @@ -use std::{ - collections::HashMap, convert::Infallible, env::current_dir, ffi::OsString, fs::File, - process::Stdio, -}; - -use miette::{miette, IntoDiagnostic, Result}; -use notify_rust::Notification; -use tracing::{debug, debug_span, error}; -use watchexec::{ - action::{Action, Outcome, PostSpawn, PreSpawn}, - command::{Command, Shell}, - config::RuntimeConfig, - error::RuntimeError, - fs::Watcher, - handler::SyncFnHandler, -}; -use watchexec_events::{Event, Keyboard, ProcessEnd, Tag}; -use watchexec_signals::Signal; - -use crate::args::{Args, ClearMode, EmitEvents, OnBusyUpdate}; -use crate::state::State; - -pub fn runtime(args: &Args, state: &State) -> Result { - let _span = debug_span!("args-runtime").entered(); - let mut config = RuntimeConfig::default(); - - config.command(interpret_command_args(args)?); - - config.pathset(if args.paths.is_empty() { - vec![current_dir().into_diagnostic()?] - } else { - args.paths.clone() - }); - - config.action_throttle(args.debounce.0); - config.command_grouped(!args.no_process_group); - config.keyboard_emit_eof(args.stdin_quit); - - if let Some(interval) = args.poll { - config.file_watcher(Watcher::Poll(interval.0)); - } - - let clear = args.screen_clear; - let notif = args.notify; - let on_busy = args.on_busy_update; - - let signal = args.signal; - let stop_signal = args.stop_signal; - let stop_timeout = args.stop_timeout.0; - - let print_events = args.print_events; - let once = args.once; - let delay_run = args.delay_run.map(|ts| ts.0); - - config.on_action(move |action: Action| { - let fut = async { Ok::<(), Infallible>(()) }; - - if print_events { - for (n, event) in action.events.iter().enumerate() { - eprintln!("[EVENT {n}] {event}"); - } - } - - if once { - action.outcome(Outcome::both( - if let Some(delay) = &delay_run { - Outcome::both(Outcome::Sleep(*delay), Outcome::Start) - } else { - Outcome::Start - }, - Outcome::wait(Outcome::Exit), - )); - return fut; - } - - let signals: Vec = action.events.iter().flat_map(Event::signals).collect(); - let has_paths = action.events.iter().flat_map(Event::paths).next().is_some(); - - if signals.contains(&Signal::Terminate) { - action.outcome(Outcome::both(Outcome::Stop, Outcome::Exit)); - return fut; - } - - if signals.contains(&Signal::Interrupt) { - action.outcome(Outcome::both(Outcome::Stop, Outcome::Exit)); - return fut; - } - - let is_keyboard_eof = action - .events - .iter() - .any(|e| e.tags.contains(&Tag::Keyboard(Keyboard::Eof))); - - if is_keyboard_eof { - action.outcome(Outcome::both(Outcome::Stop, Outcome::Exit)); - return fut; - } - - if !has_paths { - if !signals.is_empty() { - let mut out = Outcome::DoNothing; - for sig in signals { - out = Outcome::both(out, Outcome::Signal(sig)); - } - - action.outcome(out); - return fut; - } - - let completion = action.events.iter().flat_map(Event::completions).next(); - if let Some(status) = completion { - let (msg, printit) = match status { - Some(ProcessEnd::ExitError(code)) => { - (format!("Command exited with {code}"), true) - } - Some(ProcessEnd::ExitSignal(sig)) => { - (format!("Command killed by {sig:?}"), true) - } - Some(ProcessEnd::ExitStop(sig)) => { - (format!("Command stopped by {sig:?}"), true) - } - Some(ProcessEnd::Continued) => ("Command continued".to_string(), true), - Some(ProcessEnd::Exception(ex)) => { - (format!("Command ended by exception {ex:#x}"), true) - } - Some(ProcessEnd::Success) => ("Command was successful".to_string(), false), - None => ("Command completed".to_string(), false), - }; - - if printit { - eprintln!("[[{msg}]]"); - } - - if notif { - Notification::new() - .summary("Watchexec: command ended") - .body(&msg) - .show() - .map_or_else( - |err| { - eprintln!("[[Failed to send desktop notification: {err}]]"); - }, - drop, - ); - } - - action.outcome(Outcome::DoNothing); - return fut; - } - } - - let start = if let Some(mode) = clear { - Outcome::both( - match mode { - ClearMode::Clear => Outcome::Clear, - ClearMode::Reset => Outcome::Reset, - }, - Outcome::Start, - ) - } else { - Outcome::Start - }; - - let start = if let Some(delay) = &delay_run { - Outcome::both(Outcome::Sleep(*delay), start) - } else { - start - }; - - let when_idle = start.clone(); - let when_running = match on_busy { - OnBusyUpdate::Restart if cfg!(windows) => Outcome::both(Outcome::Stop, start), - OnBusyUpdate::Restart => Outcome::both( - Outcome::both( - Outcome::Signal(stop_signal.unwrap_or(Signal::Terminate)), - Outcome::wait_timeout(stop_timeout, Outcome::Stop), - ), - start, - ), - OnBusyUpdate::Signal if cfg!(windows) => Outcome::Stop, - OnBusyUpdate::Signal => { - Outcome::Signal(stop_signal.or(signal).unwrap_or(Signal::Terminate)) - } - OnBusyUpdate::Queue => Outcome::wait(start), - OnBusyUpdate::DoNothing => Outcome::DoNothing, - }; - - action.outcome(Outcome::if_running(when_running, when_idle)); - - fut - }); - - let mut add_envs = HashMap::new(); - // TODO: move to args? - for pair in &args.env { - if let Some((k, v)) = pair.split_once('=') { - add_envs.insert(k.to_owned(), OsString::from(v)); - } else { - return Err(miette!("{pair} is not in key=value format")); - } - } - debug!( - ?add_envs, - "additional environment variables to add to command" - ); - - let workdir = args.workdir.clone(); - - let emit_events_to = args.emit_events_to; - let emit_file = state.emit_file.clone(); - config.on_pre_spawn(move |prespawn: PreSpawn| { - use crate::emits::*; - - let workdir = workdir.clone(); - let mut add_envs = add_envs.clone(); - let mut stdin = None; - - match emit_events_to { - EmitEvents::Environment => { - add_envs.extend(emits_to_environment(&prespawn.events)); - } - EmitEvents::Stdin => match emits_to_file(&emit_file, &prespawn.events) - .and_then(|path| File::open(path).into_diagnostic()) - { - Ok(file) => { - stdin.replace(Stdio::from(file)); - } - Err(err) => { - error!("Failed to write events to stdin, continuing without it: {err}"); - } - }, - EmitEvents::File => match emits_to_file(&emit_file, &prespawn.events) { - Ok(path) => { - add_envs.insert("WATCHEXEC_EVENTS_FILE".into(), path.into()); - } - Err(err) => { - error!("Failed to write WATCHEXEC_EVENTS_FILE, continuing without it: {err}"); - } - }, - EmitEvents::JsonStdin => match emits_to_json_file(&emit_file, &prespawn.events) - .and_then(|path| File::open(path).into_diagnostic()) - { - Ok(file) => { - stdin.replace(Stdio::from(file)); - } - Err(err) => { - error!("Failed to write events to stdin, continuing without it: {err}"); - } - }, - EmitEvents::JsonFile => match emits_to_json_file(&emit_file, &prespawn.events) { - Ok(path) => { - add_envs.insert("WATCHEXEC_EVENTS_FILE".into(), path.into()); - } - Err(err) => { - error!("Failed to write WATCHEXEC_EVENTS_FILE, continuing without it: {err}"); - } - }, - EmitEvents::None => {} - } - - async move { - if !add_envs.is_empty() || workdir.is_some() || stdin.is_some() { - if let Some(mut command) = prespawn.command().await { - for (k, v) in add_envs { - debug!(?k, ?v, "inserting environment variable"); - command.env(k, v); - } - - if let Some(ref workdir) = workdir { - debug!(?workdir, "set command workdir"); - command.current_dir(workdir); - } - - if let Some(stdin) = stdin { - debug!("set command stdin"); - command.stdin(stdin); - } - } - } - - Ok::<(), Infallible>(()) - } - }); - - config.on_post_spawn(SyncFnHandler::from(move |postspawn: PostSpawn| { - if notif { - Notification::new() - .summary("Watchexec: change detected") - .body(&format!("Running {}", postspawn.command)) - .show() - .map_or_else( - |err| { - eprintln!("[[Failed to send desktop notification: {err}]]"); - }, - drop, - ); - } - - Ok::<(), Infallible>(()) - })); - - Ok(config) -} - -fn interpret_command_args(args: &Args) -> Result { - let mut cmd = args.command.clone(); - if cmd.is_empty() { - panic!("(clap) Bug: command is not present"); - } - - Ok(if args.no_shell || args.no_shell_long { - Command::Exec { - prog: cmd.remove(0), - args: cmd, - } - } else { - let (shell, shopts) = if let Some(s) = &args.shell { - if s.is_empty() { - return Err(RuntimeError::CommandShellEmptyShell).into_diagnostic(); - } else if s.eq_ignore_ascii_case("powershell") { - (Shell::Powershell, Vec::new()) - } else if s.eq_ignore_ascii_case("none") { - return Ok(Command::Exec { - prog: cmd.remove(0), - args: cmd, - }); - } else if s.eq_ignore_ascii_case("cmd") { - (cmd_shell(s.into()), Vec::new()) - } else { - let sh = s.split_ascii_whitespace().collect::>(); - - // UNWRAP: checked by first if branch - #[allow(clippy::unwrap_used)] - let (shprog, shopts) = sh.split_first().unwrap(); - - ( - Shell::Unix((*shprog).to_string()), - shopts.iter().map(|s| (*s).to_string()).collect(), - ) - } - } else { - (default_shell(), Vec::new()) - }; - - Command::Shell { - shell, - args: shopts, - command: cmd.join(" "), - } - }) -} - -// until 2.0, then Powershell -#[cfg(windows)] -fn default_shell() -> Shell { - Shell::Cmd -} - -#[cfg(not(windows))] -fn default_shell() -> Shell { - Shell::Unix("sh".to_string()) -} - -// because Shell::Cmd is only on windows -#[cfg(windows)] -fn cmd_shell(_: String) -> Shell { - Shell::Cmd -} - -#[cfg(not(windows))] -fn cmd_shell(s: String) -> Shell { - Shell::Unix(s) -} diff --git a/crates/cli/src/filterer/globset.rs b/crates/cli/src/filterer/globset.rs index 783a1fc..0946c3f 100644 --- a/crates/cli/src/filterer/globset.rs +++ b/crates/cli/src/filterer/globset.rs @@ -7,13 +7,10 @@ use std::{ use miette::{IntoDiagnostic, Result}; use tokio::io::{AsyncBufReadExt, BufReader}; use tracing::{info, trace, trace_span}; -use watchexec::{ - error::RuntimeError, - event::{ - filekind::{FileEventKind, ModifyKind}, - Event, Priority, Tag, - }, - filter::Filterer, +use watchexec::{error::RuntimeError, filter::Filterer}; +use watchexec_events::{ + filekind::{FileEventKind, ModifyKind}, + Event, Priority, Tag, }; use watchexec_filterer_globset::GlobsetFilterer; diff --git a/crates/cli/src/lib.rs b/crates/cli/src/lib.rs index c1645d5..79506e9 100644 --- a/crates/cli/src/lib.rs +++ b/crates/cli/src/lib.rs @@ -7,15 +7,12 @@ use args::{Args, ShellCompletion}; use clap::CommandFactory; use clap_complete::{Generator, Shell}; use clap_mangen::Man; -use command_group::AsyncCommandGroup; use is_terminal::IsTerminal; use miette::{IntoDiagnostic, Result}; use tokio::{fs::metadata, io::AsyncWriteExt, process::Command}; use tracing::{debug, info, warn}; -use watchexec::{ - event::{Event, Priority}, - Watchexec, -}; +use watchexec::Watchexec; +use watchexec_events::{Event, Priority}; pub mod args; mod config; @@ -102,14 +99,12 @@ async fn init() -> Result { async fn run_watchexec(args: Args) -> Result<()> { info!(version=%env!("CARGO_PKG_VERSION"), "constructing Watchexec from CLI"); - let init = config::init(&args); - let state = state::State::new()?; - let mut runtime = config::runtime(&args, &state)?; - runtime.filterer(filterer::globset(&args).await?); + let config = config::make_config(&args, &state)?; + config.filterer(filterer::globset(&args).await?); info!("initialising Watchexec runtime"); - let wx = Watchexec::new(init, runtime)?; + let wx = Watchexec::with_config(config)?; if !args.postpone { debug!("kicking off with empty event"); @@ -137,12 +132,10 @@ async fn run_manpage(_args: Args) -> Result<()> { .stdin(Stdio::piped()) .stdout(Stdio::inherit()) .stderr(Stdio::inherit()) - .group() .kill_on_drop(true) .spawn() .into_diagnostic()?; child - .inner() .stdin .as_mut() .unwrap() diff --git a/crates/cli/src/main.rs b/crates/cli/src/main.rs index e753dbb..99ac6bb 100644 --- a/crates/cli/src/main.rs +++ b/crates/cli/src/main.rs @@ -1,14 +1,19 @@ +use miette::IntoDiagnostic; + #[cfg(target_env = "musl")] #[global_allocator] static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc; -#[tokio::main] -async fn main() -> miette::Result<()> { - watchexec_cli::run().await?; +fn main() -> miette::Result<()> { + #[cfg(feature = "pid1")] + pid1::Pid1Settings::new() + .enable_log(cfg!(feature = "pid1-withlog")) + .launch() + .into_diagnostic()?; - if std::process::id() == 1 { - std::process::exit(0); - } - - Ok(()) + tokio::runtime::Builder::new_multi_thread() + .enable_all() + .build() + .unwrap() + .block_on(async { watchexec_cli::run().await }) } diff --git a/crates/events/CHANGELOG.md b/crates/events/CHANGELOG.md index 2e9ae74..a719c8f 100644 --- a/crates/events/CHANGELOG.md +++ b/crates/events/CHANGELOG.md @@ -2,6 +2,8 @@ ## Next (YYYY-MM-DD) +- Add `ProcessEnd::into_exitstatus` testing-only utility method. + ## v1.0.0 (2023-03-18) - Split off new `watchexec-events` crate (this one), to have a lightweight library that can parse diff --git a/crates/events/Cargo.toml b/crates/events/Cargo.toml index ec4dc17..2e947ed 100644 --- a/crates/events/Cargo.toml +++ b/crates/events/Cargo.toml @@ -15,11 +15,11 @@ rust-version = "1.61.0" edition = "2021" [dependencies.notify] -version = "5.0.0" +version = "6.0.0" optional = true [dependencies.serde] -version = "1.0.152" +version = "1.0.183" optional = true features = ["derive"] @@ -29,13 +29,13 @@ path = "../signals" default-features = false [target.'cfg(unix)'.dependencies.nix] -version = "0.26.2" +version = "0.27.1" features = ["signal"] [dev-dependencies] watchexec-events = { version = "*", features = ["serde"], path = "." } -snapbox = "0.4.10" -serde_json = "1.0.94" +snapbox = "0.4.11" +serde_json = "1.0.107" [features] default = ["notify"] diff --git a/crates/events/src/event.rs b/crates/events/src/event.rs index f954730..bc07079 100644 --- a/crates/events/src/event.rs +++ b/crates/events/src/event.rs @@ -53,7 +53,7 @@ pub enum Tag { /// The event is about a signal being delivered to the main process. Signal(Signal), - /// The event is about the subprocess ending. + /// The event is about a subprocess ending. ProcessCompletion(Option), #[cfg(feature = "serde")] diff --git a/crates/events/src/process.rs b/crates/events/src/process.rs index 12c6d04..1fc3fa6 100644 --- a/crates/events/src/process.rs +++ b/crates/events/src/process.rs @@ -88,3 +88,45 @@ impl From for ProcessEnd { } } } + +impl ProcessEnd { + /// Convert a `ProcessEnd` to an `ExitStatus`. + /// + /// This is a testing function only! **It will panic** if the `ProcessEnd` is not representable + /// as an `ExitStatus` on Unix. This is also not guaranteed to be accurate, as the waitpid() + /// status union is platform-specific. Exit codes and signals are implemented, other variants + /// are not. + #[cfg(unix)] + pub fn into_exitstatus(self) -> ExitStatus { + use std::os::unix::process::ExitStatusExt; + match self { + Self::Success => ExitStatus::from_raw(0), + Self::ExitError(code) => ExitStatus::from_raw((code.get() as u8 as i32) << 8), + Self::ExitSignal(signal) => { + ExitStatus::from_raw(signal.to_nix().map_or(0, |sig| sig as i32)) + } + Self::Continued => ExitStatus::from_raw(0xffff), + _ => unimplemented!(), + } + } + + /// Convert a `ProcessEnd` to an `ExitStatus`. + /// + /// This is a testing function only! **It will panic** if the `ProcessEnd` is not representable + /// as an `ExitStatus` on Windows. + #[cfg(windows)] + pub fn into_exitstatus(self) -> ExitStatus { + use std::os::windows::process::ExitStatusExt; + match self { + Self::Success => ExitStatus::from_raw(0), + Self::ExitError(code) => ExitStatus::from_raw(code.get().try_into().unwrap()), + _ => unimplemented!(), + } + } + + /// Unimplemented on this platform. + #[cfg(not(any(unix, windows)))] + pub fn into_exitstatus(self) -> ExitStatus { + unimplemented!() + } +} diff --git a/crates/filterer/globset/Cargo.toml b/crates/filterer/globset/Cargo.toml index 04e6d68..361ebe3 100644 --- a/crates/filterer/globset/Cargo.toml +++ b/crates/filterer/globset/Cargo.toml @@ -17,7 +17,7 @@ edition = "2021" [dependencies] ignore = "0.4.18" -tracing = "0.1.26" +tracing = "0.1.40" [dependencies.ignore-files] version = "1.3.1" @@ -27,6 +27,10 @@ path = "../../ignore-files" version = "2.3.0" path = "../../lib" +[dependencies.watchexec-events] +version = "1.0.0" +path = "../../events" + [dependencies.watchexec-filterer-ignore] version = "1.2.1" path = "../ignore" @@ -39,7 +43,7 @@ version = "1.2.0" path = "../../project-origins" [dev-dependencies.tokio] -version = "1.24.2" +version = "1.33.0" features = [ "fs", "io-std", diff --git a/crates/filterer/globset/src/lib.rs b/crates/filterer/globset/src/lib.rs index 30b7ec9..aa4327a 100644 --- a/crates/filterer/globset/src/lib.rs +++ b/crates/filterer/globset/src/lib.rs @@ -16,11 +16,8 @@ use std::{ use ignore::gitignore::{Gitignore, GitignoreBuilder}; use ignore_files::{Error, IgnoreFile, IgnoreFilter}; use tracing::{debug, trace, trace_span}; -use watchexec::{ - error::RuntimeError, - event::{Event, FileType, Priority}, - filter::Filterer, -}; +use watchexec::{error::RuntimeError, filter::Filterer}; +use watchexec_events::{Event, FileType, Priority}; use watchexec_filterer_ignore::IgnoreFilterer; /// A simple filterer in the style of the watchexec v1.17 filter. diff --git a/crates/filterer/globset/tests/filtering.rs b/crates/filterer/globset/tests/filtering.rs index f59983a..8845bb6 100644 --- a/crates/filterer/globset/tests/filtering.rs +++ b/crates/filterer/globset/tests/filtering.rs @@ -374,10 +374,8 @@ async fn extensions_fail_extensionless() { #[tokio::test] async fn multipath_allow_on_any_one_pass() { - use watchexec::{ - event::{Event, FileType, Tag}, - filter::Filterer, - }; + use watchexec::filter::Filterer; + use watchexec_events::{Event, FileType, Tag}; let filterer = filt(&[], &[], &["py"]).await; let origin = tokio::fs::canonicalize(".").await.unwrap(); @@ -442,10 +440,8 @@ async fn leading_single_glob_file() { #[tokio::test] async fn nonpath_event_passes() { - use watchexec::{ - event::{Event, Source, Tag}, - filter::Filterer, - }; + use watchexec::filter::Filterer; + use watchexec_events::{Event, Source, Tag}; let filterer = filt(&[], &[], &["py"]).await; diff --git a/crates/filterer/globset/tests/helpers/mod.rs b/crates/filterer/globset/tests/helpers/mod.rs index 166f7b8..23f9210 100644 --- a/crates/filterer/globset/tests/helpers/mod.rs +++ b/crates/filterer/globset/tests/helpers/mod.rs @@ -5,11 +5,8 @@ use std::{ use ignore_files::IgnoreFile; use project_origins::ProjectType; -use watchexec::{ - error::RuntimeError, - event::{Event, FileType, Priority, Tag}, - filter::Filterer, -}; +use watchexec::{error::RuntimeError, filter::Filterer}; +use watchexec_events::{Event, FileType, Priority, Tag}; use watchexec_filterer_globset::GlobsetFilterer; use watchexec_filterer_ignore::IgnoreFilterer; @@ -17,7 +14,7 @@ pub mod globset { pub use super::globset_filt as filt; pub use super::Applies; pub use super::PathHarness; - pub use watchexec::event::Priority; + pub use watchexec_events::Priority; } pub trait PathHarness: Filterer { diff --git a/crates/filterer/ignore/Cargo.toml b/crates/filterer/ignore/Cargo.toml index 73a57f2..64cc418 100644 --- a/crates/filterer/ignore/Cargo.toml +++ b/crates/filterer/ignore/Cargo.toml @@ -17,7 +17,7 @@ edition = "2021" [dependencies] ignore = "0.4.18" -tracing = "0.1.26" +tracing = "0.1.40" dunce = "1.0.4" [dependencies.ignore-files] @@ -28,6 +28,10 @@ path = "../../ignore-files" version = "2.3.0" path = "../../lib" +[dependencies.watchexec-events] +version = "1.0.0" +path = "../../events" + [dependencies.watchexec-signals] version = "1.0.0" path = "../../signals" @@ -40,7 +44,7 @@ version = "1.2.0" path = "../../project-origins" [dev-dependencies.tokio] -version = "1.24.2" +version = "1.33.0" features = [ "fs", "io-std", diff --git a/crates/filterer/ignore/src/lib.rs b/crates/filterer/ignore/src/lib.rs index 4a32bfc..2bfaecc 100644 --- a/crates/filterer/ignore/src/lib.rs +++ b/crates/filterer/ignore/src/lib.rs @@ -14,12 +14,8 @@ use ignore::Match; use ignore_files::IgnoreFilter; use tracing::{trace, trace_span}; - -use watchexec::{ - error::RuntimeError, - event::{Event, FileType, Priority}, - filter::Filterer, -}; +use watchexec::{error::RuntimeError, filter::Filterer}; +use watchexec_events::{Event, FileType, Priority}; /// A Watchexec [`Filterer`] implementation for [`IgnoreFilter`]. #[derive(Clone, Debug)] diff --git a/crates/filterer/ignore/tests/filtering.rs b/crates/filterer/ignore/tests/filtering.rs index 7264c02..8d50234 100644 --- a/crates/filterer/ignore/tests/filtering.rs +++ b/crates/filterer/ignore/tests/filtering.rs @@ -235,7 +235,7 @@ async fn scopes() { #[cfg(not(windows))] filterer.file_does_pass("/local.b"); // FIXME flaky - //filterer.file_doesnt_pass("tests/local.c"); + // filterer.file_doesnt_pass("tests/local.c"); filterer.file_does_pass("sublocal.a"); // #[cfg(windows)] FIXME should work diff --git a/crates/filterer/ignore/tests/helpers/mod.rs b/crates/filterer/ignore/tests/helpers/mod.rs index 1186f2b..2a877d8 100644 --- a/crates/filterer/ignore/tests/helpers/mod.rs +++ b/crates/filterer/ignore/tests/helpers/mod.rs @@ -2,10 +2,9 @@ use std::path::{Path, PathBuf}; use ignore_files::{IgnoreFile, IgnoreFilter}; use project_origins::ProjectType; -use watchexec::{ - error::RuntimeError, - event::{filekind::FileEventKind, Event, FileType, Priority, ProcessEnd, Source, Tag}, - filter::Filterer, +use watchexec::{error::RuntimeError, filter::Filterer}; +use watchexec_events::{ + filekind::FileEventKind, Event, FileType, Priority, ProcessEnd, Source, Tag, }; use watchexec_filterer_ignore::IgnoreFilterer; use watchexec_signals::Signal; @@ -15,7 +14,7 @@ pub mod ignore { pub use super::ignore_filt as filt; pub use super::Applies; pub use super::PathHarness; - pub use watchexec::event::Priority; + pub use watchexec_events::Priority; } pub trait PathHarness: Filterer { diff --git a/crates/filterer/tagged/Cargo.toml b/crates/filterer/tagged/Cargo.toml index 5fb9d79..7773144 100644 --- a/crates/filterer/tagged/Cargo.toml +++ b/crates/filterer/tagged/Cargo.toml @@ -31,7 +31,7 @@ version = "1.3.1" path = "../../ignore-files" [dependencies.tokio] -version = "1.24.2" +version = "1.32.0" features = [ "fs", ] @@ -40,6 +40,10 @@ features = [ version = "2.3.0" path = "../../lib" +[dependencies.watchexec-events] +version = "1.0.0" +path = "../../events" + [dependencies.watchexec-filterer-ignore] version = "1.2.1" path = "../ignore" @@ -56,7 +60,7 @@ version = "1.2.0" path = "../../project-origins" [dev-dependencies.tokio] -version = "1.24.2" +version = "1.32.0" features = [ "fs", "io-std", diff --git a/crates/filterer/tagged/src/error.rs b/crates/filterer/tagged/src/error.rs index 216cfcf..de08580 100644 --- a/crates/filterer/tagged/src/error.rs +++ b/crates/filterer/tagged/src/error.rs @@ -12,11 +12,9 @@ use crate::{Filter, Matcher}; /// Errors emitted by the `TaggedFilterer`. #[derive(Debug, Diagnostic, Error)] #[non_exhaustive] -#[diagnostic(url(docsrs))] pub enum TaggedFiltererError { /// Generic I/O error, with some context. #[error("io({about}): {err}")] - #[diagnostic(code(watchexec::filter::io_error))] IoError { /// What it was about. about: &'static str, @@ -28,7 +26,6 @@ pub enum TaggedFiltererError { /// Error received when a tagged filter cannot be parsed. #[error("cannot parse filter `{src}`: {err:?}")] - #[diagnostic(code(watchexec::filter::tagged::parse))] Parse { /// The source of the filter. #[source_code] @@ -40,7 +37,6 @@ pub enum TaggedFiltererError { /// Error received when a filter cannot be added or removed from a tagged filter list. #[error("cannot {action} filter: {err:?}")] - #[diagnostic(code(watchexec::filter::tagged::filter_change))] FilterChange { /// The action that was attempted. action: &'static str, @@ -52,22 +48,18 @@ pub enum TaggedFiltererError { /// Error received when a glob cannot be parsed. #[error("cannot parse glob: {0}")] - #[diagnostic(code(watchexec::filter::tagged::glob_parse))] GlobParse(#[source] ignore::Error), /// Error received when a compiled globset cannot be changed. #[error("cannot change compiled globset: {0:?}")] - #[diagnostic(code(watchexec::filter::tagged::globset_change))] GlobsetChange(#[source] SendError>), /// Error received about the internal ignore filterer. #[error("ignore filterer: {0}")] - #[diagnostic(code(watchexec::filter::tagged::ignore))] Ignore(#[source] ignore_files::Error), /// Error received when a new ignore filterer cannot be swapped in. #[error("cannot swap in new ignore filterer: {0:?}")] - #[diagnostic(code(watchexec::filter::tagged::ignore_swap))] IgnoreSwap(#[source] SendError), } diff --git a/crates/filterer/tagged/src/filter.rs b/crates/filterer/tagged/src/filter.rs index 398e79f..0c1a35c 100644 --- a/crates/filterer/tagged/src/filter.rs +++ b/crates/filterer/tagged/src/filter.rs @@ -6,7 +6,7 @@ use regex::Regex; use tokio::fs::canonicalize; use tracing::{trace, warn}; use unicase::UniCase; -use watchexec::event::Tag; +use watchexec_events::Tag; use crate::TaggedFiltererError; diff --git a/crates/filterer/tagged/src/filterer.rs b/crates/filterer/tagged/src/filterer.rs index 2e672c3..cf0ee00 100644 --- a/crates/filterer/tagged/src/filterer.rs +++ b/crates/filterer/tagged/src/filterer.rs @@ -10,11 +10,8 @@ use ignore::{ use ignore_files::{IgnoreFile, IgnoreFilter}; use tokio::fs::canonicalize; use tracing::{debug, trace, trace_span}; -use watchexec::{ - error::RuntimeError, - event::{Event, FileType, Priority, ProcessEnd, Tag}, - filter::Filterer, -}; +use watchexec::{error::RuntimeError, filter::Filterer}; +use watchexec_events::{Event, FileType, Priority, ProcessEnd, Tag}; use watchexec_filterer_ignore::IgnoreFilterer; use watchexec_signals::Signal; diff --git a/crates/filterer/tagged/tests/filter_files.rs b/crates/filterer/tagged/tests/filter_files.rs index 1dd0ec1..1c5fa2e 100644 --- a/crates/filterer/tagged/tests/filter_files.rs +++ b/crates/filterer/tagged/tests/filter_files.rs @@ -1,4 +1,4 @@ -use watchexec::event::{filekind::*, ProcessEnd, Source}; +use watchexec_events::{filekind::*, ProcessEnd, Source}; use watchexec_signals::Signal; mod helpers; diff --git a/crates/filterer/tagged/tests/helpers/mod.rs b/crates/filterer/tagged/tests/helpers/mod.rs index 79b20c8..642feee 100644 --- a/crates/filterer/tagged/tests/helpers/mod.rs +++ b/crates/filterer/tagged/tests/helpers/mod.rs @@ -9,10 +9,9 @@ use std::{ use ignore_files::{IgnoreFile, IgnoreFilter}; use project_origins::ProjectType; use tokio::fs::canonicalize; -use watchexec::{ - error::RuntimeError, - event::{filekind::FileEventKind, Event, FileType, Priority, ProcessEnd, Source, Tag}, - filter::Filterer, +use watchexec::{error::RuntimeError, filter::Filterer}; +use watchexec_events::{ + filekind::FileEventKind, Event, FileType, Priority, ProcessEnd, Source, Tag, }; use watchexec_filterer_ignore::IgnoreFilterer; use watchexec_filterer_tagged::{Filter, FilterFile, Matcher, Op, Pattern, TaggedFilterer}; @@ -26,7 +25,7 @@ pub mod tagged { pub use super::PathHarness; pub use super::TaggedHarness; pub use super::{filter, glob_filter, notglob_filter}; - pub use watchexec::event::Priority; + pub use watchexec_events::Priority; } pub mod tagged_ff { diff --git a/crates/filterer/tagged/tests/non_paths.rs b/crates/filterer/tagged/tests/non_paths.rs index c1eaabe..f113d11 100644 --- a/crates/filterer/tagged/tests/non_paths.rs +++ b/crates/filterer/tagged/tests/non_paths.rs @@ -1,6 +1,6 @@ use std::num::{NonZeroI32, NonZeroI64}; -use watchexec::event::{filekind::*, ProcessEnd, Source}; +use watchexec_events::{filekind::*, ProcessEnd, Source}; use watchexec_filterer_tagged::TaggedFilterer; use watchexec_signals::Signal; diff --git a/crates/ignore-files/Cargo.toml b/crates/ignore-files/Cargo.toml index 2c1d943..860b2aa 100644 --- a/crates/ignore-files/Cargo.toml +++ b/crates/ignore-files/Cargo.toml @@ -15,16 +15,23 @@ rust-version = "1.58.0" edition = "2021" [dependencies] -futures = "0.3.21" -gix-config = "0.25.1" +futures = "0.3.29" +gix-config = "0.31.0" ignore = "0.4.18" miette = "5.3.0" -thiserror = "1.0.31" -tokio = { version = "1.24.2", default-features = false, features = ["fs", "macros", "rt"] } -tracing = "0.1.35" +thiserror = "1.0.50" +tracing = "0.1.40" radix_trie = "0.2.1" dunce = "1.0.4" +[dependencies.tokio] +version = "1.33.0" +default-features = false +features = [ + "fs", + "macros", + "rt", +] [dependencies.project-origins] version = "1.2.0" diff --git a/crates/ignore-files/src/discover.rs b/crates/ignore-files/src/discover.rs index 77436da..99edee8 100644 --- a/crates/ignore-files/src/discover.rs +++ b/crates/ignore-files/src/discover.rs @@ -51,7 +51,7 @@ pub async fn from_origin(path: impl AsRef + Send) -> (Vec, Vec match find_file(base.join(".git/config")).await { Err(err) => errors.push(err), Ok(None) => {} - Ok(Some(path)) => match path.parent().map(File::from_git_dir) { + Ok(Some(path)) => match path.parent().map(|path| File::from_git_dir(path.into())) { None => errors.push(Error::new( ErrorKind::Other, "unreachable: .git/config must have a parent", diff --git a/crates/ignore-files/src/error.rs b/crates/ignore-files/src/error.rs index 4291505..7215c2e 100644 --- a/crates/ignore-files/src/error.rs +++ b/crates/ignore-files/src/error.rs @@ -10,7 +10,6 @@ pub enum Error { /// /// [`IgnoreFile`]: crate::IgnoreFile #[error("cannot read ignore '{file}': {err}")] - #[diagnostic(code(ignore_file::read))] Read { /// The path to the erroring ignore file. file: PathBuf, @@ -22,7 +21,6 @@ pub enum Error { /// Error received when parsing a glob fails. #[error("cannot parse glob from ignore '{file:?}': {err}")] - #[diagnostic(code(ignore_file::glob))] Glob { /// The path to the erroring ignore file. file: Option, @@ -35,7 +33,6 @@ pub enum Error { /// Multiple related [`Error`](enum@Error)s. #[error("multiple: {0:?}")] - #[diagnostic(code(ignore_file::set))] Multi(#[related] Vec), /// Error received when trying to canonicalize a path diff --git a/crates/lib/CHANGELOG.md b/crates/lib/CHANGELOG.md index f31ae97..be1e4be 100644 --- a/crates/lib/CHANGELOG.md +++ b/crates/lib/CHANGELOG.md @@ -2,6 +2,148 @@ ## Next (YYYY-MM-DD) +### General + +- Crate is more oriented around `Watchexec` the core experience rather than providing the kitchensink / components so you could build your own from the pieces; that helps the cohesion of the whole and simplifies many patterns. +- Deprecated items (mostly leftover from splitting out the `watchexec_events` and `watchexec_signals` crates) are removed. +- Watchexec can now supervise multiple commands at once. See [Action](#Action) below, the [Action docs](https://docs.rs/watchexec/latest/watchexec/action/struct.Action.html), and the [Supervisor docs](https://docs.rs/watchexec-supervisor) for more. +- Because of this new feature, the one where multiple commands could be set under the one supervisor is removed. +- Watchexec's supervisor was split up into its own crate, [`watchexec-supervisor`](https://docs.rs/watchexec-supervisor). +- Running as PID1 (e.g. in Docker) is now fully handled, with support from the [`pid1`](https://www.fpcomplete.com/blog/announcing-pid1-crate-for-easier-rust-docker-images/) crate. +- Tokio requirement is now 1.33. +- Notify was upgraded to 6.0. +- Nix was upgraded to 0.27. + +### `Watchexec` + +- `Watchexec::new()` now takes the `on_action` handler. As this is the most important handler to define and Watchexec will not be functional without one, that enforces providing it first. +- `Watchexec::with_config()` lets one provide a config upfront, otherwise the default values are used. +- `Watchexec::default()` is mostly used to avoid boilerplate in doc comment examples, and panics on initialisation errors. +- `Watchexec::reconfigure()` is removed. Use the public `config` field instead to access the "live" `Arc` (see below). +- Completion events aren't emitted anymore. They still exist in the Event enum, but they're not generated by Watchexec itself. Use `Job#to_wait` instead. Of course you can insert them as synthetic events if you want. + +### Config + +- `InitConfig` and `RuntimeConfig` have been unified into a single `Config` struct. +- Instead of module-specific `WorkingData` structures, all of the config is now flat in the same `Config`. That makes it easier to work with as all that's needed is to pass an `Arc` around, but it does mean the event sources are no longer independent. +- Instead of using `tokio::sync::watch` for some values, and `HandlerLock` for handlers, and so on, everything is now a new `Changeable` type, specialised to `ChangeableFn` for closures and `ChangeableFilterer` for the Filterer. +- There's now a `signal_change()` method which must be called after changes to the config; this is taken care of when using the methods on `Config`. This is required for the few places in Watchexec which need active reconfiguration rather than reading config values just-in-time. +- The above means that instead of using `Watchexec::reconfigure()` and keeping a clone of the config around, an `Arc` is now "live" and changes applied to it will affect the Watchexec instance directly. +- `command` / `commands` are removed from config. Instead use the Action handler API for creating new supervised commands. +- `command_grouped` is removed from config. That's now an option set on `Command`. +- `action_throttle` is renamed to `throttle` and now defaults to `50ms`, which is the default in Watchexec CLI. +- `keyboard_emit_eof` is renamed to `keyboard_events`. +- `pre_spawn_handler` is removed. Use `Job#set_spawn_hook` instead. +- `post_spawn_handler` is removed. Use `Job#run` instead. + +### Command + +The structure has been reworked to be simpler and more extensible. Instead of a Command _enum_, there's now a Command _struct_, which holds a single `Program` and behaviour-altering options. `Shell` has also been redone, with less special-casing. + +If you had: + +```rust +Command::Exec { + prog: "date".into(), + args: vec!["+%s".into()], +} +``` + +You should now write: + +```rust +Command { + program: Program::Exec { + prog: "date".into(), + args: vec!["+%s".into()], + }, + options: Default::default(), +} +``` + +The new `Program::Shell` field `args: Vec` lets you pass (trailing) arguments to the shell invocation: + +```rust +Program::Shell { + shell: Shell::new("sh"), + command: "ls".into(), + args: vec!["--".into(), "movies".into()], +} +``` + +is equivalent to: + +```console +$ sh -c "ls" -- movies +``` + +- The old `args` field of `Command::Shell` is now the `options` field of `Shell`. +- `Shell` has a new field `program_option: Option>` which is the syntax of the option used to provide the command. Ie for most shells it's `-c` and for `CMD.EXE` it's `/C`; this makes it fully customisable (including its absence!) if you want to use weird shells or non-shell programs as shells. +- The special-cased `Shell::Powershell` is removed. +- On Windows, arguments are specified with [`raw_arg`](https://doc.rust-lang.org/stable/std/os/windows/process/trait.CommandExt.html#tymethod.raw_arg) instead of `arg` to avoid quoting issues. +- `Command` can no longer take a list of programs. That was always quite a hack; now that multiple supervised commands are possible, that's how multiple programs should be handled. +- The top-level Watchexec `command_grouped` option is now Command-level, so you can start both grouped and non-grouped programs. +- There's a new `reset_sigmask` option to control whether commands should have their signal masks reset on Unix. By default the signal mask is inherited. + +### Errors + +- `RuntimeError::NoCommands`, `RuntimeError::Handler`, `RuntimeError::HandlerLockHeld`, and `CriticalError::MissingHandler` are removed as the relevant types/structures don't exist anymore. +- `RuntimeError::CommandShellEmptyCommand` and `RuntimeError::CommandShellEmptyShell` are removed; you can construct `Shell` with empty shell program and `Program::Shell` with an empty command, these will at best do nothing but they won't error early through Watchexec. +- `RuntimeError::ClearScreen` is removed, as clearing the screen is now done by the consumer of Watchexec, not Watchexec itself. +- Watchexec will now panic if locks are poisoned; we can't recover from that. +- The filesystem watcher's "too many files", "too many handles", and other initialisation errors are removed as `RuntimeErrors`, and are now `CriticalErrors`. These being runtime, nominally recoverable errors instead of end-the-world failures is one of the most common pitfalls of using the library, and though recovery _is_ technically possible, it's better approached other ways. +- The `on_error` handler is now sync only and no longer returns a `Result`; as such there's no longer the weird logic of "if the `on_error` handler errors, it will call itself on the error once, then crash". +- If you were doing async work in `on_error`, you should instead use non-async calls (like `try_send()` for Tokio channels). The error handler is expected to return as fast as possible, and _not_ do blocking work if it can at all avoid it; this was always the case but is now documented more explicitly. +- Error diagnostic codes are removed. + +### Action + +The process supervision system is entirely reworked. Instead of "applying `Outcome`s", there's now a `Job` type which is a single supervised command, provided by the separate [`watchexec-supervisor`](https://docs.rs/watchexec-supervisor) crate. The Action handler itself can only create new jobs and list existing ones, and interaction with commands is done through the `Job` type. + +The controls available on `Job` are now modeled on "real" supervisors like systemd, and are both more and less powerful than the old `Outcome` system. This can be seen clearly in how a "restart" is specified. Previously, this was an `Outcome` combinator: + +```rust +Outcome::if_running( + Outcome::both(Outcome::stop(), Outcome::start()), + Outcome::start(), +) +``` + +Now, it's a discrete method: + +```rust +job.restart(); +``` + +Previously, a graceful stop was a mess: + +```rust +Outcome::if_running( + Outcome::both( + Outcome::both( + Outcome::signal(Signal::Terminate), + Outcome::wait_timeout(Duration::from_secs(30)), + ), + Outcome::both(Outcome::stop(), Outcome::start()), + ), + Outcome::DoNothing, +) +``` + +Now, it's again a discrete method: + +```rust +job.stop_with_signal(Signal::Terminate, Duration::from_secs(30)); +``` + +The `stop()` and `start()` methods also do nothing if the process is already stopped or started, respectively, so you don't need to check the status of the job before calling them. The `try_restart()` method is available to do a restart only if the job is running, with the `try_restart_with_signal()` variant for graceful restarts. + +Further, all of these methods are non-blocking sync (and take `&self`), but they return a `Ticket`, a future which resolves when the control has been processed. That can be dropped if you don't care about it without affecting the job, or used to perform more advanced flow control. The special `to_wait()` method returns a detached, cloneable, "wait()" future, which will resolve when the process exits, without needing to hold on to the `Job` or a reference at all. + +See the [`restart_run_on_successful_build` example](./examples/restart_run_on_successful_build.rs) which starts a `cargo build`, waits for it to end, and then (re)starts `cargo run` if the build exited successfully. + +Finally: `Outcome::Clear` and `Outcome::Reset` are gone, and there's no equivalent on `Job`: that's because these are screen control actions, not job control. You should use the [clearscreen](https://docs.rs/clearscreen) crate directly in your action handler, in conjunction with job control, to achieve the desired effect. + ## v2.3.0 (2023-03-22) - New: `Outcome::Race` and `Outcome::race()` ([#548](https://github.com/watchexec/watchexec/pull/548)) diff --git a/crates/lib/Cargo.toml b/crates/lib/Cargo.toml index 056aded..146fd32 100644 --- a/crates/lib/Cargo.toml +++ b/crates/lib/Cargo.toml @@ -17,17 +17,17 @@ edition = "2021" [dependencies] async-priority-channel = "0.1.0" -async-recursion = "1.0.0" +async-recursion = "1.0.5" atomic-take = "1.0.0" -clearscreen = "2.0.1" -futures = "0.3.16" +futures = "0.3.29" miette = "5.3.0" +notify = "6.0.0" once_cell = "1.8.0" -thiserror = "1.0.26" +thiserror = "1.0.44" normalize-path = "0.2.0" [dependencies.command-group] -version = "2.1.0" +version = "5.0.1" features = ["with-tokio"] [dependencies.watchexec-events] @@ -38,19 +38,20 @@ path = "../events" version = "1.0.0" path = "../signals" +[dependencies.watchexec-supervisor] +version = "0.1.0" +path = "../supervisor" + [dependencies.ignore-files] version = "1.3.1" path = "../ignore-files" -[dependencies.notify] -version = "5.0.0" - [dependencies.project-origins] version = "1.2.0" path = "../project-origins" [dependencies.tokio] -version = "1.24.2" +version = "1.33.0" features = [ "fs", "io-std", @@ -62,12 +63,13 @@ features = [ ] [dependencies.tracing] -version = "0.1.26" +version = "0.1.40" features = ["log"] [target.'cfg(unix)'.dependencies.nix] -version = "0.26.2" +version = "0.27.1" features = ["signal"] -[dev-dependencies] -tracing-subscriber = "0.3.6" +[dev-dependencies.tracing-subscriber] +version = "0.3.6" +features = ["env-filter"] diff --git a/crates/lib/README.md b/crates/lib/README.md index a45f3c9..08e8125 100644 --- a/crates/lib/README.md +++ b/crates/lib/README.md @@ -15,97 +15,178 @@ _The library which powers [Watchexec CLI](https://watchexec.github.io) and other [license]: ../../LICENSE -## Quick start +## Examples + +Here's a complete example showing some of the library's features: ```rust ,no_run use miette::{IntoDiagnostic, Result}; -use watchexec::{ - Watchexec, - action::{Action, Outcome}, - config::{InitConfig, RuntimeConfig}, - handler::{Handler as _, PrintDebug}, +use std::{ + sync::{Arc, Mutex}, + time::Duration, }; +use watchexec::{ + command::{Command, Program, Shell}, + job::CommandState, + Watchexec, +}; +use watchexec_events::{Event, Priority}; +use watchexec_signals::Signal; #[tokio::main] async fn main() -> Result<()> { - let mut init = InitConfig::default(); - init.on_error(PrintDebug(std::io::stderr())); + // this is okay to start with, but Watchexec logs a LOT of data, + // even at error level. you will quickly want to filter it down. + tracing_subscriber::fmt() + .with_env_filter(tracing_subscriber::EnvFilter::from_default_env()) + .init(); - let mut runtime = RuntimeConfig::default(); - runtime.pathset(["watchexec.conf"]); + // initialise Watchexec with a simple initial action handler + let job = Arc::new(Mutex::new(None)); + let wx = Watchexec::new({ + let outerjob = job.clone(); + move |mut action| { + let (_, job) = action.create_job(Arc::new(Command { + program: Program::Shell { + shell: Shell::new("bash"), + command: " + echo 'Hello world' + trap 'echo Not quitting yet!' TERM + read + " + .into(), + args: Vec::new(), + }, + options: Default::default(), + })); - let conf = YourConfigFormat::load_from_file("watchexec.conf").await.into_diagnostic()?; - conf.apply(&mut runtime); + // store the job outside this closure too + *outerjob.lock().unwrap() = Some(job.clone()); - let we = Watchexec::new(init, runtime.clone())?; - let w = we.clone(); - - let c = runtime.clone(); - runtime.on_action(move |action: Action| { - let mut c = c.clone(); - let w = w.clone(); - async move { - for event in action.events.iter() { - if event.paths().any(|(p, _)| p.ends_with("/watchexec.conf")) { - let conf = YourConfigFormat::load_from_file("watchexec.conf").await?; - - conf.apply(&mut c); - let _ = w.reconfigure(c.clone()); - // tada! self-reconfiguring watchexec on config file change! - - break; + // block SIGINT + #[cfg(unix)] + job.set_spawn_hook(|cmd, _| { + use nix::sys::signal::{sigprocmask, SigSet, SigmaskHow, Signal}; + unsafe { + cmd.pre_exec(|| { + let mut newset = SigSet::empty(); + newset.add(Signal::SIGINT); + sigprocmask(SigmaskHow::SIG_BLOCK, Some(&newset), None)?; + Ok(()) + }); } - } + }); - action.outcome(Outcome::if_running( - Outcome::DoNothing, - Outcome::both(Outcome::Clear, Outcome::Start), - )); + // start the command + job.start(); - Ok(()) + action + } + })?; - // (not normally required! ignore this when implementing) - as std::result::Result<_, MietteStub> + // start the engine + let main = wx.main(); + + // send an event to start + wx.send_event(Event::default(), Priority::Urgent) + .await + .unwrap(); + // ^ this will cause the action handler we've defined above to run, + // creating and starting our little bash program, and storing it in the mutex + + // spin until we've got the job + while job.lock().unwrap().is_none() { + tokio::task::yield_now().await; + } + + // watch the job and restart it when it exits + let job = job.lock().unwrap().clone().unwrap(); + let auto_restart = tokio::spawn(async move { + loop { + job.to_wait().await; + job.run(|context| { + if let CommandState::Finished { + status, + started, + finished, + } = context.current + { + let duration = *finished - *started; + eprintln!("[Program stopped with {status:?}; ran for {duration:?}]") + } + }) + .await; + + eprintln!("[Restarting...]"); + job.start().await; } }); - we.reconfigure(runtime); - we.main().await.into_diagnostic()?; + // now we change what the action does: + let auto_restart_abort = auto_restart.abort_handle(); + wx.config.on_action(move |mut action| { + // if we get Ctrl-C on the Watchexec instance, we quit + if action.signals().any(|sig| sig == Signal::Interrupt) { + eprintln!("[Quitting...]"); + auto_restart_abort.abort(); + action.quit_gracefully(Signal::ForceStop, Duration::ZERO); + return action; + } + + // if the action was triggered by file events, gracefully stop the program + if action.paths().next().is_some() { + // watchexec can manage ("supervise") more than one program; + // here we only have one but we don't know its Id so we grab it out of the iterator + if let Some(job) = action.list_jobs().next().map(|(_, job)| job.clone()) { + eprintln!("[Asking program to stop...]"); + job.stop_with_signal(Signal::Terminate, Duration::from_secs(5)); + } + } + + action + }); + + // and watch all files in the current directory: + wx.config.pathset(["."]); + + // then keep running until Watchexec quits! + let _ = main.await.into_diagnostic()?; + auto_restart.abort(); Ok(()) } - -// ignore this! it's stuff to make the above code get checked by cargo doc tests! -struct YourConfigFormat; impl YourConfigFormat { async fn load_from_file(_: &str) -> std::result::Result { Ok(Self) } fn apply(&self, _: &mut RuntimeConfig) {} } use miette::Diagnostic; use thiserror::Error; #[derive(Debug, Error, Diagnostic)] #[error("stub")] struct MietteStub; ``` +Other examples: +- [Only Commands](./examples/only_commands.rs): skip watching files, only use the supervisor. +- [Only Events](./examples/only_events.rs): never start any processes, only print events. +- [Restart `cargo run` only when `cargo build` succeeds](./examples/restart_run_on_successful_build.rs) + ## Kitchen sink -The library also exposes a number of components which are available to make your own tool, or to -make anything else you may want: +Though not its primary usecase, the library exposes most of its relatively standalone components, +available to make other tools that are not Watchexec-shaped: -- **[Command handling](https://docs.rs/watchexec/2/watchexec/command/index.html)**, to - build a command with an arbitrary shell, deal with grouped and ungrouped processes the same way, - and supervise a process while also listening for & acting on interventions such as sending signals. +- **Event sources**: [Filesystem](https://docs.rs/watchexec/3/watchexec/sources/fs/index.html), + [Signals](https://docs.rs/watchexec/3/watchexec/sources/signal/index.html), + [Keyboard](https://docs.rs/watchexec/3/watchexec/sources/keyboard/index.html). -- **Event sources**: [Filesystem](https://docs.rs/watchexec/2/watchexec/fs/index.html), - [Signals](https://docs.rs/watchexec/2/watchexec/signal/index.html), - [Keyboard](https://docs.rs/watchexec/2/watchexec/keyboard/index.html), - (more to come). - -- Finding **[a common prefix](https://docs.rs/watchexec/2/watchexec/paths/fn.common_prefix.html)** +- Finding **[a common prefix](https://docs.rs/watchexec/3/watchexec/paths/fn.common_prefix.html)** of a set of paths. +- A **[Changeable](https://docs.rs/watchexec/3/watchexec/changeable/index.html)** type, which + powers the "live" configuration system. + - And [more][docs]! Filterers are split into their own crates, so they can be evolved independently: - The **[Globset](https://docs.rs/watchexec-filterer-globset) filterer** implements the default - Watchexec filter, and mimics the pre-1.18 behaviour as much as possible. + Watchexec CLI filtering, based on the regex crate's ignore mechanisms. -- The **[Tagged](https://docs.rs/watchexec-filterer-tagged) filterer** is an experiment in creating - a more powerful filtering solution, which can operate on every part of events, not just their - paths. +- ~~The **[Tagged](https://docs.rs/watchexec-filterer-tagged) filterer**~~ was an experiment in + creating a more powerful filtering solution, which could operate on every part of events, not + just their paths, using a custom syntax. It is no longer maintained. - The **[Ignore](https://docs.rs/watchexec-filterer-ignore) filterer** implements ignore-file semantics, and especially supports _trees_ of ignore files. It is used as a subfilterer in both @@ -113,6 +194,9 @@ Filterers are split into their own crates, so they can be evolved independently: There are also separate, standalone crates used to build Watchexec which you can tap into: +- **[Supervisor](https://docs.rs/watchexec-supervisor)** is Watchexec's process supervisor and + command abstraction. + - **[ClearScreen](https://docs.rs/clearscreen)** makes clearing the terminal screen in a cross-platform way easy by default, and provides advanced options to fit your usecase. diff --git a/crates/lib/examples/demo.rs b/crates/lib/examples/demo.rs deleted file mode 100644 index 851b7d6..0000000 --- a/crates/lib/examples/demo.rs +++ /dev/null @@ -1,74 +0,0 @@ -use std::time::Duration; - -use miette::{IntoDiagnostic, Result}; -use watchexec::{ - action::{Action, Outcome}, - command::Command, - config::{InitConfig, RuntimeConfig}, - error::ReconfigError, - event::Event, - fs::Watcher, - ErrorHook, Watchexec, -}; -use watchexec_signals::Signal; - -// Run with: `env RUST_LOG=debug cargo run --example print_out` -#[tokio::main] -async fn main() -> Result<()> { - tracing_subscriber::fmt::init(); - - let mut init = InitConfig::default(); - init.on_error(|err: ErrorHook| async move { - eprintln!("Watchexec Runtime Error: {}", err.error); - Ok::<(), std::convert::Infallible>(()) - }); - - let mut runtime = RuntimeConfig::default(); - runtime.pathset(["src", "dontexist", "examples"]); - runtime.command(Command::Exec { - prog: "date".into(), - args: Vec::new(), - }); - - let wx = Watchexec::new(init, runtime.clone())?; - let w = wx.clone(); - - let config = runtime.clone(); - runtime.on_action(move |action: Action| { - let mut config = config.clone(); - let w = w.clone(); - async move { - eprintln!("Watchexec Action: {action:?}"); - - let sigs = action - .events - .iter() - .flat_map(Event::signals) - .collect::>(); - - if sigs.iter().any(|sig| sig == &Signal::Interrupt) { - action.outcome(Outcome::Exit); - } else if sigs.iter().any(|sig| sig == &Signal::User1) { - eprintln!("Switching to native for funsies"); - config.file_watcher(Watcher::Native); - w.reconfigure(config)?; - } else if sigs.iter().any(|sig| sig == &Signal::User2) { - eprintln!("Switching to polling for funsies"); - config.file_watcher(Watcher::Poll(Duration::from_millis(50))); - w.reconfigure(config)?; - } else if action.events.iter().flat_map(Event::paths).next().is_some() { - action.outcome(Outcome::if_running( - Outcome::both(Outcome::Stop, Outcome::Start), - Outcome::Start, - )); - } - - Ok::<(), ReconfigError>(()) - } - }); - - wx.reconfigure(runtime)?; - wx.main().await.into_diagnostic()??; - - Ok(()) -} diff --git a/crates/lib/examples/fs.rs b/crates/lib/examples/fs.rs deleted file mode 100644 index aa6339d..0000000 --- a/crates/lib/examples/fs.rs +++ /dev/null @@ -1,52 +0,0 @@ -use std::time::Duration; - -use async_priority_channel as priority; -use miette::{IntoDiagnostic, Result}; -use tokio::{ - sync::{mpsc, watch}, - time::sleep, -}; -use watchexec::{ - event::{Event, Priority}, - fs, -}; - -// Run with: `env RUST_LOG=debug cargo run --example fs`, -// then touch some files within the first 15 seconds, and afterwards. -#[tokio::main] -async fn main() -> Result<()> { - tracing_subscriber::fmt::init(); - - let (ev_s, ev_r) = priority::bounded::(1024); - let (er_s, mut er_r) = mpsc::channel(64); - let (wd_s, wd_r) = watch::channel(fs::WorkingData::default()); - - let mut wkd = fs::WorkingData::default(); - wkd.pathset = vec![".".into()]; - wd_s.send(wkd.clone()).into_diagnostic()?; - - tokio::spawn(async move { - while let Ok((event, priority)) = ev_r.recv().await { - tracing::info!("event ({priority:?}): {event:?}"); - } - }); - - tokio::spawn(async move { - while let Some(error) = er_r.recv().await { - tracing::error!("error: {error}"); - } - }); - - let wd_sh = tokio::spawn(async move { - sleep(Duration::from_secs(15)).await; - wkd.pathset = Vec::new(); - tracing::info!("turning off fs watcher without stopping it"); - wd_s.send(wkd).unwrap(); - wd_s - }); - - fs::worker(wd_r, er_s, ev_s).await?; - wd_sh.await.into_diagnostic()?; - - Ok(()) -} diff --git a/crates/lib/examples/only_commands.rs b/crates/lib/examples/only_commands.rs new file mode 100644 index 0000000..bc6bd10 --- /dev/null +++ b/crates/lib/examples/only_commands.rs @@ -0,0 +1,55 @@ +use std::{ + sync::Arc, + time::{Duration, Instant}, +}; + +use miette::{IntoDiagnostic, Result}; +use tokio::time::sleep; +use watchexec::{ + command::{Command, Program}, + Watchexec, +}; +use watchexec_events::{Event, Priority}; + +#[tokio::main] +async fn main() -> Result<()> { + let wx = Watchexec::new(|mut action| { + // you don't HAVE to respond to filesystem events: + // here, we start a command every five seconds, unless we get a signal and quit + + if action.signals().next().is_some() { + eprintln!("[Quitting...]"); + action.quit(); + } else { + let (_, job) = action.create_job(Arc::new(Command { + program: Program::Exec { + prog: "echo".into(), + args: vec![ + "Hello world!".into(), + format!("Current time: {:?}", Instant::now()), + "Press Ctrl+C to quit".into(), + ], + }, + options: Default::default(), + })); + job.start(); + } + + action + })?; + + tokio::spawn({ + let wx = wx.clone(); + async move { + loop { + sleep(Duration::from_secs(5)).await; + wx.send_event(Event::default(), Priority::Urgent) + .await + .unwrap(); + } + } + }); + + let _ = wx.main().await.into_diagnostic()?; + Ok(()) +} diff --git a/crates/lib/examples/only_events.rs b/crates/lib/examples/only_events.rs new file mode 100644 index 0000000..29b4c8f --- /dev/null +++ b/crates/lib/examples/only_events.rs @@ -0,0 +1,30 @@ +use miette::{IntoDiagnostic, Result}; +use watchexec::Watchexec; + +#[tokio::main] +async fn main() -> Result<()> { + let wx = Watchexec::new(|mut action| { + // you don't HAVE to spawn jobs: + // here, we just print out the events as they come in + for event in action.events.iter() { + eprintln!("{event:?}"); + } + + // quit when we get a signal + if action.signals().next().is_some() { + eprintln!("[Quitting...]"); + action.quit(); + } + + action + })?; + + // start the engine + let main = wx.main(); + + // and watch all files in the current directory: + wx.config.pathset(["."]); + + let _ = main.await.into_diagnostic()?; + Ok(()) +} diff --git a/crates/lib/examples/readme.rs b/crates/lib/examples/readme.rs index 220e09f..31e68b0 100644 --- a/crates/lib/examples/readme.rs +++ b/crates/lib/examples/readme.rs @@ -1,64 +1,138 @@ +use std::{ + sync::{Arc, Mutex}, + time::Duration, +}; + use miette::{IntoDiagnostic, Result}; use watchexec::{ - action::{Action, Outcome}, - config::{InitConfig, RuntimeConfig}, - handler::PrintDebug, + command::{Command, Program, Shell}, + job::CommandState, Watchexec, }; +use watchexec_events::{Event, Priority}; +use watchexec_signals::Signal; #[tokio::main] async fn main() -> Result<()> { - let mut init = InitConfig::default(); - init.on_error(PrintDebug(std::io::stderr())); + // this is okay to start with, but Watchexec logs a LOT of data, + // even at error level. you will quickly want to filter it down. + tracing_subscriber::fmt() + .with_env_filter(tracing_subscriber::EnvFilter::from_default_env()) + .init(); - let mut runtime = RuntimeConfig::default(); - runtime.pathset(["watchexec.conf"]); + // initialise Watchexec with a simple initial action handler + let job = Arc::new(Mutex::new(None)); + let wx = Watchexec::new({ + let outerjob = job.clone(); + move |mut action| { + let (_, job) = action.create_job(Arc::new(Command { + program: Program::Shell { + shell: Shell::new("bash"), + command: " + echo 'Hello world' + trap 'echo Not quitting yet!' TERM + read + " + .into(), + args: Vec::new(), + }, + options: Default::default(), + })); - let conf = YourConfigFormat::load_from_file("watchexec.conf") - .await - .into_diagnostic()?; - conf.apply(&mut runtime); + // store the job outside this closure too + *outerjob.lock().unwrap() = Some(job.clone()); - let we = Watchexec::new(init, runtime.clone())?; - let w = we.clone(); - - let c = runtime.clone(); - runtime.on_action(move |action: Action| { - let mut c = c.clone(); - let w = w.clone(); - async move { - for event in action.events.iter() { - if event.paths().any(|(p, _)| p.ends_with("/watchexec.conf")) { - let conf = YourConfigFormat::load_from_file("watchexec.conf").await?; - - conf.apply(&mut c); - let _ = w.reconfigure(c.clone()); - // tada! self-reconfiguring watchexec on config file change! - - break; + // block SIGINT + #[cfg(unix)] + job.set_spawn_hook(|cmd, _| { + use nix::sys::signal::{sigprocmask, SigSet, SigmaskHow, Signal}; + unsafe { + cmd.pre_exec(|| { + let mut newset = SigSet::empty(); + newset.add(Signal::SIGINT); + sigprocmask(SigmaskHow::SIG_BLOCK, Some(&newset), None)?; + Ok(()) + }); } - } + }); - action.outcome(Outcome::if_running( - Outcome::DoNothing, - Outcome::both(Outcome::Clear, Outcome::Start), - )); + // start the command + job.start(); - Ok::<(), std::io::Error>(()) + action + } + })?; + + // start the engine + let main = wx.main(); + + // send an event to start + wx.send_event(Event::default(), Priority::Urgent) + .await + .unwrap(); + // ^ this will cause the action handler we've defined above to run, + // creating and starting our little bash program, and storing it in the mutex + + // spin until we've got the job + while job.lock().unwrap().is_none() { + tokio::task::yield_now().await; + } + + // watch the job and restart it when it exits + let job = job.lock().unwrap().clone().unwrap(); + let auto_restart = tokio::spawn(async move { + loop { + job.to_wait().await; + job.run(|context| { + if let CommandState::Finished { + status, + started, + finished, + } = context.current + { + let duration = *finished - *started; + eprintln!("[Program stopped with {status:?}; ran for {duration:?}]") + } + }) + .await; + + eprintln!("[Restarting...]"); + job.start().await; } }); - let _ = we.main().await.into_diagnostic()?; + // now we change what the action does: + let auto_restart_abort = auto_restart.abort_handle(); + wx.config.on_action(move |mut action| { + // if we get Ctrl-C on the Watchexec instance, we quit + if action.signals().any(|sig| sig == Signal::Interrupt) { + eprintln!("[Quitting...]"); + auto_restart_abort.abort(); + action.quit_gracefully(Signal::ForceStop, Duration::ZERO); + return action; + } + + // if the action was triggered by file events, gracefully stop the program + if action.paths().next().is_some() { + // watchexec can manage ("supervise") more than one program; + // here we only have one but we don't know its Id so we grab it out of the iterator + if let Some(job) = action.list_jobs().next().map(|(_, job)| job.clone()) { + eprintln!("[Asking program to stop...]"); + job.stop_with_signal(Signal::Terminate, Duration::from_secs(5)); + } + + // we could also use `action.get_or_create_job` initially and store its Id to use here, + // see the CHANGELOG.md for an example under "3.0.0 > Action". + } + + action + }); + + // and watch all files in the current directory: + wx.config.pathset(["."]); + + // then keep running until Watchexec quits! + let _ = main.await.into_diagnostic()?; + auto_restart.abort(); Ok(()) } - -struct YourConfigFormat; -impl YourConfigFormat { - async fn load_from_file(_path: impl AsRef) -> std::io::Result { - Ok(Self) - } - - fn apply(&self, _config: &mut RuntimeConfig) { - // ... - } -} diff --git a/crates/lib/examples/restart_run_on_successful_build.rs b/crates/lib/examples/restart_run_on_successful_build.rs new file mode 100644 index 0000000..02ac951 --- /dev/null +++ b/crates/lib/examples/restart_run_on_successful_build.rs @@ -0,0 +1,84 @@ +use std::sync::Arc; + +use miette::{IntoDiagnostic, Result}; +use watchexec::{ + command::{Command, Program, SpawnOptions}, + job::CommandState, + Id, Watchexec, +}; +use watchexec_events::{Event, Priority, ProcessEnd}; +use watchexec_signals::Signal; + +#[tokio::main] +async fn main() -> Result<()> { + let build_id = Id::default(); + let run_id = Id::default(); + let wx = Watchexec::new_async(move |mut action| { + Box::new(async move { + if action.signals().any(|sig| sig == Signal::Interrupt) { + eprintln!("[Quitting...]"); + action.quit(); + return action; + } + + let build = action.get_or_create_job(build_id, || { + Arc::new(Command { + program: Program::Exec { + prog: "cargo".into(), + args: vec!["build".into()], + }, + options: Default::default(), + }) + }); + + let run = action.get_or_create_job(run_id, || { + Arc::new(Command { + program: Program::Exec { + prog: "cargo".into(), + args: vec!["run".into()], + }, + options: SpawnOptions { + grouped: true, + ..Default::default() + }, + }) + }); + + if action.paths().next().is_some() + || action.events.iter().any(|event| event.tags.is_empty()) + { + build.restart().await; + } + + build.to_wait().await; + build + .run(move |context| { + if let CommandState::Finished { + status: ProcessEnd::Success, + .. + } = context.current + { + run.restart(); + } + }) + .await; + + action + }) + })?; + + // start the engine + let main = wx.main(); + + // send an event to start + wx.send_event(Event::default(), Priority::Urgent) + .await + .unwrap(); + + // and watch all files in cli src + wx.config.pathset(["crates/cli/src"]); + + // then keep running until Watchexec quits! + let _ = main.await.into_diagnostic()?; + Ok(()) +} diff --git a/crates/lib/examples/signal.rs b/crates/lib/examples/signal.rs deleted file mode 100644 index 7b729af..0000000 --- a/crates/lib/examples/signal.rs +++ /dev/null @@ -1,42 +0,0 @@ -use std::process::exit; - -use async_priority_channel as priority; -use miette::Result; -use tokio::sync::mpsc; -use watchexec::{ - event::{Event, Priority, Tag}, - signal, -}; -use watchexec_signals::Signal; - -// Run with: `env RUST_LOG=debug cargo run --example signal`, -// then issue some signals to the printed PID, or hit e.g. Ctrl-C. -// Send a SIGTERM (unix) or Ctrl-Break (windows) to exit. -#[tokio::main] -async fn main() -> Result<()> { - tracing_subscriber::fmt::init(); - - let (ev_s, ev_r) = priority::bounded::(1024); - let (er_s, mut er_r) = mpsc::channel(64); - - tokio::spawn(async move { - while let Ok((event, priority)) = ev_r.recv().await { - tracing::info!("event {priority:?}: {event:?}"); - - if event.tags.contains(&Tag::Signal(Signal::Terminate)) { - exit(0); - } - } - }); - - tokio::spawn(async move { - while let Some(error) = er_r.recv().await { - tracing::error!("error: {error}"); - } - }); - - tracing::info!("PID is {}", std::process::id()); - signal::worker(er_s.clone(), ev_s.clone()).await?; - - Ok(()) -} diff --git a/crates/lib/src/action.rs b/crates/lib/src/action.rs index b5000b3..4264d1c 100644 --- a/crates/lib/src/action.rs +++ b/crates/lib/src/action.rs @@ -1,14 +1,15 @@ //! Processor responsible for receiving events, filtering them, and scheduling actions in response. #[doc(inline)] -pub use outcome::Outcome; +pub use handler::Handler as ActionHandler; +#[doc(inline)] +pub use quit::QuitManner; +#[doc(inline)] +pub use r#return::ActionReturn; #[doc(inline)] pub use worker::worker; -#[doc(inline)] -pub use workingdata::*; -mod outcome; -mod outcome_worker; -mod process_holder; +mod handler; +mod quit; +mod r#return; mod worker; -mod workingdata; diff --git a/crates/lib/src/action/handler.rs b/crates/lib/src/action/handler.rs new file mode 100644 index 0000000..db81872 --- /dev/null +++ b/crates/lib/src/action/handler.rs @@ -0,0 +1,161 @@ +use std::{collections::HashMap, path::Path, sync::Arc, time::Duration}; +use tokio::task::JoinHandle; +use watchexec_events::{Event, FileType, ProcessEnd}; +use watchexec_signals::Signal; +use watchexec_supervisor::{ + command::Command, + job::{start_job, Job}, +}; + +use crate::id::Id; + +use super::QuitManner; + +/// The environment given to the action handler. +/// +/// The action handler is the heart of a Watchexec program. Within, you decide what happens when an +/// event successfully passes all filters. Watchexec maintains a set of Supervised [`Job`]s, which +/// are assigned a unique [`Id`] for lightweight reference. In this action handler, you should +/// add commands to be supervised with `create_job()`, or find an already-supervised job with +/// `get_job()` or `list_jobs()`. You can interact with jobs directly via their handles, and can +/// even store clones of the handles for later use outside the action handler. +/// +/// The action handler is also given the [`Event`]s which triggered the action. These are expected +/// to be the way to determine what to do with a job. However, in some applications you might not +/// care about them, and that's fine too: for example, you can build a Watchexec which only does +/// process supervision, and is triggered entirely by synthetic events. Conversely, you are also not +/// obligated to use the job handles: you can build a Watchexec which only does something with the +/// events, and never actually starts any processes. +/// +/// There are some important considerations to keep in mind when writing an action handler: +/// +/// 1. The action handler is called with the supervisor set _as of when the handler was called_. +/// This is particularly important when multiple action handlers might be running at the same +/// time: they might have incomplete views of the supervisor set. +/// +/// 2. The way the action handler communicates with the Watchexec handler is through the return +/// value of the handler. That is, when you add a job with `create_job()`, the job is not added +/// to the Watchexec instance's supervisor set until the action handler returns. Similarly, when +/// using `quit()`, the quit action is not performed until the action handler returns and the +/// Watchexec instance is able to see it. +/// +/// 3. The action handler blocks the action main loop. This means that if you have a long-running +/// action handler, the Watchexec instance will not be able to process events until the handler +/// returns. That will cause events to accumulate and then get dropped once the channel reaches +/// capacity, which will impact your ability to receive signals (such as a Ctrl-C), and may spew +/// [`EventChannelTrySend` errors](crate::error::RuntimeError::EventChannelTrySend). +/// +/// If you want to do something long-running, you should either ignore that error, and accept +/// events may be dropped, or preferrably spawn a task to do it, and return from the action +/// handler as soon as possible. +#[derive(Debug)] +pub struct Handler { + /// The collected events which triggered the action. + pub events: Arc<[Event]>, + extant: HashMap, + pub(crate) new: HashMap)>, + pub(crate) quit: Option, +} + +impl Handler { + pub(crate) fn new(events: Arc<[Event]>, jobs: HashMap) -> Self { + Self { + events, + extant: jobs, + new: HashMap::new(), + quit: None, + } + } + + /// Create a new job and return its handle. + /// + /// This starts the [`Job`] immediately, and stores a copy of its handle and [`Id`] in this + /// `Action` (and thus in the Watchexec instance, when the action handler returns). + pub fn create_job(&mut self, command: Arc) -> (Id, Job) { + let id = Id::default(); + let (job, task) = start_job(command); + self.new.insert(id, (job.clone(), task)); + (id, job) + } + + // exposing this is dangerous as it allows duplicate IDs which may leak jobs + fn create_job_with_id(&mut self, id: Id, command: Arc) -> Job { + let (job, task) = start_job(command); + self.new.insert(id, (job.clone(), task)); + job + } + + /// Get an existing job or create a new one given an Id. + /// + /// This starts the [`Job`] immediately if one with the Id doesn't exist, and stores a copy of + /// its handle and [`Id`] in this `Action` (and thus in the Watchexec instance, when the action + /// handler returns). + pub fn get_or_create_job(&mut self, id: Id, command: impl Fn() -> Arc) -> Job { + self.get_job(id) + .unwrap_or_else(|| self.create_job_with_id(id, command())) + } + + /// Get a job given its Id. + /// + /// This returns a job handle, if it existed when this handler was called. + pub fn get_job(&self, id: Id) -> Option { + self.extant.get(&id).cloned() + } + + /// List all jobs currently supervised by Watchexec. + /// + /// This returns an iterator over all jobs, in no particular order, as of when this handler was + /// called. + pub fn list_jobs(&self) -> impl Iterator + '_ { + self.extant.iter().map(|(id, job)| (*id, job.clone())) + } + + /// Shut down the Watchexec instance immediately. + /// + /// This will kill and drop all jobs without waiting on processes, then quit. + /// + /// Use `graceful_quit()` to wait for processes to finish before quitting. + /// + /// The quit is initiated once the action handler returns, not when this method is called. + pub fn quit(&mut self) { + self.quit = Some(QuitManner::Abort); + } + + /// Shut down the Watchexec instance gracefully. + /// + /// This will send graceful stops to all jobs, wait on them to finish, then reap them and quit. + /// + /// Use `quit()` to quit more abruptly. + /// + /// If you want to wait for all other actions to finish and for jobs to get cleaned up, but not + /// gracefully delay for processes, you can do: + /// + /// ```no_compile + /// action.quit_gracefully(Signal::ForceStop, Duration::ZERO); + /// ``` + /// + /// The quit is initiated once the action handler returns, not when this method is called. + pub fn quit_gracefully(&mut self, signal: Signal, grace: Duration) { + self.quit = Some(QuitManner::Graceful { signal, grace }); + } + + /// Convenience to get all signals in the event set. + pub fn signals(&self) -> impl Iterator + '_ { + self.events.iter().flat_map(Event::signals) + } + + /// Convenience to get all paths in the event set. + /// + /// An action contains a set of events, and some of those events might relate to watched + /// files, and each of *those* events may have one or more paths that were affected. + /// To hide this complexity this method just provides any and all paths in the event, + /// along with the type of file at that path, if Watchexec knows that. + pub fn paths(&self) -> impl Iterator)> + '_ { + self.events.iter().flat_map(Event::paths) + } + + /// Convenience to get all process completions in the event set. + pub fn completions(&self) -> impl Iterator> + '_ { + self.events.iter().flat_map(Event::completions) + } +} diff --git a/crates/lib/src/action/outcome.rs b/crates/lib/src/action/outcome.rs deleted file mode 100644 index 7e91866..0000000 --- a/crates/lib/src/action/outcome.rs +++ /dev/null @@ -1,156 +0,0 @@ -use std::time::Duration; - -use watchexec_signals::Signal; - -/// The outcome to execute when an action is triggered. -/// -/// Logic against the state of the command should be expressed using these variants, rather than -/// inside the action handler, as it ensures the state of the command is always the latest available -/// when the outcome is executed. -#[derive(Clone, Debug, PartialEq, Eq)] -#[non_exhaustive] -pub enum Outcome { - /// Stop processing this action silently. - DoNothing, - - /// If the command is running, stop it. - /// - /// This should be used with an `IfRunning`, and will warn if the command is not running. - Stop, - - /// If the command isn't running, start it. - /// - /// This should be used with an `IfRunning`, and will warn if the command is running. - Start, - - /// Wait for command completion. - /// - /// Does nothing if the command isn't running. - Wait, - - /// Sleep for some duration. - Sleep(Duration), - - /// Send this signal to the command. - /// - /// This does not wait for the command to complete. - Signal(Signal), - - /// Clear the (terminal) screen. - Clear, - - /// Reset the (terminal) screen. - /// - /// This invokes (in order): [`WindowsCooked`][clearscreen::ClearScreen::WindowsCooked], - /// [`WindowsVt`][clearscreen::ClearScreen::WindowsVt], - /// [`VtLeaveAlt`][clearscreen::ClearScreen::VtLeaveAlt], - /// [`VtWellDone`][clearscreen::ClearScreen::VtWellDone], - /// and [the default clear][clearscreen::ClearScreen::default()]. - Reset, - - /// Exit watchexec. - Exit, - - /// When command is running, do the first, otherwise the second. - IfRunning(Box, Box), - - /// Do both outcomes in order. - Both(Box, Box), - - /// Race both outcomes: run both at once, and when one finishes, cancel the other. - Race(Box, Box), -} - -impl Default for Outcome { - fn default() -> Self { - Self::DoNothing - } -} - -impl Outcome { - /// Convenience function to create an outcome conditional on the state of the subprocess. - #[must_use] - pub fn if_running(then: Self, otherwise: Self) -> Self { - Self::IfRunning(Box::new(then), Box::new(otherwise)) - } - - /// Convenience function to create a sequence of outcomes. - #[must_use] - pub fn both(one: Self, two: Self) -> Self { - Self::Both(Box::new(one), Box::new(two)) - } - - /// Pattern that creates a sequence of outcomes from an iterator. - #[must_use] - pub fn sequence(mut outcomes: impl Iterator) -> Self { - let mut seq = outcomes.next().unwrap_or(Self::DoNothing); - for outcome in outcomes { - seq = Self::both(seq, outcome); - } - seq - } - - /// Convenience function to create a race of outcomes. - #[must_use] - pub fn race(one: Self, two: Self) -> Self { - Self::Race(Box::new(one), Box::new(two)) - } - - /// Pattern that waits for the subprocess to complete before executing the outcome. - #[must_use] - pub fn wait(and_then: Self) -> Self { - Self::both(Self::Wait, and_then) - } - - /// Pattern that waits for the subprocess to complete with a timeout. - #[must_use] - pub fn wait_timeout(timeout: Duration, and_then: Self) -> Self { - Self::both(Self::race(Self::Sleep(timeout), Self::Wait), and_then) - } - - /// Resolves the outcome given the current state of the subprocess. - #[must_use] - pub fn resolve(self, is_running: bool) -> Self { - match (is_running, self) { - (true, Self::IfRunning(then, _)) => then.resolve(true), - (false, Self::IfRunning(_, otherwise)) => otherwise.resolve(false), - (ir, Self::Both(one, two)) => Self::both(one.resolve(ir), two.resolve(ir)), - (_, other) => other, - } - } -} - -#[cfg(test)] -mod test { - use super::*; - - #[test] - fn simple_if_running() { - assert_eq!( - Outcome::if_running(Outcome::Stop, Outcome::Start).resolve(true), - Outcome::Stop - ); - assert_eq!( - Outcome::if_running(Outcome::Stop, Outcome::Start).resolve(false), - Outcome::Start - ); - } - - #[test] - fn simple_passthrough() { - assert_eq!(Outcome::Wait.resolve(true), Outcome::Wait); - assert_eq!(Outcome::Clear.resolve(false), Outcome::Clear); - } - - #[test] - fn nested_if_runnings() { - assert_eq!( - Outcome::both( - Outcome::if_running(Outcome::Stop, Outcome::Start), - Outcome::if_running(Outcome::Wait, Outcome::Exit) - ) - .resolve(true), - Outcome::Both(Box::new(Outcome::Stop), Box::new(Outcome::Wait)) - ); - } -} diff --git a/crates/lib/src/action/outcome_worker.rs b/crates/lib/src/action/outcome_worker.rs deleted file mode 100644 index 6ea3c8f..0000000 --- a/crates/lib/src/action/outcome_worker.rs +++ /dev/null @@ -1,209 +0,0 @@ -use std::sync::{ - atomic::{AtomicUsize, Ordering}, - Arc, -}; - -use async_priority_channel as priority; -use clearscreen::ClearScreen; -use futures::{ - future::{select, Either}, - Future, -}; -use tokio::{ - spawn, - sync::{mpsc, watch::Receiver}, - time::sleep, -}; -use tracing::{debug, error, info, trace, warn}; - -use crate::{ - command::Supervisor, - error::RuntimeError, - event::{Event, Priority}, -}; - -use super::{process_holder::ProcessHolder, Outcome, WorkingData}; - -#[derive(Clone)] -pub struct OutcomeWorker { - events: Arc<[Event]>, - working: Receiver, - process: ProcessHolder, - gen: usize, - gencheck: Arc, - errors_c: mpsc::Sender, - events_c: priority::Sender, -} - -impl OutcomeWorker { - pub fn newgen() -> Arc { - Default::default() - } - - pub fn spawn( - outcome: Outcome, - events: Arc<[Event]>, - working: Receiver, - process: ProcessHolder, - gencheck: Arc, - errors_c: mpsc::Sender, - events_c: priority::Sender, - ) { - let gen = gencheck.fetch_add(1, Ordering::SeqCst).wrapping_add(1); - let this = Self { - events, - working, - process, - gen, - gencheck, - errors_c, - events_c, - }; - - debug!(?outcome, %gen, "spawning outcome worker"); - spawn(async move { - let errors_c = this.errors_c.clone(); - match this.apply(outcome.clone()).await { - Err(err) => { - if matches!(err, RuntimeError::Exit) { - info!(%gen, "propagating graceful exit"); - } else { - error!(?err, %gen, "outcome applier errored"); - } - - if let Err(err) = errors_c.send(err).await { - error!(?err, %gen, "failed to send an error, something is terribly wrong"); - } - } - Ok(_) => { - debug!(?outcome, %gen, "outcome worker finished"); - } - } - }); - } - - async fn check_gen(&self, f: impl Future + Send) -> Option { - // TODO: use a select and a notifier of some kind so it cancels tasks - if self.gencheck.load(Ordering::SeqCst) != self.gen { - warn!(when=%"pre", gen=%self.gen, "outcome worker was cycled, aborting"); - return None; - } - let o = f.await; - if self.gencheck.load(Ordering::SeqCst) != self.gen { - warn!(when=%"post", gen=%self.gen, "outcome worker was cycled, aborting"); - return None; - } - Some(o) - } - - #[async_recursion::async_recursion] - async fn apply(&self, outcome: Outcome) -> Result<(), RuntimeError> { - macro_rules! notry { - ($e:expr) => { - match self.check_gen($e).await { - None => return Ok(()), - Some(o) => o, - } - }; - } - match (notry!(self.process.is_some()), outcome) { - (_, Outcome::DoNothing) => {} - (_, Outcome::Exit) => { - return Err(RuntimeError::Exit); - } - (true, Outcome::Stop) => { - notry!(self.process.kill()); - notry!(self.process.wait())?; - notry!(self.process.drop_inner()); - } - (false, o @ (Outcome::Stop | Outcome::Wait | Outcome::Signal(_))) => { - debug!(outcome=?o, "meaningless without a process, not doing anything"); - } - (_, Outcome::Start) => { - let (cmds, grouped, pre_spawn_handler, post_spawn_handler) = { - let wrk = self.working.borrow(); - ( - wrk.commands.clone(), - wrk.grouped, - wrk.pre_spawn_handler.clone(), - wrk.post_spawn_handler.clone(), - ) - }; - - if cmds.is_empty() { - warn!("tried to start commands without anything to run"); - } else { - trace!("spawning supervisor for command"); - let sup = Supervisor::spawn( - self.errors_c.clone(), - self.events_c.clone(), - cmds, - grouped, - self.events.clone(), - pre_spawn_handler, - post_spawn_handler, - )?; - notry!(self.process.replace(sup)); - } - } - - (true, Outcome::Signal(sig)) => { - notry!(self.process.signal(sig)); - } - - (true, Outcome::Wait) => { - notry!(self.process.wait())?; - } - - (_, Outcome::Sleep(time)) => { - trace!(?time, "sleeping"); - notry!(sleep(time)); - trace!(?time, "done sleeping"); - } - - (_, Outcome::Clear) => { - clearscreen::clear()?; - } - - (_, Outcome::Reset) => { - for cs in [ - ClearScreen::WindowsCooked, - ClearScreen::WindowsVt, - ClearScreen::VtLeaveAlt, - ClearScreen::VtWellDone, - ClearScreen::default(), - ] { - cs.clear()?; - } - } - - (true, Outcome::IfRunning(then, _)) => { - notry!(self.apply(*then))?; - } - (false, Outcome::IfRunning(_, otherwise)) => { - notry!(self.apply(*otherwise))?; - } - - (_, Outcome::Both(one, two)) => { - if let Err(err) = notry!(self.apply(*one)) { - debug!( - "first outcome failed, sending an error but proceeding to the second anyway" - ); - notry!(self.errors_c.send(err)).ok(); - } - - notry!(self.apply(*two))?; - } - - (_, Outcome::Race(one, two)) => { - if let Either::Left((Err(err), _)) | Either::Right((Err(err), _)) = - select(self.apply(*one), self.apply(*two)).await - { - return Err(err); - } - } - } - - Ok(()) - } -} diff --git a/crates/lib/src/action/process_holder.rs b/crates/lib/src/action/process_holder.rs deleted file mode 100644 index d9b4a97..0000000 --- a/crates/lib/src/action/process_holder.rs +++ /dev/null @@ -1,71 +0,0 @@ -use std::sync::Arc; - -use tokio::sync::RwLock; -use tracing::trace; -use watchexec_signals::Signal; - -use crate::{command::Supervisor, error::RuntimeError}; - -#[derive(Clone, Debug, Default)] -pub struct ProcessHolder(Arc>>); -impl ProcessHolder { - pub async fn is_running(&self) -> bool { - self.0 - .read() - .await - .as_ref() - .map_or(false, Supervisor::is_running) - } - - pub async fn is_some(&self) -> bool { - self.0.read().await.is_some() - } - - pub async fn drop_inner(&self) { - trace!("dropping supervisor"); - self.0.write().await.take(); - trace!("dropped supervisor"); - } - - pub async fn replace(&self, new: Supervisor) { - trace!("replacing supervisor"); - if let Some(_old) = self.0.write().await.replace(new) { - trace!("replaced supervisor"); - // TODO: figure out what to do with old - } else { - trace!("not replaced: no supervisor"); - } - } - - pub async fn signal(&self, sig: Signal) { - if let Some(p) = self.0.read().await.as_ref() { - trace!("signaling supervisor"); - p.signal(sig).await; - trace!("signaled supervisor"); - } else { - trace!("not signaling: no supervisor"); - } - } - - pub async fn kill(&self) { - if let Some(p) = self.0.read().await.as_ref() { - trace!("killing supervisor"); - p.kill().await; - trace!("killed supervisor"); - } else { - trace!("not killing: no supervisor"); - } - } - - pub async fn wait(&self) -> Result<(), RuntimeError> { - if let Some(p) = self.0.read().await.as_ref() { - trace!("waiting on supervisor"); - p.wait().await?; - trace!("waited on supervisor"); - } else { - trace!("not waiting: no supervisor"); - } - - Ok(()) - } -} diff --git a/crates/lib/src/action/quit.rs b/crates/lib/src/action/quit.rs new file mode 100644 index 0000000..ff507e5 --- /dev/null +++ b/crates/lib/src/action/quit.rs @@ -0,0 +1,17 @@ +use std::time::Duration; +use watchexec_signals::Signal; + +/// How the Watchexec instance should quit. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum QuitManner { + /// Kill all processes and drop all jobs, then quit. + Abort, + + /// Gracefully stop all jobs, then quit. + Graceful { + /// Signal to send immediately + signal: Signal, + /// Time to wait before forceful termination + grace: Duration, + }, +} diff --git a/crates/lib/src/action/return.rs b/crates/lib/src/action/return.rs new file mode 100644 index 0000000..bb3a191 --- /dev/null +++ b/crates/lib/src/action/return.rs @@ -0,0 +1,18 @@ +use std::future::Future; + +use super::ActionHandler; + +/// The return type of an action. +/// +/// This is the type returned by the raw action handler, used internally or when setting the action +/// handler directly via the field on [`Config`](crate::Config). It is not used when setting the +/// action handler via [`Config::on_action`](crate::Config::on_action) and +/// [`Config::on_action_async`](crate::Config::on_action_async) as that takes care of wrapping the +/// return type from the specialised signature on these methods. +pub enum ActionReturn { + /// The action handler is synchronous and here's its return value. + Sync(ActionHandler), + + /// The action handler is asynchronous: this is the future that will resolve to its return value. + Async(Box + Send + Sync>), +} diff --git a/crates/lib/src/action/worker.rs b/crates/lib/src/action/worker.rs index 250f566..9ca8255 100644 --- a/crates/lib/src/action/worker.rs +++ b/crates/lib/src/action/worker.rs @@ -1,54 +1,131 @@ use std::{ + collections::HashMap, mem::take, sync::Arc, time::{Duration, Instant}, }; use async_priority_channel as priority; -use tokio::{ - sync::{ - mpsc, - watch::{self}, - }, - time::timeout, -}; -use tracing::{debug, info, trace}; +use tokio::{sync::mpsc, time::timeout}; +use tracing::{debug, trace}; +use watchexec_events::{Event, Priority}; +use watchexec_supervisor::job::Job; +use super::{handler::Handler, quit::QuitManner}; use crate::{ + action::ActionReturn, error::{CriticalError, RuntimeError}, - event::{Event, Priority}, - handler::rte, + filter::Filterer, + id::Id, + late_join_set::LateJoinSet, + Config, }; -use super::{outcome_worker::OutcomeWorker, process_holder::ProcessHolder, Action, WorkingData}; - /// The main worker of a Watchexec process. /// /// This is the main loop of the process. It receives events from the event channel, filters them, /// debounces them, obtains the desired outcome of an actioned event, calls the appropriate handlers /// and schedules processes as needed. pub async fn worker( - working: watch::Receiver, + config: Arc, errors: mpsc::Sender, - events_tx: priority::Sender, events: priority::Receiver, ) -> Result<(), CriticalError> { - let mut last = Instant::now(); - let mut set = Vec::new(); - let process = ProcessHolder::default(); - let outcome_gen = OutcomeWorker::newgen(); + let mut jobtasks = LateJoinSet::default(); + let mut jobs = HashMap::::new(); - loop { - if events.is_closed() { - trace!("events channel closed, stopping"); - break; + while let Some(mut set) = throttle_collect( + config.clone(), + events.clone(), + errors.clone(), + Instant::now(), + ) + .await? + { + let events: Arc<[Event]> = Arc::from(take(&mut set).into_boxed_slice()); + + trace!("preparing action handler"); + let action = Handler::new(events.clone(), jobs.clone()); + + debug!("running action handler"); + let action = match config.action_handler.call(action) { + ActionReturn::Sync(action) => action, + ActionReturn::Async(action) => Box::into_pin(action).await, + }; + + debug!("take control of new tasks"); + for (id, (job, task)) in action.new { + trace!(?id, "taking control of new task"); + jobtasks.insert(task); + jobs.insert(id, job); } + if let Some(manner) = action.quit { + debug!(?manner, "quitting worker"); + match manner { + QuitManner::Abort => break, + QuitManner::Graceful { signal, grace } => { + debug!(?signal, ?grace, "quitting worker gracefully"); + let mut tasks = LateJoinSet::default(); + for (id, job) in jobs.drain() { + trace!(?id, "quitting job"); + tasks.spawn(async move { + job.stop_with_signal(signal, grace); + job.delete().await; + }); + } + debug!("waiting for graceful shutdown tasks"); + tasks.join_all().await; + debug!("waiting for job tasks to end"); + jobtasks.join_all().await; + break; + } + } + } + + let gc: Vec = jobs + .iter() + .filter_map(|(id, job)| { + if job.is_dead() { + trace!(?id, "job is dead, gc'ing"); + Some(*id) + } else { + None + } + }) + .collect(); + if !gc.is_empty() { + debug!("garbage collect old tasks"); + for id in gc { + jobs.remove(&id); + } + } + + debug!("action handler finished"); + } + + debug!("action worker finished"); + Ok(()) +} + +pub async fn throttle_collect( + config: Arc, + events: priority::Receiver, + errors: mpsc::Sender, + mut last: Instant, +) -> Result>, CriticalError> { + if events.is_closed() { + trace!("events channel closed, stopping"); + return Ok(None); + } + + let mut set: Vec = vec![]; + loop { let maxtime = if set.is_empty() { trace!("nothing in set, waiting forever for next event"); Duration::from_secs(u64::MAX) } else { - working.borrow().throttle.saturating_sub(last.elapsed()) + config.throttle.get().saturating_sub(last.elapsed()) }; if maxtime.is_zero() { @@ -64,7 +141,7 @@ pub async fn worker( let maybe_event = timeout(maxtime, events.recv()).await; if events.is_closed() { trace!("events channel closed during timeout, stopping"); - break; + return Ok(None); } match maybe_event { @@ -72,7 +149,7 @@ pub async fn worker( trace!("timed out, cycling"); continue; } - Ok(Err(_empty)) => break, + Ok(Err(_empty)) => return Ok(None), Ok(Ok((event, priority))) => { trace!(?event, ?priority, "got event"); @@ -81,7 +158,7 @@ pub async fn worker( } else if event.is_empty() { trace!("empty event, by-passing filters"); } else { - let filtered = working.borrow().filterer.check_event(&event, priority); + let filtered = config.filterer.check_event(&event, priority); match filtered { Err(err) => { trace!(%err, "filter errored on event"); @@ -109,7 +186,7 @@ pub async fn worker( trace!("urgent event, by-passing throttle"); } else { let elapsed = last.elapsed(); - if elapsed < working.borrow().throttle { + if elapsed < config.throttle.get() { trace!(?elapsed, "still within throttle window, cycling"); continue; } @@ -118,49 +195,6 @@ pub async fn worker( } } - trace!("out of throttle, starting action process"); - last = Instant::now(); - - #[allow(clippy::iter_with_drain)] - let events = Arc::from(take(&mut set).into_boxed_slice()); - let action = Action::new(Arc::clone(&events)); - info!(?action, "action constructed"); - - debug!("running action handler"); - let action_handler = { - let wrk = working.borrow(); - wrk.action_handler.clone() - }; - - let outcome = action.outcome.clone(); - let err = action_handler - .call(action) - .await - .map_err(|e| rte("action worker", e.as_ref())); - if let Err(err) = err { - errors.send(err).await?; - debug!("action handler errored, skipping"); - continue; - } - - let outcome = outcome.get().cloned().unwrap_or_default(); - debug!(?outcome, "action handler finished"); - - let outcome = outcome.resolve(process.is_running().await); - info!(?outcome, "outcome resolved"); - - OutcomeWorker::spawn( - outcome, - events, - working.clone(), - process.clone(), - outcome_gen.clone(), - errors.clone(), - events_tx.clone(), - ); - debug!("action process done"); + return Ok(Some(set)); } - - debug!("action worker finished"); - Ok(()) } diff --git a/crates/lib/src/action/workingdata.rs b/crates/lib/src/action/workingdata.rs deleted file mode 100644 index 1a39856..0000000 --- a/crates/lib/src/action/workingdata.rs +++ /dev/null @@ -1,217 +0,0 @@ -use std::{ - fmt, - sync::{Arc, Weak}, - time::Duration, -}; - -use once_cell::sync::OnceCell; -use tokio::{ - process::Command as TokioCommand, - sync::{Mutex, OwnedMutexGuard}, -}; - -use crate::{command::Command, event::Event, filter::Filterer, handler::HandlerLock}; - -use super::Outcome; - -/// The configuration of the [action][crate::action] worker. -/// -/// This is marked non-exhaustive so new configuration can be added without breaking. -#[derive(Clone)] -#[non_exhaustive] -pub struct WorkingData { - /// How long to wait for events to build up before executing an action. - /// - /// This is sometimes called "debouncing." We debounce on the trailing edge: an action is - /// triggered only after that amount of time has passed since the first event in the cycle. The - /// action is called with all the collected events in the cycle. - pub throttle: Duration, - - /// The main handler to define: what to do when an action is triggered. - /// - /// This handler is called with the [`Action`] environment, which has a certain way of returning - /// the desired outcome, check out the [`Action::outcome()`] method. The handler checks for the - /// outcome as soon as the handler returns, which means that if the handler returns before the - /// outcome is set, you'll get unexpected results. For this reason, it's a bad idea to use ex. a - /// channel as the handler. - /// - /// If this handler is not provided, it defaults to a no-op, which does absolutely nothing, not - /// even quit. Hence, you really need to provide a handler. - /// - /// It is possible to change the handler or any other configuration inside the previous handler. - /// It's useful to know that the handlers are updated from this working data before any of them - /// run in any given cycle, so changing the pre-spawn and post-spawn handlers from this handler - /// will not affect the running action. - pub action_handler: HandlerLock, - - /// A handler triggered before a command is spawned. - /// - /// This handler is called with the [`PreSpawn`] environment, which provides mutable access to - /// the [`Command`](TokioCommand) which is about to be run. See the notes on the - /// [`PreSpawn::command()`] method for important information on what you can do with it. - /// - /// Returning an error from the handler will stop the action from processing further, and issue - /// a [`RuntimeError`][crate::error::RuntimeError] to the error channel. - pub pre_spawn_handler: HandlerLock, - - /// A handler triggered immediately after a command is spawned. - /// - /// This handler is called with the [`PostSpawn`] environment, which provides details on the - /// spawned command, including its PID. - /// - /// Returning an error from the handler will drop the [`Child`][tokio::process::Child], which - /// will terminate the command without triggering any of the normal Watchexec behaviour, and - /// issue a [`RuntimeError`][crate::error::RuntimeError] to the error channel. - pub post_spawn_handler: HandlerLock, - - /// Commands to execute. - /// - /// These will be run in order, and an error will stop early. - pub commands: Vec, - - /// Whether to use process groups (on Unix) or job control (on Windows) to run the command. - /// - /// This makes use of [command_group] under the hood. - /// - /// If you want to known whether a spawned command was run in a process group, you should use - /// the value in [`PostSpawn`] instead of reading this one, as it may have changed in the - /// meantime. - pub grouped: bool, - - /// The filterer implementation to use when filtering events. - /// - /// The default is a no-op, which will always pass every event. - pub filterer: Arc, -} - -impl fmt::Debug for WorkingData { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("WorkingData") - .field("throttle", &self.throttle) - .field("commands", &self.commands) - .field("grouped", &self.grouped) - .field("filterer", &self.filterer) - .finish_non_exhaustive() - } -} - -impl Default for WorkingData { - fn default() -> Self { - Self { - throttle: Duration::from_millis(50), - action_handler: Default::default(), - pre_spawn_handler: Default::default(), - post_spawn_handler: Default::default(), - commands: Vec::new(), - grouped: true, - filterer: Arc::new(()), - } - } -} - -/// The environment given to the action handler. -/// -/// This deliberately does not implement Clone to make it hard to move it out of the handler, which -/// you should not do. -/// -/// The [`Action::outcome()`] method is the only way to set the outcome of the action, and it _must_ -/// be called before the handler returns. -#[derive(Debug)] -pub struct Action { - /// The collected events which triggered the action. - pub events: Arc<[Event]>, - pub(super) outcome: Arc>, -} - -impl Action { - pub(super) fn new(events: Arc<[Event]>) -> Self { - Self { - events, - outcome: Default::default(), - } - } - - /// Set the action's outcome. - /// - /// This takes `self` and `Action` is not `Clone`, so it's only possible to call it once. - /// Regardless, if you _do_ manage to call it twice, it will do nothing beyond the first call. - /// - /// See the [`Action`] documentation about handlers to learn why it's a bad idea to clone or - /// send it elsewhere, and what kind of handlers you cannot use. - pub fn outcome(self, outcome: Outcome) { - self.outcome.set(outcome).ok(); - } -} - -/// The environment given to the pre-spawn handler. -/// -/// This deliberately does not implement Clone to make it hard to move it out of the handler, which -/// you should not do. -/// -/// The [`PreSpawn::command()`] method is the only way to mutate the command, and the mutex guard it -/// returns _must_ be dropped before the handler returns. -#[derive(Debug)] -#[non_exhaustive] -pub struct PreSpawn { - /// The command which is about to be spawned. - pub command: Command, - - /// The collected events which triggered the action this command issues from. - pub events: Arc<[Event]>, - - to_spawn_w: Weak>, -} - -impl PreSpawn { - pub(crate) fn new( - command: Command, - to_spawn: TokioCommand, - events: Arc<[Event]>, - ) -> (Self, Arc>) { - let arc = Arc::new(Mutex::new(to_spawn)); - ( - Self { - command, - events, - to_spawn_w: Arc::downgrade(&arc), - }, - arc.clone(), - ) - } - - /// Get write access to the command that will be spawned. - /// - /// Keeping the lock alive beyond the end of the handler may cause the command to be cancelled, - /// but note no guarantees are made on this behaviour. Just don't do it. See the [`Action`] - /// documentation about handlers for more. - /// - /// This will always return `Some()` under normal circumstances. - pub async fn command(&self) -> Option> { - if let Some(arc) = self.to_spawn_w.upgrade() { - Some(arc.lock_owned().await) - } else { - None - } - } -} - -/// The environment given to the post-spawn handler. -/// -/// This is Clone, as there's nothing (except returning an error) that can be done to the command -/// now that it's spawned, as far as Watchexec is concerned. Nevertheless, you should return from -/// this handler quickly, to avoid holding up anything else. -#[derive(Clone, Debug)] -#[non_exhaustive] -pub struct PostSpawn { - /// The command the process was spawned with. - pub command: Command, - - /// The collected events which triggered the action the command issues from. - pub events: Arc<[Event]>, - - /// The process ID or the process group ID. - pub id: u32, - - /// Whether the command was run in a process group. - pub grouped: bool, -} diff --git a/crates/lib/src/changeable.rs b/crates/lib/src/changeable.rs new file mode 100644 index 0000000..5a6a729 --- /dev/null +++ b/crates/lib/src/changeable.rs @@ -0,0 +1,122 @@ +//! Changeable values. + +use std::{ + any::type_name, + fmt, + sync::{Arc, RwLock}, +}; + +/// A shareable value that doesn't keep a lock when it is read. +/// +/// This is essentially an `Arc>`, with the only two methods to use it as: +/// - replace the value, which obtains a write lock +/// - get a clone of that value, which obtains a read lock +/// +/// but importantly because you get a clone of the value, the read lock is not held after the +/// `get()` method returns. +/// +/// See [`ChangeableFn`] for a specialised variant which holds an [`Fn`]. +#[derive(Clone)] +pub struct Changeable(Arc>); +impl Changeable +where + T: Clone + Send, +{ + /// Create a new Changeable. + /// + /// If `T: Default`, prefer using `::default()`. + #[must_use] + pub fn new(value: T) -> Self { + Self(Arc::new(RwLock::new(value))) + } + + /// Replace the value with a new one. + /// + /// Panics if the lock was poisoned. + pub fn replace(&self, new: T) { + *(self.0.write().expect("changeable lock poisoned")) = new; + } + + /// Get a clone of the value. + /// + /// Panics if the lock was poisoned. + #[must_use] + pub fn get(&self) -> T { + self.0.read().expect("handler lock poisoned").clone() + } +} + +impl Default for Changeable +where + T: Clone + Send + Default, +{ + fn default() -> Self { + Self::new(T::default()) + } +} + +// TODO: with specialisation, write a better impl when T: Debug +impl fmt::Debug for Changeable { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Changeable") + .field("inner type", &type_name::()) + .finish_non_exhaustive() + } +} + +/// A shareable `Fn` that doesn't hold a lock when it is called. +/// +/// This is a specialisation of [`Changeable`] for the `Fn` usecase. +/// +/// As this is for Watchexec, only `Fn`s with a single argument and return value are supported +/// here; it's simple enough to make your own if you want more. +pub struct ChangeableFn(Changeable U) + Send + Sync>>); +impl ChangeableFn +where + T: Send, + U: Send, +{ + pub(crate) fn new(f: impl (Fn(T) -> U) + Send + Sync + 'static) -> Self { + Self(Changeable::new(Arc::new(f))) + } + + /// Replace the fn with a new one. + /// + /// Panics if the lock was poisoned. + pub fn replace(&self, new: impl (Fn(T) -> U) + Send + Sync + 'static) { + self.0.replace(Arc::new(new)); + } + + /// Call the fn. + /// + /// Panics if the lock was poisoned. + pub fn call(&self, data: T) -> U { + (self.0.get())(data) + } +} + +// the derive adds a T: Clone bound +impl Clone for ChangeableFn { + fn clone(&self) -> Self { + Self(Changeable::clone(&self.0)) + } +} + +impl Default for ChangeableFn +where + T: Send, + U: Send + Default, +{ + fn default() -> Self { + Self::new(|_| U::default()) + } +} + +impl fmt::Debug for ChangeableFn { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("ChangeableFn") + .field("payload type", &type_name::()) + .field("return type", &type_name::()) + .finish_non_exhaustive() + } +} diff --git a/crates/lib/src/command.rs b/crates/lib/src/command.rs deleted file mode 100644 index df423b9..0000000 --- a/crates/lib/src/command.rs +++ /dev/null @@ -1,157 +0,0 @@ -//! Command construction, configuration, and tracking. - -use std::fmt; - -use tokio::process::Command as TokioCommand; -use tracing::trace; - -use crate::error::RuntimeError; - -#[doc(inline)] -pub use process::Process; - -#[doc(inline)] -pub use supervisor::Supervisor; - -mod process; -mod supervisor; - -#[cfg(test)] -mod tests; - -/// A command to execute. -#[derive(Clone, Debug, PartialEq, Eq, Hash)] -pub enum Command { - /// A raw command which will be executed as-is. - Exec { - /// The program to run. - prog: String, - - /// The arguments to pass. - args: Vec, - }, - - /// A shelled command line. - Shell { - /// The shell to run. - shell: Shell, - - /// Additional options or arguments to pass to the shell. - /// - /// These will be inserted before the `-c` (or equivalent) option immediately preceding the - /// command line string. - args: Vec, - - /// The command line to pass to the shell. - command: String, - }, -} - -/// Shell to use to run shelled commands. -/// -/// `Cmd` and `Powershell` are special-cased because they have different calling conventions. Also -/// `Cmd` is only available in Windows, while `Powershell` is also available on unices (provided the -/// end-user has it installed, of course). -/// -/// There is no default implemented: as consumer of this library you are encouraged to set your own -/// default as makes sense in your application / for your platform. -#[derive(Clone, Debug, PartialEq, Eq, Hash)] -pub enum Shell { - /// Use the given string as a unix shell invocation. - /// - /// This is invoked with `-c` followed by the command. - Unix(String), - - /// Use the Windows CMD.EXE shell. - /// - /// This is `cmd.exe` invoked with `/C` followed by the command. - #[cfg(windows)] - Cmd, - - /// Use Powershell, on Windows or elsewhere. - /// - /// This is `powershell.exe` invoked with `-Command` followed by the command on Windows. - /// On unices, it is equivalent to `Unix("pwsh")`. - Powershell, -} - -impl Command { - /// Obtain a [`tokio::process::Command`] from a [`Command`]. - /// - /// Behaves as described in the [`Command`] and [`Shell`] documentation. - /// - /// # Errors - /// - /// - Errors if the `command` of a `Command::Shell` is empty. - /// - Errors if the `shell` of a `Shell::Unix(shell)` is empty. - pub fn to_spawnable(&self) -> Result { - trace!(cmd=?self, "constructing command"); - - match self { - Self::Exec { prog, args } => { - let mut c = TokioCommand::new(prog); - c.args(args); - Ok(c) - } - - Self::Shell { - shell, - args, - command, - } => { - if command.is_empty() { - return Err(RuntimeError::CommandShellEmptyCommand); - } - - let (shcmd, shcliopt) = match shell { - #[cfg(windows)] - Shell::Cmd => { - use std::os::windows::process::CommandExt as _; - use std::process::Command as StdCommand; - - // TODO this is a workaround until TokioCommand has a raw_arg method. See tokio-rs/tokio#5810. - let mut std_command = StdCommand::new("cmd.exe"); - std_command.args(args).arg("/C").raw_arg(command); - return Ok(TokioCommand::from(std_command)); - } - - #[cfg(windows)] - Shell::Powershell => ("powershell.exe", "-Command"), - #[cfg(not(windows))] - Shell::Powershell => ("pwsh", "-c"), - - Shell::Unix(cmd) => { - if cmd.is_empty() { - return Err(RuntimeError::CommandShellEmptyShell); - } - - (cmd.as_str(), "-c") - } - }; - - let mut c = TokioCommand::new(shcmd); - c.args(args); - c.arg(shcliopt).arg(command); - Ok(c) - } - } - } -} - -impl fmt::Display for Command { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Exec { prog, args } => { - write!(f, "{prog}")?; - for arg in args { - write!(f, " {arg}")?; - } - - Ok(()) - } - Self::Shell { command, .. } => { - write!(f, "{command}") - } - } - } -} diff --git a/crates/lib/src/command/process.rs b/crates/lib/src/command/process.rs deleted file mode 100644 index a9c2181..0000000 --- a/crates/lib/src/command/process.rs +++ /dev/null @@ -1,148 +0,0 @@ -use std::process::ExitStatus; - -use command_group::AsyncGroupChild; -use tokio::process::Child; -use tracing::{debug, trace}; - -use crate::error::RuntimeError; - -/// Low-level wrapper around a process child, be it grouped or ungrouped. -#[derive(Debug)] -pub enum Process { - /// The initial state of the process, before it's spawned. - None, - - /// A grouped process that's been spawned. - Grouped(AsyncGroupChild), - - /// An ungrouped process that's been spawned. - Ungrouped(Child), - - /// The cached exit status of the process. - Done(ExitStatus), -} - -impl Default for Process { - /// Returns [`Process::None`]. - fn default() -> Self { - Self::None - } -} - -impl Process { - /// Sends a Unix signal to the process. - /// - /// Does nothing if the process is not running. - #[cfg(unix)] - pub fn signal(&mut self, sig: command_group::Signal) -> Result<(), RuntimeError> { - use command_group::UnixChildExt; - - match self { - Self::None | Self::Done(_) => Ok(()), - Self::Grouped(c) => { - debug!(signal=%sig, pgid=?c.id(), "sending signal to process group"); - c.signal(sig) - } - Self::Ungrouped(c) => { - debug!(signal=%sig, pid=?c.id(), "sending signal to process"); - c.signal(sig) - } - } - .map_err(RuntimeError::Process) - } - - /// Kills the process. - /// - /// Does nothing if the process is not running. - /// - /// Note that this has different behaviour for grouped and ungrouped processes due to Tokio's - /// API: it waits on ungrouped processes, but not for grouped processes. - pub async fn kill(&mut self) -> Result<(), RuntimeError> { - match self { - Self::None | Self::Done(_) => Ok(()), - Self::Grouped(c) => { - debug!(pgid=?c.id(), "killing process group"); - c.kill() - } - Self::Ungrouped(c) => { - debug!(pid=?c.id(), "killing process"); - c.kill().await - } - } - .map_err(RuntimeError::Process) - } - - /// Checks the status of the process. - /// - /// Returns `true` if the process is still running. - /// - /// This takes `&mut self` as it transitions the [`Process`] state to [`Process::Done`] if it - /// finds the process has ended, such that it will cache the exit status. Otherwise that status - /// would be lost. - /// - /// Does nothing and returns `false` immediately if the `Process` is `Done` or `None`. - pub fn is_running(&mut self) -> Result { - match self { - Self::None | Self::Done(_) => Ok(false), - Self::Grouped(c) => c.try_wait().map(|status| { - trace!("try-waiting on process group"); - if let Some(status) = status { - trace!(?status, "converting to ::Done"); - *self = Self::Done(status); - true - } else { - false - } - }), - Self::Ungrouped(c) => c.try_wait().map(|status| { - trace!("try-waiting on process"); - if let Some(status) = status { - trace!(?status, "converting to ::Done"); - *self = Self::Done(status); - true - } else { - false - } - }), - } - .map_err(RuntimeError::Process) - } - - /// Waits for the process to exit, and returns its exit status. - /// - /// This takes `&mut self` as it transitions the [`Process`] state to [`Process::Done`] if it - /// finds the process has ended, such that it will cache the exit status. - /// - /// This makes it possible to call `wait` on a process multiple times, without losing the exit - /// status. - /// - /// Returns immediately with the cached exit status if the `Process` is `Done`, and with `None` - /// if the `Process` is `None`. - pub async fn wait(&mut self) -> Result, RuntimeError> { - match self { - Self::None => Ok(None), - Self::Done(status) => Ok(Some(*status)), - Self::Grouped(c) => { - trace!("waiting on process group"); - let status = c.wait().await.map_err(|err| RuntimeError::IoError { - about: "waiting on process group", - err, - })?; - trace!(?status, "converting to ::Done"); - *self = Self::Done(status); - Ok(Some(status)) - } - Self::Ungrouped(c) => { - trace!("waiting on process"); - let status = c.wait().await.map_err(|err| RuntimeError::IoError { - about: "waiting on process (ungrouped)", - err, - })?; - trace!(?status, "converting to ::Done"); - *self = Self::Done(status); - Ok(Some(status)) - } - } - .map_err(RuntimeError::Process) - } -} diff --git a/crates/lib/src/command/supervisor.rs b/crates/lib/src/command/supervisor.rs deleted file mode 100644 index 536a54d..0000000 --- a/crates/lib/src/command/supervisor.rs +++ /dev/null @@ -1,369 +0,0 @@ -use std::sync::Arc; - -use async_priority_channel as priority; -use command_group::AsyncCommandGroup; -use tokio::{ - select, spawn, - sync::{ - mpsc::{self, Sender}, - watch, - }, -}; -use tracing::{debug, debug_span, error, info, trace, Span}; -use watchexec_signals::Signal; - -use crate::{ - action::{PostSpawn, PreSpawn}, - command::Command, - error::RuntimeError, - event::{Event, Priority, Source, Tag}, - handler::{rte, HandlerLock}, -}; - -use super::Process; - -#[derive(Clone, Copy, Debug)] -enum Intervention { - Kill, - Signal(Signal), -} - -/// A task which supervises a sequence of processes. -/// -/// This spawns processes from a vec of [`Command`]s in order and waits for each to complete while -/// handling interventions to itself: orders to terminate, or to send a signal to the current -/// process. It also immediately issues a [`Tag::ProcessCompletion`] event when the set completes. -#[derive(Debug)] -pub struct Supervisor { - intervene: Sender, - ongoing: watch::Receiver, -} - -impl Supervisor { - /// Spawns the command set, the supervision task, and returns a new control object. - pub fn spawn( - errors: Sender, - events: priority::Sender, - mut commands: Vec, - grouped: bool, - actioned_events: Arc<[Event]>, - pre_spawn_handler: HandlerLock, - post_spawn_handler: HandlerLock, - ) -> Result { - // get commands in reverse order so pop() returns the next to run - commands.reverse(); - let next = commands.pop().ok_or(RuntimeError::NoCommands)?; - - let (notify, waiter) = watch::channel(true); - let (int_s, int_r) = mpsc::channel(8); - - spawn(async move { - let span = debug_span!("supervisor"); - - let mut next = next; - let mut commands = commands; - let mut int = int_r; - - loop { - let (mut process, pid) = match spawn_process( - span.clone(), - next, - grouped, - actioned_events.clone(), - pre_spawn_handler.clone(), - post_spawn_handler.clone(), - ) - .await - { - Ok(pp) => pp, - Err(err) => { - let _enter = span.enter(); - error!(%err, "while spawning process"); - errors.send(err).await.ok(); - trace!("marking process as done"); - notify - .send(false) - .unwrap_or_else(|e| trace!(%e, "error sending process complete")); - trace!("closing supervisor task early"); - return; - } - }; - - span.in_scope(|| debug!(?process, ?pid, "spawned process")); - - loop { - select! { - p = process.wait() => { - match p { - Ok(_) => break, // deal with it below - Err(err) => { - let _enter = span.enter(); - error!(%err, "while waiting on process"); - errors.try_send(err).ok(); - trace!("marking process as done"); - notify.send(false).unwrap_or_else(|e| trace!(%e, "error sending process complete")); - trace!("closing supervisor task early"); - return; - } - } - }, - Some(int) = int.recv() => { - match int { - Intervention::Kill => { - if let Err(err) = process.kill().await { - let _enter = span.enter(); - error!(%err, "while killing process"); - errors.try_send(err).ok(); - trace!("continuing to watch command"); - } - } - #[cfg(unix)] - Intervention::Signal(sig) => { - let _enter = span.enter(); - if let Some(sig) = sig.to_nix() { - if let Err(err) = process.signal(sig) { - error!(%err, "while sending signal to process"); - errors.try_send(err).ok(); - trace!("continuing to watch command"); - } - } else { - let err = RuntimeError::UnsupportedSignal(sig); - error!(%err, "while sending signal to process"); - errors.try_send(err).ok(); - trace!("continuing to watch command"); - } - } - #[cfg(windows)] - Intervention::Signal(sig) => { - let _enter = span.enter(); - // https://github.com/watchexec/watchexec/issues/219 - let err = RuntimeError::UnsupportedSignal(sig); - error!(%err, "while sending signal to process"); - errors.try_send(err).ok(); - trace!("continuing to watch command"); - } - } - } - else => break, - } - } - - span.in_scope(|| trace!("got out of loop, waiting once more")); - match process.wait().await { - Err(err) => { - let _enter = span.enter(); - error!(%err, "while waiting on process"); - errors.try_send(err).ok(); - } - Ok(status) => { - let event = span.in_scope(|| { - let event = Event { - tags: vec![ - Tag::Source(Source::Internal), - Tag::ProcessCompletion(status.map(Into::into)), - ], - metadata: Default::default(), - }; - - debug!(?event, "creating synthetic process completion event"); - event - }); - - if let Err(err) = events.send(event, Priority::Low).await { - let _enter = span.enter(); - error!(%err, "while sending process completion event"); - errors - .try_send(RuntimeError::EventChannelSend { - ctx: "command supervisor", - err, - }) - .ok(); - } - } - } - - let _enter = span.enter(); - if let Some(cmd) = commands.pop() { - debug!(?cmd, "queuing up next command"); - next = cmd; - } else { - debug!("no more commands to supervise"); - break; - } - } - - let _enter = span.enter(); - trace!("marking process as done"); - notify - .send(false) - .unwrap_or_else(|e| trace!(%e, "error sending process complete")); - trace!("closing supervisor task"); - }); - - Ok(Self { - ongoing: waiter, - intervene: int_s, - }) - } - - /// Issues a signal to the process. - /// - /// On Windows, this currently only supports [`Signal::ForceStop`]. - /// - /// While this is async, it returns once the signal intervention has been sent internally, not - /// when the signal has been delivered. - pub async fn signal(&self, signal: Signal) { - if cfg!(windows) { - if signal == Signal::ForceStop { - self.intervene.send(Intervention::Kill).await.ok(); - } - // else: https://github.com/watchexec/watchexec/issues/219 - } else { - trace!(?signal, "sending signal intervention"); - self.intervene.send(Intervention::Signal(signal)).await.ok(); - } - // only errors on channel closed, and that only happens if the process is dead - } - - /// Stops the process. - /// - /// While this is async, it returns once the signal intervention has been sent internally, not - /// when the signal has been delivered. - pub async fn kill(&self) { - trace!("sending kill intervention"); - self.intervene.send(Intervention::Kill).await.ok(); - // only errors on channel closed, and that only happens if the process is dead - } - - /// Returns true if the supervisor is still running. - /// - /// This is almost always equivalent to whether the _process_ is still running, but may not be - /// 100% in sync. - pub fn is_running(&self) -> bool { - let ongoing = *self.ongoing.borrow(); - trace!(?ongoing, "supervisor state"); - ongoing - } - - /// Returns only when the supervisor completes. - /// - /// This is almost always equivalent to waiting for the _process_ to complete, but may not be - /// 100% in sync. - pub async fn wait(&self) -> Result<(), RuntimeError> { - if !*self.ongoing.borrow() { - trace!("supervisor already completed (pre)"); - return Ok(()); - } - - debug!("waiting on supervisor completion"); - let mut ongoing = self.ongoing.clone(); - // never completes if ongoing is marked false in between the previous check and now! - // TODO: select with something that sleeps a bit and rechecks the ongoing - ongoing - .changed() - .await - .map_err(|err| RuntimeError::InternalSupervisor(err.to_string()))?; - debug!("supervisor completed"); - - Ok(()) - } -} - -async fn spawn_process( - span: Span, - command: Command, - grouped: bool, - actioned_events: Arc<[Event]>, - pre_spawn_handler: HandlerLock, - post_spawn_handler: HandlerLock, -) -> Result<(Process, u32), RuntimeError> { - let (pre_spawn, spawnable) = span.in_scope::<_, Result<_, RuntimeError>>(|| { - debug!(%grouped, ?command, "preparing command"); - #[cfg_attr(windows, allow(unused_mut))] - let mut spawnable = command.to_spawnable()?; - - // Required from Rust 1.66: - // https://github.com/rust-lang/rust/pull/101077 - // - // We do that before the pre-spawn so that hook can be used to set a different mask if wanted. - #[cfg(unix)] - { - use nix::sys::signal::{sigprocmask, SigSet, SigmaskHow, Signal}; - unsafe { - spawnable.pre_exec(|| { - let mut oldset = SigSet::empty(); - let mut newset = SigSet::all(); - newset.remove(Signal::SIGHUP); // leave SIGHUP alone so nohup works - debug!(unblocking=?newset, "resetting process sigmask"); - sigprocmask(SigmaskHow::SIG_UNBLOCK, Some(&newset), Some(&mut oldset))?; - debug!(?oldset, "sigmask reset"); - Ok(()) - }); - } - } - - debug!("running pre-spawn handler"); - Ok(PreSpawn::new( - command.clone(), - spawnable, - actioned_events.clone(), - )) - })?; - - pre_spawn_handler - .call(pre_spawn) - .await - .map_err(|e| rte("action pre-spawn", e.as_ref()))?; - - let (proc, id, post_spawn) = span.in_scope::<_, Result<_, RuntimeError>>(|| { - let mut spawnable = Arc::try_unwrap(spawnable) - .map_err(|_| RuntimeError::HandlerLockHeld("pre-spawn"))? - .into_inner(); - - info!(command=?spawnable, "spawning command"); - let (proc, id) = if grouped { - let proc = spawnable - .group() - .kill_on_drop(true) - .spawn() - .map_err(|err| RuntimeError::IoError { - about: "spawning process group", - err, - })?; - let id = proc.id().ok_or(RuntimeError::ProcessDeadOnArrival)?; - info!(pgid=%id, "process group spawned"); - (Process::Grouped(proc), id) - } else { - let proc = - spawnable - .kill_on_drop(true) - .spawn() - .map_err(|err| RuntimeError::IoError { - about: "spawning process (ungrouped)", - err, - })?; - let id = proc.id().ok_or(RuntimeError::ProcessDeadOnArrival)?; - info!(pid=%id, "process spawned"); - (Process::Ungrouped(proc), id) - }; - - debug!("running post-spawn handler"); - Ok(( - proc, - id, - PostSpawn { - command: command.clone(), - events: actioned_events.clone(), - id, - grouped, - }, - )) - })?; - - post_spawn_handler - .call(post_spawn) - .await - .map_err(|e| rte("action post-spawn", e.as_ref()))?; - - Ok((proc, id)) -} diff --git a/crates/lib/src/command/tests.rs b/crates/lib/src/command/tests.rs deleted file mode 100644 index a324d52..0000000 --- a/crates/lib/src/command/tests.rs +++ /dev/null @@ -1,128 +0,0 @@ -use super::{Command, Shell}; -use command_group::AsyncCommandGroup; - -#[tokio::test] -#[cfg(unix)] -async fn unix_shell_none() -> Result<(), std::io::Error> { - assert!(Command::Exec { - prog: "echo".into(), - args: vec!["hi".into()] - } - .to_spawnable() - .expect("echo directly") - .group_status() - .await? - .success()); - Ok(()) -} - -#[tokio::test] -#[cfg(unix)] -async fn unix_shell_sh() -> Result<(), std::io::Error> { - assert!(Command::Shell { - shell: Shell::Unix("sh".into()), - args: Vec::new(), - command: "echo hi".into() - } - .to_spawnable() - .expect("echo with sh") - .group_status() - .await? - .success()); - Ok(()) -} - -#[tokio::test] -#[cfg(unix)] -async fn unix_shell_alternate() -> Result<(), std::io::Error> { - assert!(Command::Shell { - shell: Shell::Unix("bash".into()), - args: Vec::new(), - command: "echo hi".into() - } - .to_spawnable() - .expect("echo with bash") - .group_status() - .await? - .success()); - Ok(()) -} - -#[tokio::test] -#[cfg(unix)] -async fn unix_shell_alternate_shopts() -> Result<(), std::io::Error> { - assert!(Command::Shell { - shell: Shell::Unix("bash".into()), - args: vec!["-o".into(), "errexit".into()], - command: "echo hi".into() - } - .to_spawnable() - .expect("echo with shopts") - .group_status() - .await? - .success()); - Ok(()) -} - -#[tokio::test] -#[cfg(windows)] -async fn windows_shell_none() -> Result<(), std::io::Error> { - assert!(Command::Exec { - prog: "echo".into(), - args: vec!["hi".into()] - } - .to_spawnable() - .unwrap() - .group_status() - .await? - .success()); - Ok(()) -} - -#[tokio::test] -#[cfg(windows)] -async fn windows_shell_cmd() -> Result<(), std::io::Error> { - assert!(Command::Shell { - shell: Shell::Cmd, - args: Vec::new(), - command: r#""echo" hi"#.into() - } - .to_spawnable() - .unwrap() - .group_status() - .await? - .success()); - Ok(()) -} - -#[tokio::test] -#[cfg(windows)] -async fn windows_shell_powershell() -> Result<(), std::io::Error> { - assert!(Command::Shell { - shell: Shell::Powershell, - args: Vec::new(), - command: "echo hi".into() - } - .to_spawnable() - .unwrap() - .group_status() - .await? - .success()); - Ok(()) -} - -#[tokio::test] -#[cfg(windows)] -async fn windows_shell_unix_style_powershell() -> Result<(), std::io::Error> { - assert!(Command::Shell { - shell: Shell::Unix("powershell.exe".into()), - args: Vec::new(), - command: "echo hi".into() - } - .to_spawnable() - .unwrap() - .group_status() - .await? - .success()); - Ok(()) -} diff --git a/crates/lib/src/config.rs b/crates/lib/src/config.rs index 328c454..fa7fbf0 100644 --- a/crates/lib/src/config.rs +++ b/crates/lib/src/config.rs @@ -1,226 +1,300 @@ //! Configuration and builders for [`crate::Watchexec`]. -use std::{fmt, path::Path, sync::Arc, time::Duration}; +use std::{future::Future, path::Path, pin::pin, sync::Arc, time::Duration}; -use tracing::debug; +use tokio::sync::Notify; +use tracing::{debug, trace}; use crate::{ - action::{Action, PostSpawn, PreSpawn}, - command::Command, - filter::Filterer, - fs::Watcher, - handler::{Handler, HandlerLock}, + action::{ActionHandler, ActionReturn}, + changeable::{Changeable, ChangeableFn}, + filter::{ChangeableFilterer, Filterer}, + sources::fs::{WatchedPath, Watcher}, ErrorHook, }; -/// Runtime configuration for [`Watchexec`][crate::Watchexec]. +/// Configuration for [`Watchexec`][crate::Watchexec]. /// -/// This is used both when constructing the instance (as initial configuration) and to reconfigure -/// it at runtime via [`Watchexec::reconfigure()`][crate::Watchexec::reconfigure()]. +/// Almost every field is a [`Changeable`], such that its value can be changed from a `&self`. /// -/// Use [`RuntimeConfig::default()`] to build a new one, or modify an existing one. This struct is -/// marked non-exhaustive such that new options may be added without breaking change. You can make -/// changes through the fields directly, or use the convenience (chainable!) methods instead. +/// Fields are public for advanced use, but in most cases changes should be made through the +/// methods provided: not only are they more convenient, each calls `debug!` on the new value, +/// providing a quick insight into what your application sets. /// -/// Another advantage of using the convenience methods is that each one contains a call to the -/// [`debug!`] macro, providing insight into what config your application sets for "free". -/// -/// You should see the detailed documentation on [`fs::WorkingData`][crate::fs::WorkingData] and -/// [`action::WorkingData`][crate::action::WorkingData] for important information and particulars -/// about each field, especially the handlers. -#[derive(Clone, Debug, Default)] +/// The methods also set the "change signal" of the Config: this notifies some parts of Watchexec +/// they should re-read the config. If you modify values via the fields directly, you should call +/// `signal_change()` yourself. Note that this doesn't mean that changing values _without_ calling +/// this will prevent Watchexec changing until it's called: most parts of Watchexec take a +/// "just-in-time" approach and read a config item immediately before it's needed, every time it's +/// needed, and thus don't need to listen for the change signal. +#[derive(Clone, Debug)] #[non_exhaustive] -pub struct RuntimeConfig { - /// Working data for the filesystem event source. +pub struct Config { + /// This is set by the change methods whenever they're called, and notifies Watchexec that it + /// should read the configuration again. + pub(crate) change_signal: Arc, + + /// The main handler to define: what to do when an action is triggered. /// - /// This notably includes the path set to be watched. - pub fs: crate::fs::WorkingData, - - /// Working data for keyboard event sources. - pub keyboard: crate::keyboard::WorkingData, - - /// Working data for the action processing. + /// This handler is called with the [`Action`] environment, look at its doc for more detail. /// - /// This is the task responsible for scheduling the actions in response to events, applying the - /// filtering, etc. - pub action: crate::action::WorkingData, -} - -impl RuntimeConfig { - /// Set the pathset to be watched. - pub fn pathset(&mut self, pathset: I) -> &mut Self - where - I: IntoIterator, - P: AsRef, - { - self.fs.pathset = pathset.into_iter().map(|p| p.as_ref().into()).collect(); - debug!(pathset=?self.fs.pathset, "RuntimeConfig: pathset"); - self - } - - /// Set the file watcher type to use. - pub fn file_watcher(&mut self, watcher: Watcher) -> &mut Self { - debug!(?watcher, "RuntimeConfig: watcher"); - self.fs.watcher = watcher; - self - } - - /// Enable monitoring of 'end of file' from stdin - pub fn keyboard_emit_eof(&mut self, enable: bool) -> &mut Self { - self.keyboard.eof = enable; - self - } - - /// Set the action throttle. - pub fn action_throttle(&mut self, throttle: impl Into) -> &mut Self { - self.action.throttle = throttle.into(); - debug!(throttle=?self.action.throttle, "RuntimeConfig: throttle"); - self - } - - /// Toggle whether to use process groups or not. - pub fn command_grouped(&mut self, grouped: bool) -> &mut Self { - debug!(?grouped, "RuntimeConfig: command_grouped"); - self.action.grouped = grouped; - self - } - - /// Set a single command to run on action. + /// If this handler is not provided, or does nothing, Watchexec in turn will do nothing, not + /// even quit. Hence, you really need to provide a handler. This is enforced when using + /// [`Watchexec::new()`], but not when using [`Watchexec::default()`]. /// - /// This is a convenience for `.commands(vec![Command...])`. - pub fn command(&mut self, command: Command) -> &mut Self { - debug!(?command, "RuntimeConfig: command"); - self.action.commands = vec![command]; - self - } + /// It is possible to change the handler or any other configuration inside the previous handler. + /// This and other handlers are fetched "just in time" when needed, so changes to handlers can + /// appear instant, or may lag a little depending on lock contention, but a handler being called + /// does not hold its lock. A handler changing while it's being called doesn't affect the run of + /// a previous version of the handler: it will neither be stopped nor retried with the new code. + /// + /// It is important for this handler to return quickly: avoid performing blocking work in it. + /// This is true for all handlers, but especially for this one, as it will block the event loop + /// and you'll find that the internal event queues quickly fill up and it all grinds to a halt. + /// Spawn threads or tasks, or use channels or other async primitives to communicate with your + /// expensive code. + pub action_handler: ChangeableFn, - /// Set the commands to run on action. - pub fn commands(&mut self, commands: impl Into>) -> &mut Self { - self.action.commands = commands.into(); - debug!(commands=?self.action.commands, "RuntimeConfig: commands"); - self - } - - /// Set the filterer implementation to use. - pub fn filterer(&mut self, filterer: Arc) -> &mut Self { - debug!(?filterer, "RuntimeConfig: filterer"); - self.action.filterer = filterer; - self - } - - /// Set the action handler. - pub fn on_action(&mut self, handler: impl Handler + Send + 'static) -> &mut Self { - debug!("RuntimeConfig: on_action"); - self.action.action_handler = HandlerLock::new(Box::new(handler)); - self - } - - /// Set the pre-spawn handler. - pub fn on_pre_spawn(&mut self, handler: impl Handler + Send + 'static) -> &mut Self { - debug!("RuntimeConfig: on_pre_spawn"); - self.action.pre_spawn_handler = HandlerLock::new(Box::new(handler)); - self - } - - /// Set the post-spawn handler. - pub fn on_post_spawn( - &mut self, - handler: impl Handler + Send + 'static, - ) -> &mut Self { - debug!("RuntimeConfig: on_post_spawn"); - self.action.post_spawn_handler = HandlerLock::new(Box::new(handler)); - self - } -} - -/// Initialisation configuration for [`Watchexec`][crate::Watchexec]. -/// -/// This is used only for constructing the instance. -/// -/// Use [`InitConfig::default()`] to build a new one, and the inherent methods to change values. -/// This struct is marked non-exhaustive such that new options may be added without breaking change. -#[non_exhaustive] -pub struct InitConfig { /// Runtime error handler. /// - /// This is run on every runtime error that occurs within watchexec. By default the placeholder - /// `()` handler is used, which discards all errors. - /// - /// If the handler errors, [_that_ error][crate::error::RuntimeError::Handler] is immediately - /// given to the handler. If this second handler call errors as well, its error is ignored. - /// - /// Also see the [`ErrorHook`] documentation for returning critical errors from this handler. + /// This is run on every runtime error that occurs within Watchexec. The default handler + /// is a no-op. /// /// # Examples /// + /// Set the error handler: + /// /// ``` - /// # use std::convert::Infallible; - /// # use watchexec::{config::InitConfig, ErrorHook}; - /// let mut init = InitConfig::default(); - /// init.on_error(|err: ErrorHook| async move { + /// # use watchexec::{config::Config, ErrorHook}; + /// let mut config = Config::default(); + /// config.on_error(|err: ErrorHook| { /// tracing::error!("{}", err.error); - /// Ok::<(), Infallible>(()) /// }); /// ``` - pub error_handler: Box + Send>, + /// + /// Output a critical error (which will terminate Watchexec): + /// + /// ``` + /// # use watchexec::{config::Config, ErrorHook, error::{CriticalError, RuntimeError}}; + /// let mut config = Config::default(); + /// config.on_error(|err: ErrorHook| { + /// tracing::error!("{}", err.error); + /// + /// if matches!(err.error, RuntimeError::FsWatcher { .. }) { + /// err.critical(CriticalError::External("fs watcher failed".into())); + /// } + /// }); + /// ``` + /// + /// Elevate a runtime error to critical (will preserve the error information): + /// + /// ``` + /// # use watchexec::{config::Config, ErrorHook, error::RuntimeError}; + /// let mut config = Config::default(); + /// config.on_error(|err: ErrorHook| { + /// tracing::error!("{}", err.error); + /// + /// if matches!(err.error, RuntimeError::FsWatcher { .. }) { + /// err.elevate(); + /// } + /// }); + /// ``` + /// + /// It is important for this to return quickly: avoid performing blocking work. Locking and + /// writing to stdio is fine, but waiting on the network is a bad idea. Of course, an + /// asynchronous log writer or separate UI thread is always a better idea than `println!` if + /// have that ability. + pub error_handler: ChangeableFn, - /// Internal: the buffer size of the channel which carries runtime errors. + /// The set of filesystem paths to be watched. + /// + /// If this is non-empty, the filesystem event source is started and configured to provide + /// events for these paths. If it becomes empty, the filesystem event source is shut down. + pub pathset: Changeable>, + + /// The kind of filesystem watcher to be used. + pub file_watcher: Changeable, + + /// Watch stdin and emit events when input comes in over the keyboard. + /// + /// If this is true, the keyboard event source is started and configured to report when input + /// is received on stdin. If it becomes false, the keyboard event source is shut down and stdin + /// may flow to commands again. + /// + /// Currently only EOF is watched for and emitted. + pub keyboard_events: Changeable, + + /// How long to wait for events to build up before executing an action. + /// + /// This is sometimes called "debouncing." We debounce on the trailing edge: an action is + /// triggered only after that amount of time has passed since the first event in the cycle. The + /// action is called with all the collected events in the cycle. + /// + /// Default is 50ms. + pub throttle: Changeable, + + /// The filterer implementation to use when filtering events. + /// + /// The default is a no-op, which will always pass every event. + pub filterer: ChangeableFilterer, + + /// The buffer size of the channel which carries runtime errors. /// /// The default (64) is usually fine. If you expect a much larger throughput of runtime errors, /// or if your `error_handler` is slow, adjusting this value may help. + /// + /// This is unchangeable at runtime and must be set before Watchexec instantiation. pub error_channel_size: usize, - /// Internal: the buffer size of the channel which carries events. + /// The buffer size of the channel which carries events. /// - /// The default (1024) is usually fine. If you expect a much larger throughput of events, + /// The default (4096) is usually fine. If you expect a much larger throughput of events, /// adjusting this value may help. + /// + /// This is unchangeable at runtime and must be set before Watchexec instantiation. pub event_channel_size: usize, } -impl Default for InitConfig { +impl Default for Config { fn default() -> Self { Self { - error_handler: Box::new(()) as _, + change_signal: Default::default(), + action_handler: ChangeableFn::new(ActionReturn::Sync), + error_handler: Default::default(), + pathset: Default::default(), + file_watcher: Default::default(), + keyboard_events: Default::default(), + throttle: Changeable::new(Duration::from_millis(50)), + filterer: Default::default(), error_channel_size: 64, - event_channel_size: 1024, + event_channel_size: 4096, } } } -impl InitConfig { +impl Config { + /// Signal that the configuration has changed. + /// + /// This is called automatically by all other methods here, so most of the time calling this + /// isn't needed, but it can be useful for some advanced uses. + pub fn signal_change(&self) -> &Self { + self.change_signal.notify_waiters(); + self + } + + /// Watch the config for a change, but run once first. + /// + /// This returns a Stream where the first value is available immediately, and then every + /// subsequent one is from a change signal for this Config. + #[must_use] + pub(crate) fn watch(&self) -> ConfigWatched { + ConfigWatched::new(self.change_signal.clone()) + } + + /// Set the pathset to be watched. + pub fn pathset(&self, pathset: I) -> &Self + where + I: IntoIterator, + P: AsRef, + { + let pathset = pathset.into_iter().map(|p| p.as_ref().into()).collect(); + debug!(?pathset, "Config: pathset"); + self.pathset.replace(pathset); + self.signal_change() + } + + /// Set the file watcher type to use. + pub fn file_watcher(&self, watcher: Watcher) -> &Self { + debug!(?watcher, "Config: file watcher"); + self.file_watcher.replace(watcher); + self.signal_change() + } + + /// Enable keyboard/stdin event source. + pub fn keyboard_events(&self, enable: bool) -> &Self { + debug!(?enable, "Config: keyboard"); + self.keyboard_events.replace(enable); + self.signal_change() + } + + /// Set the throttle. + pub fn throttle(&self, throttle: impl Into) -> &Self { + let throttle = throttle.into(); + debug!(?throttle, "Config: throttle"); + self.throttle.replace(throttle); + self.signal_change() + } + + /// Set the filterer implementation to use. + pub fn filterer(&self, filterer: impl Filterer + Send + Sync + 'static) -> &Self { + debug!(?filterer, "Config: filterer"); + self.filterer.replace(filterer); + self.signal_change() + } + /// Set the runtime error handler. - /// - /// See the [documentation on the field](InitConfig#structfield.error_handler) for more details. - pub fn on_error(&mut self, handler: impl Handler + Send + 'static) -> &mut Self { - debug!("InitConfig: on_error"); - self.error_handler = Box::new(handler) as _; - self + pub fn on_error(&self, handler: impl Fn(ErrorHook) + Send + Sync + 'static) -> &Self { + debug!("Config: on_error"); + self.error_handler.replace(handler); + self.signal_change() } - /// Set the buffer size of the channel which carries runtime errors. - /// - /// See the [documentation on the field](InitConfig#structfield.error_channel_size) for more details. - pub fn error_channel_size(&mut self, size: usize) -> &mut Self { - debug!(?size, "InitConfig: error_channel_size"); - self.error_channel_size = size; - self + /// Set the action handler. + pub fn on_action( + &self, + handler: impl (Fn(ActionHandler) -> ActionHandler) + Send + Sync + 'static, + ) -> &Self { + debug!("Config: on_action"); + self.action_handler + .replace(move |action| ActionReturn::Sync(handler(action))); + self.signal_change() } - /// Set the buffer size of the channel which carries events. - /// - /// See the [documentation on the field](InitConfig#structfield.event_channel_size) for more details. - pub fn event_channel_size(&mut self, size: usize) -> &mut Self { - debug!(?size, "InitConfig: event_channel_size"); - self.event_channel_size = size; - self + /// Set the action handler to a future-returning closure. + pub fn on_action_async( + &self, + handler: impl (Fn(ActionHandler) -> Box + Send + Sync>) + + Send + + Sync + + 'static, + ) -> &Self { + debug!("Config: on_action_async"); + self.action_handler + .replace(move |action| ActionReturn::Async(handler(action))); + self.signal_change() } } -impl fmt::Debug for InitConfig { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("InitConfig") - .field("error_channel_size", &self.error_channel_size) - .field("event_channel_size", &self.event_channel_size) - .finish_non_exhaustive() +#[derive(Debug)] +pub(crate) struct ConfigWatched { + first_run: bool, + notify: Arc, +} + +impl ConfigWatched { + fn new(notify: Arc) -> Self { + let notified = notify.notified(); + pin!(notified).as_mut().enable(); + + Self { + first_run: true, + notify, + } + } + + pub async fn next(&mut self) { + let notified = self.notify.notified(); + let mut notified = pin!(notified); + notified.as_mut().enable(); + + if self.first_run { + trace!("ConfigWatched: first run"); + self.first_run = false; + } else { + trace!(?notified, "ConfigWatched: waiting for change"); + // there's a bit of a gotcha where any config changes made after a Notified resolves + // but before a new one is issued will not be caught. not sure how to fix that yet. + notified.await; + } } } diff --git a/crates/lib/src/error/critical.rs b/crates/lib/src/error/critical.rs index 873bfdb..1e5844b 100644 --- a/crates/lib/src/error/critical.rs +++ b/crates/lib/src/error/critical.rs @@ -1,19 +1,17 @@ use miette::Diagnostic; use thiserror::Error; use tokio::{sync::mpsc, task::JoinError}; +use watchexec_events::{Event, Priority}; -use crate::event::{Event, Priority}; - -use super::RuntimeError; +use super::{FsWatcherError, RuntimeError}; +use crate::sources::fs::Watcher; /// Errors which are not recoverable and stop watchexec execution. #[derive(Debug, Diagnostic, Error)] #[non_exhaustive] -#[diagnostic(url(docsrs))] pub enum CriticalError { /// Pseudo-error used to signal a graceful exit. #[error("this should never be printed (exit)")] - #[diagnostic(code(watchexec::runtime::exit))] Exit, /// For custom critical errors. @@ -21,16 +19,12 @@ pub enum CriticalError { /// This should be used for errors by external code which are not covered by the other error /// types; watchexec-internal errors should never use this. #[error("external(critical): {0}")] - #[diagnostic(code(watchexec::critical::external))] External(#[from] Box), /// For elevated runtime errors. /// - /// This should be used for runtime errors elevated to critical. This currently does not happen - /// in watchexec, but it is possible in the future. This variant is useful with the `on_error` - /// runtime error handler; see [`ErrorHook`](crate::ErrorHook). + /// This is used for runtime errors elevated to critical. #[error("a runtime error is too serious for the process to continue")] - #[diagnostic(code(watchexec::critical::elevated_runtime), help("{help:?}"))] Elevated { /// The runtime error to be elevated. #[source] @@ -42,7 +36,6 @@ pub enum CriticalError { /// A critical I/O error occurred. #[error("io({about}): {err}")] - #[diagnostic(code(watchexec::critical::io_error))] IoError { /// What it was about. about: &'static str, @@ -54,23 +47,26 @@ pub enum CriticalError { /// Error received when a runtime error cannot be sent to the errors channel. #[error("cannot send internal runtime error: {0}")] - #[diagnostic(code(watchexec::critical::error_channel_send))] ErrorChannelSend(#[from] mpsc::error::SendError), /// Error received when an event cannot be sent to the events channel. #[error("cannot send event to internal channel: {0}")] - #[diagnostic(code(watchexec::critical::event_channel_send))] EventChannelSend(#[from] async_priority_channel::SendError<(Event, Priority)>), /// Error received when joining the main watchexec task. #[error("main task join: {0}")] - #[diagnostic(code(watchexec::critical::main_task_join))] MainTaskJoin(#[source] JoinError), - /// Error received when a handler is missing on initialisation. + /// Error received when the filesystem watcher can't initialise. /// - /// This is a **bug** and should be reported. - #[error("internal: missing handler on init")] - #[diagnostic(code(watchexec::critical::internal::missing_handler))] - MissingHandler, + /// In theory this is recoverable but in practice it's generally not, so we treat it as critical. + #[error("fs: cannot initialise {kind:?} watcher")] + FsWatcherInit { + /// The kind of watcher. + kind: Watcher, + + /// The error which occurred. + #[source] + err: FsWatcherError, + }, } diff --git a/crates/lib/src/error/runtime.rs b/crates/lib/src/error/runtime.rs index 7a70a11..a2baff8 100644 --- a/crates/lib/src/error/runtime.rs +++ b/crates/lib/src/error/runtime.rs @@ -1,40 +1,32 @@ use miette::Diagnostic; use thiserror::Error; +use watchexec_events::{Event, Priority}; use watchexec_signals::Signal; -use crate::{ - event::{Event, Priority}, - fs::Watcher, -}; +use crate::sources::fs::Watcher; /// Errors which _may_ be recoverable, transient, or only affect a part of the operation, and should /// be reported to the user and/or acted upon programmatically, but will not outright stop watchexec. /// -/// Some errors that are classified here are spurious and may be ignored; in general you should not -/// use the convenience print handlers for handling these errors beyond prototyping. For example, +/// Some errors that are classified here are spurious and may be ignored. For example, /// "waiting on process" errors should not be printed to the user by default: /// /// ``` -/// # use std::convert::Infallible; /// # use tracing::error; -/// # use watchexec::{config::InitConfig, ErrorHook, error::RuntimeError, handler::SyncFnHandler}; -/// # let mut config = InitConfig::default(); -/// config.on_error(SyncFnHandler::from( -/// |err: ErrorHook| -> std::result::Result<(), Infallible> { -/// if let RuntimeError::IoError { -/// about: "waiting on process group", -/// .. -/// } = err.error -/// { -/// error!("{}", err.error); -/// return Ok(()); -/// } +/// # use watchexec::{Config, ErrorHook, error::RuntimeError}; +/// # let mut config = Config::default(); +/// config.on_error(|err: ErrorHook| { +/// if let RuntimeError::IoError { +/// about: "waiting on process group", +/// .. +/// } = err.error +/// { +/// error!("{}", err.error); +/// return; +/// } /// -/// // ... -/// -/// Ok(()) -/// }, -/// )); +/// // ... +/// }); /// ``` /// /// On the other hand, some errors may not be fatal to this library's understanding, but will be to @@ -42,36 +34,28 @@ use crate::{ /// to [`CriticalError`](super::CriticalError)s: /// /// ``` -/// # use std::convert::Infallible; -/// # use watchexec::{config::InitConfig, ErrorHook, error::{RuntimeError, FsWatcherError}, handler::SyncFnHandler}; -/// # let mut config = InitConfig::default(); -/// config.on_error(SyncFnHandler::from( -/// |err: ErrorHook| -> std::result::Result<(), Infallible> { -/// if let RuntimeError::FsWatcher { -/// err: -/// FsWatcherError::Create { .. } -/// | FsWatcherError::TooManyWatches { .. } -/// | FsWatcherError::TooManyHandles { .. }, -/// .. -/// } = err.error -/// { -/// err.elevate(); -/// return Ok(()); -/// } +/// # use watchexec::{Config, ErrorHook, error::{RuntimeError, FsWatcherError}}; +/// # let mut config = Config::default(); +/// config.on_error(|err: ErrorHook| { +/// if let RuntimeError::FsWatcher { +/// err: +/// FsWatcherError::Create { .. } +/// | FsWatcherError::TooManyWatches { .. } +/// | FsWatcherError::TooManyHandles { .. }, +/// .. +/// } = err.error { +/// err.elevate(); +/// return; +/// } /// -/// // ... -/// -/// Ok(()) -/// }, -/// )); +/// // ... +/// }); /// ``` #[derive(Debug, Diagnostic, Error)] #[non_exhaustive] -#[diagnostic(url(docsrs))] pub enum RuntimeError { /// Pseudo-error used to signal a graceful exit. #[error("this should never be printed (exit)")] - #[diagnostic(code(watchexec::runtime::exit))] Exit, /// For custom runtime errors. @@ -79,12 +63,10 @@ pub enum RuntimeError { /// This should be used for errors by external code which are not covered by the other error /// types; watchexec-internal errors should never use this. #[error("external(runtime): {0}")] - #[diagnostic(code(watchexec::runtime::external))] External(#[from] Box), /// Generic I/O error, with some context. #[error("io({about}): {err}")] - #[diagnostic(code(watchexec::runtime::io_error))] IoError { /// What it was about. about: &'static str, @@ -96,7 +78,6 @@ pub enum RuntimeError { /// Events from the filesystem watcher event source. #[error("{kind:?} fs watcher error")] - #[diagnostic(code(watchexec::runtime::fs_watcher))] FsWatcher { /// The kind of watcher that failed to instantiate. kind: Watcher, @@ -108,7 +89,6 @@ pub enum RuntimeError { /// Events from the keyboard event source #[error("keyboard watcher error")] - #[diagnostic(code(watchexec::runtime::keyboard_watcher))] KeyboardWatcher { /// The underlying error. #[source] @@ -117,12 +97,10 @@ pub enum RuntimeError { /// Opaque internal error from a command supervisor. #[error("internal: command supervisor: {0}")] - #[diagnostic(code(watchexec::runtime::internal_supervisor))] InternalSupervisor(String), /// Error received when an event cannot be sent to the event channel. #[error("cannot send event from {ctx}: {err}")] - #[diagnostic(code(watchexec::runtime::event_channel_send))] EventChannelSend { /// The context in which this error happened. /// @@ -136,7 +114,6 @@ pub enum RuntimeError { /// Error received when an event cannot be sent to the event channel. #[error("cannot send event from {ctx}: {err}")] - #[diagnostic(code(watchexec::runtime::event_channel_try_send))] EventChannelTrySend { /// The context in which this error happened. /// @@ -152,7 +129,6 @@ pub enum RuntimeError { /// /// The error is completely opaque, having been flattened into a string at the error point. #[error("handler error while {ctx}: {err}")] - #[diagnostic(code(watchexec::runtime::handler))] Handler { /// The context in which this error happened. /// @@ -165,17 +141,14 @@ pub enum RuntimeError { /// Error received when a [`Handler`][crate::handler::Handler] which has been passed a lock has kept that lock open after the handler has completed. #[error("{0} handler returned while holding a lock alive")] - #[diagnostic(code(watchexec::runtime::handler_lock_held))] HandlerLockHeld(&'static str), /// Error received when operating on a process. #[error("when operating on process: {0}")] - #[diagnostic(code(watchexec::runtime::process))] Process(#[source] std::io::Error), /// Error received when a process did not start correctly, or finished before we could even tell. #[error("process was dead on arrival")] - #[diagnostic(code(watchexec::runtime::process_doa))] ProcessDeadOnArrival, /// Error received when a [`Signal`] is unsupported @@ -183,38 +156,28 @@ pub enum RuntimeError { /// This may happen if the signal is not supported on the current platform, or if Watchexec /// doesn't support sending the signal. #[error("unsupported signal: {0:?}")] - #[diagnostic(code(watchexec::runtime::unsupported_signal))] UnsupportedSignal(Signal), /// Error received when there are no commands to run. /// /// This is generally a programmer error and should be caught earlier. #[error("no commands to run")] - #[diagnostic(code(watchexec::runtime::no_commands))] NoCommands, /// Error received when trying to render a [`Command::Shell`](crate::command::Command) that has no `command` /// /// This is generally a programmer error and should be caught earlier. #[error("empty shelled command")] - #[diagnostic(code(watchexec::runtime::command_shell::empty_command))] CommandShellEmptyCommand, /// Error received when trying to render a [`Shell::Unix`](crate::command::Shell) with an empty shell /// /// This is generally a programmer error and should be caught earlier. #[error("empty shell program")] - #[diagnostic(code(watchexec::runtime::command_shell::empty_shell))] CommandShellEmptyShell, - /// Error received when clearing the screen. - #[error("clear screen: {0}")] - #[diagnostic(code(watchexec::runtime::clearscreen))] - Clearscreen(#[from] clearscreen::Error), - /// Error received from the [`ignore-files`](ignore_files) crate. #[error("ignore files: {0}")] - #[diagnostic(code(watchexec::runtime::ignore_files))] IgnoreFiles( #[diagnostic_source] #[from] @@ -223,7 +186,6 @@ pub enum RuntimeError { /// Error emitted by a [`Filterer`](crate::filter::Filterer). #[error("{kind} filterer: {err}")] - #[diagnostic(code(watchexec::runtime::filterer))] Filterer { /// The kind of filterer that failed. /// diff --git a/crates/lib/src/error/specialised.rs b/crates/lib/src/error/specialised.rs index 858c1a4..32f3e81 100644 --- a/crates/lib/src/error/specialised.rs +++ b/crates/lib/src/error/specialised.rs @@ -2,58 +2,22 @@ use std::path::PathBuf; use miette::Diagnostic; use thiserror::Error; -use tokio::sync::watch; - -use crate::{action, fs, keyboard}; - -// compatibility re-export -#[deprecated( - note = "use the `watchexec_signals` crate directly instead", - since = "2.2.0" -)] -pub use watchexec_signals::SignalParseError; - -/// Errors occurring from reconfigs. -#[derive(Debug, Diagnostic, Error)] -#[non_exhaustive] -#[diagnostic(url(docsrs))] -pub enum ReconfigError { - /// Error received when the action processor cannot be updated. - #[error("reconfig: action watch: {0}")] - #[diagnostic(code(watchexec::reconfig::action_watch))] - ActionWatch(#[from] watch::error::SendError), - - /// Error received when the fs event source cannot be updated. - #[error("reconfig: fs watch: {0}")] - #[diagnostic(code(watchexec::reconfig::fs_watch))] - FsWatch(#[from] watch::error::SendError), - - /// Error received when the keyboard event source cannot be updated. - #[error("reconfig: keyboard watch: {0}")] - #[diagnostic(code(watchexec::reconfig::keyboard_watch))] - KeyboardWatch(#[from] watch::error::SendError), -} /// Errors emitted by the filesystem watcher. #[derive(Debug, Diagnostic, Error)] #[non_exhaustive] -#[diagnostic(url(docsrs))] pub enum FsWatcherError { /// Error received when creating a filesystem watcher fails. /// /// Also see `TooManyWatches` and `TooManyHandles`. #[error("failed to instantiate")] - #[diagnostic( - code(watchexec::fs_watcher::create), - help("perhaps retry with the poll watcher") - )] + #[diagnostic(help("perhaps retry with the poll watcher"))] Create(#[source] notify::Error), /// Error received when creating or updating a filesystem watcher fails because there are too many watches. /// /// This is the OS error 28 on Linux. #[error("failed to instantiate: too many watches")] - #[diagnostic(code(watchexec::fs_watcher::too_many_watches))] #[cfg_attr(target_os = "linux", diagnostic(help("you will want to increase your inotify.max_user_watches, see inotify(7) and https://watchexec.github.io/docs/inotify-limits.html")))] #[cfg_attr( not(target_os = "linux"), @@ -65,7 +29,6 @@ pub enum FsWatcherError { /// /// This is the OS error 24 on Linux. It may also occur when the limit for inotify instances is reached. #[error("failed to instantiate: too many handles")] - #[diagnostic(code(watchexec::fs_watcher::too_many_handles))] #[cfg_attr(target_os = "linux", diagnostic(help("you will want to increase your `nofile` limit, see pam_limits(8); or increase your inotify.max_user_instances, see inotify(7) and https://watchexec.github.io/docs/inotify-limits.html")))] #[cfg_attr( not(target_os = "linux"), @@ -75,12 +38,10 @@ pub enum FsWatcherError { /// Error received when reading a filesystem event fails. #[error("received an event that we could not read")] - #[diagnostic(code(watchexec::fs_watcher::event))] Event(#[source] notify::Error), /// Error received when adding to the pathset for the filesystem watcher fails. #[error("while adding {path:?}")] - #[diagnostic(code(watchexec::fs_watcher::path_add))] PathAdd { /// The path that was attempted to be added. path: PathBuf, @@ -92,7 +53,6 @@ pub enum FsWatcherError { /// Error received when removing from the pathset for the filesystem watcher fails. #[error("while removing {path:?}")] - #[diagnostic(code(watchexec::fs_watcher::path_remove))] PathRemove { /// The path that was attempted to be removed. path: PathBuf, @@ -106,10 +66,8 @@ pub enum FsWatcherError { /// Errors emitted by the keyboard watcher. #[derive(Debug, Diagnostic, Error)] #[non_exhaustive] -#[diagnostic(url(docsrs))] pub enum KeyboardWatcherError { /// Error received when shutting down stdin watcher fails. #[error("failed to shut down stdin watcher")] - #[diagnostic(code(watchexec::keyboard_watcher))] StdinShutdown, } diff --git a/crates/lib/src/filter.rs b/crates/lib/src/filter.rs index 5afc60f..84491bb 100644 --- a/crates/lib/src/filter.rs +++ b/crates/lib/src/filter.rs @@ -1,11 +1,10 @@ //! The `Filterer` trait for event filtering. -use std::sync::Arc; +use std::{fmt, sync::Arc}; -use crate::{ - error::RuntimeError, - event::{Event, Priority}, -}; +use watchexec_events::{Event, Priority}; + +use crate::{changeable::Changeable, error::RuntimeError}; /// An interface for filtering events. pub trait Filterer: std::fmt::Debug + Send + Sync { @@ -33,3 +32,43 @@ impl Filterer for Arc { Self::as_ref(self).check_event(event, priority) } } + +/// A shareable `Filterer` that doesn't hold a lock when it is called. +/// +/// This is a specialisation of [`Changeable`] for `Filterer`. +pub struct ChangeableFilterer(Changeable>); +impl ChangeableFilterer { + /// Replace the filterer with a new one. + /// + /// Panics if the lock was poisoned. + pub fn replace(&self, new: impl Filterer + Send + Sync + 'static) { + self.0.replace(Arc::new(new)); + } +} + +impl Filterer for ChangeableFilterer { + fn check_event(&self, event: &Event, priority: Priority) -> Result { + Arc::as_ref(&self.0.get()).check_event(event, priority) + } +} + +// the derive adds a T: Clone bound +impl Clone for ChangeableFilterer { + fn clone(&self) -> Self { + Self(Changeable::clone(&self.0)) + } +} + +impl Default for ChangeableFilterer { + fn default() -> Self { + Self(Changeable::new(Arc::new(()))) + } +} + +impl fmt::Debug for ChangeableFilterer { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("ChangeableFilterer") + .field("filterer", &format!("{:?}", self.0.get())) + .finish_non_exhaustive() + } +} diff --git a/crates/lib/src/handler.rs b/crates/lib/src/handler.rs deleted file mode 100644 index b2c12ab..0000000 --- a/crates/lib/src/handler.rs +++ /dev/null @@ -1,264 +0,0 @@ -//! Trait and implementations for hook handlers. -//! -//! You can implement the trait yourself, or use any of the provided implementations: -//! - for closures, -//! - for std and tokio channels, -//! - for printing to writers, in `Debug` and `Display` (where supported) modes (generally used for -//! debugging and testing, as they don't allow any other output customisation), -//! - for `()`, as placeholder. -//! -//! The implementation for [`FnMut`] only supports fns that return a [`Future`]. Unfortunately -//! it's not possible to provide an implementation for fns that don't return a `Future` as well, -//! so to call sync code you must either provide an async handler, or use the [`SyncFnHandler`] -//! wrapper. -//! -//! # Examples -//! -//! In each example `on_data` is the following function: -//! -//! ``` -//! # use watchexec::handler::Handler; -//! fn on_data>>(_: T) {} -//! ``` -//! -//! Async closure: -//! -//! ``` -//! use tokio::io::{AsyncWriteExt, stdout}; -//! # use watchexec::handler::Handler; -//! # fn on_data>>(_: T) {} -//! on_data(|data: Vec| async move { -//! stdout().write_all(&data).await -//! }); -//! ``` -//! -//! Sync code in async closure: -//! -//! ``` -//! use std::io::{Write, stdout}; -//! # use watchexec::handler::Handler; -//! # fn on_data>>(_: T) {} -//! on_data(|data: Vec| async move { -//! stdout().write_all(&data) -//! }); -//! ``` -//! -//! Sync closure with wrapper: -//! -//! ``` -//! use std::io::{Write, stdout}; -//! # use watchexec::handler::{Handler, SyncFnHandler}; -//! # fn on_data>>(_: T) {} -//! on_data(SyncFnHandler::from(|data: Vec| { -//! stdout().write_all(&data) -//! })); -//! ``` -//! -//! Std channel: -//! -//! ``` -//! use std::sync::mpsc; -//! # use watchexec::handler::Handler; -//! # fn on_data>>(_: T) {} -//! let (s, r) = mpsc::channel(); -//! on_data(s); -//! ``` -//! -//! Tokio channel: -//! -//! ``` -//! use tokio::sync::mpsc; -//! # use watchexec::handler::Handler; -//! # fn on_data>>(_: T) {} -//! let (s, r) = mpsc::channel(123); -//! on_data(s); -//! ``` -//! -//! Printing to console: -//! -//! ``` -//! use std::io::{Write, stderr, stdout}; -//! # use watchexec::handler::{Handler, PrintDebug, PrintDisplay}; -//! # fn on_data>(_: T) {} -//! on_data(PrintDebug(stdout())); -//! on_data(PrintDisplay(stderr())); -//! ``` - -use std::{error::Error, future::Future, io::Write, marker::PhantomData, sync::Arc}; - -use tokio::{runtime::Handle, sync::Mutex, task::block_in_place}; - -use crate::error::RuntimeError; - -/// A callable that can be used to hook into watchexec. -pub trait Handler { - /// Call the handler with the given data. - fn handle(&mut self, _data: T) -> Result<(), Box>; -} - -/// A shareable wrapper for a [`Handler`]. -/// -/// Internally this is a Tokio [`Mutex`]. -pub struct HandlerLock(Arc + Send>>>); -impl HandlerLock -where - T: Send, -{ - /// Wrap a [`Handler`] into a lock. - #[must_use] - pub fn new(handler: Box + Send>) -> Self { - Self(Arc::new(Mutex::new(handler))) - } - - /// Replace the handler with a new one. - pub async fn replace(&self, new: Box + Send>) { - let mut handler = self.0.lock().await; - *handler = new; - } - - /// Call the handler. - pub async fn call(&self, data: T) -> Result<(), Box> { - let mut handler = self.0.lock().await; - handler.handle(data) - } -} - -impl Clone for HandlerLock { - fn clone(&self) -> Self { - Self(Arc::clone(&self.0)) - } -} - -impl Default for HandlerLock -where - T: Send, -{ - fn default() -> Self { - Self::new(Box::new(())) - } -} - -pub(crate) fn rte(ctx: &'static str, err: &dyn Error) -> RuntimeError { - RuntimeError::Handler { - ctx, - err: err.to_string(), - } -} - -/// Wrapper for [`Handler`]s that are non-future [`FnMut`]s. -/// -/// Construct using [`Into::into`]: -/// -/// ``` -/// # use watchexec::handler::{Handler as _, SyncFnHandler}; -/// # let f: SyncFnHandler<(), std::io::Error, _> = -/// (|data| { dbg!(data); Ok(()) }).into() -/// # ; -/// ``` -/// -/// or [`From::from`]: -/// -/// ``` -/// # use watchexec::handler::{Handler as _, SyncFnHandler}; -/// # let f: SyncFnHandler<(), std::io::Error, _> = -/// SyncFnHandler::from(|data| { dbg!(data); Ok(()) }); -/// ``` -pub struct SyncFnHandler -where - E: Error + 'static, - F: FnMut(T) -> Result<(), E> + Send + 'static, -{ - inner: F, - _t: PhantomData, - _e: PhantomData, -} - -impl From for SyncFnHandler -where - E: Error + 'static, - F: FnMut(T) -> Result<(), E> + Send + 'static, -{ - fn from(inner: F) -> Self { - Self { - inner, - _t: PhantomData, - _e: PhantomData, - } - } -} - -impl Handler for SyncFnHandler -where - E: Error + 'static, - F: FnMut(T) -> Result<(), E> + Send + 'static, -{ - fn handle(&mut self, data: T) -> Result<(), Box> { - (self.inner)(data).map_err(|e| Box::new(e) as _) - } -} - -impl Handler for F -where - E: Error + 'static, - F: FnMut(T) -> U + Send + 'static, - U: Future>, -{ - fn handle(&mut self, data: T) -> Result<(), Box> { - // this will always be called within watchexec context, which runs within tokio - block_in_place(|| { - Handle::current() - .block_on((self)(data)) - .map_err(|e| Box::new(e) as _) - }) - } -} - -impl Handler for () { - fn handle(&mut self, _data: T) -> Result<(), Box> { - Ok::<(), std::convert::Infallible>(()).map_err(|e| Box::new(e) as _) - } -} - -impl Handler for std::sync::mpsc::Sender -where - T: Send + 'static, -{ - fn handle(&mut self, data: T) -> Result<(), Box> { - self.send(data).map_err(|e| Box::new(e) as _) - } -} - -impl Handler for tokio::sync::mpsc::Sender -where - T: std::fmt::Debug + 'static, -{ - fn handle(&mut self, data: T) -> Result<(), Box> { - self.try_send(data).map_err(|e| Box::new(e) as _) - } -} - -/// A handler implementation to print to any [`Write`]r (e.g. stdout) in `Debug` format. -pub struct PrintDebug(pub W); - -impl Handler for PrintDebug -where - T: std::fmt::Debug, - W: Write, -{ - fn handle(&mut self, data: T) -> Result<(), Box> { - writeln!(self.0, "{data:?}").map_err(|e| Box::new(e) as _) - } -} - -/// A handler implementation to print to any [`Write`]r (e.g. stdout) in `Display` format. -pub struct PrintDisplay(pub W); - -impl Handler for PrintDisplay -where - T: std::fmt::Display, - W: Write, -{ - fn handle(&mut self, data: T) -> Result<(), Box> { - writeln!(self.0, "{data}").map_err(|e| Box::new(e) as _) - } -} diff --git a/crates/lib/src/id.rs b/crates/lib/src/id.rs new file mode 100644 index 0000000..b358a70 --- /dev/null +++ b/crates/lib/src/id.rs @@ -0,0 +1,67 @@ +use std::{cell::Cell, num::NonZeroU64}; + +/// Unique opaque identifier. +#[must_use] +#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy)] +pub struct Id { + thread: NonZeroU64, + counter: u64, +} + +thread_local! { + static COUNTER: Cell = const { Cell::new(0) }; +} + +impl Default for Id { + fn default() -> Self { + let counter = COUNTER.get(); + COUNTER.set(counter.wrapping_add(1)); + + Self { + thread: threadid(), + counter, + } + } +} + +fn threadid() -> NonZeroU64 { + use std::hash::{Hash, Hasher}; + + struct Extractor { + id: u64, + } + + impl Hasher for Extractor { + fn finish(&self) -> u64 { + self.id + } + + fn write(&mut self, _bytes: &[u8]) {} + fn write_u64(&mut self, n: u64) { + self.id = n; + } + } + + let mut ex = Extractor { id: 0 }; + std::thread::current().id().hash(&mut ex); + + // SAFETY: guaranteed to be > 0 + // safeguarded by the max(1), but this is already guaranteed by the thread id being a NonZeroU64 + // internally; as that guarantee is not stable, we do make sure, just to be on the safe side. + unsafe { NonZeroU64::new_unchecked(ex.finish().max(1)) } +} + +// Replace with this when the thread_id_value feature is stable +// fn threadid() -> NonZeroU64 { +// std::thread::current().id().as_u64() +// } + +#[test] +fn test_threadid() { + let top = threadid(); + std::thread::spawn(move || { + assert_ne!(top, threadid()); + }) + .join() + .expect("thread failed"); +} diff --git a/crates/lib/src/late_join_set.rs b/crates/lib/src/late_join_set.rs new file mode 100644 index 0000000..c14d0ef --- /dev/null +++ b/crates/lib/src/late_join_set.rs @@ -0,0 +1,105 @@ +use std::future::Future; + +use futures::{stream::FuturesUnordered, StreamExt}; +use tokio::task::{JoinError, JoinHandle}; + +/// A collection of tasks spawned on a Tokio runtime. +/// +/// This is conceptually a variant of Tokio's [`JoinSet`](tokio::task::JoinSet) which can attach +/// tasks after they've been spawned. +/// +/// # Examples +/// +/// Spawn multiple tasks and wait for them. +/// +/// ```no_compile +/// use crate::late_join_set::LateJoinSet; +/// +/// #[tokio::main] +/// async fn main() { +/// let mut set = LateJoinSet::default(); +/// +/// for i in 0..10 { +/// set.spawn(async move { println!("{i}"); }); +/// } +/// +/// let mut seen = [false; 10]; +/// while let Some(res) = set.join_next().await { +/// let idx = res.unwrap(); +/// seen[idx] = true; +/// } +/// +/// for i in 0..10 { +/// assert!(seen[i]); +/// } +/// } +/// ``` +/// +/// Attach a task to a set after it's been spawned. +/// +/// ```no_compile +/// use crate::late_join_set::LateJoinSet; +/// +/// #[tokio::main] +/// async fn main() { +/// let mut set = LateJoinSet::default(); +/// +/// let handle = tokio::spawn(async move { println!("Hello, world!"); }); +/// set.insert(handle); +/// set.abort_all(); +/// } +/// ``` +#[derive(Debug, Default)] +pub struct LateJoinSet { + tasks: FuturesUnordered>, +} + +impl LateJoinSet { + /// Spawn the provided task on the `LateJoinSet`. + /// + /// The provided future will start running in the background immediately when this method is + /// called, even if you don't await anything on this `LateJoinSet`. + /// + /// # Panics + /// + /// This method panics if called outside of a Tokio runtime. + #[track_caller] + pub fn spawn(&self, task: impl Future + Send + 'static) { + self.insert(tokio::spawn(task)); + } + + /// Insert an already-spawned task into the [`LateJoinSet`]. + pub fn insert(&self, task: JoinHandle<()>) { + self.tasks.push(task); + } + + /// Waits until one of the tasks in the set completes. + /// + /// Returns `None` if the set is empty. + pub async fn join_next(&mut self) -> Option> { + self.tasks.next().await + } + + /// Waits until all the tasks in the set complete. + /// + /// Ignores any panics in the tasks shutting down. + pub async fn join_all(&mut self) { + while self.join_next().await.is_some() {} + self.tasks.clear(); + } + + /// Aborts all tasks on this `LateJoinSet`. + /// + /// This does not remove the tasks from the `LateJoinSet`. To wait for the tasks to complete + /// cancellation, use `join_all` or call `join_next` in a loop until the `LateJoinSet` is empty. + pub fn abort_all(&self) { + self.tasks.iter().for_each(|jh| jh.abort()); + } +} + +impl Drop for LateJoinSet { + fn drop(&mut self) { + self.abort_all(); + self.tasks.clear(); + } +} diff --git a/crates/lib/src/lib.rs b/crates/lib/src/lib.rs index 8446281..d50793f 100644 --- a/crates/lib/src/lib.rs +++ b/crates/lib/src/lib.rs @@ -1,93 +1,54 @@ -//! Watchexec: a library for utilities and programs which respond to events; -//! file changes, human interaction, and more. +//! Watchexec: a library for utilities and programs which respond to (file, signal, etc) events +//! primarily by launching or managing other programs. //! //! Also see the CLI tool: //! //! This library is powered by [Tokio](https://tokio.rs). //! -//! The main way to use this crate involves constructing a [`Watchexec`] around an -//! [`InitConfig`][config::InitConfig] and a [`RuntimeConfig`][config::RuntimeConfig], then running -//! it. [`Handler`][handler::Handler]s are used to hook into watchexec at various points. The -//! runtime config can be changed at any time with the [`Watchexec::reconfigure()`] method. +//! The main way to use this crate involves constructing a [`Watchexec`] around a [`Config`], then +//! running it. [`Handler`][handler::Handler]s are used to hook into Watchexec at various points. +//! The config can be changed at any time with the [`Watchexec::reconfigure()`] method. //! //! It's recommended to use the [miette] erroring library in applications, but all errors implement //! [`std::error::Error`] so your favourite error handling library can of course be used. //! //! ```no_run //! use miette::{IntoDiagnostic, Result}; -//! use watchexec::{ -//! Watchexec, -//! action::{Action, Outcome}, -//! config::{InitConfig, RuntimeConfig}, -//! handler::{Handler as _, PrintDebug}, -//! }; +//! use watchexec_signals::Signal; +//! use watchexec::Watchexec; //! //! #[tokio::main] //! async fn main() -> Result<()> { -//! let mut init = InitConfig::default(); -//! init.on_error(PrintDebug(std::io::stderr())); -//! -//! let mut runtime = RuntimeConfig::default(); -//! runtime.pathset(["watchexec.conf"]); -//! -//! let conf = YourConfigFormat::load_from_file("watchexec.conf").await?; -//! conf.apply(&mut runtime); -//! -//! let we = Watchexec::new(init, runtime.clone())?; -//! let w = we.clone(); -//! -//! let c = runtime.clone(); -//! runtime.on_action(move |action: Action| { -//! let mut c = c.clone(); -//! let w = w.clone(); -//! async move { -//! for event in action.events.iter() { -//! if event.paths().any(|(p, _)| p.ends_with("/watchexec.conf")) { -//! let conf = YourConfigFormat::load_from_file("watchexec.conf").await?; -//! -//! conf.apply(&mut c); -//! w.reconfigure(c.clone()); -//! // tada! self-reconfiguring watchexec on config file change! -//! -//! break; -//! } -//! } -//! -//! action.outcome(Outcome::if_running( -//! Outcome::DoNothing, -//! Outcome::both(Outcome::Clear, Outcome::Start), -//! )); -//! -//! Ok(()) -//! # as std::result::Result<_, MietteStub> +//! let wx = Watchexec::new(|mut action| { +//! // print any events +//! for event in action.events.iter() { +//! eprintln!("EVENT: {event:?}"); //! } -//! }); //! -//! we.reconfigure(runtime); -//! we.main().await.into_diagnostic()?; +//! // if Ctrl-C is received, quit +//! if action.signals().any(|sig| sig == Signal::Interrupt) { +//! action.quit(); +//! } +//! +//! action +//! })?; +//! +//! // watch the current directory +//! wx.config.pathset(["."]); +//! +//! wx.main().await.into_diagnostic()?; //! Ok(()) //! } -//! # struct YourConfigFormat; -//! # impl YourConfigFormat { -//! # async fn load_from_file(_: &str) -> std::result::Result { Ok(Self) } -//! # fn apply(&self, _: &mut RuntimeConfig) { } -//! # } -//! # use miette::Diagnostic; -//! # use thiserror::Error; -//! # #[derive(Debug, Error, Diagnostic)] -//! # #[error("stub")] -//! # struct MietteStub; //! ``` //! -//! Alternatively, one can use the modules exposed by the crate and the external crates such as +//! Alternatively, you can use the modules exposed by the crate and the external crates such as //! [ClearScreen][clearscreen] and [Command Group][command_group] to build something more advanced, -//! at the cost of reimplementing the glue code. See the examples folder for some basic/demo tools -//! written with the individual modules. +//! at the cost of reimplementing the glue code. //! -//! Note that the library generates a _lot_ of debug messaging with [tracing]. You should not enable -//! printing even error log messages for this crate unless it's for debugging. Instead, make use of -//! the [`InitConfig::on_error()`][config::InitConfig::on_error()] method to define a handler for -//! errors occurring at runtime that are _meant_ for you to handle (by printing out or otherwise). +//! Note that the library generates a _lot_ of debug messaging with [tracing]. **You should not +//! enable printing even `error`-level log messages for this crate unless it's for debugging.** +//! Instead, make use of the [`Config::on_error()`] method to define a handler for errors +//! occurring at runtime that are _meant_ for you to handle (by printing out or otherwise). #![doc(html_favicon_url = "https://watchexec.github.io/logo:watchexec.svg")] #![doc(html_logo_url = "https://watchexec.github.io/logo:watchexec.svg")] @@ -96,29 +57,31 @@ // the toolkit to make your own pub mod action; -pub mod command; pub mod error; pub mod filter; -pub mod fs; -pub mod keyboard; pub mod paths; -pub mod signal; +pub mod sources; // the core experience +pub mod changeable; pub mod config; -pub mod handler; + +mod id; +mod late_join_set; mod watchexec; -// compatibility -#[deprecated( - note = "use the `watchexec-events` crate directly instead", - since = "2.2.0" -)] -pub use watchexec_events as event; - #[doc(inline)] -pub use crate::watchexec::{ErrorHook, Watchexec}; +pub use crate::{ + id::Id, + watchexec::{ErrorHook, Watchexec}, +}; +#[doc(no_inline)] +pub use crate::config::Config; +#[doc(no_inline)] +pub use watchexec_supervisor::{command, job}; + +#[cfg(debug_assertions)] #[doc(hidden)] pub mod readme_doc_check { #[doc = include_str!("../README.md")] diff --git a/crates/lib/src/paths.rs b/crates/lib/src/paths.rs index c8b6d05..54aa745 100644 --- a/crates/lib/src/paths.rs +++ b/crates/lib/src/paths.rs @@ -6,7 +6,7 @@ use std::{ path::{Path, PathBuf}, }; -use crate::event::{Event, FileType, Tag}; +use watchexec_events::{Event, FileType, Tag}; /// The separator for paths used in environment variables. #[cfg(unix)] diff --git a/crates/lib/src/sources.rs b/crates/lib/src/sources.rs new file mode 100644 index 0000000..39877c2 --- /dev/null +++ b/crates/lib/src/sources.rs @@ -0,0 +1,5 @@ +//! Sources of events. + +pub mod fs; +pub mod keyboard; +pub mod signal; diff --git a/crates/lib/src/fs.rs b/crates/lib/src/sources/fs.rs similarity index 61% rename from crates/lib/src/fs.rs rename to crates/lib/src/sources/fs.rs index a24aac0..3239d43 100644 --- a/crates/lib/src/fs.rs +++ b/crates/lib/src/sources/fs.rs @@ -5,48 +5,46 @@ use std::{ fs::metadata, mem::take, path::{Path, PathBuf}, + sync::Arc, time::Duration, }; use async_priority_channel as priority; use normalize_path::NormalizePath; -use notify::{Config, Watcher as _}; -use tokio::sync::{mpsc, watch}; +use tokio::sync::mpsc; use tracing::{debug, error, trace}; +use watchexec_events::{Event, Priority, Source, Tag}; use crate::{ error::{CriticalError, FsWatcherError, RuntimeError}, - event::{Event, Priority, Source, Tag}, + Config, }; /// What kind of filesystem watcher to use. /// /// For now only native and poll watchers are supported. In the future there may be additional /// watchers available on some platforms. -#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] #[non_exhaustive] pub enum Watcher { /// The Notify-recommended watcher on the platform. /// /// For platforms Notify supports, that's a [native implementation][notify::RecommendedWatcher], /// for others it's polling with a default interval. + #[default] Native, /// Notify’s [poll watcher][notify::PollWatcher] with a custom interval. Poll(Duration), } -impl Default for Watcher { - fn default() -> Self { - Self::Native - } -} - impl Watcher { fn create( self, f: impl notify::EventHandler, - ) -> Result, RuntimeError> { + ) -> Result, CriticalError> { + use notify::{Config, Watcher as _}; + match self { Self::Native => { notify::RecommendedWatcher::new(f, Config::default()).map(|w| Box::new(w) as _) @@ -56,7 +54,7 @@ impl Watcher { .map(|w| Box::new(w) as _) } } - .map_err(|err| RuntimeError::FsWatcher { + .map_err(|err| CriticalError::FsWatcherInit { kind: self, err: if cfg!(target_os = "linux") && (matches!(err.kind, notify::ErrorKind::MaxFilesWatch) @@ -74,19 +72,6 @@ impl Watcher { } } -/// The configuration of the [fs][self] worker. -/// -/// This is marked non-exhaustive so new configuration can be added without breaking. -#[derive(Clone, Debug, Default)] -#[non_exhaustive] -pub struct WorkingData { - /// The set of paths to be watched. - pub pathset: Vec, - - /// The kind of watcher to be used. - pub watcher: Watcher, -} - /// A path to watch. /// /// This is currently only a wrapper around a [`PathBuf`], but may be augmented in the future. @@ -128,9 +113,7 @@ impl AsRef for WatchedPath { /// While you can run several, you should only have one. /// /// This only does a bare minimum of setup; to actually start the work, you need to set a non-empty -/// pathset on the [`WorkingData`] with the [`watch`] channel, and send a notification. Take care -/// _not_ to drop the watch sender: this will cause the worker to stop gracefully, which may not be -/// what was expected. +/// pathset in the [`Config`]. /// /// Note that the paths emitted by the watcher are normalised. No guarantee is made about the /// implementation or output of that normalisation (it may change without notice). @@ -141,25 +124,23 @@ impl AsRef for WatchedPath { /// /// ```no_run /// use async_priority_channel as priority; -/// use tokio::sync::{mpsc, watch}; -/// use watchexec::fs::{worker, WorkingData}; +/// use tokio::sync::mpsc; +/// use watchexec::{Config, sources::fs::worker}; /// /// #[tokio::main] /// async fn main() -> Result<(), Box> { /// let (ev_s, _) = priority::bounded(1024); /// let (er_s, _) = mpsc::channel(64); -/// let (wd_s, wd_r) = watch::channel(WorkingData::default()); /// -/// let mut wkd = WorkingData::default(); -/// wkd.pathset = vec![".".into()]; -/// wd_s.send(wkd)?; +/// let config = Config::default(); +/// config.pathset(["."]); /// -/// worker(wd_r, er_s, ev_s).await?; +/// worker(config.into(), er_s, ev_s).await?; /// Ok(()) /// } /// ``` pub async fn worker( - mut working: watch::Receiver, + config: Arc, errors: mpsc::Sender, events: priority::Sender, ) -> Result<(), CriticalError> { @@ -169,95 +150,102 @@ pub async fn worker( let mut watcher = None; let mut pathset = HashSet::new(); - while working.changed().await.is_ok() { - // In separate scope so we drop the working read lock as early as we can - let (new_watcher, to_watch, to_drop) = { - let data = working.borrow(); - trace!(?data, "filesystem worker got a working data change"); + let mut config_watch = config.watch(); + loop { + config_watch.next().await; + trace!("filesystem worker got a config change"); - if data.pathset.is_empty() { - trace!("no more watched paths, dropping watcher"); - watcher.take(); - pathset.drain(); - continue; - } - - if watcher.is_none() || watcher_type != data.watcher { - pathset.drain(); - - (Some(data.watcher), data.pathset.clone(), Vec::new()) - } else { - let mut to_watch = Vec::with_capacity(data.pathset.len()); - let mut to_drop = Vec::with_capacity(pathset.len()); - for path in &data.pathset { - if !pathset.contains(path) { - to_watch.push(path.clone()); - } + if config.pathset.get().is_empty() { + trace!( + "{}", + if pathset.is_empty() { + "no watched paths, no watcher needed" + } else { + "no more watched paths, dropping watcher" } + ); + watcher.take(); + pathset.clear(); + continue; + } - for path in &pathset { - if !data.pathset.contains(path) { - to_drop.push(path.clone()); - } - } + // now we know the watcher should be alive, so let's start it if it's not already: - (None, to_watch, to_drop) - } - }; - - if let Some(kind) = new_watcher { - debug!(?kind, "creating new watcher"); + let config_watcher = config.file_watcher.get(); + if watcher.is_none() || watcher_type != config_watcher { + debug!(kind=?config_watcher, "creating new watcher"); let n_errors = errors.clone(); let n_events = events.clone(); - match kind.create(move |nev: Result| { - trace!(event = ?nev, "receiving possible event from watcher"); - if let Err(e) = process_event(nev, kind, &n_events) { - n_errors.try_send(e).ok(); + watcher_type = config_watcher; + watcher = config_watcher + .create(move |nev: Result| { + trace!(event = ?nev, "receiving possible event from watcher"); + if let Err(e) = process_event(nev, config_watcher, &n_events) { + n_errors.try_send(e).ok(); + } + }) + .map(Some)?; + } + + // now let's calculate which paths we should add to the watch, and which we should drop: + + let config_pathset = config.pathset.get(); + let (to_watch, to_drop) = if pathset.is_empty() { + // if the current pathset is empty, we can take a shortcut + (config_pathset, Vec::new()) + } else { + let mut to_watch = Vec::with_capacity(config_pathset.len()); + let mut to_drop = Vec::with_capacity(pathset.len()); + + for path in &pathset { + if !config_pathset.contains(path) { + to_drop.push(path.clone()); // try dropping the clone? } - }) { - Ok(w) => { - watcher = Some(w); - watcher_type = kind; + } + + for path in config_pathset { + if !pathset.contains(&path) { + to_watch.push(path); } - Err(e) => { + } + + (to_watch, to_drop) + }; + + // now apply it to the watcher + + let Some(watcher) = watcher.as_mut() else { + panic!("BUG: watcher should exist at this point"); + }; + + debug!(?to_watch, ?to_drop, "applying changes to the watcher"); + + for path in to_drop { + trace!(?path, "removing path from the watcher"); + if let Err(err) = watcher.unwatch(path.as_ref()) { + error!(?err, "notify unwatch() error"); + for e in notify_multi_path_errors(watcher_type, path, err, true) { errors.send(e).await?; } + } else { + pathset.remove(&path); } } - if let Some(w) = watcher.as_mut() { - debug!(?to_watch, ?to_drop, "applying changes to the watcher"); - - for path in to_drop { - trace!(?path, "removing path from the watcher"); - if let Err(err) = w.unwatch(path.as_ref()) { - error!(?err, "notify unwatch() error"); - for e in notify_multi_path_errors(watcher_type, path, err, true) { - errors.send(e).await?; - } - } else { - pathset.remove(&path); - } - } - - for path in to_watch { - trace!(?path, "adding path to the watcher"); - if let Err(err) = w.watch(path.as_ref(), notify::RecursiveMode::Recursive) { - error!(?err, "notify watch() error"); - for e in notify_multi_path_errors(watcher_type, path, err, false) { - errors.send(e).await?; - } - // TODO: unwatch and re-watch manually while ignoring all the erroring paths - // See https://github.com/watchexec/watchexec/issues/218 - } else { - pathset.insert(path); + for path in to_watch { + trace!(?path, "adding path to the watcher"); + if let Err(err) = watcher.watch(path.as_ref(), notify::RecursiveMode::Recursive) { + error!(?err, "notify watch() error"); + for e in notify_multi_path_errors(watcher_type, path, err, false) { + errors.send(e).await?; } + // TODO: unwatch and re-watch manually while ignoring all the erroring paths + // See https://github.com/watchexec/watchexec/issues/218 + } else { + pathset.insert(path); } } } - - debug!("ending file watcher"); - Ok(()) } fn notify_multi_path_errors( diff --git a/crates/lib/src/keyboard.rs b/crates/lib/src/sources/keyboard.rs similarity index 61% rename from crates/lib/src/keyboard.rs rename to crates/lib/src/sources/keyboard.rs index 950f090..2ddc864 100644 --- a/crates/lib/src/keyboard.rs +++ b/crates/lib/src/sources/keyboard.rs @@ -1,68 +1,56 @@ //! Event source for keyboard input and related events +use std::sync::Arc; + use async_priority_channel as priority; use tokio::{ io::AsyncReadExt, - sync::{mpsc, oneshot, watch}, + select, spawn, + sync::{mpsc, oneshot}, }; use tracing::trace; -pub use watchexec_events::Keyboard; +use watchexec_events::{Event, Keyboard, Priority, Source, Tag}; use crate::{ - error::{CriticalError, KeyboardWatcherError, RuntimeError}, - event::{Event, Priority, Source, Tag}, + error::{CriticalError, RuntimeError}, + Config, }; -/// The configuration of the [keyboard][self] worker. -/// -/// This is marked non-exhaustive so new configuration can be added without breaking. -#[derive(Debug, Clone, Default)] -#[non_exhaustive] -pub struct WorkingData { - /// Whether or not to watch for 'end of file' on stdin - pub eof: bool, -} - /// Launch the filesystem event worker. /// /// While you can run several, you should only have one. /// /// Sends keyboard events via to the provided 'events' channel pub async fn worker( - mut working: watch::Receiver, + config: Arc, errors: mpsc::Sender, events: priority::Sender, ) -> Result<(), CriticalError> { let mut send_close = None; - while working.changed().await.is_ok() { - let watch_for_eof = { working.borrow().eof }; - match (watch_for_eof, &send_close) { - // If we want to watch stdin and we're not already watching it then spawn a task to watch it + let mut config_watch = config.watch(); + loop { + config_watch.next().await; + match (config.keyboard_events.get(), &send_close) { + // if we want to watch stdin and we're not already watching it then spawn a task to watch it (true, None) => { - let (close_s, close_r) = tokio::sync::oneshot::channel::<()>(); + let (close_s, close_r) = oneshot::channel::<()>(); send_close = Some(close_s); - tokio::spawn(watch_stdin(errors.clone(), events.clone(), close_r)); + spawn(watch_stdin(errors.clone(), events.clone(), close_r)); } - // If we don't want to watch stdin but we are already watching it then send a close signal to end the - // watching + // if we don't want to watch stdin but we are already watching it then send a close signal to end + // the watching (false, Some(_)) => { - // Repeat match using 'take' - if let Some(close_s) = send_close.take() { - if close_s.send(()).is_err() { - errors - .send(RuntimeError::KeyboardWatcher { - err: KeyboardWatcherError::StdinShutdown, - }) - .await?; - } - } + // ignore send error as if channel is closed watch is already gone + send_close + .take() + .expect("unreachable due to match") + .send(()) + .ok(); } - // Otherwise no action is required + // otherwise no action is required _ => {} } } - - Ok(()) } async fn watch_stdin( @@ -73,7 +61,7 @@ async fn watch_stdin( let mut stdin = tokio::io::stdin(); let mut buffer = [0; 10]; loop { - tokio::select! { + select! { result = stdin.read(&mut buffer[..]) => { // Read from stdin and if we've read 0 bytes then we assume stdin has received an 'eof' so // we send that event into the system and break out of the loop as 'eof' means that there will diff --git a/crates/lib/src/signal.rs b/crates/lib/src/sources/signal.rs similarity index 69% rename from crates/lib/src/signal.rs rename to crates/lib/src/sources/signal.rs index ced30c5..1f39e86 100644 --- a/crates/lib/src/signal.rs +++ b/crates/lib/src/sources/signal.rs @@ -1,41 +1,22 @@ //! Event source for signals / notifications sent to the main process. +use std::sync::Arc; + use async_priority_channel as priority; use tokio::{select, sync::mpsc}; use tracing::{debug, trace}; +use watchexec_events::{Event, Priority, Source, Tag}; use watchexec_signals::Signal; use crate::{ error::{CriticalError, RuntimeError}, - event::{Event, Priority, Source, Tag}, + Config, }; -/// Compatibility shim for the old `watchexec::signal::process` module. -pub mod process { - #[deprecated( - note = "use the `watchexec-signals` crate directly instead", - since = "2.2.0" - )] - pub use watchexec_signals::Signal as SubSignal; -} - -/// Compatibility shim for the old `watchexec::signal::source` module. -pub mod source { - #[deprecated( - note = "use `watchexec::signal::worker` directly instead", - since = "2.2.0" - )] - pub use super::worker; - #[deprecated( - note = "use the `watchexec-signals` crate directly instead", - since = "2.2.0" - )] - pub use watchexec_signals::Signal as MainSignal; -} - /// Launch the signal event worker. /// -/// While you _can_ run several, you **must** only have one. This may be enforced later. +/// While you _could_ run several (it won't panic), you **must** only have one (for correctness). +/// This may be enforced later. /// /// # Examples /// @@ -44,26 +25,28 @@ pub mod source { /// ```no_run /// use tokio::sync::mpsc; /// use async_priority_channel as priority; -/// use watchexec::signal::source::worker; +/// use watchexec::sources::signal::worker; /// /// #[tokio::main] /// async fn main() -> Result<(), Box> { /// let (ev_s, _) = priority::bounded(1024); /// let (er_s, _) = mpsc::channel(64); /// -/// worker(er_s, ev_s).await?; +/// worker(Default::default(), er_s, ev_s).await?; /// Ok(()) /// } /// ``` pub async fn worker( + config: Arc, errors: mpsc::Sender, events: priority::Sender, ) -> Result<(), CriticalError> { - imp_worker(errors, events).await + imp_worker(config, errors, events).await } #[cfg(unix)] async fn imp_worker( + _config: Arc, errors: mpsc::Sender, events: priority::Sender, ) -> Result<(), CriticalError> { @@ -72,12 +55,12 @@ async fn imp_worker( debug!("launching unix signal worker"); macro_rules! listen { - ($sig:ident) => {{ - trace!(kind=%stringify!($sig), "listening for unix signal"); - signal(SignalKind::$sig()).map_err(|err| CriticalError::IoError { - about: concat!("setting ", stringify!($sig), " signal listener"), err - })? - }} + ($sig:ident) => {{ + trace!(kind=%stringify!($sig), "listening for unix signal"); + signal(SignalKind::$sig()).map_err(|err| CriticalError::IoError { + about: concat!("setting ", stringify!($sig), " signal listener"), err + })? + }} } let mut s_hangup = listen!(hangup); @@ -104,6 +87,7 @@ async fn imp_worker( #[cfg(windows)] async fn imp_worker( + _config: Arc, errors: mpsc::Sender, events: priority::Sender, ) -> Result<(), CriticalError> { @@ -112,12 +96,12 @@ async fn imp_worker( debug!("launching windows signal worker"); macro_rules! listen { - ($sig:ident) => {{ - trace!(kind=%stringify!($sig), "listening for windows process notification"); - $sig().map_err(|err| CriticalError::IoError { - about: concat!("setting ", stringify!($sig), " signal listener"), err - })? - }} + ($sig:ident) => {{ + trace!(kind=%stringify!($sig), "listening for windows process notification"); + $sig().map_err(|err| CriticalError::IoError { + about: concat!("setting ", stringify!($sig), " signal listener"), err + })? + }} } let mut sigint = listen!(ctrl_c); diff --git a/crates/lib/src/watchexec.rs b/crates/lib/src/watchexec.rs index 22b51a2..df89496 100644 --- a/crates/lib/src/watchexec.rs +++ b/crates/lib/src/watchexec.rs @@ -1,33 +1,24 @@ -use std::{ - fmt, - future::Future, - mem::{replace, take}, - ops::{Deref, DerefMut}, - pin::Pin, - sync::Arc, - task::{Context, Poll}, -}; +use std::{fmt, future::Future, sync::Arc}; use async_priority_channel as priority; use atomic_take::AtomicTake; +use futures::TryFutureExt; use miette::Diagnostic; use once_cell::sync::OnceCell; use tokio::{ spawn, - sync::{mpsc, watch, Notify}, - task::JoinHandle, - try_join, + sync::{mpsc, Notify}, + task::{JoinHandle, JoinSet}, }; use tracing::{debug, error, trace}; +use watchexec_events::{Event, Priority}; use crate::{ - action, - config::{InitConfig, RuntimeConfig}, - error::{CriticalError, ReconfigError, RuntimeError}, - event::{Event, Priority}, - fs, - handler::{rte, Handler}, - keyboard, signal, + action::{self, ActionHandler}, + changeable::ChangeableFn, + error::{CriticalError, RuntimeError}, + sources::{fs, keyboard, signal}, + Config, }; /// The main watchexec runtime. @@ -38,14 +29,63 @@ use crate::{ /// error hook, and provides an interface to change the runtime configuration during the runtime, /// inject synthetic events, and wait for graceful shutdown. pub struct Watchexec { - handle: Arc>>>, + /// The configuration of this Watchexec instance. + /// + /// Configuration can be changed at any time using the provided methods on [`Config`]. + /// + /// Treat this field as readonly: replacing it with a different instance of `Config` will not do + /// anything except potentially lose you access to the actual Watchexec config. In normal use + /// you'll have obtained `Watchexec` behind an `Arc` so that won't be an issue. + /// + /// # Examples + /// + /// Change the action handler: + /// + /// ```no_run + /// # use watchexec::Watchexec; + /// let wx = Watchexec::default(); + /// wx.config.on_action(|mut action| { + /// if action.signals().next().is_some() { + /// action.quit(); + /// } + /// + /// action + /// }); + /// ``` + /// + /// Set paths to be watched: + /// + /// ```no_run + /// # use watchexec::Watchexec; + /// let wx = Watchexec::new(|mut action| { + /// if action.signals().next().is_some() { + /// action.quit(); + /// } else { + /// for event in action.events.iter() { + /// println!("{event:?}"); + /// } + /// } + /// + /// action + /// }).unwrap(); + /// + /// wx.config.pathset(["."]); + /// ``` + pub config: Arc, start_lock: Arc, - - action_watch: watch::Sender, - fs_watch: watch::Sender, - keyboard_watch: watch::Sender, - event_input: priority::Sender, + handle: Arc>>>, +} + +impl Default for Watchexec { + /// Instantiate with default config. + /// + /// Note that this will panic if the constructor errors. + /// + /// Prefer calling `new()` instead. + fn default() -> Self { + Self::with_config(Default::default()).expect("Use Watchexec::new() to avoid this panic") + } } impl fmt::Debug for Watchexec { @@ -55,109 +95,106 @@ impl fmt::Debug for Watchexec { } impl Watchexec { - /// Instantiates a new `Watchexec` runtime from configuration. + /// Instantiates a new `Watchexec` runtime given an initial action handler. /// /// Returns an [`Arc`] for convenience; use [`try_unwrap`][Arc::try_unwrap()] to get the value - /// directly if needed. - /// - /// Note that `RuntimeConfig` is not a "live" or "shared" instance: if using reconfiguration, - /// you'll usually pass a `clone()` of your `RuntimeConfig` instance to this function; changes - /// made to the instance you _keep_ will not automatically be used by Watchexec, you need to - /// call [`reconfigure()`](Watchexec::reconfigure) with your updated config to apply the changes. + /// directly if needed, or use `new_with_config`. /// + /// Look at the [`Config`] documentation for more on the required action handler. /// Watchexec will subscribe to most signals sent to the process it runs in and send them, as /// [`Event`]s, to the action handler. At minimum, you should check for interrupt/ctrl-c events - /// and return an [`Outcome::Exit`], otherwise hitting ctrl-c will do nothing. - /// - /// [`Outcome::Exit`]: crate::action::Outcome::Exit + /// and call `action.quit()` in your handler, otherwise hitting ctrl-c will do nothing. pub fn new( - mut init: InitConfig, - mut runtime: RuntimeConfig, + action_handler: impl (Fn(ActionHandler) -> ActionHandler) + Send + Sync + 'static, ) -> Result, CriticalError> { - debug!(?init, ?runtime, pid=%std::process::id(), version=%env!("CARGO_PKG_VERSION"), "initialising"); + let config = Config::default(); + config.on_action(action_handler); + Self::with_config(config).map(Arc::new) + } - let (ev_s, ev_r) = priority::bounded(init.event_channel_size); - let (ac_s, ac_r) = watch::channel(take(&mut runtime.action)); - let (fs_s, fs_r) = watch::channel(fs::WorkingData::default()); - let (keyboard_s, keyboard_r) = watch::channel(keyboard::WorkingData::default()); + /// Instantiates a new `Watchexec` runtime given an initial async action handler. + /// + /// This is the same as [`new`](fn@Self::new) except the action handler is async. + pub fn new_async( + action_handler: impl (Fn(ActionHandler) -> Box + Send + Sync>) + + Send + + Sync + + 'static, + ) -> Result, CriticalError> { + let config = Config::default(); + config.on_action_async(action_handler); + Self::with_config(config).map(Arc::new) + } - let event_input = ev_s.clone(); + /// Instantiates a new `Watchexec` runtime with a config. + /// + /// This is generally not needed: the config can be changed after instantiation (before and + /// after _starting_ Watchexec with `main()`). The only time this should be used is to set the + /// "unchangeable" configuration items for internal details like buffer sizes for queues, or to + /// obtain Self unwrapped by an Arc like `new()` does. + pub fn with_config(config: Config) -> Result { + debug!(?config, pid=%std::process::id(), version=%env!("CARGO_PKG_VERSION"), "initialising"); + let config = Arc::new(config); + let outer_config = config.clone(); - // TODO: figure out how to do this (aka start the fs work) after the main task start lock - trace!("sending initial config to fs worker"); - fs_s.send(take(&mut runtime.fs)) - .expect("cannot send to just-created fs watch (bug)"); - - trace!("sending initial config to keyboard worker"); - keyboard_s - .send(take(&mut runtime.keyboard)) - .expect("cannot send to just-created keyboard watch (bug)"); - - trace!("creating main task"); let notify = Arc::new(Notify::new()); let start_lock = notify.clone(); + + let (ev_s, ev_r) = priority::bounded(config.event_channel_size); + let event_input = ev_s.clone(); + + trace!("creating main task"); let handle = spawn(async move { trace!("waiting for start lock"); notify.notified().await; debug!("starting main task"); - let (er_s, er_r) = mpsc::channel(init.error_channel_size); + let (er_s, er_r) = mpsc::channel(config.error_channel_size); - let eh = replace(&mut init.error_handler, Box::new(()) as _); + let mut tasks = JoinSet::new(); - let action = SubTask::spawn( - "action", - action::worker(ac_r, er_s.clone(), ev_s.clone(), ev_r), + tasks.spawn(action::worker(config.clone(), er_s.clone(), ev_r).map_ok(|_| "action")); + tasks.spawn(fs::worker(config.clone(), er_s.clone(), ev_s.clone()).map_ok(|_| "fs")); + tasks.spawn( + signal::worker(config.clone(), er_s.clone(), ev_s.clone()).map_ok(|_| "signal"), ); - let fs = SubTask::spawn("fs", fs::worker(fs_r, er_s.clone(), ev_s.clone())); - let signal = - SubTask::spawn("signal", signal::source::worker(er_s.clone(), ev_s.clone())); - let keyboard = SubTask::spawn( - "keyboard", - keyboard::worker(keyboard_r, er_s.clone(), ev_s.clone()), + tasks.spawn( + keyboard::worker(config.clone(), er_s.clone(), ev_s.clone()).map_ok(|_| "keyboard"), ); + tasks.spawn(error_hook(er_r, config.error_handler.clone()).map_ok(|_| "error")); - let error_hook = SubTask::spawn("error_hook", error_hook(er_r, eh)); - - // Use Tokio TaskSet when that lands - try_join!(action, error_hook, fs, signal, keyboard) - .map(drop) - .or_else(|e| { - // Close event channel to signal worker task to stop - ev_s.close(); - - if matches!(e, CriticalError::Exit) { - trace!("got graceful exit request via critical error, erasing the error"); - Ok(()) - } else { - Err(e) + while let Some(Ok(res)) = tasks.join_next().await { + match res { + Ok("action") => { + debug!("action worker exited, ending watchexec"); + break; } - }) - .map(|_| { - debug!("main task graceful exit"); - }) + Ok(task) => { + debug!(task, "worker exited"); + } + Err(CriticalError::Exit) => { + trace!("got graceful exit request via critical error, erasing the error"); + // Close event channel to signal worker task to stop + ev_s.close(); + } + Err(e) => { + return Err(e); + } + } + } + + debug!("main task graceful exit"); + tasks.shutdown().await; + Ok(()) }); trace!("done with setup"); - Ok(Arc::new(Self { - handle: Arc::new(AtomicTake::new(handle)), + Ok(Self { + config: outer_config, start_lock, - - action_watch: ac_s, - fs_watch: fs_s, - keyboard_watch: keyboard_s, - event_input, - })) - } - - /// Applies a new [`RuntimeConfig`] to the runtime. - pub fn reconfigure(&self, config: RuntimeConfig) -> Result<(), ReconfigError> { - debug!(?config, "reconfiguring"); - self.action_watch.send(config.action)?; - self.fs_watch.send(config.fs)?; - self.keyboard_watch.send(config.keyboard)?; - Ok(()) + handle: Arc::new(AtomicTake::new(handle)), + }) } /// Inputs an [`Event`] directly. @@ -190,7 +227,7 @@ impl Watchexec { async fn error_hook( mut errors: mpsc::Receiver, - mut handler: Box + Send>, + handler: ChangeableFn, ) -> Result<(), CriticalError> { while let Some(err) = errors.recv().await { if matches!(err, RuntimeError::Exit) { @@ -199,20 +236,10 @@ async fn error_hook( } error!(%err, "runtime error"); - - let hook = ErrorHook::new(err); - let crit = hook.critical.clone(); - if let Err(err) = handler.handle(hook) { - error!(%err, "error while handling error"); - let rehook = ErrorHook::new(rte("error hook", err.as_ref())); - let recrit = rehook.critical.clone(); - handler.handle(rehook).unwrap_or_else(|err| { - error!(%err, "error while handling error of handling error"); - }); - ErrorHook::handle_crit(recrit, "error handler error handler")?; - } else { - ErrorHook::handle_crit(crit, "error handler")?; - } + let payload = ErrorHook::new(err); + let crit = payload.critical.clone(); + handler.call(payload); + ErrorHook::handle_crit(crit)?; } Ok(()) @@ -242,19 +269,16 @@ impl ErrorHook { } } - fn handle_crit( - crit: Arc>, - name: &'static str, - ) -> Result<(), CriticalError> { + fn handle_crit(crit: Arc>) -> Result<(), CriticalError> { match Arc::try_unwrap(crit) { Err(err) => { - error!(?err, "{name} hook has an outstanding ref"); + error!(?err, "error handler hook has an outstanding ref"); Ok(()) } Ok(crit) => crit.into_inner().map_or_else( || Ok(()), |crit| { - debug!(%crit, "{name} output a critical error"); + debug!(%crit, "error handler output a critical error"); Err(crit) }, ), @@ -282,62 +306,3 @@ impl ErrorHook { .ok(); } } - -#[derive(Debug)] -struct SubTask { - name: &'static str, - handle: JoinHandle>, -} - -impl SubTask { - pub fn spawn( - name: &'static str, - task: impl Future> + Send + 'static, - ) -> Self { - debug!(subtask=%name, "spawning subtask"); - Self { - name, - handle: spawn(task), - } - } -} - -impl Drop for SubTask { - fn drop(&mut self) { - debug!(subtask=%self.name, "aborting subtask"); - self.handle.abort(); - } -} - -impl Deref for SubTask { - type Target = JoinHandle>; - - fn deref(&self) -> &Self::Target { - &self.handle - } -} - -impl DerefMut for SubTask { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.handle - } -} - -impl Future for SubTask { - type Output = Result<(), CriticalError>; - - fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - let subtask = self.name; - match Pin::new(&mut Pin::into_inner(self).handle).poll(cx) { - Poll::Pending => Poll::Pending, - Poll::Ready(join_res) => { - debug!(%subtask, "finishing subtask"); - Poll::Ready( - join_res - .map_err(CriticalError::MainTaskJoin) - .and_then(|x| x), - ) - } - } - } -} diff --git a/crates/lib/tests/env_reporting.rs b/crates/lib/tests/env_reporting.rs index 3dfc947..79c3156 100644 --- a/crates/lib/tests/env_reporting.rs +++ b/crates/lib/tests/env_reporting.rs @@ -1,10 +1,8 @@ use std::{collections::HashMap, ffi::OsString, path::MAIN_SEPARATOR}; use notify::event::CreateKind; -use watchexec::{ - event::{filekind::*, Event, Tag}, - paths::summarise_events_to_env, -}; +use watchexec::paths::summarise_events_to_env; +use watchexec_events::{filekind::*, Event, Tag}; #[cfg(unix)] const ENV_SEP: &str = ":"; diff --git a/crates/lib/tests/error_handler.rs b/crates/lib/tests/error_handler.rs index de740ed..e006f01 100644 --- a/crates/lib/tests/error_handler.rs +++ b/crates/lib/tests/error_handler.rs @@ -2,24 +2,16 @@ use std::time::Duration; use miette::Result; use tokio::time::sleep; -use watchexec::{ - config::{InitConfig, RuntimeConfig}, - ErrorHook, Watchexec, -}; +use watchexec::{ErrorHook, Watchexec}; #[tokio::main] async fn main() -> Result<()> { tracing_subscriber::fmt::init(); - let mut init = InitConfig::default(); - init.on_error(|err: ErrorHook| async move { + let wx = Watchexec::default(); + wx.config.on_error(|err: ErrorHook| { eprintln!("Watchexec Runtime Error: {}", err.error); - Ok::<(), std::convert::Infallible>(()) }); - - let runtime = RuntimeConfig::default(); - - let wx = Watchexec::new(init, runtime)?; wx.main(); // TODO: induce an error here diff --git a/crates/project-origins/Cargo.toml b/crates/project-origins/Cargo.toml index 1e921d8..bd5304a 100644 --- a/crates/project-origins/Cargo.toml +++ b/crates/project-origins/Cargo.toml @@ -15,8 +15,8 @@ rust-version = "1.58.0" edition = "2021" [dependencies] -futures = "0.3.21" -tokio = { version = "1.24.2", features = ["fs"] } +futures = "0.3.29" +tokio = { version = "1.33.0", features = ["fs"] } tokio-stream = { version = "0.1.9", features = ["fs"] } [dev-dependencies] diff --git a/crates/signals/Cargo.toml b/crates/signals/Cargo.toml index 4a346c3..5af0d3f 100644 --- a/crates/signals/Cargo.toml +++ b/crates/signals/Cargo.toml @@ -19,16 +19,16 @@ version = "5.3.0" optional = true [dependencies.thiserror] -version = "1.0.26" +version = "1.0.50" optional = true [dependencies.serde] -version = "1.0.152" +version = "1.0.183" optional = true features = ["derive"] [target.'cfg(unix)'.dependencies.nix] -version = "0.26.2" +version = "0.27.1" features = ["signal"] [features] diff --git a/crates/supervisor/CHANGELOG.md b/crates/supervisor/CHANGELOG.md new file mode 100644 index 0000000..1ab686f --- /dev/null +++ b/crates/supervisor/CHANGELOG.md @@ -0,0 +1,5 @@ +# Changelog + +## Next (YYYY-MM-DD) + +- Initial release as a separate crate. diff --git a/crates/supervisor/Cargo.toml b/crates/supervisor/Cargo.toml new file mode 100644 index 0000000..580267e --- /dev/null +++ b/crates/supervisor/Cargo.toml @@ -0,0 +1,45 @@ +[package] +name = "watchexec-supervisor" +version = "0.1.0" + +authors = ["Félix Saparelli "] +license = "Apache-2.0 OR MIT" +description = "Watchexec's process supervisor component" +keywords = ["process", "command", "supervisor", "watchexec"] + +documentation = "https://docs.rs/watchexec-supervisor" +repository = "https://github.com/watchexec/watchexec" +readme = "README.md" + +rust-version = "1.58.0" +edition = "2021" + +[dependencies] +futures = "0.3.29" +tracing = "0.1.40" + +[dependencies.command-group] +version = "5.0.1" +features = ["with-tokio"] + +[dependencies.tokio] +version = "1.33.0" +default-features = false +features = ["macros", "process", "rt", "sync", "time"] + +[dependencies.watchexec-events] +version = "1.0.0" +default-features = false +path = "../events" + +[dependencies.watchexec-signals] +version = "1.0.0" +default-features = false +path = "../signals" + +[target.'cfg(unix)'.dependencies.nix] +version = "0.27.1" +features = ["signal"] + +[dev-dependencies] +boxcar = "0.2.4" diff --git a/crates/supervisor/README.md b/crates/supervisor/README.md new file mode 100644 index 0000000..a74c9d3 --- /dev/null +++ b/crates/supervisor/README.md @@ -0,0 +1,15 @@ +[![Crates.io page](https://badgen.net/crates/v/watchexec-supervisor)](https://crates.io/crates/watchexec-supervisor) +[![API Docs](https://docs.rs/watchexec-supervisor/badge.svg)][docs] +[![Crate license: Apache 2.0](https://badgen.net/badge/license/Apache%202.0)][license] +[![CI status](https://github.com/watchexec/watchexec/actions/workflows/check.yml/badge.svg)](https://github.com/watchexec/watchexec/actions/workflows/check.yml) + +# Supervisor + +_Watchexec's process supervisor._ + +- **[API documentation][docs]**. +- Licensed under [Apache 2.0][license]. +- Status: maintained. + +[docs]: https://docs.rs/watchexec-supervisor +[license]: ../../LICENSE diff --git a/crates/supervisor/release.toml b/crates/supervisor/release.toml new file mode 100644 index 0000000..e5bfd75 --- /dev/null +++ b/crates/supervisor/release.toml @@ -0,0 +1,10 @@ +pre-release-commit-message = "release: supervisor v{{version}}" +tag-prefix = "supervisor-" +tag-message = "watchexec-supervisor {{version}}" + +[[pre-release-replacements]] +file = "CHANGELOG.md" +search = "^## Next.*$" +replace = "## Next (YYYY-MM-DD)\n\n## v{{version}} ({{date}})" +prerelease = true +max = 1 diff --git a/crates/supervisor/src/command.rs b/crates/supervisor/src/command.rs new file mode 100644 index 0000000..a5bef6a --- /dev/null +++ b/crates/supervisor/src/command.rs @@ -0,0 +1,72 @@ +//! Command construction and configuration. + +#[doc(inline)] +pub use self::{program::Program, shell::Shell}; + +mod conversions; +mod program; +mod shell; + +/// A command to execute. +/// +/// # Example +/// +/// ``` +/// # use watchexec_supervisor::command::{Command, Program}; +/// Command { +/// program: Program::Exec { +/// prog: "make".into(), +/// args: vec!["check".into()], +/// }, +/// options: Default::default(), +/// }; +/// ``` +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub struct Command { + /// Program to execute for this command. + pub program: Program, + + /// Options for spawning the program. + pub options: SpawnOptions, +} + +/// Options set when constructing or spawning a command. +/// +/// It's recommended to use the [`Default`] implementation for this struct, and only set the options +/// you need to change, to proof against new options being added in future. +/// +/// # Examples +/// +/// ``` +/// # use watchexec_supervisor::command::{Command, Program, SpawnOptions}; +/// Command { +/// program: Program::Exec { +/// prog: "make".into(), +/// args: vec!["check".into()], +/// }, +/// options: SpawnOptions { +/// grouped: true, +/// ..Default::default() +/// }, +/// }; +/// ``` +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)] +pub struct SpawnOptions { + /// Run the program in a new process group. + /// + /// This will use either of Unix [process groups] or Windows [Job Objects] via the + /// [`command-group`](command_group) crate. + /// + /// [process groups]: https://en.wikipedia.org/wiki/Process_group + /// [Job Objects]: https://en.wikipedia.org/wiki/Object_Manager_(Windows) + pub grouped: bool, + + /// Reset the signal mask of the process before we spawn it. + /// + /// By default, the signal mask of the process is inherited from the parent process. This means + /// that if the parent process has blocked any signals, the child process will also block those + /// signals. This can cause problems if the child process is expecting to receive those signals. + /// + /// This is only supported on Unix systems. + pub reset_sigmask: bool, +} diff --git a/crates/supervisor/src/command/conversions.rs b/crates/supervisor/src/command/conversions.rs new file mode 100644 index 0000000..5ee4108 --- /dev/null +++ b/crates/supervisor/src/command/conversions.rs @@ -0,0 +1,100 @@ +use std::fmt; + +use tokio::process::Command as TokioCommand; +use tracing::trace; + +use super::{Command, Program}; + +impl Command { + /// Obtain a [`tokio::process::Command`]. + pub fn to_spawnable(&self) -> TokioCommand { + trace!(program=?self.program, "constructing command"); + + #[cfg_attr(not(unix), allow(unused_mut))] + let mut cmd = match &self.program { + Program::Exec { prog, args, .. } => { + let mut c = TokioCommand::new(prog); + c.args(args); + c + } + + Program::Shell { + shell, + args, + command, + } => { + let mut c = TokioCommand::new(shell.prog.clone()); + + // Avoid quoting issues on Windows by using raw_arg everywhere + #[cfg(windows)] + { + for opt in &shell.options { + c.raw_arg(opt); + } + if let Some(progopt) = &shell.program_option { + c.raw_arg(progopt); + } + c.raw_arg(command); + for arg in args { + c.raw_arg(arg); + } + } + + #[cfg(not(windows))] + { + c.args(shell.options.clone()); + if let Some(progopt) = &shell.program_option { + c.arg(progopt); + } + c.arg(command); + for arg in args { + c.arg(arg); + } + } + + c + } + }; + + #[cfg(unix)] + if self.options.reset_sigmask { + use nix::sys::signal::{sigprocmask, SigSet, SigmaskHow}; + unsafe { + cmd.pre_exec(|| { + let mut oldset = SigSet::empty(); + let newset = SigSet::all(); + trace!(unblocking=?newset, "resetting process sigmask"); + sigprocmask(SigmaskHow::SIG_UNBLOCK, Some(&newset), Some(&mut oldset))?; + trace!(?oldset, "sigmask reset"); + Ok(()) + }); + } + } + + cmd + } +} + +impl fmt::Display for Program { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Exec { prog, args, .. } => { + write!(f, "{}", prog.display())?; + for arg in args { + write!(f, " {arg}")?; + } + + Ok(()) + } + Self::Shell { command, .. } => { + write!(f, "{command}") + } + } + } +} + +impl fmt::Display for Command { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.program) + } +} diff --git a/crates/supervisor/src/command/program.rs b/crates/supervisor/src/command/program.rs new file mode 100644 index 0000000..aa79f13 --- /dev/null +++ b/crates/supervisor/src/command/program.rs @@ -0,0 +1,37 @@ +use std::path::PathBuf; + +use super::Shell; + +/// A single program call. +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub enum Program { + /// A raw program call: the path or name of a program and its argument list. + Exec { + /// Path or name of the program. + prog: PathBuf, + + /// The arguments to pass. + args: Vec, + }, + + /// A shell program: a string which is to be executed by a shell. + /// + /// (Tip: in general, a shell will handle its own job control, so there's no inherent need to + /// set `grouped: true` at the [`Command`](super::Command) level.) + Shell { + /// The shell to run. + shell: Shell, + + /// The command line to pass to the shell. + command: String, + + /// The arguments to pass to the shell invocation. + /// + /// This may not be supported by all shells. Note that some shells require the use of `--` + /// for disambiguation: this is not handled by Watchexec, and will need to be the first + /// item in this vec if desired. + /// + /// This appends the values within to the shell process invocation. + args: Vec, + }, +} diff --git a/crates/supervisor/src/command/shell.rs b/crates/supervisor/src/command/shell.rs new file mode 100644 index 0000000..d88cf90 --- /dev/null +++ b/crates/supervisor/src/command/shell.rs @@ -0,0 +1,40 @@ +use std::{borrow::Cow, ffi::OsStr, path::PathBuf}; + +/// How to call the shell used to run shelled programs. +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub struct Shell { + /// Path or name of the shell. + pub prog: PathBuf, + + /// Additional options or arguments to pass to the shell. + /// + /// These will be inserted before the `program_option` immediately preceding the program string. + pub options: Vec, + + /// The syntax of the option which precedes the program string. + /// + /// For most shells, this is `-c`. On Windows, CMD.EXE prefers `/C`. If this is `None`, then no + /// option is prepended; this may be useful for non-shell or non-standard shell programs. + pub program_option: Option>, +} + +impl Shell { + /// Shorthand for most shells, using the `-c` convention. + pub fn new(name: impl Into) -> Self { + Self { + prog: name.into(), + options: Vec::new(), + program_option: Some(Cow::Borrowed(OsStr::new("-c"))), + } + } + + #[cfg(windows)] + /// Shorthand for the CMD.EXE shell. + pub fn cmd() -> Self { + Self { + prog: "CMD.EXE".into(), + options: Vec::new(), + program_option: Some(Cow::Borrowed(OsStr::new("/C"))), + } + } +} diff --git a/crates/supervisor/src/errors.rs b/crates/supervisor/src/errors.rs new file mode 100644 index 0000000..7458651 --- /dev/null +++ b/crates/supervisor/src/errors.rs @@ -0,0 +1,16 @@ +//! Error types. + +use std::{ + io::Error, + sync::{Arc, OnceLock}, +}; + +/// Convenience type for a [`std::io::Error`] which can be shared across threads. +pub type SyncIoError = Arc>; + +/// Make a [`SyncIoError`] from a [`std::io::Error`]. +pub fn sync_io_error(err: Error) -> SyncIoError { + let lock = OnceLock::new(); + lock.set(err).expect("unreachable: lock was just created"); + Arc::new(lock) +} diff --git a/crates/supervisor/src/flag.rs b/crates/supervisor/src/flag.rs new file mode 100644 index 0000000..39a1f2b --- /dev/null +++ b/crates/supervisor/src/flag.rs @@ -0,0 +1,71 @@ +//! A flag that can be raised to wake a task. +//! +//! Copied wholesale from +//! unfortunately not aware of crated version! + +use std::{ + pin::Pin, + sync::{ + atomic::{AtomicBool, Ordering::Relaxed}, + Arc, + }, +}; + +use futures::{ + future::Future, + task::{AtomicWaker, Context, Poll}, +}; + +#[derive(Debug)] +struct Inner { + waker: AtomicWaker, + set: AtomicBool, +} + +#[derive(Clone, Debug)] +pub struct Flag(Arc); + +impl Default for Flag { + fn default() -> Self { + Self::new(false) + } +} + +impl Flag { + pub fn new(value: bool) -> Self { + Self(Arc::new(Inner { + waker: AtomicWaker::new(), + set: AtomicBool::new(value), + })) + } + + pub fn raised(&self) -> bool { + self.0.set.load(Relaxed) + } + + pub fn raise(&self) { + self.0.set.store(true, Relaxed); + self.0.waker.wake(); + } +} + +impl Future for Flag { + type Output = (); + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> { + // quick check to avoid registration if already done. + if self.0.set.load(Relaxed) { + return Poll::Ready(()); + } + + self.0.waker.register(cx.waker()); + + // Need to check condition **after** `register` to avoid a race + // condition that would result in lost notifications. + if self.0.set.load(Relaxed) { + Poll::Ready(()) + } else { + Poll::Pending + } + } +} diff --git a/crates/supervisor/src/job.rs b/crates/supervisor/src/job.rs new file mode 100644 index 0000000..739d56d --- /dev/null +++ b/crates/supervisor/src/job.rs @@ -0,0 +1,31 @@ +//! Job supervision. + +#[doc(inline)] +pub use self::{ + job::Job, + messages::{Control, Ticket}, + state::CommandState, + task::JobTaskContext, +}; + +#[cfg(test)] +pub(crate) use self::{ + priority::Priority, + testchild::{TestChild, TestChildCall}, +}; + +#[doc(inline)] +pub use task::start_job; + +#[allow(clippy::module_inception)] +mod job; +mod messages; +mod priority; +mod state; +mod task; + +#[cfg(test)] +mod testchild; + +#[cfg(test)] +mod test; diff --git a/crates/supervisor/src/job/job.rs b/crates/supervisor/src/job/job.rs new file mode 100644 index 0000000..ccde571 --- /dev/null +++ b/crates/supervisor/src/job/job.rs @@ -0,0 +1,351 @@ +use std::{future::Future, sync::Arc, time::Duration}; + +use tokio::process::Command as TokioCommand; +use watchexec_signals::Signal; + +use crate::{command::Command, errors::SyncIoError, flag::Flag}; + +use super::{ + messages::{Control, ControlMessage, Ticket}, + priority::{Priority, PrioritySender}, + JobTaskContext, +}; + +/// A handle to a job task spawned in the supervisor. +/// +/// A job is a task which manages a [`Command`]. It is responsible for spawning the command's +/// program, for handling messages which control it, for managing the program's lifetime, and for +/// collecting its exit status and some timing information. +/// +/// Most of the methods here queue [`Control`]s to the job task and return [`Ticket`]s. Controls +/// execute in order, except where noted. Tickets are futures which resolve when the corresponding +/// control has been run. Unlike most futures, tickets don't need to be polled for controls to make +/// progress; the future is only used to signal completion. Dropping a ticket will not drop the +/// control, so it's safe to do so if you don't care about when the control completes. +/// +/// Note that controls are not guaranteed to run, like if the job task stops or panics before a +/// control is processed. If a job task stops gracefully, all pending tickets will resolve +/// immediately. If a job task panics (outside of hooks, panics are bugs!), pending tickets will +/// never resolve. +/// +/// This struct is cloneable (internally it is made of Arcs). Dropping the last instance of a Job +/// will close the job's control queue, which will cause the job task to stop gracefully. Note that +/// a task graceful stop is not the same as a graceful stop of the contained command; when the job +/// drops, the command will be dropped in turn, and forcefully terminated via `kill_on_drop`. +#[derive(Debug, Clone)] +pub struct Job { + pub(crate) command: Arc, + pub(crate) control_queue: PrioritySender, + + /// Set to true when the command task has stopped gracefully. + pub(crate) gone: Flag, +} + +impl Job { + /// The [`Command`] this job is managing. + pub fn command(&self) -> Arc { + self.command.clone() + } + + /// If this job is dead. + pub fn is_dead(&self) -> bool { + self.gone.raised() + } + + fn prepare_control(&self, control: Control) -> (Ticket, ControlMessage) { + let done = Flag::default(); + ( + Ticket { + job_gone: self.gone.clone(), + control_done: done.clone(), + }, + ControlMessage { control, done }, + ) + } + + pub(crate) fn send_controls( + &self, + controls: [Control; N], + priority: Priority, + ) -> Ticket { + if N == 0 || self.gone.raised() { + Ticket::cancelled() + } else if N == 1 { + let control = controls.into_iter().next().expect("UNWRAP: N > 0"); + let (ticket, control) = self.prepare_control(control); + self.control_queue.send(control, priority); + ticket + } else { + let mut last_ticket = None; + for control in controls { + let (ticket, control) = self.prepare_control(control); + last_ticket = Some(ticket); + self.control_queue.send(control, priority); + } + last_ticket.expect("UNWRAP: N > 0") + } + } + + /// Send a control message to the command. + /// + /// All control messages are queued in the order they're sent and processed in order. + /// + /// In general prefer using the other methods on this struct rather than sending [`Control`]s + /// directly. + pub fn control(&self, control: Control) -> Ticket { + self.send_controls([control], Priority::Normal) + } + + /// Start the command if it's not running. + pub fn start(&self) -> Ticket { + self.control(Control::Start) + } + + /// Stop the command if it's running and wait for completion. + /// + /// If you don't want to wait for completion, use `signal(Signal::ForceStop)` instead. + pub fn stop(&self) -> Ticket { + self.control(Control::Stop) + } + + /// Gracefully stop the command if it's running. + /// + /// The command will be sent `signal` and then given `grace` time before being forcefully + /// terminated. If `grace` is zero, that still happens, but the command is terminated forcefully + /// on the next "tick" of the supervisor loop, which doesn't leave the process a lot of time to + /// do anything. + pub fn stop_with_signal(&self, signal: Signal, grace: Duration) -> Ticket { + if cfg!(unix) { + self.control(Control::GracefulStop { signal, grace }) + } else { + self.stop() + } + } + + /// Restart the command if it's running, or start it if it's not. + pub fn restart(&self) -> Ticket { + self.send_controls([Control::Stop, Control::Start], Priority::Normal) + } + + /// Gracefully restart the command if it's running, or start it if it's not. + /// + /// The command will be sent `signal` and then given `grace` time before being forcefully + /// terminated. If `grace` is zero, that still happens, but the command is terminated forcefully + /// on the next "tick" of the supervisor loop, which doesn't leave the process a lot of time to + /// do anything. + pub fn restart_with_signal(&self, signal: Signal, grace: Duration) -> Ticket { + if cfg!(unix) { + self.send_controls( + [Control::GracefulStop { signal, grace }, Control::Start], + Priority::Normal, + ) + } else { + self.restart() + } + } + + /// Restart the command if it's running, but don't start it if it's not. + pub fn try_restart(&self) -> Ticket { + self.control(Control::TryRestart) + } + + /// Restart the command if it's running, but don't start it if it's not. + /// + /// The command will be sent `signal` and then given `grace` time before being forcefully + /// terminated. If `grace` is zero, that still happens, but the command is terminated forcefully + /// on the next "tick" of the supervisor loop, which doesn't leave the process a lot of time to + /// do anything. + pub fn try_restart_with_signal(&self, signal: Signal, grace: Duration) -> Ticket { + if cfg!(unix) { + self.control(Control::TryGracefulRestart { signal, grace }) + } else { + self.try_restart() + } + } + + /// Send a signal to the command. + /// + /// Sends a signal to the current program, if there is one. If there isn't, this is a no-op. + /// + /// On Windows, this is a no-op for all signals but [`Signal::ForceStop`], which tries to stop + /// the command like a `stop()` would, but doesn't wait for completion. This is because Windows + /// doesn't have signals; in future [`Hangup`](Signal::Hangup), [`Interrupt`](Signal::Interrupt), + /// and [`Terminate`](Signal::Terminate) may be implemented using [GenerateConsoleCtrlEvent], + /// see [tracking issue #219](https://github.com/watchexec/watchexec/issues/219). + /// + /// [GenerateConsoleCtrlEvent]: https://learn.microsoft.com/en-us/windows/console/generateconsolectrlevent + pub fn signal(&self, sig: Signal) -> Ticket { + self.control(Control::Signal(sig)) + } + + /// Stop the command, then mark it for garbage collection. + /// + /// The underlying control messages are sent like normal, so they wait for all pending controls + /// to process. If you want to delete the command immediately, use `delete_now()`. + pub fn delete(&self) -> Ticket { + self.send_controls([Control::Stop, Control::Delete], Priority::Normal) + } + + /// Stop the command immediately, then mark it for garbage collection. + /// + /// The underlying control messages are sent with higher priority than normal, so they bypass + /// all others. If you want to delete after all current controls are processed, use `delete()`. + pub fn delete_now(&self) -> Ticket { + self.send_controls([Control::Stop, Control::Delete], Priority::Urgent) + } + + /// Get a future which resolves when the command ends. + /// + /// If the command is not running, the future resolves immediately. + /// + /// The underlying control message is sent with higher priority than normal, so it targets the + /// actively running command, not the one that will be running after the rest of the controls + /// get done; note that may still be racy if the command ends between the time the message is + /// sent and the time it's processed. + pub fn to_wait(&self) -> Ticket { + self.send_controls([Control::NextEnding], Priority::High) + } + + /// Run an arbitrary function. + /// + /// The function is given [`&JobTaskContext`](JobTaskContext), which contains the state of the + /// currently executing, next-to-start, or just-finished command, as well as the final state of + /// the _previous_ run of the command. + /// + /// Technically, some operations can be done through a `&self` shared borrow on the running + /// command's [`ErasedChild`](command_group::tokio::ErasedChild), but this library recommends + /// against taking advantage of this, and prefer using the methods here instead, so that the + /// supervisor can keep track of what's going on. + pub fn run(&self, fun: impl FnOnce(&JobTaskContext<'_>) + Send + Sync + 'static) -> Ticket { + self.control(Control::SyncFunc(Box::new(fun))) + } + + /// Run an arbitrary function and await the returned future. + /// + /// The function is given [`&JobTaskContext`](JobTaskContext), which contains the state of the + /// currently executing, next-to-start, or just-finished command, as well as the final state of + /// the _previous_ run of the command. + /// + /// Technically, some operations can be done through a `&self` shared borrow on the running + /// command's [`ErasedChild`](command_group::tokio::ErasedChild), but this library recommends + /// against taking advantage of this, and prefer using the methods here instead, so that the + /// supervisor can keep track of what's going on. + /// + /// A gotcha when using this method is that the future returned by the function can live longer + /// than the `&JobTaskContext` it was given, so you can't bring the context into the async block + /// and instead must clone or copy the parts you need beforehand, in the sync portion. + /// + /// For example, this won't compile: + /// + /// ```compile_fail + /// # use std::sync::Arc; + /// # use tokio::sync::mpsc; + /// # use watchexec_supervisor::command::{Command, Program}; + /// # use watchexec_supervisor::job::{CommandState, start_job}; + /// # + /// # let (job, _task) = start_job(Arc::new(Command { program: Program::Exec { prog: "/bin/date".into(), args: Vec::new() }.into(), options: Default::default() })); + /// let (channel, receiver) = mpsc::channel(10); + /// job.run_async(|context| Box::new(async move { + /// if let CommandState::Finished { status, .. } = context.current { + /// channel.send(status).await.ok(); + /// } + /// })); + /// ``` + /// + /// But this does: + /// + /// ```no_run + /// # use std::sync::Arc; + /// # use tokio::sync::mpsc; + /// # use watchexec_supervisor::command::{Command, Program}; + /// # use watchexec_supervisor::job::{CommandState, start_job}; + /// # + /// # let (job, _task) = start_job(Arc::new(Command { program: Program::Exec { prog: "/bin/date".into(), args: Vec::new() }.into(), options: Default::default() })); + /// let (channel, receiver) = mpsc::channel(10); + /// job.run_async(|context| { + /// let status = if let CommandState::Finished { status, .. } = context.current { + /// Some(*status) + /// } else { + /// None + /// }; + /// + /// Box::new(async move { + /// if let Some(status) = status { + /// channel.send(status).await.ok(); + /// } + /// }) + /// }); + /// ``` + pub fn run_async( + &self, + fun: impl (FnOnce(&JobTaskContext<'_>) -> Box + Send + Sync>) + + Send + + Sync + + 'static, + ) -> Ticket { + self.control(Control::AsyncFunc(Box::new(fun))) + } + + /// Set the spawn hook. + /// + /// The hook will be called once per process spawned, before the process is spawned. It's given + /// a mutable reference to the [`tokio::process::Command`] and some context; it can modify the + /// command as it sees fit. + pub fn set_spawn_hook( + &self, + fun: impl Fn(&mut TokioCommand, &JobTaskContext<'_>) + Send + Sync + 'static, + ) -> Ticket { + self.control(Control::SetSyncSpawnHook(Arc::new(fun))) + } + + /// Set the spawn hook (async version). + /// + /// The hook will be called once per process spawned, before the process is spawned. It's given + /// a mutable reference to the [`tokio::process::Command`] and some context; it can modify the + /// command as it sees fit. + /// + /// A gotcha when using this method is that the future returned by the function can live longer + /// than the references it was given, so you can't bring the command or context into the async + /// block and instead must clone or copy the parts you need beforehand, in the sync portion. See + /// the documentation for [`run_async`](Job::run_async) for an example. + /// + /// Fortunately, async spawn hooks should be exceedingly rare: there's very few things to do in + /// spawn hooks that can't be done in the simpler sync version. + pub fn set_spawn_async_hook( + &self, + fun: impl (Fn(&mut TokioCommand, &JobTaskContext<'_>) -> Box + Send + Sync>) + + Send + + Sync + + 'static, + ) -> Ticket { + self.control(Control::SetAsyncSpawnHook(Arc::new(fun))) + } + + /// Unset any spawn hook. + pub fn unset_spawn_hook(&self) -> Ticket { + self.control(Control::UnsetSpawnHook) + } + + /// Set the error handler. + pub fn set_error_handler(&self, fun: impl Fn(SyncIoError) + Send + Sync + 'static) -> Ticket { + self.control(Control::SetSyncErrorHandler(Arc::new(fun))) + } + + /// Set the error handler (async version). + pub fn set_async_error_handler( + &self, + fun: impl (Fn(SyncIoError) -> Box + Send + Sync>) + + Send + + Sync + + 'static, + ) -> Ticket { + self.control(Control::SetAsyncErrorHandler(Arc::new(fun))) + } + + /// Unset the error handler. + /// + /// Errors will be silently ignored. + pub fn unset_error_handler(&self) -> Ticket { + self.control(Control::UnsetErrorHandler) + } +} diff --git a/crates/supervisor/src/job/messages.rs b/crates/supervisor/src/job/messages.rs new file mode 100644 index 0000000..16dd8f8 --- /dev/null +++ b/crates/supervisor/src/job/messages.rs @@ -0,0 +1,148 @@ +use std::{ + future::Future, + pin::Pin, + task::{Context, Poll}, + time::Duration, +}; + +use futures::{future::select, FutureExt}; +use watchexec_signals::Signal; + +use crate::flag::Flag; + +use super::task::{ + AsyncErrorHandler, AsyncFunc, AsyncSpawnHook, SyncErrorHandler, SyncFunc, SyncSpawnHook, +}; + +/// The underlying control message types for [`Job`](super::Job). +/// +/// You may use [`Job::control()`](super::Job::control()) to send these messages directly, but in +/// general should prefer the higher-level methods on [`Job`](super::Job) itself. +pub enum Control { + /// For [`Job::start()`](super::Job::start()). + Start, + /// For [`Job::stop()`](super::Job::stop()). + Stop, + /// For [`Job::stop_with_signal()`](super::Job::stop_with_signal()). + GracefulStop { + /// Signal to send immediately + signal: Signal, + /// Time to wait before forceful termination + grace: Duration, + }, + /// For [`Job::try_restart()`](super::Job::try_restart()). + TryRestart, + /// For [`Job::try_restart_with_signal()`](super::Job::try_restart_with_signal()). + TryGracefulRestart { + /// Signal to send immediately + signal: Signal, + /// Time to wait before forceful termination and restart + grace: Duration, + }, + /// Internal implementation detail of [`Control::TryGracefulRestart`]. + ContinueTryGracefulRestart, + /// For [`Job::signal()`](super::Job::signal()). + Signal(Signal), + /// For [`Job::delete()`](super::Job::delete()) and [`Job::delete_now()`](super::Job::delete_now()). + Delete, + + /// For [`Job::to_wait()`](super::Job::to_wait()). + NextEnding, + + /// For [`Job::run()`](super::Job::run()). + SyncFunc(SyncFunc), + /// For [`Job::run_async()`](super::Job::run_async()). + AsyncFunc(AsyncFunc), + + /// For [`Job::set_spawn_hook()`](super::Job::set_spawn_hook()). + SetSyncSpawnHook(SyncSpawnHook), + /// For [`Job::set_spawn_async_hook()`](super::Job::set_spawn_async_hook()). + SetAsyncSpawnHook(AsyncSpawnHook), + /// For [`Job::unset_spawn_hook()`](super::Job::unset_spawn_hook()). + UnsetSpawnHook, + /// For [`Job::set_error_handler()`](super::Job::set_error_handler()). + SetSyncErrorHandler(SyncErrorHandler), + /// For [`Job::set_async_error_handler()`](super::Job::set_async_error_handler()). + SetAsyncErrorHandler(AsyncErrorHandler), + /// For [`Job::unset_error_handler()`](super::Job::unset_error_handler()). + UnsetErrorHandler, +} + +impl std::fmt::Debug for Control { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Start => f.debug_struct("Start").finish(), + Self::Stop => f.debug_struct("Stop").finish(), + Self::GracefulStop { signal, grace } => f + .debug_struct("GracefulStop") + .field("signal", signal) + .field("grace", grace) + .finish(), + Self::TryRestart => f.debug_struct("TryRestart").finish(), + Self::TryGracefulRestart { signal, grace } => f + .debug_struct("TryGracefulRestart") + .field("signal", signal) + .field("grace", grace) + .finish(), + Self::ContinueTryGracefulRestart => { + f.debug_struct("ContinueTryGracefulRestart").finish() + } + Self::Signal(signal) => f.debug_struct("Signal").field("signal", signal).finish(), + Self::Delete => f.debug_struct("Delete").finish(), + + Self::NextEnding => f.debug_struct("NextEnding").finish(), + + Self::SyncFunc(_) => f.debug_struct("SyncFunc").finish_non_exhaustive(), + Self::AsyncFunc(_) => f.debug_struct("AsyncFunc").finish_non_exhaustive(), + + Self::SetSyncSpawnHook(_) => f.debug_struct("SetSyncSpawnHook").finish_non_exhaustive(), + Self::SetAsyncSpawnHook(_) => { + f.debug_struct("SetSpawnAsyncHook").finish_non_exhaustive() + } + Self::UnsetSpawnHook => f.debug_struct("UnsetSpawnHook").finish(), + Self::SetSyncErrorHandler(_) => f + .debug_struct("SetSyncErrorHandler") + .finish_non_exhaustive(), + Self::SetAsyncErrorHandler(_) => f + .debug_struct("SetAsyncErrorHandler") + .finish_non_exhaustive(), + Self::UnsetErrorHandler => f.debug_struct("UnsetErrorHandler").finish(), + } + } +} + +#[derive(Debug)] +pub(crate) struct ControlMessage { + pub control: Control, + pub done: Flag, +} + +/// Lightweight future which resolves when the corresponding control has been run. +/// +/// Unlike most futures, tickets don't need to be polled for controls to make progress; the future +/// is only used to signal completion. Dropping a ticket will not drop the control, so it's safe to +/// do so if you don't care about when the control completes. +/// +/// Tickets can be cloned, and all clones will resolve at the same time. +#[derive(Debug, Clone)] +pub struct Ticket { + pub(crate) job_gone: Flag, + pub(crate) control_done: Flag, +} + +impl Ticket { + pub(crate) fn cancelled() -> Self { + Self { + job_gone: Flag::new(true), + control_done: Flag::new(true), + } + } +} + +impl Future for Ticket { + type Output = (); + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + Pin::new(&mut select(self.job_gone.clone(), self.control_done.clone()).map(|_| ())).poll(cx) + } +} diff --git a/crates/supervisor/src/job/priority.rs b/crates/supervisor/src/job/priority.rs new file mode 100644 index 0000000..7a7f777 --- /dev/null +++ b/crates/supervisor/src/job/priority.rs @@ -0,0 +1,146 @@ +use std::time::Duration; + +use tokio::{ + select, + sync::mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender}, + time::{sleep_until, Instant, Sleep}, +}; + +use crate::flag::Flag; + +use super::{messages::ControlMessage, Control}; + +#[derive(Debug, Copy, Clone)] +pub(crate) enum Priority { + Normal, + High, + Urgent, +} + +#[derive(Debug)] +pub(crate) struct PriorityReceiver { + pub normal: UnboundedReceiver, + pub high: UnboundedReceiver, + pub urgent: UnboundedReceiver, +} + +#[derive(Debug, Clone)] +pub(crate) struct PrioritySender { + pub normal: UnboundedSender, + pub high: UnboundedSender, + pub urgent: UnboundedSender, +} + +impl PrioritySender { + pub fn send(&self, message: ControlMessage, priority: Priority) { + // drop errors: if the channel is closed, the job is dead + let _ = match priority { + Priority::Normal => self.normal.send(message), + Priority::High => self.high.send(message), + Priority::Urgent => self.urgent.send(message), + }; + } +} + +impl PriorityReceiver { + /// Receive a control message from the command. + /// + /// If `stop_timer` is `Some`, normal priority messages are not received; instead, only high and + /// urgent priority messages are received until the timer expires, and when the timer completes, + /// a `Stop` control message is returned and the `stop_timer` is `None`d. + /// + /// This is used to implement stop's, restart's, and try-restart's graceful stopping logic. + pub async fn recv(&mut self, stop_timer: &mut Option) -> Option { + if stop_timer.as_ref().map_or(false, Timer::is_past) { + return stop_timer.take().map(|timer| timer.to_control()); + } + + if let Ok(message) = self.urgent.try_recv() { + return Some(message); + } + + if let Ok(message) = self.high.try_recv() { + return Some(message); + } + + if let Some(timer) = stop_timer.clone() { + select! { + _ = timer.to_sleep() => { + *stop_timer = None; + Some(timer.to_control()) + } + message = self.urgent.recv() => message, + message = self.high.recv() => message, + } + } else { + select! { + message = self.urgent.recv() => message, + message = self.high.recv() => message, + message = self.normal.recv() => message, + } + } + } +} + +pub(crate) fn new() -> (PrioritySender, PriorityReceiver) { + let (normal_tx, normal_rx) = unbounded_channel(); + let (high_tx, high_rx) = unbounded_channel(); + let (urgent_tx, urgent_rx) = unbounded_channel(); + + ( + PrioritySender { + normal: normal_tx, + high: high_tx, + urgent: urgent_tx, + }, + PriorityReceiver { + normal: normal_rx, + high: high_rx, + urgent: urgent_rx, + }, + ) +} + +#[derive(Debug, Clone)] +pub(crate) struct Timer { + pub until: Instant, + pub done: Flag, + pub is_restart: bool, +} + +impl Timer { + pub fn stop(grace: Duration, done: Flag) -> Self { + Self { + until: Instant::now() + grace, + done, + is_restart: false, + } + } + + pub fn restart(grace: Duration, done: Flag) -> Self { + Self { + until: Instant::now() + grace, + done, + is_restart: true, + } + } + + fn to_sleep(&self) -> Sleep { + sleep_until(self.until) + } + + fn is_past(&self) -> bool { + self.until <= Instant::now() + } + + fn to_control(&self) -> ControlMessage { + ControlMessage { + control: if self.is_restart { + Control::ContinueTryGracefulRestart + } else { + Control::Stop + }, + done: self.done.clone(), + } + } +} diff --git a/crates/supervisor/src/job/state.rs b/crates/supervisor/src/job/state.rs new file mode 100644 index 0000000..c35746d --- /dev/null +++ b/crates/supervisor/src/job/state.rs @@ -0,0 +1,142 @@ +use std::{sync::Arc, time::Instant}; + +#[cfg(not(test))] +use command_group::{tokio::ErasedChild, AsyncCommandGroup}; +use tokio::process::Command as TokioCommand; +use watchexec_events::ProcessEnd; + +use crate::command::Command; + +/// The state of the job's command / process. +/// +/// This is used both internally to represent the current state (ready/pending, running, finished) +/// of the command, and can be queried via the [`JobTaskContext`](super::JobTaskContext) by hooks. +/// +/// Technically, some operations can be done through a `&self` shared borrow on the running +/// command's [`ErasedChild`](command_group::tokio::ErasedChild), but this library recommends +/// against taking advantage of this, and prefer using the methods on [`Job`](super::Job) instead, +/// so that the job can keep track of what's going on. +#[derive(Debug)] +#[cfg_attr(test, derive(Clone))] +pub enum CommandState { + /// The command is neither running nor has finished. This is the initial state. + Pending, + + /// The command is currently running. Note that this is established after the process is spawned + /// and not precisely synchronised with the process' aliveness: in some cases the process may be + /// exited but still `Running` in this enum. + Running { + /// The child process (test version). + #[cfg(test)] + child: super::TestChild, + + /// The child process. + #[cfg(not(test))] + child: ErasedChild, + + /// The time at which the process was spawned. + started: Instant, + }, + + /// The command has completed and its status was collected. + Finished { + /// The command's exit status. + status: ProcessEnd, + + /// The time at which the process was spawned. + started: Instant, + + /// The time at which the process finished, or more precisely, when its status was collected. + finished: Instant, + }, +} + +impl CommandState { + /// Whether the command is pending, i.e. not running or finished. + pub fn is_pending(&self) -> bool { + matches!(self, Self::Pending) + } + + /// Whether the command is running. + pub fn is_running(&self) -> bool { + matches!(self, Self::Running { .. }) + } + + /// Whether the command is finished. + pub fn is_finished(&self) -> bool { + matches!(self, Self::Finished { .. }) + } + + #[cfg_attr(test, allow(unused_mut, unused_variables))] + pub(crate) async fn spawn( + &mut self, + command: Arc, + mut spawnable: TokioCommand, + ) -> std::io::Result { + if let Self::Running { .. } = self { + return Ok(false); + } + + #[cfg(test)] + let child = super::TestChild::new(command)?; + + #[cfg(not(test))] + let child = if command.options.grouped { + ErasedChild::Grouped(spawnable.group().spawn()?) + } else { + ErasedChild::Ungrouped(spawnable.spawn()?) + }; + + *self = Self::Running { + child, + started: Instant::now(), + }; + Ok(true) + } + + #[must_use] + pub(crate) fn reset(&mut self) -> Self { + match self { + Self::Pending => Self::Pending, + Self::Finished { + status, + started, + finished, + .. + } => { + let copy = Self::Finished { + status: *status, + started: *started, + finished: *finished, + }; + + *self = Self::Pending; + copy + } + Self::Running { started, .. } => { + let copy = Self::Finished { + status: ProcessEnd::Continued, + started: *started, + finished: Instant::now(), + }; + + *self = Self::Pending; + copy + } + } + } + + pub(crate) async fn wait(&mut self) -> std::io::Result { + if let Self::Running { child, started } = self { + let end = child.wait().await?; + *self = Self::Finished { + status: end.into(), + started: *started, + finished: Instant::now(), + }; + Ok(true) + } else { + Ok(false) + } + } +} diff --git a/crates/supervisor/src/job/task.rs b/crates/supervisor/src/job/task.rs new file mode 100644 index 0000000..45ffd8c --- /dev/null +++ b/crates/supervisor/src/job/task.rs @@ -0,0 +1,354 @@ +use std::{future::Future, sync::Arc, time::Instant}; + +use tokio::{process::Command as TokioCommand, select, task::JoinHandle}; +use watchexec_signals::Signal; + +use crate::{ + command::Command, + errors::{sync_io_error, SyncIoError}, + flag::Flag, + job::priority::Timer, +}; + +use super::{ + job::Job, + messages::{Control, ControlMessage}, + priority, + state::CommandState, +}; + +/// Spawn a job task and return a [`Job`] handle and a [`JoinHandle`]. +/// +/// The job task immediately starts in the background: it does not need polling. +pub fn start_job(command: Arc) -> (Job, JoinHandle<()>) { + let (sender, mut receiver) = priority::new(); + + let gone = Flag::default(); + let done = gone.clone(); + + ( + Job { + command: command.clone(), + control_queue: sender, + gone, + }, + tokio::spawn(async move { + let mut error_handler = ErrorHandler::None; + let mut spawn_hook = SpawnHook::None; + let mut command_state = CommandState::Pending; + let mut previous_run = None; + let mut stop_timer = None; + let mut on_end: Vec = Vec::new(); + let mut on_end_restart: Option = None; + + 'main: loop { + select! { + result = command_state.wait(), if command_state.is_running() => { + #[cfg(test)] eprintln!("[{:?}] waited: {result:?}", Instant::now()); + + match result { + Err(err) => { + let fut = error_handler.call(sync_io_error(err)); + fut.await; + continue 'main; + } + Ok(true) => { + stop_timer = None; + for done in on_end.drain(..) { + done.raise(); + } + + if let Some(flag) = on_end_restart.take() { + let mut spawnable = command.to_spawnable(); + previous_run = Some(command_state.reset()); + spawn_hook + .call( + &mut spawnable, + &JobTaskContext { + command: command.clone(), + current: &command_state, + previous: previous_run.as_ref(), + }, + ) + .await; + if let Err(err) = command_state.spawn(command.clone(), spawnable).await { + let fut = error_handler.call(sync_io_error(err)); + fut.await; + continue 'main; + } + flag.raise(); + } + } + Ok(false) => {} + } + } + Some(ControlMessage { control, done }) = receiver.recv(&mut stop_timer) => { + macro_rules! try_with_handler { + ($erroring:expr) => { + match $erroring { + Err(err) => { + let fut = error_handler.call(sync_io_error(err)); + fut.await; + done.raise(); + continue 'main; + } + Ok(value) => value, + } + }; + } + + #[cfg(test)] eprintln!("[{:?}] control: {control:?}", Instant::now()); + + match control { + Control::Start => { + let mut spawnable = command.to_spawnable(); + previous_run = Some(command_state.reset()); + spawn_hook + .call( + &mut spawnable, + &JobTaskContext { + command: command.clone(), + current: &command_state, + previous: previous_run.as_ref(), + }, + ) + .await; + try_with_handler!(command_state.spawn(command.clone(), spawnable).await); + } + Control::Stop => { + if let CommandState::Running { child, started, .. } = &mut command_state { + try_with_handler!(child.kill().await); + let status = try_with_handler!(child.wait().await); + + command_state = CommandState::Finished { + status: status.into(), + started: *started, + finished: Instant::now(), + }; + + for done in on_end.drain(..) { + done.raise(); + } + } + } + Control::GracefulStop { signal, grace } => { + if let CommandState::Running { child, .. } = &mut command_state { + try_with_handler!(signal_child(signal, child).await); + + stop_timer.replace(Timer::stop(grace, done)); + continue 'main; + } + } + Control::TryRestart => { + if let CommandState::Running { child, started, .. } = &mut command_state { + try_with_handler!(child.kill().await); + let status = try_with_handler!(child.wait().await); + + command_state = CommandState::Finished { + status: status.into(), + started: *started, + finished: Instant::now(), + }; + previous_run = Some(command_state.reset()); + + for done in on_end.drain(..) { + done.raise(); + } + + let mut spawnable = command.to_spawnable(); + spawn_hook + .call( + &mut spawnable, + &JobTaskContext { + command: command.clone(), + current: &command_state, + previous: previous_run.as_ref(), + }, + ) + .await; + try_with_handler!(command_state.spawn(command.clone(), spawnable).await); + } + } + Control::TryGracefulRestart { signal, grace } => { + if let CommandState::Running { child, .. } = &mut command_state { + try_with_handler!(signal_child(signal, child).await); + + stop_timer.replace(Timer::restart(grace, done.clone())); + on_end_restart = Some(done); + continue 'main; + } + } + Control::ContinueTryGracefulRestart => { + if let CommandState::Running { child, started, .. } = &mut command_state { + try_with_handler!(child.kill().await); + let status = try_with_handler!(child.wait().await); + + command_state = CommandState::Finished { + status: status.into(), + started: *started, + finished: Instant::now(), + }; + + for done in on_end.drain(..) { + done.raise(); + } + } + + let mut spawnable = command.to_spawnable(); + previous_run = Some(command_state.reset()); + spawn_hook + .call( + &mut spawnable, + &JobTaskContext { + command: command.clone(), + current: &command_state, + previous: previous_run.as_ref(), + }, + ) + .await; + try_with_handler!(command_state.spawn(command.clone(), spawnable).await); + } + Control::Signal(signal) => { + if let CommandState::Running { child, .. } = &mut command_state { + try_with_handler!(signal_child(signal, child).await); + } + } + Control::Delete => { + done.raise(); + break 'main; + } + + Control::NextEnding => { + if !matches!(command_state, CommandState::Finished { .. }) { + on_end.push(done); + continue 'main; + } + } + + Control::SyncFunc(f) => { + f(&JobTaskContext { + command: command.clone(), + current: &command_state, + previous: previous_run.as_ref(), + }); + } + Control::AsyncFunc(f) => { + Box::into_pin(f(&JobTaskContext { + command: command.clone(), + current: &command_state, + previous: previous_run.as_ref(), + })) + .await; + } + + Control::SetSyncErrorHandler(f) => { + error_handler = ErrorHandler::Sync(f); + } + Control::SetAsyncErrorHandler(f) => { + error_handler = ErrorHandler::Async(f); + } + Control::UnsetErrorHandler => { + error_handler = ErrorHandler::None; + } + Control::SetSyncSpawnHook(f) => { + spawn_hook = SpawnHook::Sync(f); + } + Control::SetAsyncSpawnHook(f) => { + spawn_hook = SpawnHook::Async(f); + } + Control::UnsetSpawnHook => { + spawn_hook = SpawnHook::None; + } + } + + done.raise(); + } + } + } + + done.raise(); + }), + ) +} + +macro_rules! sync_async_callbox { + ($name:ident, $synct:ty, $asynct:ty, ($($argname:ident : $argtype:ty),*)) => { + pub(crate) enum $name { + None, + Sync($synct), + Async($asynct), + } + + impl $name { + pub async fn call(&self, $($argname: $argtype),*) { + match self { + $name::None => (), + $name::Sync(f) => f($($argname),*), + $name::Async(f) => Box::into_pin(f($($argname),*)).await, + } + } + } + }; +} + +/// Job task internals exposed via hooks. +#[derive(Debug)] +pub struct JobTaskContext<'task> { + /// The job's [`Command`]. + pub command: Arc, + + /// The current state of the job. + pub current: &'task CommandState, + + /// The state of the previous iteration of the job, if any. + /// + /// This is generally [`CommandState::Finished`], but may be other states in rare cases. + pub previous: Option<&'task CommandState>, +} + +pub(crate) type SyncFunc = Box) + Send + Sync + 'static>; +pub(crate) type AsyncFunc = Box< + dyn (FnOnce(&JobTaskContext<'_>) -> Box + Send + Sync>) + + Send + + Sync + + 'static, +>; + +pub(crate) type SyncSpawnHook = + Arc) + Send + Sync + 'static>; +pub(crate) type AsyncSpawnHook = Arc< + dyn (Fn(&mut TokioCommand, &JobTaskContext<'_>) -> Box + Send + Sync>) + + Send + + Sync + + 'static, +>; + +sync_async_callbox!(SpawnHook, SyncSpawnHook, AsyncSpawnHook, (command: &mut TokioCommand, context: &JobTaskContext<'_>)); + +pub(crate) type SyncErrorHandler = Arc; +pub(crate) type AsyncErrorHandler = Arc< + dyn (Fn(SyncIoError) -> Box + Send + Sync>) + Send + Sync + 'static, +>; + +sync_async_callbox!(ErrorHandler, SyncErrorHandler, AsyncErrorHandler, (error: SyncIoError)); + +async fn signal_child( + signal: Signal, + #[cfg(test)] child: &mut super::TestChild, + #[cfg(not(test))] child: &mut command_group::tokio::ErasedChild, +) -> std::io::Result<()> { + #[cfg(unix)] + child.signal( + signal + .to_nix() + .or_else(|| Signal::Terminate.to_nix()) + .expect("UNWRAP: guaranteed for Signal::Terminate default"), + )?; + + #[cfg(windows)] + if signal == Signal::ForceStop { + child.start_kill()?; + } + + Ok(()) +} diff --git a/crates/supervisor/src/job/test.rs b/crates/supervisor/src/job/test.rs new file mode 100644 index 0000000..db32e2b --- /dev/null +++ b/crates/supervisor/src/job/test.rs @@ -0,0 +1,849 @@ +#![allow(clippy::unwrap_used)] + +use std::{ + num::NonZeroI64, + process::{ExitStatus, Output}, + sync::{ + atomic::{AtomicBool, Ordering}, + Arc, Mutex, + }, + time::{Duration, Instant}, +}; + +use tokio::time::sleep; +use watchexec_events::ProcessEnd; + +#[cfg(unix)] +use crate::job::TestChildCall; +use crate::{ + command::{Command, Program}, + job::{start_job, CommandState}, +}; + +use super::{Control, Job, Priority, TestChild}; + +const GRACE: u64 = 10; // millis + +fn erroring_command() -> Arc { + Arc::new(Command { + program: Program::Exec { + prog: "/does/not/exist".into(), + args: Vec::new(), + }, + options: Default::default(), + }) +} + +fn working_command() -> Arc { + Arc::new(Command { + program: Program::Exec { + prog: "/does/not/run".into(), + args: Vec::new(), + }, + options: Default::default(), + }) +} + +fn ungraceful_command() -> Arc { + Arc::new(Command { + program: Program::Exec { + prog: "sleep".into(), + args: vec![(GRACE * 2).to_string()], + }, + options: Default::default(), + }) +} + +fn graceful_command() -> Arc { + Arc::new(Command { + program: Program::Exec { + prog: "sleep".into(), + args: vec![(2 * GRACE / 3).to_string()], + }, + options: Default::default(), + }) +} + +#[tokio::test] +async fn sync_error_handler() { + let (job, task) = start_job(erroring_command()); + let error_handler_called = Arc::new(AtomicBool::new(false)); + + job.set_error_handler({ + let error_handler_called = error_handler_called.clone(); + move |_| { + error_handler_called.store(true, Ordering::Relaxed); + } + }) + .await; + + job.start().await; + + assert!( + error_handler_called.load(Ordering::Relaxed), + "called on start" + ); + + task.abort(); +} + +#[tokio::test] +async fn async_error_handler() { + let (job, task) = start_job(erroring_command()); + let error_handler_called = Arc::new(AtomicBool::new(false)); + + job.set_async_error_handler({ + let error_handler_called = error_handler_called.clone(); + move |_| { + let error_handler_called = error_handler_called.clone(); + Box::new(async move { + error_handler_called.store(true, Ordering::Relaxed); + }) + } + }) + .await; + + job.start().await; + + assert!( + error_handler_called.load(Ordering::Relaxed), + "called on start" + ); + + task.abort(); +} + +#[tokio::test] +async fn unset_error_handler() { + let (job, task) = start_job(erroring_command()); + let error_handler_called = Arc::new(AtomicBool::new(false)); + + job.set_error_handler({ + let error_handler_called = error_handler_called.clone(); + move |_| { + error_handler_called.store(true, Ordering::Relaxed); + } + }) + .await; + + job.unset_error_handler().await; + + job.start().await; + + assert!( + !error_handler_called.load(Ordering::Relaxed), + "not called even after start" + ); + + task.abort(); +} + +#[tokio::test] +async fn queue_ordering() { + let (job, task) = start_job(working_command()); + let error_handler_called = Arc::new(AtomicBool::new(false)); + + job.set_error_handler({ + let error_handler_called = error_handler_called.clone(); + move |_| { + error_handler_called.store(true, Ordering::Relaxed); + } + }); + + job.unset_error_handler(); + + // We're not awaiting until this one, but because the queue is processed in + // order, it's effectively the same as waiting them all. + job.start().await; + + assert!( + !error_handler_called.load(Ordering::Relaxed), + "called after queue await" + ); + + task.abort(); +} + +#[tokio::test] +async fn sync_func() { + let (job, task) = start_job(working_command()); + let func_called = Arc::new(AtomicBool::new(false)); + + let ticket = job.run({ + let func_called = func_called.clone(); + move |_| { + func_called.store(true, Ordering::Relaxed); + } + }); + + assert!( + !func_called.load(Ordering::Relaxed), + "immediately after submit, likely before processed" + ); + + ticket.await; + assert!( + func_called.load(Ordering::Relaxed), + "after it's been processed" + ); + + task.abort(); +} + +#[tokio::test] +async fn async_func() { + let (job, task) = start_job(working_command()); + let func_called = Arc::new(AtomicBool::new(false)); + + let ticket = job.run_async({ + let func_called = func_called.clone(); + move |_| { + let func_called = func_called.clone(); + Box::new(async move { + func_called.store(true, Ordering::Relaxed); + }) + } + }); + + assert!( + !func_called.load(Ordering::Relaxed), + "immediately after submit, likely before processed" + ); + + ticket.await; + assert!( + func_called.load(Ordering::Relaxed), + "after it's been processed" + ); + + task.abort(); +} + +// TODO: figure out how to test spawn hooks + +async fn refresh_state(job: &Job, state: &Arc>>, current: bool) { + job.send_controls( + [Control::SyncFunc(Box::new({ + let state = state.clone(); + move |context| { + if current { + state.lock().unwrap().replace(context.current.clone()); + } else { + *state.lock().unwrap() = context.previous.cloned(); + } + } + }))], + Priority::Urgent, + ) + .await; +} + +async fn set_running_child_status(job: &Job, status: ExitStatus) { + job.send_controls( + [Control::AsyncFunc(Box::new({ + move |context| { + let output_lock = if let CommandState::Running { child, .. } = context.current { + Some(child.output.clone()) + } else { + None + }; + + Box::new(async move { + if let Some(output_lock) = output_lock { + *output_lock.lock().await = Some(Output { + status, + stdout: Vec::new(), + stderr: Vec::new(), + }); + } + }) + } + }))], + Priority::Urgent, + ) + .await; +} + +macro_rules! expect_state { + ($current:literal, $job:expr, $expected:pat, $reason:literal) => { + let state = Arc::new(Mutex::new(None)); + refresh_state(&$job, &state, $current).await; + { + let state = state.lock().unwrap(); + let reason = $reason; + let reason = if reason.is_empty() { + String::new() + } else { + format!(" ({reason})") + }; + assert!( + matches!(*state, Some($expected)), + "expected Some({}), got {state:?}{reason}", + stringify!($expected), + ); + } + }; + + ($job:expr, $expected:pat, $reason:literal) => { + expect_state!(true, $job, $expected, $reason) + }; + + ($job:expr, $expected:pat) => { + expect_state!(true, $job, $expected, "") + }; + + (previous: $job:expr, $expected:pat, $reason:literal) => { + expect_state!(false, $job, $expected, $reason) + }; + + (previous: $job:expr, $expected:pat) => { + expect_state!(false, $job, $expected, "") + }; +} + +async fn get_child(job: &Job) -> TestChild { + let state = Arc::new(Mutex::new(None)); + refresh_state(job, &state, true).await; + let state = state.lock().unwrap(); + let state = state.as_ref().expect("no state"); + match state { + CommandState::Running { ref child, .. } => child.clone(), + _ => panic!("get_child: expected IsRunning, got {state:?}"), + } +} + +#[tokio::test] +async fn start() { + let (job, task) = start_job(working_command()); + + expect_state!(job, CommandState::Pending); + + job.start().await; + + expect_state!(job, CommandState::Running { .. }); + + task.abort(); +} + +#[cfg(unix)] +#[tokio::test] +async fn signal_unix() { + let (job, task) = start_job(working_command()); + + expect_state!(job, CommandState::Pending); + + job.start(); + job.signal(watchexec_signals::Signal::User1).await; + + let calls = get_child(&job).await.calls; + assert!(calls + .iter() + .any(|(_, call)| matches!(call, TestChildCall::Signal(command_group::Signal::SIGUSR1)))); + + task.abort(); +} + +#[tokio::test] +async fn stop() { + let (job, task) = start_job(working_command()); + + expect_state!(job, CommandState::Pending); + + job.start().await; + + expect_state!(job, CommandState::Running { .. }); + + set_running_child_status(&job, ProcessEnd::Success.into_exitstatus()).await; + + job.stop().await; + + expect_state!( + job, + CommandState::Finished { + status: ProcessEnd::Success, + .. + } + ); + + task.abort(); +} + +#[tokio::test] +async fn stop_when_running() { + let (job, task) = start_job(working_command()); + + expect_state!(job, CommandState::Pending); + + job.stop().await; + + expect_state!(job, CommandState::Pending); + + job.start().await; + + expect_state!(job, CommandState::Running { .. }); + + task.abort(); +} + +#[tokio::test] +async fn stop_fail() { + let (job, task) = start_job(working_command()); + + expect_state!(job, CommandState::Pending); + + job.start().await; + + expect_state!(job, CommandState::Running { .. }); + + set_running_child_status( + &job, + ProcessEnd::ExitError(NonZeroI64::new(1).unwrap()).into_exitstatus(), + ) + .await; + + job.stop().await; + + expect_state!( + job, + CommandState::Finished { + status: ProcessEnd::ExitError(_), + .. + } + ); + + task.abort(); +} + +#[tokio::test] +async fn restart() { + let (job, task) = start_job(working_command()); + + expect_state!(job, CommandState::Pending); + + job.start().await; + + expect_state!(job, CommandState::Running { .. }); + + set_running_child_status( + &job, + ProcessEnd::ExitError(NonZeroI64::new(1).unwrap()).into_exitstatus(), + ) + .await; + + job.restart().await; + + expect_state!(job, CommandState::Running { .. }); + + set_running_child_status(&job, ProcessEnd::Success.into_exitstatus()).await; + + job.stop().await; + + expect_state!( + previous: job, + CommandState::Finished { + status: ProcessEnd::ExitError(_), + .. + } + ); + + expect_state!( + job, + CommandState::Finished { + status: ProcessEnd::Success, + .. + } + ); + + task.abort(); +} + +#[tokio::test] +async fn graceful_stop() { + let (job, task) = start_job(working_command()); + + expect_state!(job, CommandState::Pending); + + job.start().await; + + expect_state!(job, CommandState::Running { .. }); + + set_running_child_status(&job, ProcessEnd::Success.into_exitstatus()).await; + + let stop = job.stop_with_signal( + watchexec_signals::Signal::Terminate, + Duration::from_millis(GRACE), + ); + + sleep(Duration::from_millis(GRACE / 2)).await; + + expect_state!( + job, + CommandState::Finished { .. }, + "after signal but before delayed force-stop" + ); + + stop.await; + + expect_state!(job, CommandState::Finished { .. }); + + task.abort(); +} + +#[tokio::test] +async fn graceful_restart() { + let (job, task) = start_job(working_command()); + + expect_state!(job, CommandState::Pending); + + job.start().await; + + expect_state!(job, CommandState::Running { .. }); + + set_running_child_status( + &job, + ProcessEnd::ExitError(NonZeroI64::new(1).unwrap()).into_exitstatus(), + ) + .await; + + job.restart_with_signal( + watchexec_signals::Signal::Terminate, + Duration::from_millis(GRACE), + ) + .await; + + set_running_child_status(&job, ProcessEnd::Success.into_exitstatus()).await; + + job.stop().await; + + expect_state!( + previous: job, + CommandState::Finished { + status: ProcessEnd::ExitError(_), + .. + } + ); + + expect_state!( + job, + CommandState::Finished { + status: ProcessEnd::Success, + .. + } + ); + + task.abort(); +} + +#[tokio::test] +async fn graceful_stop_beyond_grace() { + let (job, task) = start_job(ungraceful_command()); + + expect_state!(job, CommandState::Pending); + + job.start().await; + + expect_state!(job, CommandState::Running { .. }); + + set_running_child_status(&job, ProcessEnd::Success.into_exitstatus()).await; + + let stop = job.stop_with_signal( + watchexec_signals::Signal::User1, + Duration::from_millis(GRACE), + ); + + #[cfg(unix)] + { + expect_state!( + job, + CommandState::Running { .. }, + "after USR1 but before delayed stop" + ); + + let calls = get_child(&job).await.calls; + assert!(calls.iter().any(|(_, call)| matches!( + call, + TestChildCall::Signal(command_group::Signal::SIGUSR1) + ))); + } + + stop.await; + + expect_state!(job, CommandState::Finished { .. }); + + task.abort(); +} + +#[tokio::test] +async fn graceful_restart_beyond_grace() { + let (job, task) = start_job(ungraceful_command()); + + expect_state!(job, CommandState::Pending); + + job.start().await; + + expect_state!(job, CommandState::Running { .. }); + + set_running_child_status( + &job, + ProcessEnd::ExitError(NonZeroI64::new(1).unwrap()).into_exitstatus(), + ) + .await; + + let restart = job.restart_with_signal( + watchexec_signals::Signal::User1, + Duration::from_millis(GRACE), + ); + + #[cfg(unix)] + { + expect_state!( + job, + CommandState::Running { .. }, + "after USR1 but before delayed restart" + ); + + let calls = get_child(&job).await.calls; + assert!(calls.iter().any(|(_, call)| matches!( + call, + TestChildCall::Signal(command_group::Signal::SIGUSR1) + ))); + } + + restart.await; + + set_running_child_status(&job, ProcessEnd::Success.into_exitstatus()).await; + + job.stop().await; + + expect_state!( + previous: job, + CommandState::Finished { + status: ProcessEnd::ExitError(_), + .. + } + ); + + expect_state!( + job, + CommandState::Finished { + status: ProcessEnd::Success, + .. + } + ); + + task.abort(); +} + +#[tokio::test] +async fn try_restart() { + let (job, task) = start_job(graceful_command()); + + expect_state!(job, CommandState::Pending); + + job.try_restart().await; + + expect_state!( + job, + CommandState::Pending, + "command still not running after try-restart" + ); + + job.start().await; + + expect_state!(job, CommandState::Running { .. }); + + let try_restart = job.try_restart(); + + eprintln!("[{:?}] test: await try_restart", Instant::now()); + try_restart.await; + + expect_state!(job, CommandState::Running { .. }); + + job.stop().await; + + expect_state!( + previous: job, + CommandState::Finished { .. } + ); + + expect_state!(job, CommandState::Finished { .. }); + + task.abort(); +} + +#[tokio::test] +async fn try_graceful_restart() { + let (job, task) = start_job(graceful_command()); + + expect_state!(job, CommandState::Pending); + + job.try_restart_with_signal( + watchexec_signals::Signal::User1, + Duration::from_millis(GRACE), + ) + .await; + + expect_state!( + job, + CommandState::Pending, + "command still not running after try-graceful-restart" + ); + + job.start().await; + + expect_state!(job, CommandState::Running { .. }); + + set_running_child_status( + &job, + ProcessEnd::ExitError(NonZeroI64::new(1).unwrap()).into_exitstatus(), + ) + .await; + + let restart = job.try_restart_with_signal( + watchexec_signals::Signal::User1, + Duration::from_millis(GRACE), + ); + + expect_state!(job, CommandState::Running { .. }); + + eprintln!("[{:?}] await restart", Instant::now()); + restart.await; + eprintln!("[{:?}] awaited restart", Instant::now()); + + expect_state!( + previous: job, + CommandState::Finished { + status: ProcessEnd::ExitError(_), + .. + } + ); + + expect_state!(job, CommandState::Running { .. }); + + set_running_child_status(&job, ProcessEnd::Success.into_exitstatus()).await; + + job.stop().await; + + expect_state!( + job, + CommandState::Finished { + status: ProcessEnd::Success, + .. + } + ); + + task.abort(); +} + +#[tokio::test] +async fn try_restart_beyond_grace() { + let (job, task) = start_job(ungraceful_command()); + + expect_state!(job, CommandState::Pending); + + job.try_restart().await; + + expect_state!( + job, + CommandState::Pending, + "command still not running after try-restart" + ); + + job.start().await; + + expect_state!(job, CommandState::Running { .. }); + + set_running_child_status( + &job, + ProcessEnd::ExitError(NonZeroI64::new(1).unwrap()).into_exitstatus(), + ) + .await; + + job.try_restart().await; + + expect_state!(job, CommandState::Running { .. }); + + set_running_child_status(&job, ProcessEnd::Success.into_exitstatus()).await; + + job.stop().await; + + expect_state!( + previous: job, + CommandState::Finished { + status: ProcessEnd::ExitError(_), + .. + } + ); + + expect_state!( + job, + CommandState::Finished { + status: ProcessEnd::Success, + .. + } + ); + + task.abort(); +} + +#[tokio::test] +async fn try_graceful_restart_beyond_grace() { + let (job, task) = start_job(ungraceful_command()); + + expect_state!(job, CommandState::Pending); + + job.try_restart_with_signal( + watchexec_signals::Signal::User1, + Duration::from_millis(GRACE), + ) + .await; + + expect_state!( + job, + CommandState::Pending, + "command still not running after try-graceful-restart" + ); + + job.start().await; + + expect_state!(job, CommandState::Running { .. }); + + set_running_child_status( + &job, + ProcessEnd::ExitError(NonZeroI64::new(1).unwrap()).into_exitstatus(), + ) + .await; + + let restart = job.try_restart_with_signal( + watchexec_signals::Signal::User1, + Duration::from_millis(GRACE), + ); + + expect_state!(job, CommandState::Running { .. }); + + restart.await; + + expect_state!( + previous: job, + CommandState::Finished { + status: ProcessEnd::ExitError(_), + .. + } + ); + + expect_state!(job, CommandState::Running { .. }); + + set_running_child_status(&job, ProcessEnd::Success.into_exitstatus()).await; + + job.stop().await; + + expect_state!( + job, + CommandState::Finished { + status: ProcessEnd::Success, + .. + } + ); + + task.abort(); +} diff --git a/crates/supervisor/src/job/testchild.rs b/crates/supervisor/src/job/testchild.rs new file mode 100644 index 0000000..d77899b --- /dev/null +++ b/crates/supervisor/src/job/testchild.rs @@ -0,0 +1,150 @@ +use std::{ + io::Result, + path::Path, + process::{ExitStatus, Output}, + sync::Arc, + time::{Duration, Instant}, +}; + +#[cfg(unix)] +use command_group::Signal; +use tokio::{sync::Mutex, time::sleep}; +use watchexec_events::ProcessEnd; + +use crate::command::{Command, Program}; + +/// Mock version of [`ErasedChild`](command_group::ErasedChild). +#[derive(Debug, Clone)] +pub struct TestChild { + pub grouped: bool, + pub command: Arc, + pub calls: Arc>, + pub output: Arc>>, + pub spawned: Instant, +} + +impl TestChild { + pub fn new(command: Arc) -> std::io::Result { + if let Program::Exec { prog, .. } = &command.program { + if prog == Path::new("/does/not/exist") { + return Err(std::io::Error::new( + std::io::ErrorKind::NotFound, + "file not found", + )); + } + } + + Ok(Self { + grouped: command.options.grouped, + command, + calls: Arc::new(boxcar::Vec::new()), + output: Arc::new(Mutex::new(None)), + spawned: Instant::now(), + }) + } +} + +#[derive(Debug)] +pub enum TestChildCall { + Id, + Kill, + StartKill, + TryWait, + Wait, + #[cfg(unix)] + Signal(Signal), +} + +// Exact same signatures as ErasedChild +impl TestChild { + pub fn id(&mut self) -> Option { + self.calls.push(TestChildCall::Id); + None + } + + pub async fn kill(&mut self) -> Result<()> { + self.calls.push(TestChildCall::Kill); + Ok(()) + } + + pub fn start_kill(&mut self) -> Result<()> { + self.calls.push(TestChildCall::StartKill); + Ok(()) + } + + pub fn try_wait(&mut self) -> Result> { + self.calls.push(TestChildCall::TryWait); + + if let Program::Exec { prog, args } = &self.command.program { + if prog == Path::new("sleep") { + if let Some(time) = args + .get(0) + .and_then(|arg| arg.parse().ok()) + .map(Duration::from_millis) + { + if self.spawned.elapsed() < time { + return Ok(None); + } + } + } + } + + Ok(self + .output + .try_lock() + .ok() + .and_then(|o| o.as_ref().map(|o| o.status))) + } + + pub async fn wait(&mut self) -> Result { + self.calls.push(TestChildCall::Wait); + if let Program::Exec { prog, args } = &self.command.program { + if prog == Path::new("sleep") { + if let Some(time) = args + .get(0) + .and_then(|arg| arg.parse().ok()) + .map(Duration::from_millis) + { + if self.spawned.elapsed() < time { + sleep(time - self.spawned.elapsed()).await; + if let Ok(guard) = self.output.try_lock() { + if let Some(output) = guard.as_ref() { + return Ok(output.status); + } + } + + return Ok(ProcessEnd::Success.into_exitstatus()); + } + } + } + } + + loop { + eprintln!("[{:?}] child: output lock", Instant::now()); + let output = self.output.lock().await; + if let Some(output) = output.as_ref() { + return Ok(output.status); + } + eprintln!("[{:?}] child: output unlock", Instant::now()); + + sleep(Duration::from_secs(1)).await; + } + } + + pub async fn wait_with_output(self) -> Result { + loop { + let mut output = self.output.lock().await; + if let Some(output) = output.take() { + return Ok(output); + } else { + sleep(Duration::from_secs(1)).await; + } + } + } + + #[cfg(unix)] + pub fn signal(&self, sig: Signal) -> Result<()> { + self.calls.push(TestChildCall::Signal(sig)); + Ok(()) + } +} diff --git a/crates/supervisor/src/lib.rs b/crates/supervisor/src/lib.rs new file mode 100644 index 0000000..3494f8b --- /dev/null +++ b/crates/supervisor/src/lib.rs @@ -0,0 +1,147 @@ +//! Watchexec's process supervisor. +//! +//! This crate implements the process supervisor for Watchexec. It is responsible for spawning and +//! managing processes, and for sending events to them. +//! +//! You may use this crate to implement your own process supervisor, but keep in mind its direction +//! will always primarily be driven by the needs of Watchexec itself. +//! +//! # Usage +//! +//! There is no struct or implementation of a single supervisor, as the particular needs of the +//! application will dictate how that is designed. Instead, this crate provides a [`Job`](job::Job) +//! construct, which is a handle to a single [`Command`](command::Command), and manages its +//! lifecycle. The `Job` API has been modeled after the `systemctl` set of commands for service +//! control, with operations for starting, stopping, restarting, sending signals, waiting for the +//! process to complete, etc. +//! +//! There are also methods for running hooks within the job's runtime task, and for handling errors. +//! +//! # Theory of Operation +//! +//! A [`Job`](job::Job) is, properly speaking, a handle which lets one control a Tokio task. That +//! task is spawned on the Tokio runtime, and so runs in the background. A `Job` takes as input a +//! [`Command`](command::Command), which describes how to start a single process, through either a +//! shell command or a direct executable invocation, and if the process should be grouped (using +//! [`command-group`](command_group)) or not. +//! +//! The job's task runs an event loop on two sources: the process's `wait()` (i.e. when the process +//! ends) and the job's control queue. The control queue is a hybrid MPSC queue, with three priority +//! levels and a timer. When the timer is active, the lowest ("Normal") priority queue is disabled. +//! This is an internal detail which serves to implement graceful stops and restarts. The internals +//! of the job's task are not available to the API user, actions and queries are performed by +//! sending messages on this control queue. +//! +//! The control queue is executed in priority and in order within priorities. Sending a control to +//! the task returns a [`Ticket`](job::Ticket), which is a future that resolves when the control has +//! been processed. Dropping the ticket will not cancel the control. This provides two complementary +//! ways to orchestrate actions: queueing controls in the desired order if there is no need for +//! branching flow or for signaling, and sending controls or performing other actions after awaiting +//! tickets. +//! +//! Do note that both of these can be used together. There is no need for the below pattern: +//! +//! ```no_run +//! # #[tokio::main(flavor = "current_thread")] async fn main() { // single-threaded for doctest only +//! # use std::sync::Arc; +//! # use watchexec_supervisor::Signal; +//! # use watchexec_supervisor::command::{Command, Program}; +//! # use watchexec_supervisor::job::{CommandState, start_job}; +//! # +//! # let (job, task) = start_job(Arc::new(Command { program: Program::Exec { prog: "/bin/date".into(), args: Vec::new() }.into(), options: Default::default() })); +//! # +//! job.start().await; +//! job.signal(Signal::User1).await; +//! job.stop().await; +//! # task.abort(); +//! # } +//! ``` +//! +//! Because of ordering, it behaves the same as this: +//! +//! ```no_run +//! # #[tokio::main(flavor = "current_thread")] async fn main() { // single-threaded for doctest only +//! # use std::sync::Arc; +//! # use watchexec_supervisor::Signal; +//! # use watchexec_supervisor::command::{Command, Program}; +//! # use watchexec_supervisor::job::{CommandState, start_job}; +//! # +//! # let (job, task) = start_job(Arc::new(Command { program: Program::Exec { prog: "/bin/date".into(), args: Vec::new() }.into(), options: Default::default() })); +//! # +//! job.start(); +//! job.signal(Signal::User1); +//! job.stop().await; // here, all of start(), signal(), and stop() will have run in order +//! # task.abort(); +//! # } +//! ``` +//! +//! However, this is a different program: +//! +//! ```no_run +//! # #[tokio::main(flavor = "current_thread")] async fn main() { // single-threaded for doctest only +//! # use std::sync::Arc; +//! # use std::time::Duration; +//! # use tokio::time::sleep; +//! # use watchexec_supervisor::Signal; +//! # use watchexec_supervisor::command::{Command, Program}; +//! # use watchexec_supervisor::job::{CommandState, start_job}; +//! # +//! # let (job, task) = start_job(Arc::new(Command { program: Program::Exec { prog: "/bin/date".into(), args: Vec::new() }.into(), options: Default::default() })); +//! # +//! job.start().await; +//! println!("program started!"); +//! sleep(Duration::from_secs(5)).await; // wait until program is fully started +//! +//! job.signal(Signal::User1).await; +//! sleep(Duration::from_millis(150)).await; // wait until program has dumped stats +//! println!("program stats dumped via USR1 signal!"); +//! +//! job.stop().await; +//! println!("program stopped"); +//! # +//! # task.abort(); +//! # } +//! ``` +//! +//! # Example +//! +//! ```no_run +//! # #[tokio::main(flavor = "current_thread")] async fn main() { // single-threaded for doctest only +//! # use std::sync::Arc; +//! use watchexec_supervisor::Signal; +//! use watchexec_supervisor::command::{Command, Program}; +//! use watchexec_supervisor::job::{CommandState, start_job}; +//! +//! let (job, task) = start_job(Arc::new(Command { +//! program: Program::Exec { +//! prog: "/bin/date".into(), +//! args: Vec::new(), +//! }.into(), +//! options: Default::default(), +//! })); +//! +//! job.start().await; +//! job.signal(Signal::User1).await; +//! job.stop().await; +//! +//! job.delete_now().await; +//! +//! task.await; // make sure the task is fully cleaned up +//! # } +//! ``` + +#![doc(html_favicon_url = "https://watchexec.github.io/logo:watchexec.svg")] +#![doc(html_logo_url = "https://watchexec.github.io/logo:watchexec.svg")] +#![warn(clippy::unwrap_used, missing_docs, rustdoc::unescaped_backticks)] +#![deny(rust_2018_idioms)] + +#[doc(no_inline)] +pub use watchexec_events::ProcessEnd; +#[doc(no_inline)] +pub use watchexec_signals::Signal; + +pub mod command; +pub mod errors; +pub mod job; + +mod flag; diff --git a/crates/supervisor/tests/programs.rs b/crates/supervisor/tests/programs.rs new file mode 100644 index 0000000..b4bb7d1 --- /dev/null +++ b/crates/supervisor/tests/programs.rs @@ -0,0 +1,129 @@ +use command_group::AsyncCommandGroup; +use watchexec_supervisor::command::{Command, Program, Shell}; + +#[tokio::test] +#[cfg(unix)] +async fn unix_shell_none() -> Result<(), std::io::Error> { + assert!(Command { + program: Program::Exec { + prog: "echo".into(), + args: vec!["hi".into()], + }, + options: Default::default() + } + .to_spawnable() + .group_status() + .await? + .success()); + Ok(()) +} + +#[tokio::test] +#[cfg(unix)] +async fn unix_shell_sh() -> Result<(), std::io::Error> { + assert!(Command { + program: Program::Shell { + shell: Shell::new("sh"), + command: "echo hi".into(), + args: Vec::new(), + }, + options: Default::default() + } + .to_spawnable() + .group_status() + .await? + .success()); + Ok(()) +} + +#[tokio::test] +#[cfg(unix)] +async fn unix_shell_alternate() -> Result<(), std::io::Error> { + assert!(Command { + program: Program::Shell { + shell: Shell::new("bash"), + command: "echo".into(), + args: vec!["--".into(), "hi".into()], + }, + options: Default::default() + } + .to_spawnable() + .group_status() + .await? + .success()); + Ok(()) +} + +#[tokio::test] +#[cfg(unix)] +async fn unix_shell_alternate_shopts() -> Result<(), std::io::Error> { + assert!(Command { + program: Program::Shell { + shell: Shell { + options: vec!["-o".into(), "errexit".into()], + ..Shell::new("bash") + }, + command: "echo hi".into(), + args: Vec::new(), + }, + options: Default::default() + } + .to_spawnable() + .group_status() + .await? + .success()); + Ok(()) +} + +#[tokio::test] +#[cfg(windows)] +async fn windows_shell_none() -> Result<(), std::io::Error> { + assert!(Command { + program: Program::Exec { + prog: "echo".into(), + args: vec!["hi".into()], + }, + options: Default::default() + } + .to_spawnable() + .group_status() + .await? + .success()); + Ok(()) +} + +#[tokio::test] +#[cfg(windows)] +async fn windows_shell_cmd() -> Result<(), std::io::Error> { + assert!(Command { + program: Program::Shell { + shell: Shell::cmd(), + args: Vec::new(), + command: r#""echo" hi"#.into() + }, + options: Default::default() + } + .to_spawnable() + .group_status() + .await? + .success()); + Ok(()) +} + +#[tokio::test] +#[cfg(windows)] +async fn windows_shell_powershell() -> Result<(), std::io::Error> { + assert!(Command { + program: Program::Shell { + shell: Shell::new("pwsh.exe"), + args: Vec::new(), + command: "echo hi".into() + }, + options: Default::default() + } + .to_spawnable() + .group_status() + .await? + .success()); + Ok(()) +} diff --git a/doc/watchexec.1 b/doc/watchexec.1 index e26a667..29e931b 100644 --- a/doc/watchexec.1 +++ b/doc/watchexec.1 @@ -45,6 +45,8 @@ When watching a single file, it\*(Aqs often better to watch the containing direc Upon starting, Watchexec resolves a "project origin" from the watched paths. See the help for \*(Aq\-\-project\-origin\*(Aq for more information. This option can be specified multiple times to watch multiple files or directories. + +The special value \*(Aq/dev/null\*(Aq, provided as the only path watched, will cause Watchexec to not watch any paths. Other event sources (like signals or key events) may still be used. .TP \fB\-c\fR, \fB\-\-clear\fR=\fIMODE\fR Clear screen before running command @@ -478,6 +480,8 @@ $ watchexec \-\-shell=none echo \*(Aqsrc/*.rs\*(Aq Behaviour depends on the value of \*(Aq\-\-shell\*(Aq: for all except \*(Aqnone\*(Aq, every part of the command is joined together into one string with a single ascii space character, and given to the shell as described in the help for \*(Aq\-\-shell\*(Aq. For \*(Aqnone\*(Aq, each distinct element the command is passed as per the execvp(3) convention: first argument is the program, as a path or searched for in the \*(AqPATH\*(Aq environment variable, rest are arguments. .SH EXTRA Use @argfile as first argument to load arguments from the file \*(Aqargfile\*(Aq (one argument per line) which will be inserted in place of the @argfile (further arguments on the CLI will override or add onto those in the file). + +Didn\*(Aqt expect this much output? Use the short \*(Aq\-h\*(Aq flag to get short help. .SH VERSION v1.23.0 .SH AUTHORS diff --git a/doc/watchexec.1.md b/doc/watchexec.1.md index 6087acc..1d4cf25 100644 --- a/doc/watchexec.1.md +++ b/doc/watchexec.1.md @@ -75,6 +75,10 @@ paths. See the help for \--project-origin for more information. This option can be specified multiple times to watch multiple files or directories. +The special value /dev/null, provided as the only path watched, will +cause Watchexec to not watch any paths. Other event sources (like +signals or key events) may still be used. + **-c**, **\--clear**=*MODE* : Clear screen before running command @@ -682,6 +686,8 @@ Use \@argfile as first argument to load arguments from the file argfile (further arguments on the CLI will override or add onto those in the file). +Didnt expect this much output? Use the short -h flag to get short help. + # VERSION v1.23.0 diff --git a/doc/watchexec.1.pdf b/doc/watchexec.1.pdf index 7c1e58575349ed254794be08248dc3d94c8a57aa..6f37dc6e0cc3a48d7b17555b57e5a653c544a0dd 100644 GIT binary patch delta 25519 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 delta 25194 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 P&IJOYGcZUhNumE21JfL3