Merge branch 'dev'

This commit is contained in:
Filipe Rodrigues 2023-10-05 23:41:23 +01:00
commit 7e32620e16
Signed by: zenithsiz
SSH Key Fingerprint: SHA256:Mb5ppb3Sh7IarBO/sBTXLHbYEOz37hJAlslLQPPAPaU
20 changed files with 725 additions and 513 deletions

15
CHANGELOG Normal file
View File

@ -0,0 +1,15 @@
# 0.1.2
## Major
- Added commands within commands. Arguments to commands may now be commands themselves. The stdout of the command will be captured and passed as the argument.
- (BREAKING) Working directory is now for each command, rather than for the executable section
## Minor
- (BREAKING) Option `--file-log` is now `--log-file`
- Parent directories of built files now watched to ensure that deleted files are still properly watched.
This may cause some slowdowns if you're watching a file in a directory with a lot of movement, but it otherwise fixes some files randomly not being watched.

394
Cargo.lock generated
View File

@ -4,9 +4,9 @@ version = 3
[[package]]
name = "addr2line"
version = "0.20.0"
version = "0.21.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f4fa78e18c64fce05e902adecd7a5eed15a5e0a3439f7b0e169f0252214865e3"
checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb"
dependencies = [
"gimli",
]
@ -19,24 +19,23 @@ checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
[[package]]
name = "anstream"
version = "0.3.2"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ca84f3628370c59db74ee214b3263d58f9aadd9b4fe7e711fd87dc452b7f163"
checksum = "b1f58811cfac344940f1a400b6e6231ce35171f614f26439e80f8c1465c5cc0c"
dependencies = [
"anstyle",
"anstyle-parse",
"anstyle-query",
"anstyle-wincon",
"colorchoice",
"is-terminal",
"utf8parse",
]
[[package]]
name = "anstyle"
version = "1.0.1"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a30da5c5f2d5e72842e00bcb57657162cdabef0931f40e2deb9b4140440cecd"
checksum = "15c4c2c83f81532e5845a733998b6971faca23490340a418e9b72a3ec9de12ea"
[[package]]
name = "anstyle-parse"
@ -53,24 +52,24 @@ 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 = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "180abfa45703aebe0093f79badacc01b8fd4ea2e35118747e5811127f926e188"
checksum = "58f54d10c6dfa51283a066ceab3ec1ab78d13fae00aa49243a45e4571fb79dfd"
dependencies = [
"anstyle",
"windows-sys 0.48.0",
"windows-sys",
]
[[package]]
name = "anyhow"
version = "1.0.71"
version = "1.0.75"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8"
checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6"
[[package]]
name = "async-broadcast"
@ -84,9 +83,9 @@ dependencies = [
[[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",
@ -101,9 +100,9 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "backtrace"
version = "0.3.68"
version = "0.3.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4319208da049c43661739c5fade2ba182f09d1dc2299b32298d3a31692b17e12"
checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837"
dependencies = [
"addr2line",
"cc",
@ -122,21 +121,24 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bitflags"
version = "2.3.3"
version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "630be753d4e58660abd17930c71b647fe46c27ea6b63cc59e1e3851406972e42"
checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635"
[[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"
version = "1.0.79"
version = "1.0.83"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f"
checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0"
dependencies = [
"libc",
]
[[package]]
name = "cfg-if"
@ -146,20 +148,19 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "clap"
version = "4.3.10"
version = "4.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "384e169cc618c613d5e3ca6404dda77a8685a63e08660dcc64abaf7da7cb0c7a"
checksum = "84ed82781cea27b43c9b106a979fe450a13a31aab0500595fb3fc06616de08e6"
dependencies = [
"clap_builder",
"clap_derive",
"once_cell",
]
[[package]]
name = "clap_builder"
version = "4.3.10"
version = "4.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef137bbe35aab78bdb468ccfba75a5f4d8321ae011d34063770780545176af2d"
checksum = "2bb9faaa7c2ef94b2743a21f5a29e6f0010dff4caa69ac8e9d6cf8b6fa74da08"
dependencies = [
"anstream",
"anstyle",
@ -169,9 +170,9 @@ dependencies = [
[[package]]
name = "clap_derive"
version = "4.3.2"
version = "4.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8cd2b2a819ad6eec39e8f1d6b53001af1e5469f8c177579cdaeb313115b825f"
checksum = "0862016ff20d69b84ef8247369fabf5c008a7417002411897d40ee1f4532b873"
dependencies = [
"heck",
"proc-macro2",
@ -181,9 +182,9 @@ dependencies = [
[[package]]
name = "clap_lex"
version = "0.5.0"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2da6da31387c7e4ef160ffab6d5e7f00c42626fe39aea70a7b0f1773f7dd6c1b"
checksum = "cd7cc57abe963c6d3b9d8be5b06ba7c8957a930305ca90304f24ef040aa6f961"
[[package]]
name = "colorchoice"
@ -212,12 +213,12 @@ dependencies = [
[[package]]
name = "dashmap"
version = "5.4.0"
version = "5.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "907076dfda823b0b36d2a1bb5f90c96660a5bbcd7729e10727f07858f22c4edc"
checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856"
dependencies = [
"cfg-if",
"hashbrown 0.12.3",
"hashbrown",
"lock_api",
"once_cell",
"parking_lot_core",
@ -231,30 +232,9 @@ checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91"
[[package]]
name = "equivalent"
version = "1.0.0"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "88bffebc5d80432c9b140ee17875ff173a8ab62faad5b257da912bd2f6c1c0a1"
[[package]]
name = "errno"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a"
dependencies = [
"errno-dragonfly",
"libc",
"windows-sys 0.48.0",
]
[[package]]
name = "errno-dragonfly"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf"
dependencies = [
"cc",
"libc",
]
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
[[package]]
name = "event-listener"
@ -264,23 +244,23 @@ checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0"
[[package]]
name = "file-id"
version = "0.1.0"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e13be71e6ca82e91bc0cb862bebaac0b2d1924a5a1d970c822b2f98b63fda8c3"
checksum = "6584280525fb2059cba3db2c04abf947a1a29a45ddae89f3870f8281704fafc9"
dependencies = [
"winapi-util",
"windows-sys",
]
[[package]]
name = "filetime"
version = "0.2.21"
version = "0.2.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5cbc844cecaee9d4443931972e1289c8ff485cb4cc2767cb03ca139ed6885153"
checksum = "d4029edd3e734da6fe05b6cd7bd2960760a616bd2ddd0d59a0124746d6272af0"
dependencies = [
"cfg-if",
"libc",
"redox_syscall 0.2.16",
"windows-sys 0.48.0",
"redox_syscall",
"windows-sys",
]
[[package]]
@ -383,15 +363,9 @@ dependencies = [
[[package]]
name = "gimli"
version = "0.27.3"
version = "0.28.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6c80984affa11d98d1b88b66ac8853f143217b399d3c74116778ff8fdb4ed2e"
[[package]]
name = "hashbrown"
version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0"
[[package]]
name = "hashbrown"
@ -407,9 +381,9 @@ checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
[[package]]
name = "hermit-abi"
version = "0.3.1"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286"
checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b"
[[package]]
name = "indexmap"
@ -418,7 +392,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d"
dependencies = [
"equivalent",
"hashbrown 0.14.0",
"hashbrown",
]
[[package]]
@ -441,17 +415,6 @@ dependencies = [
"libc",
]
[[package]]
name = "is-terminal"
version = "0.4.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24fddda5af7e54bf7da53067d6e802dbcc381d0a8eef629df528e3ebf68755cb"
dependencies = [
"hermit-abi",
"rustix",
"windows-sys 0.48.0",
]
[[package]]
name = "itertools"
version = "0.11.0"
@ -463,9 +426,9 @@ dependencies = [
[[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 = "kqueue"
@ -499,12 +462,6 @@ version = "0.2.147"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3"
[[package]]
name = "linux-raw-sys"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09fc20d2ca12cb9f044c93e3bd6d32d523e6e2ec3db4f7b2939cd99026ecd3f0"
[[package]]
name = "lock_api"
version = "0.4.10"
@ -517,9 +474,9 @@ dependencies = [
[[package]]
name = "log"
version = "0.4.19"
version = "0.4.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4"
checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
[[package]]
name = "matchers"
@ -532,9 +489,9 @@ dependencies = [
[[package]]
name = "memchr"
version = "2.5.0"
version = "2.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
checksum = "5486aed0026218e61b8a01d5fbd5a0a134649abb71a0e53b7bc088529dced86e"
[[package]]
name = "miniz_oxide"
@ -554,35 +511,37 @@ dependencies = [
"libc",
"log",
"wasi",
"windows-sys 0.48.0",
"windows-sys",
]
[[package]]
name = "notify"
version = "6.0.1"
version = "6.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5738a2795d57ea20abec2d6d76c6081186709c0024187cd5977265eda6598b51"
checksum = "6205bd8bb1e454ad2e27422015fb5e4f2bcc7e08fa8f27058670d208324a4d2d"
dependencies = [
"bitflags 1.3.2",
"bitflags 2.4.0",
"crossbeam-channel",
"filetime",
"fsevent-sys",
"inotify",
"kqueue",
"libc",
"log",
"mio",
"walkdir",
"windows-sys 0.45.0",
"windows-sys",
]
[[package]]
name = "notify-debouncer-full"
version = "0.2.0"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "416969970ec751a5d702a88c6cd19ac1332abe997fce43f96db0418550426241"
checksum = "49f5dab59c348b9b50cf7f261960a20e389feb2713636399cd9082cd4b536154"
dependencies = [
"crossbeam-channel",
"file-id",
"log",
"notify",
"parking_lot",
"walkdir",
@ -615,9 +574,9 @@ dependencies = [
[[package]]
name = "object"
version = "0.31.1"
version = "0.32.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8bda667d9f2b5051b8833f59f3bf748b28ef54f850f4fcb389a252aa383866d1"
checksum = "77ac5bbd07aea88c60a577a1ce218075ffd59208b2d7ca97adf9bfc5aeb21ebe"
dependencies = [
"memchr",
]
@ -652,25 +611,25 @@ checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447"
dependencies = [
"cfg-if",
"libc",
"redox_syscall 0.3.5",
"redox_syscall",
"smallvec",
"windows-targets 0.48.1",
"windows-targets",
]
[[package]]
name = "pin-project"
version = "1.1.1"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e138fdd8263907a2b0e1b4e80b7e58c721126479b6e6eedfb1b402acea7b9bd"
checksum = "fda4ed1c6c173e3fc7a83629421152e01d7b1f9b7f65fb301e490e8cfc656422"
dependencies = [
"pin-project-internal",
]
[[package]]
name = "pin-project-internal"
version = "1.1.1"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d1fef411b303e3e12d534fb6e7852de82da56edd937d895125821fb7c09436c7"
checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405"
dependencies = [
"proc-macro2",
"quote",
@ -679,9 +638,9 @@ dependencies = [
[[package]]
name = "pin-project-lite"
version = "0.2.9"
version = "0.2.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116"
checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58"
[[package]]
name = "pin-utils"
@ -691,31 +650,22 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]]
name = "proc-macro2"
version = "1.0.63"
version = "1.0.66"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b368fba921b0dce7e60f5e04ec15e565b3303972b42bcfde1d0713b881959eb"
checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.29"
version = "1.0.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "573015e8ab27661678357f27dc26460738fd2b6c86e46f386fde94cb5d913105"
checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae"
dependencies = [
"proc-macro2",
]
[[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"
@ -761,24 +711,11 @@ version = "0.1.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76"
[[package]]
name = "rustix"
version = "0.38.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fbc6396159432b5c8490d4e301d8c705f61860b8b6c863bf79942ce5401968f3"
dependencies = [
"bitflags 2.3.3",
"errno",
"libc",
"linux-raw-sys",
"windows-sys 0.48.0",
]
[[package]]
name = "ryu"
version = "1.0.13"
version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041"
checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741"
[[package]]
name = "same-file"
@ -791,24 +728,24 @@ 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.164"
version = "1.0.188"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e8c8cf938e98f769bc164923b06dce91cea1751522f46f8466461af04c9027d"
checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.164"
version = "1.0.188"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9735b638ccc51c28bf6914d90a2e9725b377144fc612c49a611fddd1b631d68"
checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2"
dependencies = [
"proc-macro2",
"quote",
@ -817,9 +754,9 @@ dependencies = [
[[package]]
name = "serde_yaml"
version = "0.9.22"
version = "0.9.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "452e67b9c20c37fa79df53201dc03839651086ed9bbe92b3ca585ca9fdaa7d85"
checksum = "1a49e178e4452f45cb61d0cd8cebc1b0fafd3e41929e996cef79aa3aca91f574"
dependencies = [
"indexmap",
"itoa",
@ -857,18 +794,18 @@ dependencies = [
[[package]]
name = "smallvec"
version = "1.10.0"
version = "1.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0"
checksum = "62bb4feee49fdd9f707ef802e22365a35de4b7b299de4763d44bfea899442ff9"
[[package]]
name = "socket2"
version = "0.4.9"
version = "0.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662"
checksum = "4031e820eb552adee9295814c0ced9e5cf38ddf1e8b7d566d6de8e2538ea989e"
dependencies = [
"libc",
"winapi",
"windows-sys",
]
[[package]]
@ -879,9 +816,9 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
[[package]]
name = "syn"
version = "2.0.22"
version = "2.0.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2efbeae7acf4eabd6bcdcbd11c92f45231ddda7539edc7806bd1a04a03b24616"
checksum = "239814284fd6f1a4ffe4ca893952cdd93c224b6a1571c9a9eadd670295c0c9e2"
dependencies = [
"proc-macro2",
"quote",
@ -890,18 +827,18 @@ dependencies = [
[[package]]
name = "thiserror"
version = "1.0.40"
version = "1.0.48"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac"
checksum = "9d6d7a740b8a666a7e828dd00da9c0dc290dff53154ea77ac109281de90589b7"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.40"
version = "1.0.48"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f"
checksum = "49922ecae66cc8a249b77e68d1d0623c1b2c514f0060c27cdc68bd62a1219d35"
dependencies = [
"proc-macro2",
"quote",
@ -920,11 +857,10 @@ dependencies = [
[[package]]
name = "tokio"
version = "1.29.1"
version = "1.32.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "532826ff75199d5833b9d2c5fe410f29235e25704ee5f0ef599fb51c21f4a4da"
checksum = "17ed6077ed6cd6c74735e21f37eb16dc3935f96878b1fe961074089cc80893f9"
dependencies = [
"autocfg",
"backtrace",
"bytes",
"libc",
@ -935,7 +871,7 @@ dependencies = [
"signal-hook-registry",
"socket2",
"tokio-macros",
"windows-sys 0.48.0",
"windows-sys",
]
[[package]]
@ -1024,15 +960,15 @@ dependencies = [
[[package]]
name = "unicode-ident"
version = "1.0.9"
version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b15811caf2415fb889178633e7724bad2509101cde276048e013b9def5e51fa0"
checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c"
[[package]]
name = "unsafe-libyaml"
version = "0.2.8"
version = "0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1865806a559042e51ab5414598446a5871b561d21b6764f2eabb0dd481d880a6"
checksum = "f28467d3e1d3c6586d8f25fa243f544f5800fec42d97032474e17222c2b75cfa"
[[package]]
name = "utf8parse"
@ -1093,141 +1029,75 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows-sys"
version = "0.45.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0"
dependencies = [
"windows-targets 0.42.2",
]
[[package]]
name = "windows-sys"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
dependencies = [
"windows-targets 0.48.1",
"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.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05d4b17490f70499f20b9e791dcf6a299785ce8af4d709018206dc5b4953e95f"
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"
[[package]]
name = "windows_x86_64_msvc"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a"
checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
[[package]]
name = "zbuild"
version = "0.1.1"
version = "0.1.2"
dependencies = [
"anyhow",
"async-broadcast",

View File

@ -1,45 +1,45 @@
[package]
edition = "2021"
name = "zbuild"
version = "0.1.1"
version = "0.1.2"
[dependencies]
# Futures
tokio = { version = "1.29.1", features = ["full"] }
tokio = { version = "1.32.0", features = ["full"] }
tokio-stream = "0.1.14"
async-broadcast = "0.5.1"
async-recursion = "1.0.4"
async-recursion = "1.0.5"
futures = "0.3.28"
# Concurrency
dashmap = "5.4.0"
dashmap = "5.5.3"
# Cmd
clap = { version = "4.3.10", features = ["derive"] }
clap = { version = "4.4.3", features = ["derive"] }
# Logging
tracing = "0.1.37"
tracing-subscriber = { version = "0.3.17", features = ["env-filter"] }
# Error handling
anyhow = "1.0.71"
thiserror = "1.0.40"
anyhow = "1.0.75"
thiserror = "1.0.48"
# Serde
serde = { version = "1.0.164", features = ["derive"] }
serde_yaml = "0.9.22"
serde = { version = "1.0.188", features = ["derive"] }
serde_yaml = "0.9.25"
# File system
notify = "6.0.1"
notify-debouncer-full = "0.2.0"
filetime = "0.2.21"
notify = "6.1.1"
notify-debouncer-full = "0.3.1"
filetime = "0.2.22"
# Util
itertools = "0.11.0"
npath = { git = "https://github.com/gdzx/npath", rev = "00acdd2974bb1682b6d1dcc6f0ea7cd54da42381" }
pin-project = "1.1.1"
pin-project = "1.1.3"
[lints]
@ -102,8 +102,15 @@ clippy.unreachable = "allow"
clippy.mem_forget = "allow"
clippy.shadow_same = "allow"
clippy.shadow_reuse = "allow"
clippy.shadow_unrelated = "allow" # TODO: Maybe check this one every once in a while? Pretty noisy though
clippy.wildcard_enum_match_arm = "allow"
clippy.shadow_unrelated = "allow" # TODO: Maybe check this one every once in a while? Pretty noisy though
clippy.min_ident_chars = "allow" # Useful for generics such as `f: impl FnOnce()`
clippy.single_call_fn = "allow" # It's still useful to separate blocks of code into functions
clippy.float_arithmetic = "allow"
clippy.default_numeric_fallback = "allow" # TODO: This isn't very useful for floats, but might be for integers
# We prefer the short version
clippy.pub_with_shorthand = "allow"
clippy.pub_without_shorthand = "warn"
# Triggers in macro-generated code
# TODO: Turn these on once they don't trigger in macro-generated code
@ -118,6 +125,7 @@ clippy.missing_const_for_fn = "allow"
clippy.significant_drop_in_scrutinee = "allow"
clippy.significant_drop_tightening = "allow"
clippy.tests_outside_test_module = "allow" # Triggers for benches
clippy.multiple_unsafe_ops_per_block = "allow" # Triggers for async?
# Style
clippy.implicit_return = "allow"
@ -134,8 +142,6 @@ clippy.separated_literal_suffix = "allow"
# Matching on a vale and adding `ref` is easier than matching on ref and de-referencing values within the body
clippy.ref_patterns = "allow"
clippy.semicolon_outside_block = "allow"
clippy.manual_let_else = "allow" # Rustfmt doesn't support let-else yet
# Performance of floats isn't paramount
clippy.suboptimal_flops = "allow"
@ -175,3 +181,6 @@ clippy.missing_trait_methods = "allow"
# We only panic when it's an unrecoverable error
clippy.unwrap_in_result = "allow"
clippy.panic_in_result_fn = "allow"
# Sometimes small structs defined inline don't need documentation
clippy.missing_docs_in_private_items = "allow"

View File

@ -154,7 +154,7 @@ You may specify the execution as either:
You may not specify any options (such as the working directory) using this form
```
```yaml
exec:
- [bash, ...]
- [cp, ...]
@ -163,17 +163,60 @@ You may specify the execution as either:
2. Full form
The full form allow you to fully specify everything, but you must put the commands within an inner `cmds` key.
The full form allow you to fully specify everything, but you must put the arguments of each command within an inner `args` key.
```
```yaml
exec:
cwd: "..."
cmds:
- [bash, ...]
- [cp, ...]
- ...
- cwd: "..."
args: [bash, ...]
- [cp, ...]
- ...
```
Each argument may also be a command. The `stdout` of the sub-command will be passed as the argument to the parent command. Supports the following versions:
1. Short form
Similarly to top-level exec, you may just specify an array
```yaml
exec:
- - cat
- pwd: ".."
args: [find, ".", -iname, "myfile.txt"]
```
2. Long form
```yaml
exec:
- - cat
- strip_on_fail: false
cmd:
pwd: ".."
args: [find, ".", -iname, "myfile.txt"]
```
3. Special case for `strip_on_fail`
```yaml
exec:
- - cat
- strip_on_fail:
pwd: ".."
args: [find, ".", -iname, "myfile.txt"]
```
`strip_on_fail` will strip the argument if the command within it fails. This is useful for optional arguments.
The example uses the full syntax for the inner command, but you can use the short syntax. For example the following works:
```yaml
exec:
- - cat
- [find, ".", -iname, "myfile.txt"]
```
## Aliases
You may define aliases (either global or rule-scoped) which give a name to a value.

7
clippy.toml Normal file
View File

@ -0,0 +1,7 @@
absolute-paths-allowed-crates = [
# `tracing_subscriber::fmt` would be easily confused with `std::fmt`
"tracing_subscriber",
# TODO: Review these?
"futures",
"notify",
]

View File

@ -49,7 +49,7 @@ pub struct Args {
/// Watcher file event debouncer timeout
#[clap(long = "watcher-debouncer-timeout-ms")]
pub watch_debouncer_timeout_ms: Option<f64>,
pub watcher_debouncer_timeout_ms: Option<f64>,
/// Logs output to a file.
///

View File

@ -244,7 +244,6 @@ impl<'a, 'de: 'a> serde::Deserialize<'de> for Expr<'a> {
}
// Then check if it was an alias or pattern
#[expect(clippy::missing_docs_in_private_items)]
enum Kind {
Alias,
Pattern,
@ -344,38 +343,63 @@ pub struct Rule<'a> {
}
/// Execution
#[derive(Clone, Debug)]
#[derive(Clone, Default, Debug)]
#[derive(serde::Serialize, serde::Deserialize)]
#[serde(untagged)]
pub enum Exec<'a> {
/// Only commands
OnlyCmds(Vec<Command<'a>>),
/// Full
Full {
/// working directory
#[serde(default)]
#[serde(borrow)]
cwd: Option<Expr<'a>>,
/// Commands
cmds: Vec<Command<'a>>,
},
#[serde(transparent)]
pub struct Exec<'a> {
/// Commands
#[serde(borrow)]
pub cmds: Vec<Command<'a>>,
}
impl Default for Exec<'_> {
fn default() -> Self {
Self::OnlyCmds(vec![])
}
}
/// Command
#[derive(Clone, Debug)]
#[derive(serde::Serialize, serde::Deserialize)]
#[serde(transparent)]
pub struct Command<'a> {
/// All arguments
#[serde(borrow)]
pub args: Vec<Expr<'a>>,
#[serde(untagged)]
pub enum Command<'a> {
/// Only arguments
OnlyArgs(Vec<CommandArg<'a>>),
/// Full
Full {
/// Working directory
#[serde(default)]
#[serde(borrow)]
cwd: Option<Expr<'a>>,
/// Arguments
args: Vec<CommandArg<'a>>,
},
}
/// Command argument
#[derive(Clone, Debug)]
#[derive(serde::Serialize, serde::Deserialize)]
#[serde(untagged)]
pub enum CommandArg<'a> {
/// Expression
#[serde(borrow)]
Expr(Expr<'a>),
/// Command
#[serde(borrow)]
Command(Command<'a>),
/// Command full
CommandFull {
/// Strip the command if failed
strip_on_fail: bool,
/// Command
#[serde(borrow)]
cmd: Command<'a>,
},
/// Strip on fail
StripOnFail {
/// Command
#[serde(rename = "strip_on_fail")]
#[serde(borrow)]
cmd: Command<'a>,
},
}

View File

@ -16,22 +16,24 @@ use {
match_expr::match_expr,
},
crate::{
rules::{DepItem, Expr, OutItem, Rule, Target},
rules::{Command, CommandArg, DepItem, Expr, OutItem, Rule, Target},
util::{self, CowStr},
AppError,
Expander,
Rules,
},
anyhow::Context,
dashmap::DashMap,
filetime::FileTime,
futures::{stream::FuturesUnordered, StreamExt, TryStreamExt},
itertools::Itertools,
std::{
borrow::Cow,
collections::HashMap,
mem,
time::{Duration, SystemTime},
},
tokio::{fs, process::Command, sync::Semaphore},
tokio::{fs, process, sync::Semaphore},
};
// TODO: If the user zbuild file is not generated properly, it can
@ -73,7 +75,7 @@ pub struct Builder<'s> {
_event_rx: async_broadcast::InactiveReceiver<Event<'s>>,
/// Expander
expander: Expander,
expander: Expander<'s>,
/// All rules' build lock
rules_lock: DashMap<TargetRule<'s>, BuildLock<'s>>,
@ -110,7 +112,9 @@ impl<'s> Builder<'s> {
// Note: We only send them if there are any receivers (excluding ours, which is inactive),
// to ensure we don't deadlock waiting for someone to read the events
if self.event_tx.receiver_count() > 0 {
self.event_tx
// Note: We don't care about the event
let _: Option<Event<'_>> = self
.event_tx
.broadcast(make_event())
.await
.expect("Event channel was closed");
@ -171,7 +175,9 @@ impl<'s> Builder<'s> {
/// Resets a build
pub async fn reset_build(&self, target: &Target<'s, CowStr<'s>>, rules: &Rules<'s>) -> Result<(), AppError> {
// Get the rule for the target
let Some((_, target_rule)) = self.target_rule(target, rules)? else { return Ok(()) };
let Some((_, target_rule)) = self.target_rule(target, rules)? else {
return Ok(());
};
// Get the built lock, or create it
// Note: Important to clone since we'll be `await`ing with it.
@ -223,9 +229,8 @@ impl<'s> Builder<'s> {
};
// Get the rule for the target
let (rule, target_rule) = match self.target_rule(&target, rules)? {
Some((rule, target_rule)) => (rule, target_rule),
None => match target {
let Some((rule, target_rule)) = self.target_rule(&target, rules)? else {
match target {
Target::File { ref file, .. } => match fs::metadata(&**file).await {
Ok(metadata) => {
let build_time = self::file_modified_time(metadata);
@ -260,7 +265,7 @@ impl<'s> Builder<'s> {
},
// Note: If `target_rule` returns `Err` if this was a rule, so we can never reach here
Target::Rule { .. } => unreachable!(),
},
}
};
// Get the built lock, or create it
@ -311,7 +316,6 @@ impl<'s> Builder<'s> {
) -> Result<BuildResult, AppError> {
/// Dependency
#[derive(Clone, Debug)]
#[expect(clippy::missing_docs_in_private_items)]
enum Dep<'s, 'a> {
/// File
File {
@ -455,6 +459,7 @@ impl<'s> Builder<'s> {
};
// If the dependency if a dependency deps file or an output deps file (and exists), build it's dependencies too
#[allow(clippy::wildcard_enum_match_arm, reason = "We only care about some variants")]
let dep_deps = match &dep {
Dep::File {
file,
@ -619,41 +624,107 @@ impl<'s> Builder<'s> {
/// Rebuilds a rule
pub async fn rebuild_rule(&self, rule: &Rule<'s, CowStr<'s>>) -> Result<(), AppError> {
// Lock the semaphore
let _permit = self.exec_semaphore.acquire().await;
let _permit = self
.exec_semaphore
.acquire()
.await
.context("Executable semaphore was closed")
.map_err(AppError::Other)?;
for cmd in &rule.exec.cmds {
let (program, args) = cmd.args.split_first().ok_or_else(|| AppError::RuleExecEmpty {
rule_name: rule.name.to_owned(),
})?;
// Create the command
let mut os_cmd = Command::new(&**program);
os_cmd.args(args.iter().map(|arg| &**arg));
// Set the working directory, if we have any
if let Some(cwd) = &rule.exec.cwd {
os_cmd.current_dir(&**cwd);
}
// Then spawn it and measure
tracing::debug!(target: "zbuild_exec", "{} {}", program, args.join(" "));
let (duration, ()) = util::try_measure_async(async {
os_cmd
.spawn()
.map_err(AppError::spawn_command(cmd))?
.wait()
.await
.map_err(AppError::wait_command(cmd))?
.exit_ok()
.map_err(AppError::command_failed(cmd))
})
.await?;
tracing::trace!(target: "zbuild_exec", rule_name=?rule.name, ?program, ?args, ?duration, "Execution duration");
// Note: We don't care about the stdout here and don't capture it anyway.
let _: String = self.exec_cmd(rule.name, cmd, false).await?;
}
Ok(())
}
/// Executes a command, returning it's stdout
#[expect(unused_results, reason = "Due to the builder pattern of `Command`")]
#[expect(clippy::only_used_in_recursion, reason = "It might be used in the future")]
#[async_recursion::async_recursion]
async fn exec_cmd(
&self,
rule_name: &str,
cmd: &Command<CowStr<'s>>,
capture_stdout: bool,
) -> Result<String, AppError> {
// Process all arguments
// Note: When recursing, always capture stdout
let args = cmd
.args
.iter()
.map(async move |arg| match arg {
CommandArg::Expr(arg) => Ok(Some(Cow::Borrowed(&**arg))),
CommandArg::Command { strip_on_fail, cmd } => {
let res = self.exec_cmd(rule_name, cmd, true).await;
match (res, strip_on_fail) {
(Ok(arg), _) => Ok(Some(Cow::Owned(arg))),
(Err(err), true) => {
tracing::debug!(?arg, ?err, "Stripping argument from failure");
Ok(None)
},
(Err(err), false) => Err(err),
}
},
})
.enumerate()
.map(async move |(idx, fut)| fut.await.map(|arg| (idx, arg)))
.collect::<FuturesUnordered<_>>()
.try_collect::<Vec<_>>()
.await?
.into_iter()
.sorted_by_key(|&(idx, _)| idx)
.filter_map(|(_, arg)| arg)
.collect::<Vec<_>>();
let (program, args) = args.split_first().ok_or_else(|| AppError::RuleExecEmpty {
rule_name: rule_name.to_owned(),
})?;
// Create the command
let mut os_cmd = process::Command::new(&**program);
os_cmd.args(args.iter().map(|arg| &**arg));
// Set the working directory, if we have any
if let Some(cwd) = &cmd.cwd {
os_cmd.current_dir(&**cwd);
}
// If we capture pipe stdout, do it
if capture_stdout {
#[expect(
clippy::absolute_paths,
reason = "We're already using a `process` (`tokio::process`)"
)]
os_cmd.stdout(std::process::Stdio::piped());
}
// Then spawn it and measure
tracing::debug!(target: "zbuild_exec", "{} {}", program, args.join(" "));
let (duration, stdout) = util::try_measure_async(async {
let output = os_cmd
.spawn()
.map_err(AppError::spawn_command(cmd))?
.wait_with_output()
.await
.map_err(AppError::wait_command(cmd))?;
output.status.exit_ok().map_err(AppError::command_failed(cmd))?;
Ok(output.stdout)
})
.await?;
tracing::trace!(target: "zbuild_exec", ?rule_name, ?program, ?args, ?duration, "Execution duration");
// Finally parse stdout
// TODO: Not require utf8?
let stdout = String::from_utf8(stdout)
.context("Stdout was non-utf8")
.map_err(AppError::Other)?;
Ok(stdout)
}
/// Finds a rule for `file`
// TODO: Not make this `O(N)` for the number of rules.
#[expect(clippy::type_complexity)] // TODO: Add some type aliases / struct
@ -734,7 +805,11 @@ async fn rule_last_build_time<'s>(rule: &Rule<'s, CowStr<'s>>) -> Result<Option<
}
/// Returns the file modified time
#[expect(clippy::needless_pass_by_value)] // We use it in `.map`, which makes it convenient to receive by value
#[expect(
clippy::needless_pass_by_value,
reason = "We use it in `.map`, which makes it convenient to receive by value"
)]
#[expect(clippy::absolute_paths, reason = "We're already using a `fs` (`tokio::fs`)")]
fn file_modified_time(metadata: std::fs::Metadata) -> SystemTime {
let file_time = FileTime::from_last_modification_time(&metadata);
let unix_offset = Duration::new(

View File

@ -3,7 +3,7 @@
// Imports
use {
crate::{rules::Target, util::CowStr, AppError},
std::{collections::HashMap, sync::Arc, time::SystemTime},
std::{assert_matches::assert_matches, collections::HashMap, sync::Arc, time::SystemTime},
tokio::sync::{OwnedRwLockReadGuard, OwnedRwLockWriteGuard, RwLock},
};
@ -100,7 +100,8 @@ impl<'s> BuildLockBuildGuard<'s> {
/// Resets this build.
pub fn reset(&mut self, target: &Target<'s, CowStr<'s>>) {
self.state.targets_res.remove(target);
// Note: We don't care about the previous build result
let _: Option<Result<BuildResult, _>> = self.state.targets_res.remove(target);
}
/// Finishes a build
@ -110,7 +111,8 @@ impl<'s> BuildLockBuildGuard<'s> {
res: Result<BuildResult, AppError>,
) -> Result<BuildResult, AppError> {
let res = res.map_err(Arc::new);
self.state.targets_res.insert(target.clone(), res.clone());
let prev_res = self.state.targets_res.insert(target.clone(), res.clone());
assert_matches!(prev_res, None, "Build was already finished");
res.map_err(AppError::Shared)
}
}

View File

@ -7,7 +7,7 @@ use {
util::CowStr,
AppError,
},
std::collections::HashMap,
std::{assert_matches::assert_matches, collections::HashMap},
};
/// Returns if `value` matches all `cmpts` and returns all patterns resolved
@ -65,7 +65,8 @@ pub fn match_expr<'s>(
// If we get here, match everything
// TODO: Borrow some cases?
patterns.insert(CowStr::Borrowed(pat.name), CowStr::Owned(value.to_owned()));
let prev_value = patterns.insert(CowStr::Borrowed(pat.name), CowStr::Owned(value.to_owned()));
assert_matches!(prev_value, None, "Found repeated pattern");
cur_cmpts = &[];
value = "";
},

View File

@ -2,7 +2,7 @@
// Imports
use {
crate::rules::{AliasOp, Command, Expr, Target},
crate::rules::{self, AliasOp, Command, Expr, Target},
itertools::Itertools,
std::{fmt, io, path::PathBuf, process::ExitStatusError, sync::Arc},
};
@ -374,7 +374,7 @@ impl AppError {
/// Returns a function to create a [`Self::SpawnCommand`] error from it's inner error.
pub fn spawn_command<T: fmt::Display>(cmd: &Command<T>) -> impl FnOnce(io::Error) -> Self + '_ {
move |err| Self::SpawnCommand {
cmd_fmt: cmd.args.iter().join(" "),
cmd_fmt: Self::cmd_to_string(cmd),
err,
}
}
@ -382,7 +382,7 @@ impl AppError {
/// Returns a function to create a [`Self::WaitCommand`] error from it's inner error.
pub fn wait_command<T: fmt::Display>(cmd: &Command<T>) -> impl FnOnce(io::Error) -> Self + '_ {
move |err| Self::WaitCommand {
cmd_fmt: cmd.args.iter().join(" "),
cmd_fmt: Self::cmd_to_string(cmd),
err,
}
}
@ -390,11 +390,24 @@ impl AppError {
/// Returns a function to create a [`Self::CommandFailed`] error from it's inner error.
pub fn command_failed<T: fmt::Display>(cmd: &Command<T>) -> impl FnOnce(ExitStatusError) -> Self + '_ {
move |err| Self::CommandFailed {
cmd_fmt: cmd.args.iter().join(" "),
cmd_fmt: Self::cmd_to_string(cmd),
err,
}
}
/// Helper function to format a `Command` for errors
fn cmd_to_string<T: fmt::Display>(cmd: &Command<T>) -> String {
let inner = cmd
.args
.iter()
.map(|arg| match arg {
rules::CommandArg::Expr(expr) => format!("\"{expr}\""),
rules::CommandArg::Command { cmd, .. } => Self::cmd_to_string(cmd),
})
.join(" ");
format!("[{inner}]")
}
/// Returns a function to create a [`Self::GetDefaultJobs`] error from it's inner error.
pub fn get_default_jobs() -> impl FnOnce(io::Error) -> Self {
move |err| Self::GetDefaultJobs { err }

View File

@ -4,26 +4,29 @@
use {
crate::{
error::AppError,
rules::{AliasOp, Command, DepItem, Exec, Expr, ExprCmpt, OutItem, Rule, Target},
rules::{AliasOp, Command, CommandArg, DepItem, Exec, Expr, ExprCmpt, OutItem, Rule, Target},
util::CowStr,
},
itertools::Itertools,
std::path::PathBuf,
std::{marker::PhantomData, path::PathBuf},
};
/// Expander
#[derive(Debug)]
pub struct Expander;
pub struct Expander<'s> {
/// Phantom for `'s`
_phantom: PhantomData<&'s ()>,
}
#[expect(clippy::unused_self)] // Currently expander doesn't do anything
impl Expander {
impl<'s> Expander<'s> {
/// Creates a new expander
pub const fn new() -> Self {
Self
Self { _phantom: PhantomData }
}
/// Expands an expression to it's components
pub fn expand_expr<'s>(&self, expr: &Expr<'s>, visitor: &mut impl Visitor<'s>) -> Result<Expr<'s>, AppError> {
pub fn expand_expr(&self, expr: &Expr<'s>, visitor: &mut impl Visitor<'s>) -> Result<Expr<'s>, AppError> {
// Go through all components
let cmpts = expr
.cmpts
@ -104,11 +107,7 @@ impl Expander {
}
/// Expands an expression into a string
pub fn expand_expr_string<'s>(
&self,
expr: &Expr<'s>,
visitor: &mut impl Visitor<'s>,
) -> Result<CowStr<'s>, AppError> {
pub fn expand_expr_string(&self, expr: &Expr<'s>, visitor: &mut impl Visitor<'s>) -> Result<CowStr<'s>, AppError> {
let expr_cmpts = self.expand_expr(expr, visitor)?.cmpts.into_boxed_slice();
let res = match Box::<[_; 0]>::try_from(expr_cmpts) {
Ok(box []) => Ok("".into()),
@ -148,7 +147,7 @@ impl Expander {
}
/// Expands a rule of all it's aliases and patterns
pub fn expand_rule<'s>(
pub fn expand_rule(
&self,
rule: &Rule<'s, Expr<'s>>,
visitor: &mut impl Visitor<'s>,
@ -210,25 +209,11 @@ impl Expander {
.collect::<Result<_, _>>()?;
let exec = Exec {
cwd: rule
.exec
.cwd
.as_ref()
.map(|cwd| self.expand_expr_string(cwd, visitor))
.transpose()?,
cmds: rule
.exec
.cmds
.iter()
.map(|cmd| {
Ok(Command {
args: cmd
.args
.iter()
.map(|arg| self.expand_expr_string(arg, visitor))
.collect::<Result<_, _>>()?,
})
})
.map(|cmd| self.expand_cmd(cmd, visitor))
.collect::<Result<_, _>>()?,
};
@ -241,8 +226,37 @@ impl Expander {
})
}
/// Expands a command
pub fn expand_cmd(
&self,
cmd: &Command<Expr<'s>>,
visitor: &mut impl Visitor<'s>,
) -> Result<Command<CowStr<'s>>, AppError> {
Ok(Command {
cwd: cmd
.cwd
.as_ref()
.map(|cwd| self.expand_expr_string(cwd, visitor))
.transpose()?,
args: cmd
.args
.iter()
.map(|arg| {
let arg = match *arg {
CommandArg::Expr(ref expr) => CommandArg::Expr(self.expand_expr_string(expr, visitor)?),
CommandArg::Command { strip_on_fail, ref cmd } => CommandArg::Command {
strip_on_fail,
cmd: self.expand_cmd(cmd, visitor)?,
},
};
Ok(arg)
})
.collect::<Result<_, _>>()?,
})
}
/// Expands a target expression
pub fn expand_target<'s>(
pub fn expand_target(
&self,
target: &Target<'s, Expr<'s>>,
visitor: &mut impl Visitor<'s>,

View File

@ -1,81 +1,121 @@
//! Logger
// TODO: Parse the environment non-lossy first, then use a default.
// Modules
mod pre_init;
// Imports
use {
anyhow::Context,
std::{
env::{self, VarError},
fs,
io::{self, IsTerminal},
path::Path,
sync::Mutex,
},
tracing::metadata::LevelFilter,
tracing_subscriber::{prelude::*, EnvFilter},
tracing_subscriber::{prelude::*, EnvFilter, Registry},
};
/// Initializes the logger
pub fn init(log_file: Option<&Path>) {
// Warnings to emit after configuring the logger
let mut warnings = vec![];
///
/// Logs to `stderr`, and to `{log_file}`.
pub fn init(log_path: Option<&Path>) {
// Create the terminal layer
let term_use_colors = self::colors_enabled(&mut warnings);
let term_layer = tracing_subscriber::fmt::layer().with_ansi(term_use_colors).with_filter(
EnvFilter::builder()
.with_default_directive(LevelFilter::INFO.into())
.from_env_lossy(),
);
let term_layer = self::term_layer();
// Create the file layer, if requested
let file_layer = log_file.and_then(|log_file| {
// Try to create the file
let file = match std::fs::File::create(log_file) {
Ok(file) => file,
Err(err) => {
warnings.push(format!("Unable to create log file: {err}"));
return None;
},
};
// Then create the layer
let layer = tracing_subscriber::fmt::layer()
.with_writer(file)
.with_ansi(false)
.with_filter(
EnvFilter::builder()
.with_default_directive(LevelFilter::DEBUG.into())
.with_env_var("RUST_LOG_FILE")
.from_env_lossy(),
);
Some(layer)
// Create the file layer
let file_layer = log_path.and_then(|log_path| match self::file_layer(log_path) {
Ok(layer) => Some(layer),
Err(err) => {
pre_init::warn(format!("Unable to create file logging layer: {err:?}"));
None
},
});
// Finally initialize
tracing_subscriber::registry().with(term_layer).with(file_layer).init();
tracing::debug!(?log_file, ?term_use_colors, "Initialized logging");
// Create a registry with all the layers and initialize it
Registry::default().with(term_layer).with(file_layer).init();
tracing::debug!(?log_path, "Initialized logging");
// And emit any warnings
for warning in warnings {
tracing::warn!("{warning}");
// And emit all pre-init warnings
for message in pre_init::take_traces() {
tracing::trace!("{message}");
}
for message in pre_init::take_debugs() {
tracing::debug!("{message}");
}
for message in pre_init::take_warnings() {
tracing::warn!("{message}");
}
}
/// Returns whether to colors should be enabled for the terminal layer.
fn colors_enabled(warnings: &mut Vec<String>) -> bool {
match env::var("RUST_LOG_COLOR").map(|var| var.to_lowercase()).as_deref() {
// By default / `1` / `yes` / `true`, use colors
Err(VarError::NotPresent) | Ok("1" | "yes" | "true") => true,
/// Creates the terminal layer
fn term_layer<S>() -> impl tracing_subscriber::Layer<S>
where
S: tracing::Subscriber + for<'span> tracing_subscriber::registry::LookupSpan<'span> + 'static,
{
let enable_colors = self::term_enable_colors();
pre_init::debug(format!("Stderr logging colors: {enable_colors}"));
// On `0`, `no`, `false`, don't
let env = env::var("RUST_LOG").unwrap_or_else(|_| "info".to_owned());
pre_init::debug(format!("Stderr logging filter: {env}"));
tracing_subscriber::fmt::layer()
.with_ansi(enable_colors)
.with_writer(io::stderr)
.with_filter(
EnvFilter::builder()
.with_default_directive(LevelFilter::INFO.into())
.parse_lossy(env),
)
}
/// Creates the file layer
fn file_layer<S>(log_path: &Path) -> Result<impl tracing_subscriber::Layer<S>, anyhow::Error>
where
S: tracing::Subscriber + for<'span> tracing_subscriber::registry::LookupSpan<'span> + 'static,
{
// Parse the environment
let env = env::var("RUST_FILE_LOG").unwrap_or_else(|_| "debug".to_owned());
pre_init::debug(format!("File logging filter: {env}"));
// Try to create the log file parent, if it doesn't exist.
let parent_dir = log_path.parent().context("Log path has no parent directory")?;
fs::create_dir_all(parent_dir).context("Unable to create log path parent directory")?;
// Then create the file
let file = fs::File::create(log_path).context("Unable to create log file")?;
// And finally the layer
let layer = tracing_subscriber::fmt::layer()
.with_writer(Mutex::new(file))
.with_ansi(false)
.with_filter(EnvFilter::builder().parse_lossy(env));
Ok(layer)
}
/// Returns whether to colors should be enabled for the terminal layer.
fn term_enable_colors() -> bool {
match env::var("RUST_LOG_COLOR").map(|var| var.to_lowercase()).as_deref() {
// If it isn't present, check if we're in a terminal
Err(VarError::NotPresent) => io::stderr().is_terminal(),
// Else check user input
Ok("1" | "yes" | "true") => true,
Ok("0" | "no" | "false") => false,
// Else don't use colors, but warn
// On invalid input, warn and don't use colors
Ok(env) => {
warnings.push(format!(
pre_init::warn(format!(
"Ignoring unknown `RUST_LOG_COLOR` value: {env:?}, expected `0`, `1`, `yes`, `no`, `true`, `false`"
));
false
},
Err(VarError::NotUnicode(err)) => {
warnings.push(format!("Ignoring non-utf8 `RUST_LOG_COLOR`: {err:?}"));
pre_init::warn(format!("Ignoring non-utf8 `RUST_LOG_COLOR`: {err:?}"));
false
},
}

48
src/logger/pre_init.rs Normal file
View File

@ -0,0 +1,48 @@
//! Pre-initialization
// TODO: Use a proper solution that initializes a temporary subscriber and keeps all the records and events.
// Imports
use std::sync::{
atomic::{self, AtomicBool},
Mutex,
};
/// If logging as already initialized
static IS_INIT: AtomicBool = AtomicBool::new(false);
/// Creates the pre-init functions for a level
macro pre_init_fns(
$(
$NAME:ident, $log:ident, $take:ident;
)*
) {
$(
static $NAME: Mutex<Vec<String>> = Mutex::new(Vec::new());
/// Emits a log
#[allow(dead_code)]
pub fn $log(message: impl Into<String>) {
let message = message.into();
match IS_INIT.load(atomic::Ordering::Acquire) {
true => {
tracing::warn!(?message, "Using pre-initialization logging after initialization");
tracing::$log!("{message}")
},
false => $NAME.lock().expect("Poisoned").push(message),
}
}
/// Retrieves all logs
pub(super) fn $take() -> Vec<String> {
IS_INIT.store(true, atomic::Ordering::Release);
$NAME.lock().expect("Poisoned").drain(..).collect()
}
)*
}
pre_init_fns! {
TRACES , trace, take_traces ;
DEBUGS , debug, take_debugs ;
WARNINGS, warn , take_warnings;
}

View File

@ -11,8 +11,11 @@
async_fn_in_trait,
yeet_expr,
must_not_suspend,
strict_provenance
strict_provenance,
assert_matches
)]
// Lints
#![allow(clippy::print_stdout, reason = "We're a binary that should talk to the user")]
// Modules
mod args;
@ -35,8 +38,10 @@ use {
borrow::Cow,
collections::HashMap,
env,
fmt,
fs,
path::{Path, PathBuf},
thread,
time::{Duration, SystemTime},
},
util::CowStr,
@ -66,10 +71,10 @@ async fn main() -> Result<(), anyhow::Error> {
let zbuild_path = zbuild_path.file_name().expect("Zbuild path had no file name");
let zbuild_path = Path::new(zbuild_path);
tracing::debug!(?zbuild_dir, "Moving to zbuild directory");
std::env::set_current_dir(zbuild_dir).map_err(AppError::set_current_dir(zbuild_dir))?;
env::set_current_dir(zbuild_dir).map_err(AppError::set_current_dir(zbuild_dir))?;
// Parse the ast
let zbuild_file = fs::read_to_string(&zbuild_path).map_err(AppError::read_file(&zbuild_path))?;
let zbuild_file = fs::read_to_string(zbuild_path).map_err(AppError::read_file(&zbuild_path))?;
tracing::trace!(?zbuild_file, "Read zbuild.yaml");
let ast = serde_yaml::from_str::<Ast<'_>>(&zbuild_file).map_err(AppError::parse_yaml(&zbuild_path))?;
tracing::trace!(?ast, "Parsed ast");
@ -85,7 +90,7 @@ async fn main() -> Result<(), anyhow::Error> {
1
},
Some(jobs) => jobs,
None => std::thread::available_parallelism()
None => thread::available_parallelism()
.map_err(AppError::get_default_jobs())?
.into(),
};
@ -115,7 +120,7 @@ async fn main() -> Result<(), anyhow::Error> {
// If there was a rule, use it without any patterns
// TODO: If it requires patterns maybe error out here?
|rule| rules::Target::Rule {
rule: rules::Expr::string(rule.name.to_string()),
rule: rules::Expr::string(rule.name.to_owned()),
pats: HashMap::new(),
},
)
@ -131,11 +136,11 @@ async fn main() -> Result<(), anyhow::Error> {
let watcher = args
.watch
.then(|| {
Watcher::new(
builder.subscribe_events(),
// TODO: Better default?
args.watch_debouncer_timeout_ms.unwrap_or(0.0),
)
// TODO: Better default?
let debouncer_timeout_ms = args.watcher_debouncer_timeout_ms.unwrap_or(0.0);
let debouncer_timeout = { Duration::from_secs_f64(debouncer_timeout_ms / 1000.0) };
Watcher::new(builder.subscribe_events(), debouncer_timeout)
})
.transpose()?;
@ -188,8 +193,7 @@ async fn find_zbuild() -> Result<PathBuf, AppError> {
}
/// Builds a target.
#[expect(clippy::future_not_send)] // Auto-traits are propagated (TODO: Maybe? Check if this is true)
async fn build_target<'s, T: BuildableTargetInner<'s> + std::fmt::Display + std::fmt::Debug>(
async fn build_target<'s, T: BuildableTargetInner<'s> + fmt::Display + fmt::Debug>(
builder: &Builder<'s>,
target: &rules::Target<'s, T>,
rules: &Rules<'s>,
@ -213,7 +217,7 @@ async fn build_target<'s, T: BuildableTargetInner<'s> + std::fmt::Display + std:
println!("{target}");
};
},
Err(err) => tracing::error!(?target, err=?anyhow::Error::new(err), "Unable to build target"),
Err(err) => tracing::error!(%target, err=?anyhow::Error::new(err), "Unable to build target"),
}
}

View File

@ -14,7 +14,7 @@ pub use {
expr::{Expr, ExprCmpt},
item::{DepItem, OutItem},
pattern::{Pattern, PatternOp},
rule::{Command, Exec, Rule},
rule::{Command, CommandArg, Exec, Rule},
target::Target,
};

View File

@ -36,26 +36,7 @@ impl<'s> Rule<'s, Expr<'s>> {
.collect();
let output = rule.out.into_iter().map(OutItem::new).collect();
let deps = rule.deps.into_iter().map(DepItem::new).collect();
let exec = match rule.exec {
ast::Exec::OnlyCmds(cmds) => Exec {
cwd: None,
cmds: cmds
.into_iter()
.map(|cmd| Command {
args: cmd.args.into_iter().map(Expr::new).collect(),
})
.collect(),
},
ast::Exec::Full { cwd, cmds } => Exec {
cwd: cwd.map(Expr::new),
cmds: cmds
.into_iter()
.map(|cmd| Command {
args: cmd.args.into_iter().map(Expr::new).collect(),
})
.collect(),
},
};
let exec = Exec::new(rule.exec);
Self {
name,
@ -71,17 +52,79 @@ impl<'s> Rule<'s, Expr<'s>> {
/// Exec
#[derive(Clone, Debug)]
pub struct Exec<T> {
/// Working directory
pub cwd: Option<T>,
/// Commands
pub cmds: Vec<Command<T>>,
}
impl<'s> Exec<Expr<'s>> {
/// Creates a new exec from it's ast
pub fn new(exec: ast::Exec<'s>) -> Self {
Self {
cmds: exec.cmds.into_iter().map(Command::new).collect(),
}
}
}
/// Command
#[derive(Clone, Debug)]
pub struct Command<T> {
/// Working directory
pub cwd: Option<T>,
/// All arguments
pub args: Vec<T>,
pub args: Vec<CommandArg<T>>,
}
impl<'s> Command<Expr<'s>> {
/// Creates a new command from it's ast
pub fn new(cmd: ast::Command<'s>) -> Self {
match cmd {
ast::Command::OnlyArgs(args) => Self {
cwd: None,
args: args.into_iter().map(CommandArg::new).collect(),
},
ast::Command::Full { cwd, args } => Self {
cwd: cwd.map(Expr::new),
args: args.into_iter().map(CommandArg::new).collect(),
},
}
}
}
/// Command argument
#[derive(Clone, Debug)]
pub enum CommandArg<T> {
/// Expression
Expr(T),
/// Command
Command {
/// Strip the command if failed
strip_on_fail: bool,
/// Command
cmd: Command<T>,
},
}
impl<'s> CommandArg<Expr<'s>> {
/// Creates a new command argument from it's ast
pub fn new(arg: ast::CommandArg<'s>) -> Self {
match arg {
ast::CommandArg::Expr(expr) => Self::Expr(Expr::new(expr)),
ast::CommandArg::Command(cmd) => Self::Command {
strip_on_fail: false,
cmd: Command::new(cmd),
},
ast::CommandArg::CommandFull { strip_on_fail, cmd } => Self::Command {
strip_on_fail,
cmd: Command::new(cmd),
},
ast::CommandArg::StripOnFail { cmd } => Self::Command {
strip_on_fail: true,
cmd: Command::new(cmd),
},
}
}
}

View File

@ -9,6 +9,7 @@ use {
collections::HashMap,
fmt,
hash::{Hash, Hasher},
mem,
},
};
@ -62,7 +63,7 @@ impl<'s> Target<'s, Expr<'s>> {
impl<T: Hash + Ord> Hash for Target<'_, T> {
fn hash<H: Hasher>(&self, state: &mut H) {
core::mem::discriminant(self).hash(state);
mem::discriminant(self).hash(state);
match self {
Self::File { file, is_static } => {
file.hash(state);

View File

@ -7,7 +7,8 @@ use {
pin_project::pin_project,
std::{
borrow::Cow,
path::Path,
io,
path::{self, Path},
pin::Pin,
task,
time::{Duration, Instant},
@ -30,17 +31,17 @@ pub macro chain {
}
/// Async `std::fs_try_exists`
pub async fn fs_try_exists(path: impl AsRef<Path> + Send) -> Result<bool, std::io::Error> {
pub async fn fs_try_exists(path: impl AsRef<Path> + Send) -> Result<bool, io::Error> {
match fs::metadata(path).await {
Ok(_) => Ok(true),
Err(err) if err.kind() == std::io::ErrorKind::NotFound => Ok(false),
Err(err) if err.kind() == io::ErrorKind::NotFound => Ok(false),
Err(err) => Err(err),
}
}
/// Measures the duration of a fallible future
#[expect(clippy::future_not_send)] // It is send if `F: Send` (TODO: Check if this is true)
pub async fn try_measure_async<F: Future<Output = Result<T, E>>, T, E>(fut: F) -> Result<(Duration, T), E> {
/// Wrapper future for measuring the future
#[pin_project]
struct Wrapper<F> {
/// Future
@ -71,7 +72,7 @@ pub async fn try_measure_async<F: Future<Output = Result<T, E>>, T, E>(fut: F) -
/// Normalizes a string path
pub fn normalize_path(path: &str) -> String {
let ends_with_sep = path.ends_with(std::path::MAIN_SEPARATOR_STR);
let ends_with_sep = path.ends_with(path::MAIN_SEPARATOR_STR);
let mut path = Path::new(&path)
.normalized()
@ -82,7 +83,7 @@ pub fn normalize_path(path: &str) -> String {
// Note: `npath` doesn't keep `/` at the end, so we have to do it manually
match ends_with_sep {
true => {
path.push_str(std::path::MAIN_SEPARATOR_STR);
path.push_str(path::MAIN_SEPARATOR_STR);
path
},
false => path,

View File

@ -15,10 +15,12 @@ use {
notify::Watcher as _,
notify_debouncer_full::Debouncer,
std::{
io,
path::{Path, PathBuf},
sync::Arc,
time::Duration,
},
tokio::sync::mpsc,
tokio_stream::wrappers::ReceiverStream,
};
@ -51,26 +53,24 @@ impl<'s> Watcher<'s> {
/// Creates a new watcher
pub fn new(
builder_event_rx: async_broadcast::Receiver<build::Event<'s>>,
watch_debouncer_timeout_ms: f64,
debouncer_timeout: Duration,
) -> Result<Self, AppError> {
// Create the watcher
let (fs_event_tx, fs_event_rx) = tokio::sync::mpsc::channel(16);
let watcher = notify_debouncer_full::new_debouncer(
Duration::from_secs_f64(watch_debouncer_timeout_ms / 1000.0),
None,
move |fs_events| match fs_events {
Ok(fs_events) =>
for fs_event in fs_events {
tracing::trace!(?fs_event, "Watcher fs event");
#[expect(let_underscore_drop)] // We don't care if it succeeded or not
let _ = fs_event_tx.blocking_send(fs_event);
},
Err(errs) =>
for err in errs {
tracing::warn!(err=?anyhow::Error::from(err), "Error while watching");
},
},
)
let (fs_event_tx, fs_event_rx) = mpsc::channel(16);
let watcher = notify_debouncer_full::new_debouncer(debouncer_timeout, None, move |fs_events| match fs_events {
Ok(fs_events) =>
for fs_event in fs_events {
tracing::trace!(?fs_event, "Watcher fs event");
// Note: We don't care if it succeeded or not
#[expect(let_underscore_drop, clippy::let_underscore_must_use)]
let _: Result<(), _> = fs_event_tx.blocking_send(fs_event);
},
Err(errs) =>
for err in errs {
tracing::warn!(err=?anyhow::Error::from(err), "Error while watching");
},
})
.context("Unable to create file watcher")
.map_err(AppError::Other)?;
@ -122,14 +122,16 @@ impl<'s> Watcher<'s> {
// until the root, and by that point we'd just be watching
// all filesystem changes...
tracing::trace!(?dep_path, "Starting to watch path");
if let Err(err) = self.watcher.watcher().watch(
&dep_path.parent().unwrap_or(&dep_path),
notify::RecursiveMode::Recursive,
) {
tracing::warn!(?dep_path, ?err, "Unable to watch path")
if let Err(err) = self
.watcher
.watcher()
.watch(dep_path.parent().unwrap_or(&dep_path), notify::RecursiveMode::Recursive)
{
tracing::warn!(?dep_path, ?err, "Unable to watch path");
}
rev_deps
// Note: We don't care if we add a duplicate target
let _: bool = rev_deps
.entry(dep_path)
.or_insert_with(|| RevDep {
target: dep,
@ -153,7 +155,7 @@ impl<'s> Watcher<'s> {
Err(err) => {
// TODO: Warn on all occasions once this code path isn't hit
// by random files that aren't actually dependencies.
if err.kind() != std::io::ErrorKind::NotFound {
if err.kind() != io::ErrorKind::NotFound {
tracing::warn!(?path, ?err, "Unable to canonicalize");
}
return;
@ -182,7 +184,7 @@ impl<'s> Watcher<'s> {
builder
.reset_build(&rev_dep.target, rules)
.await
.expect("Unable to reset existing build")
.expect("Unable to reset existing build");
},
dep_parents
.iter()