diff --git a/Cargo.lock b/Cargo.lock index 3bd43c38..e6ca8a3c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,21 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "addr2line" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5fb1d8e4442bd405fdfd1dacb42792696b0cf9cb15882e5d097b742a676d375" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" + [[package]] name = "aho-corasick" version = "0.7.20" @@ -17,7 +32,7 @@ version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" dependencies = [ - "hermit-abi", + "hermit-abi 0.1.17", "libc", "winapi", ] @@ -28,12 +43,33 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" +[[package]] +name = "backtrace" +version = "0.3.74" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" +dependencies = [ + "addr2line", + "cfg-if 1.0.0", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-targets 0.52.6", +] + [[package]] name = "bitflags" version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" +[[package]] +name = "bitflags" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" + [[package]] name = "bytes" version = "1.0.1" @@ -81,7 +117,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4bd1061998a501ee7d4b6d449020df3266ca3124b941ec56cf2005c3779ca142" dependencies = [ "atty", - "bitflags", + "bitflags 1.2.1", "clap_derive", "indexmap", "lazy_static", @@ -104,7 +140,20 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn", + "syn 1.0.73", +] + +[[package]] +name = "console" +version = "0.15.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e1f83fc076bd6dd27517eacdf25fef6c4dfe5f1d7448bafaaf3a26f13b5e4eb" +dependencies = [ + "encode_unicode", + "lazy_static", + "libc", + "unicode-width", + "windows-sys 0.52.0", ] [[package]] @@ -135,6 +184,7 @@ dependencies = [ "flexi_logger", "fork", "futures-util", + "indicatif", "log", "merge", "notify", @@ -146,6 +196,7 @@ dependencies = [ "smol_str", "thiserror", "tokio", + "tokio-stream", "toml", "whoami", "yn", @@ -172,6 +223,12 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "encode_unicode" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" + [[package]] name = "filetime" version = "0.2.13" @@ -233,7 +290,7 @@ dependencies = [ "proc-macro-hack", "proc-macro2", "quote", - "syn", + "syn 1.0.73", ] [[package]] @@ -272,6 +329,12 @@ dependencies = [ "wasi 0.11.0+wasi-snapshot-preview1", ] +[[package]] +name = "gimli" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32085ea23f3234fc7846555e85283ba4de91e21016dc0455a16286d87a292d64" + [[package]] name = "glob" version = "0.3.0" @@ -302,6 +365,12 @@ dependencies = [ "libc", ] +[[package]] +name = "hermit-abi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" + [[package]] name = "indexmap" version = "1.6.0" @@ -312,13 +381,28 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "indicatif" +version = "0.17.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "763a5a8f45087d6bcea4222e7b72c291a054edf80e4ef6efd2a4979878c7bea3" +dependencies = [ + "console", + "futures-core", + "instant", + "number_prefix", + "portable-atomic", + "tokio", + "unicode-width", +] + [[package]] name = "inotify" version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8069d3ec154eb856955c1c0fbffefbf5f3c40a104ec912d4797314c1801abff" dependencies = [ - "bitflags", + "bitflags 1.2.1", "inotify-sys", "libc", ] @@ -332,6 +416,15 @@ dependencies = [ "libc", ] +[[package]] +name = "instant" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" +dependencies = [ + "cfg-if 1.0.0", +] + [[package]] name = "itoa" version = "0.4.6" @@ -354,7 +447,7 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8367585489f01bc55dd27404dcf56b95e6da061a256a666ab23be9ba96a2e587" dependencies = [ - "bitflags", + "bitflags 1.2.1", "libc", ] @@ -366,9 +459,18 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.147" +version = "0.2.158" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" + +[[package]] +name = "lock_api" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" +checksum = "88943dd7ef4a2e5a4bfa2753aaab3013e34ce2533d1996fb18ef591e315e2b3b" +dependencies = [ + "scopeguard", +] [[package]] name = "log" @@ -404,20 +506,16 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn", + "syn 1.0.73", ] [[package]] -name = "mio" -version = "0.7.6" +name = "miniz_oxide" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f33bc887064ef1fd66020c9adfc45bb9f33d75a42096c81e7c56c65b75dd1a8b" +checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" dependencies = [ - "libc", - "log", - "miow", - "ntapi", - "winapi", + "adler2", ] [[package]] @@ -433,13 +531,15 @@ dependencies = [ ] [[package]] -name = "miow" -version = "0.3.6" +name = "mio" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a33c1b55807fbed163481b5ba66db4b2fa6cde694a5027be10fb724206c5897" +checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" dependencies = [ - "socket2", - "winapi", + "hermit-abi 0.3.9", + "libc", + "wasi 0.11.0+wasi-snapshot-preview1", + "windows-sys 0.52.0", ] [[package]] @@ -448,7 +548,7 @@ version = "5.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "58ea850aa68a06e48fdb069c0ec44d0d64c8dbffa49bf3b6f7f0a901fdea1ba9" dependencies = [ - "bitflags", + "bitflags 1.2.1", "crossbeam-channel", "filetime", "fsevent-sys", @@ -460,15 +560,6 @@ dependencies = [ "windows-sys 0.42.0", ] -[[package]] -name = "ntapi" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f6bb902e437b6d86e03cce10a7e2af662292c5dfef23b65899ea3ac9354ad44" -dependencies = [ - "winapi", -] - [[package]] name = "num-integer" version = "0.1.44" @@ -489,13 +580,18 @@ dependencies = [ ] [[package]] -name = "num_cpus" -version = "1.13.0" +name = "number_prefix" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" + +[[package]] +name = "object" +version = "0.36.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3" +checksum = "084f1a5821ac4c651660a94a7153d27ac9d8a53736203f58b31945ded098070a" dependencies = [ - "hermit-abi", - "libc", + "memchr", ] [[package]] @@ -516,6 +612,29 @@ version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "afb2e1c3ee07430c2cf76151675e583e0f19985fa6efae47d6848a3e2c824f85" +[[package]] +name = "parking_lot" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "redox_syscall 0.5.4", + "smallvec", + "windows-targets 0.52.6", +] + [[package]] name = "pin-project" version = "1.0.2" @@ -533,14 +652,14 @@ checksum = "f8e8d2bf0b23038a4424865103a4df472855692821aab4e4f5c3312d461d9e5f" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.73", ] [[package]] name = "pin-project-lite" -version = "0.2.0" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b063f57ec186e6140e2b8b6921e5f1bd89c7356dda5b33acc5401203ca6131c" +checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" [[package]] name = "pin-utils" @@ -548,6 +667,12 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "portable-atomic" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da544ee218f0d287a911e9c99a39a8c9bc8fcad3cb8db5959940044ecfc67265" + [[package]] name = "proc-macro-error" version = "1.0.4" @@ -557,7 +682,7 @@ dependencies = [ "proc-macro-error-attr", "proc-macro2", "quote", - "syn", + "syn 1.0.73", "version_check", ] @@ -586,18 +711,18 @@ checksum = "eba180dafb9038b050a4c280019bbedf9f2467b61e5d892dcad585bb57aadc5a" [[package]] name = "proc-macro2" -version = "1.0.27" +version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0d8caf72986c1a598726adc988bb5984792ef84f5ee5aa50209145ee8077038" +checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" dependencies = [ - "unicode-xid", + "unicode-ident", ] [[package]] name = "quote" -version = "1.0.7" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" dependencies = [ "proc-macro2", ] @@ -614,7 +739,16 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" dependencies = [ - "bitflags", + "bitflags 1.2.1", +] + +[[package]] +name = "redox_syscall" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0884ad60e090bf1345b93da0a5de8923c93884cd03f40dfcfddd3b4bee661853" +dependencies = [ + "bitflags 2.6.0", ] [[package]] @@ -688,6 +822,12 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "rustc-demangle" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" + [[package]] name = "rustc-hash" version = "1.1.0" @@ -709,6 +849,12 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + [[package]] name = "serde" version = "1.0.118" @@ -726,7 +872,7 @@ checksum = "c84d3526699cd55261af4b941e4e725444df67aa4f9e6a3564f18030d12672df" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.73", ] [[package]] @@ -765,6 +911,12 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" +[[package]] +name = "smallvec" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" + [[package]] name = "smol_str" version = "0.1.16" @@ -773,14 +925,12 @@ checksum = "2f7909a1d8bc166a862124d84fdc11bda0ea4ed3157ccca662296919c2972db1" [[package]] name = "socket2" -version = "0.3.17" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c29947abdee2a218277abeca306f25789c938e500ea5a9d4b12a5a504466902" +checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" dependencies = [ - "cfg-if 1.0.0", "libc", - "redox_syscall 0.1.57", - "winapi", + "windows-sys 0.52.0", ] [[package]] @@ -800,6 +950,17 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "syn" +version = "2.0.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "termcolor" version = "1.1.2" @@ -858,7 +1019,7 @@ checksum = "9ba20f23e85b10754cd195504aebf6a27e2e6cbe28c17778a0c930724628dd56" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.73", ] [[package]] @@ -874,32 +1035,42 @@ dependencies = [ [[package]] name = "tokio" -version = "1.9.0" +version = "1.40.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b7b349f11a7047e6d1276853e612d152f5e8a352c61917887cc2169e2366b4c" +checksum = "e2b070231665d27ad9ec9b8df639893f46727666c6767db40317fbe920a5d998" dependencies = [ - "autocfg", + "backtrace", "bytes", "libc", - "memchr", - "mio 0.7.6", - "num_cpus", - "once_cell", + "mio 1.0.2", + "parking_lot", "pin-project-lite", "signal-hook-registry", + "socket2", "tokio-macros", - "winapi", + "windows-sys 0.52.0", ] [[package]] name = "tokio-macros" -version = "1.3.0" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54473be61f4ebe4efd09cec9bd5d16fa51d70ea0192213d754d2d500457db110" +checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.77", +] + +[[package]] +name = "tokio-stream" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f4e6ce100d0eb49a2734f8c0812bcd324cf357d21810932c5df6b96ef2b86f1" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", ] [[package]] @@ -911,6 +1082,12 @@ dependencies = [ "serde", ] +[[package]] +name = "unicode-ident" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" + [[package]] name = "unicode-segmentation" version = "1.7.1" @@ -1034,6 +1211,15 @@ dependencies = [ "windows-targets 0.48.5", ] +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + [[package]] name = "windows-targets" version = "0.42.2" @@ -1064,6 +1250,22 @@ dependencies = [ "windows_x86_64_msvc 0.48.5", ] +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + [[package]] name = "windows_aarch64_gnullvm" version = "0.42.2" @@ -1076,6 +1278,12 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + [[package]] name = "windows_aarch64_msvc" version = "0.42.2" @@ -1088,6 +1296,12 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + [[package]] name = "windows_i686_gnu" version = "0.42.2" @@ -1100,6 +1314,18 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + [[package]] name = "windows_i686_msvc" version = "0.42.2" @@ -1112,6 +1338,12 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + [[package]] name = "windows_x86_64_gnu" version = "0.42.2" @@ -1124,6 +1356,12 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + [[package]] name = "windows_x86_64_gnullvm" version = "0.42.2" @@ -1136,6 +1374,12 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + [[package]] name = "windows_x86_64_msvc" version = "0.42.2" @@ -1148,6 +1392,12 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + [[package]] name = "yansi" version = "0.5.0" diff --git a/Cargo.toml b/Cargo.toml index d8847713..13b5aade 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,7 +24,7 @@ serde = { version = "1.0.104", features = [ "derive" ] } serde_json = "1.0.48" signal-hook = "0.3" thiserror = "1.0" -tokio = { version = "1.9.0", features = [ "process", "macros", "sync", "rt-multi-thread", "fs", "time", "io-util" ] } +tokio = { version = "1.40.0", features = ["process", "macros", "sync", "rt-multi-thread", "fs", "time", "io-util", "rt", "full"] } toml = "0.5" whoami = "0.9.0" yn = "0.1" @@ -34,6 +34,8 @@ yn = "0.1" # : smol_str = "=0.1.16" rpassword = "7.3.1" +indicatif = { version = "0.17.8", features = ["tokio", "futures"] } +tokio-stream = { version = "0.1.16", features = ["io-util"] } [lib] diff --git a/src/cli.rs b/src/cli.rs index f3bce4df..1932d390 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -5,10 +5,13 @@ use std::collections::HashMap; use std::io::{stdin, stdout, Write}; +use std::time::Duration; use clap::{ArgMatches, Clap, FromArgMatches}; +use indicatif::{MultiProgress, ProgressBar, ProgressStyle}; +use tokio::join; -use crate as deploy; +use crate::{self as deploy}; use self::deploy::{DeployFlake, ParseFlakeError}; use futures_util::stream::{StreamExt, TryStreamExt}; @@ -18,6 +21,7 @@ use std::path::PathBuf; use std::process::Stdio; use thiserror::Error; use tokio::process::Command; +use tokio::task::JoinSet; /// Simple Rust rewrite of a simple Nix Flake deployment tool #[derive(Clap, Debug, Clone)] @@ -380,12 +384,13 @@ fn prompt_deployment( #[derive(Error, Debug)] pub enum RunDeployError { - #[error("Failed to deploy profile to node {0}: {1}")] - DeployProfile(String, deploy::deploy::DeployProfileError), - #[error("Failed to build profile on node {0}: {0}")] - BuildProfile(String, deploy::push::PushProfileError), - #[error("Failed to push profile to node {0}: {0}")] - PushProfile(String, deploy::push::PushProfileError), + #[error("Failed to deploy profile {0} to node {1}: {2}")] + DeployProfile(String, String, deploy::deploy::DeployProfileError), + #[error("Failed to build profile {0} on node {1}: {2}")] + BuildProfile(String, String, deploy::push::PushProfileError), + #[error("Failed to push profile {0} to node {1}: {2}")] + PushProfile(String, String,deploy::push::PushProfileError), + #[error("No profile named `{0}` was found")] ProfileNotFound(String), #[error("No node named `{0}` was found")] @@ -398,15 +403,15 @@ pub enum RunDeployError { TomlFormat(#[from] toml::ser::Error), #[error("{0}")] PromptDeployment(#[from] PromptDeploymentError), - #[error("Failed to revoke profile for node {0}: {1}")] - RevokeProfile(String, deploy::deploy::RevokeProfileError), + #[error("Failed to revoke profile {0} for node {1}: {2}")] + RevokeProfile(String, String, deploy::deploy::RevokeProfileError), #[error("Deployment to node {0} failed, rolled back to previous generation")] - Rollback(String) + Rollback(String), } type ToDeploy<'a> = Vec<( &'a deploy::DeployFlake<'a>, - &'a deploy::data::Data, + deploy::data::Data, (&'a str, &'a deploy::data::Node), (&'a str, &'a deploy::data::Profile), )>; @@ -426,6 +431,7 @@ async fn run_deploy( boot: bool, log_dir: &Option, rollback_succeeded: bool, + mp: MultiProgress, ) -> Result<(), RunDeployError> { let to_deploy: ToDeploy = deploy_flakes .iter() @@ -444,7 +450,7 @@ async fn run_deploy( vec![( deploy_flake, - data, + data.clone(), (node_name.as_str(), node), (profile_name.as_str(), profile), )] @@ -477,7 +483,7 @@ async fn run_deploy( profiles_list .into_iter() - .map(|x| (deploy_flake, data, (node_name.as_str(), node), x)) + .map(|x| (deploy_flake, data.clone(), (node_name.as_str(), node), x)) .collect() } (None, None) => { @@ -508,7 +514,7 @@ async fn run_deploy( let ll: ToDeploy = profiles_list .into_iter() - .map(|x| (deploy_flake, data, (node_name.as_str(), node), x)) + .map(|x| (deploy_flake, data.clone(), (node_name.as_str(), node), x)) .collect(); l.extend(ll); @@ -535,12 +541,12 @@ async fn run_deploy( let deploy_data = deploy::make_deploy_data( &data.generic_settings, node, - node_name, + node_name.to_string(), profile, - profile_name, + profile_name.to_string(), cmd_overrides, debug_logs, - log_dir.as_deref(), + log_dir.clone(), ); let mut deploy_defs = deploy_data.defs()?; @@ -577,38 +583,158 @@ async fn run_deploy( |(deploy_flake, deploy_data, deploy_defs)| deploy::push::PushProfileData { supports_flakes, check_sigs, - repo: deploy_flake.repo, - deploy_data, - deploy_defs, + repo: deploy_flake.repo.to_string(), + deploy_data: deploy_data.clone(), + deploy_defs: deploy_defs.clone(), keep_result, - result_path, - extra_build_args, + result_path: result_path.map(str::to_string), + extra_build_args: extra_build_args.to_vec(), }, ) }; - for data in data_iter() { - let node_name: String = data.deploy_data.node_name.to_string(); - deploy::push::build_profile(data).await.map_err(|e| { - RunDeployError::BuildProfile(node_name, e) - })?; - } - for data in data_iter() { - let node_name: String = data.deploy_data.node_name.to_string(); - deploy::push::push_profile(data).await.map_err(|e| { - RunDeployError::PushProfile(node_name, e) - })?; - } + let (remote_builds, local_builds): (Vec<_>, Vec<_>) = data_iter().partition(|data| { + data.deploy_data + .merged_settings + .remote_build + .unwrap_or_default() + }); + + // the grouping by host will retain each hosts ordering by profiles_order since the fold is synchronous + let remote_build_map: HashMap<_, Vec<_>> = + remote_builds + .into_iter() + .fold(HashMap::new(), |mut accum, elem| { + match accum.get_mut(&elem.deploy_data.node_name) { + Some(v) => { + v.push(elem); + accum + } + None => { + accum.insert(elem.deploy_data.node_name.clone(), vec![elem]); + accum + } + } + }); + + // show progress information + let remote_mp = mp.clone(); + let spinner_style = ProgressStyle::with_template("{spinner:.blue} {prefix} {msg}").expect("invalid template").tick_strings(&["⢎ ", "⠎⠁", "⠊⠑", "⠈⠱", " ⡱", "⢀⡰", "⢄⡠", "⢆⡀"]); + let finish_style = || ProgressStyle::with_template("✅ {prefix} {msg}").expect("invalid template"); + let finish_style_error = || ProgressStyle::with_template("❌ {prefix} {msg}").expect("invalid template"); + let new_spinner = || ProgressBar::new_spinner().with_style(spinner_style.clone()); + + let (remote_results, local_results) = join!( + // remote builds can be run asynchronously + async move { + let mut set = JoinSet::new(); + + #[allow(clippy::iter_kv_map)] + for (_, profiles) in remote_build_map { + // spawn one future for each host + let pb = remote_mp.add(new_spinner()); + pb.enable_steady_tick(Duration::from_millis(80)); + + set.spawn(async move { + let mut res = Ok(()); + + // build profile in order, one after the other + for mut profile in profiles { + let nodename = profile.deploy_data.node_name.clone(); + let profilename = profile.deploy_data.profile_name.clone(); + pb.set_prefix(format!("Building profile '{}' on host '{}'", profilename, nodename)); + pb.set_message("..."); + profile.deploy_data.progressbar = Some(pb.clone()); + + info!("starting build of profile {} on node {}", profilename, nodename); + + res = deploy::push::build_profile(&profile).await.map_err(|e| { RunDeployError::BuildProfile(profilename.to_string(), nodename.to_string(), e) }); + if !res.is_ok() { + break; + } + } - let mut succeeded: Vec<(&deploy::DeployData, &deploy::DeployDefs)> = vec![]; + match res { + Ok(()) => { + pb.set_style(finish_style()); + pb.finish_with_message("Done!"); + }, + Err(ref e) => { + pb.set_style(finish_style_error()); + pb.finish_with_message(format!("Error: {}", e.to_string())) + } + } - // Run all deployments - // In case of an error rollback any previoulsy made deployment. + res + }); + } + + set.join_all().await + }, + + // run local builds synchronously to prevent hardware deadlocks + async move { + let mut set = JoinSet::new(); + + for mut data in local_builds.into_iter() { + let pb = mp.add(new_spinner()); + pb.enable_steady_tick(Duration::from_millis(80)); + + let node_name = data.deploy_data.node_name.to_string(); + let profile_name = data.deploy_data.profile_name.to_string(); + pb.set_prefix(format!("Building profile '{}' for host '{}'", profile_name, node_name)); + + let res = deploy::push::build_profile(&data) + .await + .map_err(|e| RunDeployError::BuildProfile(profile_name.clone(), node_name.clone(), e)); + + match res { + Ok(()) => { + data.deploy_data.progressbar = Some(pb.clone()); + set.spawn(async move { + let data = data.clone(); + pb.set_prefix(format!("Pushing profile '{}' to host '{}'", profile_name, node_name)); + let res = deploy::push::push_profile(&data).await.map_err(|e| RunDeployError::PushProfile(profile_name, node_name, e)); + match res { + Ok(()) => { + pb.set_style(finish_style()); + pb.finish_with_message("Done!"); + }, + Err(ref e) => { + pb.set_style(finish_style_error()); + pb.finish_with_message(format!("Error: {}", e.to_string())) + } + } + res + }); + }, + Err(ref e) => { + pb.set_style(finish_style_error()); + pb.finish_with_message(format!("Error: {}", e.to_string())); + // "spawn" a future that just returns the error when building locally fails + // this will ensure that the deployment is actually aborted in the error + // handling code below + set.spawn(async move { res }); + } + } + } + set.join_all().await + } + ); + + // abort here if any build + push or push + build failed + for result in remote_results { result? } + for result in local_results { result? } + + // Run all activations + // In case of an error, rollback any previoulsy made deployment. // Rollbacks adhere to the global seeting to auto_rollback and secondary // the profile's configuration + let mut succeeded: Vec<(&deploy::DeployData, &deploy::DeployDefs)> = vec![]; for (_, deploy_data, deploy_defs) in &parts { - if let Err(e) = deploy::deploy::deploy_profile(deploy_data, deploy_defs, dry_activate, boot).await + if let Err(e) = + deploy::deploy::deploy_profile(deploy_data, deploy_defs, dry_activate, boot).await { error!("{}", e); if dry_activate { @@ -621,14 +747,24 @@ async fn run_deploy( // the command line) for (deploy_data, deploy_defs) in &succeeded { if deploy_data.merged_settings.auto_rollback.unwrap_or(true) { - deploy::deploy::revoke(*deploy_data, *deploy_defs).await.map_err(|e| { - RunDeployError::RevokeProfile(deploy_data.node_name.to_string(), e) - })?; + deploy::deploy::revoke(*deploy_data, *deploy_defs) + .await + .map_err(|e| { + RunDeployError::RevokeProfile( + deploy_data.profile_name.to_string(), + deploy_data.node_name.to_string(), + e + ) + })?; } } return Err(RunDeployError::Rollback(deploy_data.node_name.to_string())); } - return Err(RunDeployError::DeployProfile(deploy_data.node_name.to_string(), e)) + return Err(RunDeployError::DeployProfile( + deploy_data.profile_name.to_string(), + deploy_data.node_name.to_string(), + e, + )); } succeeded.push((deploy_data, deploy_defs)) } @@ -662,7 +798,7 @@ pub async fn run(args: Option<&ArgMatches>) -> Result<(), RunError> { None => Opts::parse(), }; - deploy::init_logger( + let (mp, _handle) = deploy::init_logger( opts.debug_logs, opts.log_dir.as_deref(), &deploy::LoggerType::Deploy, @@ -696,7 +832,7 @@ pub async fn run(args: Option<&ArgMatches>) -> Result<(), RunError> { dry_activate: opts.dry_activate, remote_build: opts.remote_build, sudo: opts.sudo, - interactive_sudo: opts.interactive_sudo + interactive_sudo: opts.interactive_sudo, }; let supports_flakes = test_flake_support().await.map_err(RunError::FlakeTest)?; @@ -727,6 +863,7 @@ pub async fn run(args: Option<&ArgMatches>) -> Result<(), RunError> { opts.boot, &opts.log_dir, opts.rollback_succeeded.unwrap_or(true), + mp ) .await?; diff --git a/src/deploy.rs b/src/deploy.rs index 9f79d646..4431bc0b 100644 --- a/src/deploy.rs +++ b/src/deploy.rs @@ -270,7 +270,7 @@ pub enum ConfirmProfileError { } pub async fn confirm_profile( - deploy_data: &super::DeployData<'_>, + deploy_data: &super::DeployData, deploy_defs: &super::DeployDefs, temp_path: &Path, ssh_addr: &str, @@ -300,7 +300,7 @@ pub async fn confirm_profile( .arg(confirm_command) .spawn() .map_err(ConfirmProfileError::SSHConfirm)?; - + if deploy_data.merged_settings.interactive_sudo.unwrap_or(false) { trace!("[confirm] Piping in sudo password"); handle_sudo_stdin(&mut ssh_confirm_child, deploy_defs) @@ -311,7 +311,7 @@ pub async fn confirm_profile( let ssh_confirm_exit_status = ssh_confirm_child .wait() .await - .map_err(ConfirmProfileError::SSHConfirm)?; + .map_err(ConfirmProfileError::SSHConfirm)?; match ssh_confirm_exit_status.code() { Some(0) => (), @@ -348,7 +348,7 @@ pub enum DeployProfileError { } pub async fn deploy_profile( - deploy_data: &super::DeployData<'_>, + deploy_data: &super::DeployData, deploy_defs: &super::DeployDefs, dry_activate: bool, boot: bool, @@ -378,11 +378,11 @@ pub async fn deploy_profile( profile_info: &deploy_data.get_profile_info()?, closure: &deploy_data.profile.profile_settings.path, auto_rollback, - temp_path: temp_path, + temp_path, confirm_timeout, magic_rollback, debug_logs: deploy_data.debug_logs, - log_dir: deploy_data.log_dir, + log_dir: deploy_data.log_dir.as_deref(), dry_activate, boot, }); @@ -439,10 +439,10 @@ pub async fn deploy_profile( let self_wait_command = build_wait_command(&WaitCommandData { sudo: &deploy_defs.sudo, closure: &deploy_data.profile.profile_settings.path, - temp_path: temp_path, - activation_timeout: activation_timeout, + temp_path, + activation_timeout, debug_logs: deploy_data.debug_logs, - log_dir: deploy_data.log_dir, + log_dir: deploy_data.log_dir.as_deref(), }); debug!("Constructed wait command: {}", self_wait_command); @@ -465,7 +465,7 @@ pub async fn deploy_profile( ssh_wait_command .arg(&ssh_addr) .stdin(std::process::Stdio::piped()); - + for ssh_opt in &deploy_data.merged_settings.ssh_opts { ssh_wait_command.arg(ssh_opt); } @@ -545,7 +545,7 @@ pub enum RevokeProfileError { InvalidDeployDataDefs(#[from] DeployDataDefsError), } pub async fn revoke( - deploy_data: &crate::DeployData<'_>, + deploy_data: &crate::DeployData, deploy_defs: &crate::DeployDefs, ) -> Result<(), RevokeProfileError> { let self_revoke_command = build_revoke_command(&RevokeCommandData { @@ -553,7 +553,7 @@ pub async fn revoke( closure: &deploy_data.profile.profile_settings.path, profile_info: deploy_data.get_profile_info()?, debug_logs: deploy_data.debug_logs, - log_dir: deploy_data.log_dir, + log_dir: deploy_data.log_dir.as_deref(), }); debug!("Constructed revoke command: {}", self_revoke_command); diff --git a/src/lib.rs b/src/lib.rs index 61fac6a5..36a4c9c2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,6 +4,7 @@ // // SPDX-License-Identifier: MPL-2.0 +use indicatif::MultiProgress; use rnix::{types::*, SyntaxKind::*}; use merge::Merge; @@ -101,11 +102,63 @@ pub enum LoggerType { Revoke, } +use log::Log; + +pub struct LogWrapper { + bar: MultiProgress, + log: Box, +} + +impl LogWrapper { + pub fn new(bar: MultiProgress, log: Box) -> Self { + Self { bar, log } + } + + pub fn try_init(self) -> Result<(), log::SetLoggerError> { + use log::LevelFilter::*; + let levels = [Off, Error, Warn, Info, Debug, Trace]; + + for level_filter in levels.iter().rev() { + let level = if let Some(level) = level_filter.to_level() { + level + } else { + continue; + }; + let meta = log::Metadata::builder().level(level).build(); + if self.enabled(&meta) { + log::set_max_level(*level_filter); + break; + } + } + + log::set_boxed_logger(Box::new(self)) + } + pub fn multi(&self) -> MultiProgress { + self.bar.clone() + } +} + +impl Log for LogWrapper { + fn enabled(&self, metadata: &log::Metadata) -> bool { + self.log.enabled(metadata) + } + + fn log(&self, record: &log::Record) { + if self.log.enabled(record.metadata()) { + self.bar.suspend(|| self.log.log(record)) + } + } + + fn flush(&self) { + self.log.flush() + } +} + pub fn init_logger( debug_logs: bool, log_dir: Option<&str>, logger_type: &LoggerType, -) -> Result<(), FlexiLoggerError> { +) -> Result<(MultiProgress, ReconfigurationHandle), FlexiLoggerError> { let logger_formatter = match &logger_type { LoggerType::Deploy => logger_formatter_deploy, LoggerType::Activate => logger_formatter_activate, @@ -113,7 +166,7 @@ pub fn init_logger( LoggerType::Revoke => logger_formatter_revoke, }; - if let Some(log_dir) = log_dir { + let (logger, handle) = if let Some(log_dir) = log_dir { let mut logger = Logger::with_env_or_str("debug") .log_to_file() .format_for_stderr(logger_formatter) @@ -132,7 +185,7 @@ pub fn init_logger( LoggerType::Deploy => (), } - logger.start()?; + logger.build()? } else { Logger::with_env_or_str(match debug_logs { true => "debug", @@ -141,10 +194,13 @@ pub fn init_logger( .log_target(LogTarget::StdErr) .format(logger_formatter) .set_palette("196;208;51;7;8".to_string()) - .start()?; - } + .build()? + }; + + let multi = MultiProgress::new(); + LogWrapper::new(multi.clone(), logger).try_init().unwrap(); - Ok(()) + Ok((multi, handle)) } pub mod cli; @@ -152,7 +208,7 @@ pub mod data; pub mod deploy; pub mod push; -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct CmdOverrides { pub ssh_user: Option, pub profile_user: Option, @@ -316,21 +372,23 @@ fn test_parse_flake() { } #[derive(Debug, Clone)] -pub struct DeployData<'a> { - pub node_name: &'a str, - pub node: &'a data::Node, - pub profile_name: &'a str, - pub profile: &'a data::Profile, +pub struct DeployData { + pub node_name: String, + pub node: data::Node, + pub profile_name: String, + pub profile: data::Profile, - pub cmd_overrides: &'a CmdOverrides, + pub cmd_overrides: CmdOverrides, pub merged_settings: data::GenericSettings, pub debug_logs: bool, - pub log_dir: Option<&'a str>, + pub log_dir: Option, + + pub progressbar: Option, } -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct DeployDefs { pub ssh_user: String, pub profile_user: String, @@ -353,8 +411,8 @@ pub enum DeployDataDefsError { NoProfileUser(String, String), } -impl<'a> DeployData<'a> { - pub fn defs(&'a self) -> Result { +impl DeployData { + pub fn defs(&self) -> Result { let ssh_user = match self.merged_settings.ssh_user { Some(ref u) => u.clone(), None => whoami::username(), @@ -375,7 +433,7 @@ impl<'a> DeployData<'a> { }) } - fn get_profile_user(&'a self) -> Result { + fn get_profile_user(&self) -> Result { let profile_user = match self.merged_settings.user { Some(ref x) => x.clone(), None => match self.merged_settings.ssh_user { @@ -391,14 +449,14 @@ impl<'a> DeployData<'a> { Ok(profile_user) } - fn get_sudo(&'a self) -> String { + fn get_sudo(&self) -> String { match self.merged_settings.sudo { Some(ref x) => x.clone(), None => "sudo -u".to_string(), } } - fn get_profile_info(&'a self) -> Result { + fn get_profile_info(&self) -> Result { match self.profile.profile_settings.profile_path { Some(ref profile_path) => Ok(ProfileInfo::ProfilePath { profile_path: profile_path.to_string() }), None => { @@ -409,16 +467,16 @@ impl<'a> DeployData<'a> { } } -pub fn make_deploy_data<'a, 's>( - top_settings: &'s data::GenericSettings, - node: &'a data::Node, - node_name: &'a str, - profile: &'a data::Profile, - profile_name: &'a str, - cmd_overrides: &'a CmdOverrides, +pub fn make_deploy_data( + top_settings: &data::GenericSettings, + node: &data::Node, + node_name: String, + profile: &data::Profile, + profile_name: String, + cmd_overrides: &CmdOverrides, debug_logs: bool, - log_dir: Option<&'a str>, -) -> DeployData<'a> { + log_dir: Option, +) -> DeployData { let mut merged_settings = profile.generic_settings.clone(); merged_settings.merge(node.generic_settings.clone()); merged_settings.merge(top_settings.clone()); @@ -457,12 +515,13 @@ pub fn make_deploy_data<'a, 's>( DeployData { node_name, - node, + node: node.clone(), profile_name, - profile, - cmd_overrides, + profile: profile.clone(), + cmd_overrides: cmd_overrides.clone(), merged_settings, debug_logs, log_dir, + progressbar: None } } diff --git a/src/push.rs b/src/push.rs index 864c3369..821ab113 100644 --- a/src/push.rs +++ b/src/push.rs @@ -2,12 +2,18 @@ // // SPDX-License-Identifier: MPL-2.0 +use indicatif::ProgressBar; use log::{debug, info}; +use tokio::process::Child; +use tokio_stream::wrappers::LinesStream; use std::collections::HashMap; use std::path::Path; use std::process::Stdio; use thiserror::Error; use tokio::process::Command; +use tokio::io::AsyncBufReadExt; +use tokio::io::BufReader; +use tokio_stream::StreamExt; #[derive(Error, Debug)] pub enum PushProfileError { @@ -48,18 +54,19 @@ pub enum PushProfileError { PathInfo(std::io::Error), } -pub struct PushProfileData<'a> { +#[derive(Clone)] +pub struct PushProfileData { pub supports_flakes: bool, pub check_sigs: bool, - pub repo: &'a str, - pub deploy_data: &'a super::DeployData<'a>, - pub deploy_defs: &'a super::DeployDefs, + pub repo: String, + pub deploy_data: super::DeployData, + pub deploy_defs: super::DeployDefs, pub keep_result: bool, - pub result_path: Option<&'a str>, - pub extra_build_args: &'a [String], + pub result_path: Option, + pub extra_build_args: Vec, } -pub async fn build_profile_locally(data: &PushProfileData<'_>, derivation_name: &str) -> Result<(), PushProfileError> { +pub async fn build_profile_locally(data: &PushProfileData, derivation_name: &str) -> Result<(), PushProfileError> { info!( "Building profile `{}` for node `{}`", data.deploy_data.profile_name, data.deploy_data.node_name @@ -79,7 +86,7 @@ pub async fn build_profile_locally(data: &PushProfileData<'_>, derivation_name: match (data.keep_result, data.supports_flakes) { (true, _) => { - let result_path = data.result_path.unwrap_or("./.deploy-gc"); + let result_path = data.result_path.clone().unwrap_or("./.deploy-gc".to_string()); build_command.arg("--out-link").arg(format!( "{}/{}/{}", @@ -90,7 +97,7 @@ pub async fn build_profile_locally(data: &PushProfileData<'_>, derivation_name: (false, true) => build_command.arg("--no-link"), }; - build_command.args(data.extra_build_args); + build_command.args(data.extra_build_args.clone()); let build_exit_status = build_command // Logging should be in stderr, this just stops the store path from printing for no reason @@ -152,7 +159,20 @@ pub async fn build_profile_locally(data: &PushProfileData<'_>, derivation_name: Ok(()) } -pub async fn build_profile_remotely(data: &PushProfileData<'_>, derivation_name: &str) -> Result<(), PushProfileError> { +async fn update_pb_with_child_output(pb: &ProgressBar, child: &mut Child) { + let stdout = child.stdout.take().expect("child did not have a stdout handle"); + let stderr = child.stderr.take().expect("child did not have a stderr handle"); + + let stdout = LinesStream::new(BufReader::new(stdout).lines()); + let stderr = LinesStream::new(BufReader::new(stderr).lines()); + let mut merged = StreamExt::merge(stdout, stderr); + + while let Some(line) = merged.next().await { + pb.set_message(line.expect("expected a valid line")); + } +} + +pub async fn build_profile_remotely(data: &PushProfileData, derivation_name: &str) -> Result<(), PushProfileError> { info!( "Building profile `{}` for node `{}` on remote host", data.deploy_data.profile_name, data.deploy_data.node_name @@ -169,48 +189,63 @@ pub async fn build_profile_remotely(data: &PushProfileData<'_>, derivation_name: // copy the derivation to remote host so it can be built there - let copy_command_status = Command::new("nix").arg("copy") - .arg("-s") // fetch dependencies from substitures, not localhost - .arg("--to").arg(&store_address) - .arg("--derivation").arg(derivation_name) - .env("NIX_SSHOPTS", ssh_opts_str.clone()) - .stdout(Stdio::null()) - .status() - .await - .map_err(PushProfileError::Copy)?; + let copy_command_status = { + let mut copy_command = Command::new("nix"); + copy_command.arg("copy") + .arg("-s") // fetch dependencies from substitures, not localhost + .arg("--to").arg(&store_address) + .arg("--derivation").arg(derivation_name) + .env("NIX_SSHOPTS", ssh_opts_str.clone()); + + debug!("copy command: {:?}", copy_command); + + let mut child = copy_command.stderr(Stdio::piped()).stdout(Stdio::piped()).spawn().expect("failed to spawn nix copy command"); + + if let Some(pb) = &data.deploy_data.progressbar { + update_pb_with_child_output(pb, &mut child).await; + } + + child.wait().await.map_err(PushProfileError::Copy)? + }; match copy_command_status.code() { Some(0) => (), a => return Err(PushProfileError::CopyExit(a)), }; - let mut build_command = Command::new("nix"); - build_command - .arg("build").arg(derivation_name) - .arg("--eval-store").arg("auto") - .arg("--store").arg(&store_address) - .args(data.extra_build_args) - .env("NIX_SSHOPTS", ssh_opts_str.clone()); - - debug!("build command: {:?}", build_command); + let build_exit_status = { + let mut build_command = Command::new("nix"); + build_command + .arg("build").arg(derivation_name) + .arg("--eval-store").arg("auto") + .arg("--store").arg(&store_address) + .args(data.extra_build_args.clone()) + .env("NIX_SSHOPTS", ssh_opts_str.clone()); + + debug!("build command: {:?}", build_command); + + let mut child = build_command + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn() + .expect("failed to spawn nix build command"); + + if let Some(pb) = &data.deploy_data.progressbar { + update_pb_with_child_output(pb, &mut child).await; + } - let build_exit_status = build_command - // Logging should be in stderr, this just stops the store path from printing for no reason - .stdout(Stdio::null()) - .status() - .await - .map_err(PushProfileError::Build)?; + child.wait().await.map_err(PushProfileError::Build)? + }; match build_exit_status.code() { Some(0) => (), a => return Err(PushProfileError::BuildExit(a)), }; - Ok(()) } -pub async fn build_profile(data: PushProfileData<'_>) -> Result<(), PushProfileError> { +pub async fn build_profile(data: &PushProfileData) -> Result<(), PushProfileError> { debug!( "Finding the deriver of store path for {}", &data.deploy_data.profile.profile_settings.path @@ -287,7 +322,7 @@ pub async fn build_profile(data: PushProfileData<'_>) -> Result<(), PushProfileE Ok(()) } -pub async fn push_profile(data: PushProfileData<'_>) -> Result<(), PushProfileError> { +pub async fn push_profile(data: &PushProfileData) -> Result<(), PushProfileError> { let ssh_opts_str = data .deploy_data .merged_settings