diff --git a/.env b/.env deleted file mode 100644 index b1ec0a0d..00000000 --- a/.env +++ /dev/null @@ -1 +0,0 @@ -DATABASE_URL=postgres://pgdev:password@localhost/butido diff --git a/.github/workflows/cargo.yml b/.github/workflows/cargo.yml index e228f02a..21108a5f 100644 --- a/.github/workflows/cargo.yml +++ b/.github/workflows/cargo.yml @@ -14,7 +14,7 @@ jobs: - name: Install toolchain uses: dtolnay/rust-toolchain@v1 with: - toolchain: 1.74.0 # MSRV + toolchain: 1.85.0 # MSRV components: rustfmt - name: Run cargo fmt @@ -26,7 +26,7 @@ jobs: strategy: matrix: rust: - - 1.74.0 # MSRV + - 1.85.0 # MSRV - stable - beta @@ -53,7 +53,7 @@ jobs: strategy: matrix: rust: - - 1.74.0 # MSRV + - 1.85.0 # MSRV - stable - beta steps: @@ -81,12 +81,13 @@ jobs: - advisories - bans licenses sources - # Prevent sudden announcement of a new advisory from failing ci: + # Prevent sudden announcement of a new advisory from failing CI: continue-on-error: ${{ matrix.checks == 'advisories' }} steps: - uses: actions/checkout@v4 - - uses: EmbarkStudios/cargo-deny-action@v1 + # https://github.com/EmbarkStudios/cargo-deny-action: + - uses: EmbarkStudios/cargo-deny-action@v2 with: command: check ${{ matrix.checks }} @@ -99,9 +100,8 @@ jobs: fail-fast: false matrix: include: - - rust: 1.74.0 # MSRV + - rust: 1.85.0 # MSRV optional: false - extra_opts: "-A unknown-lints" - rust: beta optional: true steps: @@ -114,7 +114,7 @@ jobs: with: shared-key: "ci" - name: cargo clippy - run: cargo clippy --all-targets -- -D warnings ${{ matrix.extra_opts }} + run: cargo clippy --all-targets -- -D warnings # This "accumulation" job is used as the required CI check for PRs. # We could require multiple jobs but the MSRV is subject to change and makes diff --git a/CHANGELOG.md b/CHANGELOG.md index 11f44f59..6129a7bb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,9 +8,52 @@ is too old (i.e., must be updated). ## Upcoming +## v0.5.0 + ### Major/Breaking changes * The configuration must be updated, see `CHANGELOG.toml` for details. + * The `compatibility` setting is now independent of the butido version and + will be used to output the required configuration changes +* Butido now rejects unknown fields in its configuration source(s) + (`config.toml` files and environment variables) and package definitions + (`pkg.toml` files). This is done to detect typos/mistakes and requires + changes if such unknown/misspelled fields are present. +* The "merging" of `patches` arrays in `pkg.toml` files has been fixed (the old + logic silently ignored a patch if a patch with the same name was declared in + an upper level/layer `pkg.toml` file) + +### Highlights + +* Lots of dependency updates (including security updates) + * All dependencies are up-to-date now (except for the `config` crate due to + breaking changes in `0.14.x` that currently prevent us from updating) + * Some deprecated/unmaintained crates have been replaced or dropped +* CLI improvements and fixes (partly due to the clap update from version 3 to 4) + * Better / more consistent argument parsing + * Output tweaks/improvements + * Improved error messages (e.g., for dependency cycles and unsupported + version specifications) + * Support for short build image names + * More filtering options for DB commands + * Build dependencies are now marked with a star (`*`) in the `tree-of` output + * A new "release list" subcommand ("db releases" alias for convenience) +* Fixed the default log level (info -> warn) +* Source download improvements (warnings when downloading HTML files, no more + empty source files after failed downloads, a check for the HTTP status code + to avoid downloading "404 Not Found" HTML responses, and the User-Agent + header is now set) +* The included example configuration and packages repo have been fixed (and new + CI tests should ensure that they remain valid) +* It is now possible to depend on packages whose name starts with a number +* The tracing has been improved (some spans and structured fields are now used) + and the `--tracing-chrome` flag has been added to generate `chrome://tracing` + compatible JSON files +* Butido no longer crashes when `..` is used in paths of `patches` (`pkg.toml`) +* Lots of cleanups, refactorings, new tests (+ CI improvements), and minor fixes +* Various minor improvements (additional directory/filesystem checks, handling + of multiple release stores, etc.) +* Minor documentation improvements (typos, fixes, additional comments, etc.) ## v0.4.0 diff --git a/Cargo.lock b/Cargo.lock index fd8efe1a..fa9494cc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,24 +4,24 @@ version = 3 [[package]] name = "addr2line" -version = "0.21.0" +version = "0.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" dependencies = [ "gimli", ] [[package]] -name = "adler" -version = "1.0.2" +name = "adler2" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" [[package]] name = "aho-corasick" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" dependencies = [ "memchr", ] @@ -43,108 +43,110 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.13" +version = "0.6.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d96bd03f33fe50a863e394ee9718a706f988b9079b20c3784fb726e7678b62fb" +checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" dependencies = [ "anstyle", "anstyle-parse", "anstyle-query", "anstyle-wincon", "colorchoice", + "is_terminal_polyfill", "utf8parse", ] [[package]] name = "anstyle" -version = "1.0.6" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc" +checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" [[package]] name = "anstyle-parse" -version = "0.2.3" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c" +checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.0.2" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648" +checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] name = "anstyle-wincon" -version = "3.0.2" +version = "3.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" +checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e" dependencies = [ "anstyle", - "windows-sys 0.52.0", + "once_cell", + "windows-sys 0.59.0", ] [[package]] name = "anyhow" -version = "1.0.80" +version = "1.0.97" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ad32ce52e4161730f7098c077cd2ed6229b5804ccf99e5366be1ab72a98b4e1" +checksum = "dcfed56ad506cb2c684a14971b8861fdc3baaaae314b9e5f9bb532cbe3ba7a4f" [[package]] name = "aquamarine" -version = "0.5.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21cc1548309245035eb18aa7f0967da6bc65587005170c56e6ef2788a4cf3f4e" +checksum = "0f50776554130342de4836ba542aa85a4ddb361690d7e8df13774d7284c3d5c2" dependencies = [ "include_dir", "itertools 0.10.5", - "proc-macro-error", + "proc-macro-error2", "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.99", ] [[package]] name = "ascii_table" -version = "4.0.3" +version = "4.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c2bee9b9ee0e5768772e38c07ef0ba23a490d7e1336ec7207c25712a2661c55" +checksum = "8bc070afe45e3d9e628b82d77d61c90531dc6997b341d57715b82461af415669" +dependencies = [ + "lazy_static", + "regex", + "unicode-width", +] [[package]] -name = "async-trait" -version = "0.1.77" +name = "atomic-waker" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c980ee35e870bd1a4d2c8294d4c04d0499e67bca1e4b5cefcc693c2fa00caea9" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.52", -] +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] name = "autocfg" -version = "1.1.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "backtrace" -version = "0.3.69" +version = "0.3.74" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" +checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" dependencies = [ "addr2line", - "cc", "cfg-if", "libc", "miniz_oxide", "object", "rustc-demangle", + "windows-targets", ] [[package]] @@ -155,9 +157,9 @@ checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" [[package]] name = "base64" -version = "0.21.7" +version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "bincode" @@ -176,9 +178,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.4.2" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" +checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" [[package]] name = "block-buffer" @@ -191,24 +193,24 @@ dependencies = [ [[package]] name = "bstr" -version = "1.9.1" +version = "1.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05efc5cfd9110c8416e471df0e96702d58690178e206e61b7173706673c93706" +checksum = "531a9155a481e2ee699d4f98f43c0ca4ff8ee1bfd55c31e9e98fb29d2b176fe0" dependencies = [ "memchr", - "regex-automata 0.4.5", + "regex-automata 0.4.9", "serde", ] [[package]] name = "bumpalo" -version = "3.15.3" +version = "3.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ea184aa71bb362a1157c896979544cc23974e08fd265f29ea96b59f0b4a555b" +checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" [[package]] name = "butido" -version = "0.4.0" +version = "0.5.0" dependencies = [ "anyhow", "aquamarine", @@ -220,7 +222,6 @@ dependencies = [ "colored", "config", "csv", - "daggy", "dialoguer", "diesel", "diesel_migrations", @@ -234,9 +235,11 @@ dependencies = [ "humantime", "indicatif", "indoc", - "itertools 0.12.1", + "itertools 0.14.0", "lazy_static", + "once_cell", "parse-display", + "petgraph", "pom", "ptree", "rand", @@ -244,9 +247,9 @@ dependencies = [ "regex", "reqwest", "resiter", - "result-inspect", "rlimit", "rustversion", + "semver", "serde", "serde_json", "sha1", @@ -257,8 +260,10 @@ dependencies = [ "terminal_size", "tokio", "tokio-stream", - "toml 0.8.10", + "tokio-util", + "toml", "tracing", + "tracing-chrome", "tracing-subscriber", "typed-builder", "unindent", @@ -284,30 +289,30 @@ checksum = "0e4cec68f03f32e44924783795810fa50a7035d8c8ebe78580ad7e6c703fba38" [[package]] name = "bytes" -version = "1.5.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" +checksum = "f61dac84819c6588b558454b194026eb1f09c293b9036ae9b159e74e73ab6cf9" [[package]] name = "bytesize" -version = "1.3.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3e368af43e418a04d52505cf3dbc23dda4e3407ae2fa99fd0e4f308ce546acc" +checksum = "a3c8f83209414aacf0eeae3cf730b18d6981697fba62f200fcfb92b9f082acba" [[package]] name = "camino" -version = "1.1.6" +version = "1.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c59e92b5a388f549b863a7bea62612c09f24c8393560709a54558a9abdfb3b9c" +checksum = "8b96ec4966b5813e2c0507c1f86115c8c5abaadc3980879c3424042a02fd1ad3" dependencies = [ "serde", ] [[package]] name = "cargo-platform" -version = "0.1.7" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "694c8807f2ae16faecc43dc17d74b3eb042482789fd0eb64b39a2e04e087053f" +checksum = "e35af189006b9c0f00a064685c727031e3ed2d8020f7ba284d78cc2671bd36ea" dependencies = [ "serde", ] @@ -323,16 +328,18 @@ dependencies = [ "semver", "serde", "serde_json", - "thiserror", + "thiserror 1.0.69", ] [[package]] name = "cc" -version = "1.0.88" +version = "1.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02f341c093d19155a6e41631ce5971aac4e9a868262212153124c15fa22d1cdc" +checksum = "be714c154be609ec7f5dad223a33bf1482fff90472de28f7362806e6d4832b8c" dependencies = [ + "jobserver", "libc", + "shlex", ] [[package]] @@ -343,9 +350,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.34" +version = "0.4.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bc015644b92d5890fab7489e49d21f879d5c990186827d42ec511919404f38b" +checksum = "1a7964611d71df112cb1730f2ee67324fcf4d0fc6606acbbe9bfe06df124637c" dependencies = [ "android-tzdata", "iana-time-zone", @@ -353,23 +360,23 @@ dependencies = [ "num-traits", "serde", "wasm-bindgen", - "windows-targets 0.52.4", + "windows-link", ] [[package]] name = "clap" -version = "4.5.1" +version = "4.5.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c918d541ef2913577a0f9566e9ce27cb35b6df072075769e0b26cb5a554520da" +checksum = "027bb0d98429ae334a8698531da7077bdf906419543a35a55c2cb1b66437d767" dependencies = [ "clap_builder", ] [[package]] name = "clap_builder" -version = "4.5.1" +version = "4.5.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f3e7391dad68afb0c2ede1bf619f579a3dc9c2ec67f089baa397123a2f3d1eb" +checksum = "5589e0cba072e0f3d23791efac0fd8627b49c829c196a492e88168e6a669d863" dependencies = [ "anstream", "anstyle", @@ -379,60 +386,57 @@ dependencies = [ [[package]] name = "clap_complete" -version = "4.5.1" +version = "4.5.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "885e4d7d5af40bfb99ae6f9433e292feac98d452dcb3ec3d25dfe7552b77da8c" +checksum = "f5c5508ea23c5366f77e53f5a0070e5a84e51687ec3ef9e0464c86dc8d13ce98" dependencies = [ "clap", ] [[package]] name = "clap_lex" -version = "0.7.0" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" +checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" [[package]] name = "colorchoice" -version = "1.0.0" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" +checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" [[package]] name = "colored" -version = "2.1.0" +version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbf2150cce219b664a8a70df7a1f933836724b503f8a413af9365b4dcc4d90b8" +checksum = "fde0e0ec90c9dfb3b4b1a0891a7dcd0e2bffde2f7efed5fe7c9bb00e5bfb915e" dependencies = [ - "lazy_static", - "windows-sys 0.48.0", + "windows-sys 0.59.0", ] [[package]] name = "config" -version = "0.13.4" +version = "0.15.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23738e11972c7643e4ec947840fc463b6a571afcd3e735bdfce7d03c7a784aca" +checksum = "8cf9dc8d4ef88e27a8cb23e85cb116403dedd57f7971964dc4b18ccead548901" dependencies = [ - "async-trait", - "lazy_static", - "nom", "pathdiff", "serde", - "toml 0.5.11", + "toml", + "winnow", ] [[package]] name = "console" -version = "0.15.8" +version = "0.15.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e1f83fc076bd6dd27517eacdf25fef6c4dfe5f1d7448bafaaf3a26f13b5e4eb" +checksum = "054ccb5b10f9f2cbf51eb355ca1d05c2d279ce1804688d0db74b4733a5aeafd8" dependencies = [ "encode_unicode", - "lazy_static", "libc", + "once_cell", "unicode-width", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -447,33 +451,33 @@ dependencies = [ [[package]] name = "core-foundation-sys" -version = "0.8.6" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "cpufeatures" -version = "0.2.12" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" dependencies = [ "libc", ] [[package]] name = "crc32fast" -version = "1.4.0" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3855a8a784b474f333699ef2bbca9db2c4a1f6d9088a90a2d25b1eb53111eaa" +checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" dependencies = [ "cfg-if", ] [[package]] name = "crossbeam-deque" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" dependencies = [ "crossbeam-epoch", "crossbeam-utils", @@ -490,9 +494,9 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.19" +version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" [[package]] name = "crypto-common" @@ -506,9 +510,9 @@ dependencies = [ [[package]] name = "csv" -version = "1.3.0" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac574ff4d437a7b5ad237ef331c17ccca63c46479e5b5453eb8e10bb99a759fe" +checksum = "acdc4883a9c96732e4733212c01447ebd805833b7275a73ca3ee080fd77afdaf" dependencies = [ "csv-core", "itoa", @@ -518,21 +522,46 @@ dependencies = [ [[package]] name = "csv-core" -version = "0.1.11" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5efa2b3d7902f4b634a20cae3c9c4e6209dc4779feb6863329607560143efa70" +checksum = "7d02f3b0da4c6504f86e9cd789d8dbafab48c2321be74e9987593de5a894d93d" dependencies = [ "memchr", ] [[package]] -name = "daggy" -version = "0.8.0" +name = "darling" +version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91a9304e55e9d601a39ae4deaba85406d5c0980e106f65afcf0460e9af1e7602" +checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" dependencies = [ - "petgraph", - "serde", + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.99", +] + +[[package]] +name = "darling_macro" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" +dependencies = [ + "darling_core", + "quote", + "syn 2.0.99", ] [[package]] @@ -544,6 +573,37 @@ dependencies = [ "powerfmt", ] +[[package]] +name = "derive_builder" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "507dfb09ea8b7fa618fcf76e953f4f5e192547945816d5358edffe39f6f94947" +dependencies = [ + "derive_builder_macro", +] + +[[package]] +name = "derive_builder_core" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d5bcf7b024d6835cfb3d473887cd966994907effbe9227e8c8219824d06c4e8" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 2.0.99", +] + +[[package]] +name = "derive_builder_macro" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" +dependencies = [ + "derive_builder_core", + "syn 2.0.99", +] + [[package]] name = "dialoguer" version = "0.11.0" @@ -553,17 +613,17 @@ dependencies = [ "console", "shell-words", "tempfile", - "thiserror", + "thiserror 1.0.69", "zeroize", ] [[package]] name = "diesel" -version = "2.1.4" +version = "2.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62c6fcf842f17f8c78ecf7c81d75c5ce84436b41ee07e03f490fbb5f5a8731d8" +checksum = "04001f23ba8843dc315804fa324000376084dfb1c30794ff68dd279e6e5696d5" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.9.0", "byteorder", "chrono", "diesel_derives", @@ -576,21 +636,22 @@ dependencies = [ [[package]] name = "diesel_derives" -version = "2.1.2" +version = "2.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef8337737574f55a468005a83499da720f20c65586241ffea339db9ecdfd2b44" +checksum = "e7f2c3de51e2ba6bf2a648285696137aaf0f5f487bcbea93972fe8a364e131a4" dependencies = [ "diesel_table_macro_syntax", + "dsl_auto_type", "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.99", ] [[package]] name = "diesel_migrations" -version = "2.1.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6036b3f0120c5961381b570ee20a02432d7e2d27ea60de9578799cf9156914ac" +checksum = "8a73ce704bad4231f001bff3314d91dce4aba0770cee8b233991859abc15c1f6" dependencies = [ "diesel", "migrations_internals", @@ -599,11 +660,11 @@ dependencies = [ [[package]] name = "diesel_table_macro_syntax" -version = "0.1.0" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc5557efc453706fed5e4fa85006fe9817c224c3f480a34c7e5959fd700921c5" +checksum = "209c735641a413bc68c4923a9d6ad4bcb3ca306b794edaa7eb0b3228a99ffb25" dependencies = [ - "syn 2.0.52", + "syn 2.0.99", ] [[package]] @@ -616,59 +677,90 @@ dependencies = [ "crypto-common", ] +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.99", +] + +[[package]] +name = "dsl_auto_type" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "139ae9aca7527f85f26dd76483eb38533fd84bd571065da1739656ef71c5ff5b" +dependencies = [ + "darling", + "either", + "heck", + "proc-macro2", + "quote", + "syn 2.0.99", +] + [[package]] name = "either" -version = "1.10.0" +version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a" +checksum = "b7914353092ddf589ad78f25c5c1c21b7f80b0ff8621e7c814c3485b5306da9d" [[package]] name = "encode_unicode" -version = "0.3.6" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" +checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" [[package]] name = "encoding_rs" -version = "0.8.33" +version = "0.8.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" dependencies = [ "cfg-if", ] +[[package]] +name = "env_home" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7f84e12ccf0a7ddc17a6c41c93326024c42920d7ee630d04950e6926645c0fe" + [[package]] name = "equivalent" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "errno" -version = "0.3.8" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" +checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] name = "fastrand" -version = "2.0.1" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] name = "filetime" -version = "0.2.23" +version = "0.2.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ee447700ac8aa0b2f2bd7bc4462ad686ba06baa6727ac149a2d6277f0d240fd" +checksum = "35c0522e981e68cbfa8c3f978441a5f34b30b96e146b33cd3359176b50fe8586" dependencies = [ "cfg-if", "libc", - "redox_syscall", - "windows-sys 0.52.0", + "libredox", + "windows-sys 0.59.0", ] [[package]] @@ -679,15 +771,15 @@ checksum = "0ea2a3582a3bb84139554126bc608bf3e2a1ced8354dbdef38c41140ca10b3e3" [[package]] name = "fixedbitset" -version = "0.4.2" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" +checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" [[package]] name = "flate2" -version = "1.0.28" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e" +checksum = "11faaf5a5236997af9848be0bef4db95824b1d534ebc64d0f0c6cf3e67bd38dc" dependencies = [ "crc32fast", "miniz_oxide", @@ -725,9 +817,9 @@ dependencies = [ [[package]] name = "futures" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" dependencies = [ "futures-channel", "futures-core", @@ -740,9 +832,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" dependencies = [ "futures-core", "futures-sink", @@ -750,15 +842,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" [[package]] name = "futures-executor" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" dependencies = [ "futures-core", "futures-task", @@ -767,38 +859,38 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" [[package]] name = "futures-macro" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.99", ] [[package]] name = "futures-sink" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" [[package]] name = "futures-task" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" [[package]] name = "futures-util" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ "futures-channel", "futures-core", @@ -836,40 +928,52 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.12" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", "libc", - "wasi", + "wasi 0.11.0+wasi-snapshot-preview1", +] + +[[package]] +name = "getrandom" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a49c392881ce6d5c3b8cb70f98717b7c07aabbdff06687b9030dbfbe2725f8" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.13.3+wasi-0.2.2", + "windows-targets", ] [[package]] name = "getset" -version = "0.1.2" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e45727250e75cc04ff2846a66397da8ef2b3db8e40e0cef4df67950a07621eb9" +checksum = "f3586f256131df87204eb733da72e3d3eb4f343c639f4b7be279ac7c48baeafe" dependencies = [ - "proc-macro-error", + "proc-macro-error2", "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.99", ] [[package]] name = "gimli" -version = "0.28.1" +version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" [[package]] name = "git2" -version = "0.18.2" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b3ba52851e73b46a4c3df1d89343741112003f0f6f13beb0dfac9e457c3fdcd" +checksum = "3fda788993cc341f69012feba8bf45c0ba4f3291fcc08e214b4d5a7332d88aff" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.9.0", "libc", "libgit2-sys", "log", @@ -880,22 +984,22 @@ dependencies = [ [[package]] name = "git_info" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "641b847f0375f4b2c595438eefc17a9c0fbf47b400cbdd1ad9332bf1e16b779d" +checksum = "9525474359e360de19580a77590f3491c94d57b1ef2b8bd468877a83cbc7f4cb" [[package]] name = "h2" -version = "0.3.24" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb2c4422095b67ee78da96fbb51a4cc413b3b25883c7717ff7ca1ab31022c9c9" +checksum = "5017294ff4bb30944501348f6f8e42e6ad28f42c8bbef7a74029aff064a4e3c2" dependencies = [ - "bytes 1.5.0", + "atomic-waker", + "bytes 1.10.0", "fnv", "futures-core", "futures-sink", - "futures-util", - "http", + "http 1.2.0", "indexmap", "slab", "tokio", @@ -905,29 +1009,31 @@ dependencies = [ [[package]] name = "handlebars" -version = "5.1.0" +version = "6.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab283476b99e66691dee3f1640fea91487a8d81f50fb5ecc75538f8f8879a1e4" +checksum = "d752747ddabc4c1a70dd28e72f2e3c218a816773e0d7faf67433f1acfa6cba7c" dependencies = [ + "derive_builder", "log", + "num-order", "pest", "pest_derive", "serde", "serde_json", - "thiserror", + "thiserror 2.0.12", ] [[package]] name = "hashbrown" -version = "0.14.3" +version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" [[package]] -name = "hermit-abi" -version = "0.3.9" +name = "heck" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "hex" @@ -936,21 +1042,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] -name = "home" -version = "0.5.9" +name = "http" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" dependencies = [ - "windows-sys 0.52.0", + "bytes 1.10.0", + "fnv", + "itoa", ] [[package]] name = "http" -version = "0.2.11" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8947b1a6fad4393052c7ba1f4cd97bed3e953a95c79c92ad9b051a04611d9fbb" +checksum = "f16ca2af56261c99fba8bac40a10251ce8188205a4c448fbb745a2e4daa76fea" dependencies = [ - "bytes 1.5.0", + "bytes 1.10.0", "fnv", "itoa", ] @@ -961,16 +1069,39 @@ version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" dependencies = [ - "bytes 1.5.0", - "http", + "bytes 1.10.0", + "http 0.2.12", + "pin-project-lite", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes 1.10.0", + "http 1.2.0", +] + +[[package]] +name = "http-body-util" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" +dependencies = [ + "bytes 1.10.0", + "futures-util", + "http 1.2.0", + "http-body 1.0.1", "pin-project-lite", ] [[package]] name = "httparse" -version = "1.8.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" +checksum = "f2d708df4e7140240a16cd6ab0ab65c972d7433ab77819ea693fde9c43811e2a" [[package]] name = "httpdate" @@ -980,9 +1111,9 @@ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "human-panic" -version = "1.2.3" +version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4f016c89920bbb30951a8405ecacbb4540db5524313b9445736e7e1855cf370" +checksum = "80b84a66a325082740043a6c28bbea400c129eac0d3a27673a1de971e44bf1f7" dependencies = [ "anstream", "anstyle", @@ -990,7 +1121,7 @@ dependencies = [ "os_info", "serde", "serde_derive", - "toml 0.8.10", + "toml", "uuid", ] @@ -1002,17 +1133,16 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] name = "hyper" -version = "0.14.28" +version = "0.14.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf96e135eb83a2a8ddf766e426a841d8ddd7449d5f00d34ea02b41d2f19eef80" +checksum = "41dfc780fdec9373c01bae43289ea34c972e40ee3c9f6b3c8801a35f35586ce7" dependencies = [ - "bytes 1.5.0", + "bytes 1.10.0", "futures-channel", "futures-core", "futures-util", - "h2", - "http", - "http-body", + "http 0.2.12", + "http-body 0.4.6", "httparse", "httpdate", "itoa", @@ -1024,14 +1154,34 @@ dependencies = [ "want", ] +[[package]] +name = "hyper" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80" +dependencies = [ + "bytes 1.10.0", + "futures-channel", + "futures-util", + "h2", + "http 1.2.0", + "http-body 1.0.1", + "httparse", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + [[package]] name = "hyper-openssl" version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6ee5d7a8f718585d1c3c61dfde28ef5b0bb14734b4db13f5ada856cdc6c612b" dependencies = [ - "http", - "hyper", + "http 0.2.12", + "hyper 0.14.32", "linked_hash_set", "once_cell", "openssl", @@ -1042,17 +1192,56 @@ dependencies = [ "tower-layer", ] +[[package]] +name = "hyper-rustls" +version = "0.27.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d191583f3da1305256f22463b9bb0471acad48a4e534a5218b9963e9c1f59b2" +dependencies = [ + "futures-util", + "http 1.2.0", + "hyper 1.6.0", + "hyper-util", + "rustls", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tower-service", +] + [[package]] name = "hyper-tls" -version = "0.5.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" dependencies = [ - "bytes 1.5.0", - "hyper", + "bytes 1.10.0", + "http-body-util", + "hyper 1.6.0", + "hyper-util", "native-tls", "tokio", "tokio-native-tls", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df2dcfbe0677734ab2f3ffa7fa7bfd4706bfdc1ef393f2ee30184aed67e631b4" +dependencies = [ + "bytes 1.10.0", + "futures-channel", + "futures-util", + "http 1.2.0", + "http-body 1.0.1", + "hyper 1.6.0", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", ] [[package]] @@ -1063,16 +1252,16 @@ checksum = "0fafdf7b2b2de7c9784f76e02c0935e65a8117ec3b768644379983ab333ac98c" dependencies = [ "futures-util", "hex", - "hyper", - "pin-project 1.1.4", + "hyper 0.14.32", + "pin-project 1.1.9", "tokio", ] [[package]] name = "iana-time-zone" -version = "0.1.60" +version = "0.1.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141" +checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" dependencies = [ "android_system_properties", "core-foundation-sys", @@ -1092,128 +1281,270 @@ dependencies = [ ] [[package]] -name = "idna" -version = "0.5.0" +name = "icu_collections" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" +checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" dependencies = [ - "unicode-bidi", - "unicode-normalization", + "displaydoc", + "yoke", + "zerofrom", + "zerovec", ] [[package]] -name = "include_dir" -version = "0.7.3" +name = "icu_locid" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18762faeff7122e89e0857b02f7ce6fcc0d101d5e9ad2ad7846cc01d61b7f19e" +checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" dependencies = [ - "include_dir_macros", + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", ] [[package]] -name = "include_dir_macros" -version = "0.7.3" +name = "icu_locid_transform" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b139284b5cf57ecfa712bcc66950bb635b31aff41c188e8a4cfc758eca374a3f" +checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" dependencies = [ - "proc-macro2", - "quote", + "displaydoc", + "icu_locid", + "icu_locid_transform_data", + "icu_provider", + "tinystr", + "zerovec", ] [[package]] -name = "indexmap" -version = "2.2.4" +name = "icu_locid_transform_data" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "967d6dd42f16dbf0eb8040cb9e477933562684d3918f7d253f2ff9087fb3e7a3" -dependencies = [ - "equivalent", - "hashbrown", -] +checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" [[package]] -name = "indicatif" -version = "0.17.8" +name = "icu_normalizer" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "763a5a8f45087d6bcea4222e7b72c291a054edf80e4ef6efd2a4979878c7bea3" +checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" dependencies = [ - "console", - "instant", - "number_prefix", - "portable-atomic", - "unicode-width", + "displaydoc", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "utf16_iter", + "utf8_iter", + "write16", + "zerovec", ] [[package]] -name = "indoc" -version = "2.0.4" +name = "icu_normalizer_data" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e186cfbae8084e513daff4240b4797e342f988cecda4fb6c939150f96315fd8" +checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" [[package]] -name = "instant" -version = "0.1.12" +name = "icu_properties" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" dependencies = [ - "cfg-if", + "displaydoc", + "icu_collections", + "icu_locid_transform", + "icu_properties_data", + "icu_provider", + "tinystr", + "zerovec", ] [[package]] -name = "ipnet" -version = "2.9.0" +name = "icu_properties_data" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" +checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" [[package]] -name = "itertools" -version = "0.10.5" +name = "icu_provider" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" dependencies = [ - "either", + "displaydoc", + "icu_locid", + "icu_provider_macros", + "stable_deref_trait", + "tinystr", + "writeable", + "yoke", + "zerofrom", + "zerovec", ] [[package]] -name = "itertools" -version = "0.12.1" +name = "icu_provider_macros" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" dependencies = [ - "either", + "proc-macro2", + "quote", + "syn 2.0.99", ] [[package]] -name = "itoa" -version = "1.0.10" +name = "ident_case" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] -name = "js-sys" -version = "0.3.68" +name = "idna" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "406cda4b368d531c842222cf9d2600a9a4acce8d29423695379c6868a143a9ee" +checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "include_dir" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "923d117408f1e49d914f1a379a309cffe4f18c05cf4e3d12e613a15fc81bd0dd" +dependencies = [ + "include_dir_macros", +] + +[[package]] +name = "include_dir_macros" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cab85a7ed0bd5f0e76d93846e0147172bed2e2d3f859bcc33a8d9699cad1a75" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "indexmap" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c9c992b02b5b4c94ea26e32fe5bccb7aa7d9f390ab5c1221ff895bc7ea8b652" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "indicatif" +version = "0.17.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "183b3088984b400f4cfac3620d5e076c84da5364016b4f49473de574b2586235" +dependencies = [ + "console", + "number_prefix", + "portable-atomic", + "unicode-width", + "web-time", +] + +[[package]] +name = "indoc" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b248f5224d1d606005e02c97f5aa4e88eeb230488bcc03bc9ca4d7991399f2b5" + +[[package]] +name = "ipnet" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" + +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" + +[[package]] +name = "jobserver" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0" +dependencies = [ + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +dependencies = [ + "once_cell", "wasm-bindgen", ] [[package]] name = "lazy_static" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.153" +version = "0.2.170" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" +checksum = "875b3680cb2f8f71bdcf9a30f38d48282f5d3c95cbf9b3fa57269bb5d5c06828" [[package]] name = "libgit2-sys" -version = "0.16.2+1.7.2" +version = "0.18.0+1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee4126d8b4ee5c9d9ea891dd875cfdc1e9d0950437179104b183d7d8a74d24e8" +checksum = "e1a117465e7e1597e8febea8bb0c410f1c7fb93b1e1cddf34363f8390367ffec" dependencies = [ "cc", "libc", @@ -1223,11 +1554,22 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "libredox" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" +dependencies = [ + "bitflags 2.9.0", + "libc", + "redox_syscall", +] + [[package]] name = "libssh2-sys" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dc8a030b787e2119a731f1951d6a773e2280c660f8ec4b0f5e1505a386e71ee" +checksum = "220e4f05ad4a218192533b300327f5150e809b54c4ec83b5a1d91833601811b9" dependencies = [ "cc", "libc", @@ -1239,9 +1581,9 @@ dependencies = [ [[package]] name = "libz-sys" -version = "1.1.15" +version = "1.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "037731f5d3aaa87a5675e895b63ddff1a87624bc29f77004ea829809654e48f6" +checksum = "df9b68e50e6e0b26f672573834882eb57759f6db9b3be2ea3c35c91188bb4eaa" dependencies = [ "cc", "libc", @@ -1249,15 +1591,6 @@ dependencies = [ "vcpkg", ] -[[package]] -name = "line-wrap" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f30344350a2a51da54c1d53be93fade8a237e545dbcc4bdbe635413f2117cab9" -dependencies = [ - "safemem", -] - [[package]] name = "linked-hash-map" version = "0.5.6" @@ -1266,24 +1599,30 @@ checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" [[package]] name = "linked_hash_set" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47186c6da4d81ca383c7c47c1bfc80f4b95f4720514d860a5407aaf4233f9588" +checksum = "bae85b5be22d9843c80e5fc80e9b64c8a3b1f98f867c709956eca3efff4e92e2" dependencies = [ "linked-hash-map", ] [[package]] name = "linux-raw-sys" -version = "0.4.13" +version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" + +[[package]] +name = "litemap" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23fb14cb19457329c82206317a5663005a4d404783dc74f4252769b0d5f42856" [[package]] name = "lock_api" -version = "0.4.11" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" dependencies = [ "autocfg", "scopeguard", @@ -1291,9 +1630,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.21" +version = "0.4.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" +checksum = "30bde2b3dc3671ae49d8e2e9f044c7c005836e7a023ee57cffa25ab82764bb9e" [[package]] name = "matchers" @@ -1306,25 +1645,25 @@ dependencies = [ [[package]] name = "memchr" -version = "2.7.1" +version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "migrations_internals" -version = "2.1.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f23f71580015254b020e856feac3df5878c2c7a8812297edd6c0a485ac9dada" +checksum = "fd01039851e82f8799046eabbb354056283fb265c8ec0996af940f4e85a380ff" dependencies = [ "serde", - "toml 0.7.8", + "toml", ] [[package]] name = "migrations_macros" -version = "2.1.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cce3325ac70e67bbab5bd837a31cae01f1a6db64e0e744a33cb03a543469ef08" +checksum = "ffb161cc72176cb37aa47f1fc520d3ef02263d67d661f44f05d05a079e1237fd" dependencies = [ "migrations_internals", "proc-macro2", @@ -1337,39 +1676,32 @@ version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" -[[package]] -name = "minimal-lexical" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" - [[package]] name = "miniz_oxide" -version = "0.7.2" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7" +checksum = "8e3e04debbb59698c15bacbb6d93584a8c0ca9cc3213cb423d31f760d8843ce5" dependencies = [ - "adler", + "adler2", ] [[package]] name = "mio" -version = "0.8.11" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" +checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" dependencies = [ "libc", - "wasi", - "windows-sys 0.48.0", + "wasi 0.11.0+wasi-snapshot-preview1", + "windows-sys 0.52.0", ] [[package]] name = "native-tls" -version = "0.2.11" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" +checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e" dependencies = [ - "lazy_static", "libc", "log", "openssl", @@ -1381,16 +1713,6 @@ dependencies = [ "tempfile", ] -[[package]] -name = "nom" -version = "7.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" -dependencies = [ - "memchr", - "minimal-lexical", -] - [[package]] name = "nu-ansi-term" version = "0.46.0" @@ -1408,22 +1730,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" [[package]] -name = "num-traits" -version = "0.2.18" +name = "num-modular" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17bb261bf36fa7d83f4c294f834e91256769097b3cb505d44831e0a179ac647f" + +[[package]] +name = "num-order" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" +checksum = "537b596b97c40fcf8056d153049eb22f481c17ebce72a513ec9286e4986d1bb6" dependencies = [ - "autocfg", + "num-modular", ] [[package]] -name = "num_cpus" -version = "1.16.0" +name = "num-traits" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ - "hermit-abi", - "libc", + "autocfg", ] [[package]] @@ -1443,18 +1770,18 @@ checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" [[package]] name = "object" -version = "0.32.2" +version = "0.36.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" +checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" dependencies = [ "memchr", ] [[package]] name = "once_cell" -version = "1.19.0" +version = "1.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +checksum = "945462a4b81e43c4e3ba96bd7b49d834c6f61198356aa858733bc4acf3cbe62e" [[package]] name = "onig" @@ -1480,11 +1807,11 @@ dependencies = [ [[package]] name = "openssl" -version = "0.10.64" +version = "0.10.71" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95a0481286a310808298130d22dd1fef0fa571e05a8f44ec801801e84b216b1f" +checksum = "5e14130c6a98cd258fdcb0fb6d744152343ff729cbfcb28c656a9d12b999fbcd" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.9.0", "cfg-if", "foreign-types", "libc", @@ -1501,20 +1828,20 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.99", ] [[package]] name = "openssl-probe" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" +checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" [[package]] name = "openssl-sys" -version = "0.9.101" +version = "0.9.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dda2b0f344e78efc2facf7d195d098df0dd72151b26ab98da807afc26c198dff" +checksum = "8bb61ea9811cc39e3c2069f40b8b8e2e70d8569b361f879786cc7ed48b777cdd" dependencies = [ "cc", "libc", @@ -1524,13 +1851,13 @@ dependencies = [ [[package]] name = "os_info" -version = "3.7.0" +version = "3.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "006e42d5b888366f1880eda20371fedde764ed2213dc8496f49622fa0c99cd5e" +checksum = "2a604e53c24761286860eba4e2c8b23a0161526476b1de520139d69cdb85a6b5" dependencies = [ "log", "serde", - "winapi", + "windows-sys 0.52.0", ] [[package]] @@ -1541,9 +1868,9 @@ checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" [[package]] name = "parking_lot" -version = "0.12.1" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" dependencies = [ "lock_api", "parking_lot_core", @@ -1551,47 +1878,47 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.9" +version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ "cfg-if", "libc", "redox_syscall", "smallvec", - "windows-targets 0.48.5", + "windows-targets", ] [[package]] name = "parse-display" -version = "0.9.0" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06af5f9333eb47bd9ba8462d612e37a8328a5cb80b13f0af4de4c3b89f52dee5" +checksum = "287d8d3ebdce117b8539f59411e4ed9ec226e0a4153c7f55495c6070d68e6f72" dependencies = [ "parse-display-derive", "regex", - "regex-syntax 0.8.2", + "regex-syntax 0.8.5", ] [[package]] name = "parse-display-derive" -version = "0.9.0" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc9252f259500ee570c75adcc4e317fa6f57a1e47747d622e0bf838002a7b790" +checksum = "7fc048687be30d79502dea2f623d052f3a074012c6eac41726b7ab17213616b1" dependencies = [ "proc-macro2", "quote", "regex", - "regex-syntax 0.8.2", + "regex-syntax 0.8.5", "structmeta", - "syn 2.0.52", + "syn 2.0.99", ] [[package]] name = "pathdiff" -version = "0.2.1" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd" +checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3" [[package]] name = "percent-encoding" @@ -1601,20 +1928,20 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pest" -version = "2.7.7" +version = "2.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "219c0dcc30b6a27553f9cc242972b67f75b60eb0db71f0b5462f38b058c41546" +checksum = "8b7cafe60d6cf8e62e1b9b2ea516a089c008945bb5a275416789e7db0bc199dc" dependencies = [ "memchr", - "thiserror", + "thiserror 2.0.12", "ucd-trie", ] [[package]] name = "pest_derive" -version = "2.7.7" +version = "2.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22e1288dbd7786462961e69bfd4df7848c1e37e8b74303dbdab82c3a9cdd2809" +checksum = "816518421cfc6887a0d62bf441b6ffb4536fcc926395a69e1a85852d4363f57e" dependencies = [ "pest", "pest_generator", @@ -1622,22 +1949,22 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.7.7" +version = "2.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1381c29a877c6d34b8c176e734f35d7f7f5b3adaefe940cb4d1bb7af94678e2e" +checksum = "7d1396fd3a870fc7838768d171b4616d5c91f6cc25e377b673d714567d99377b" dependencies = [ "pest", "pest_meta", "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.99", ] [[package]] name = "pest_meta" -version = "2.7.7" +version = "2.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0934d6907f148c22a3acbda520c7eed243ad7487a30f51f6ce52b58b7077a8a" +checksum = "e1e58089ea25d717bfd31fb534e4f3afcc2cc569c70de3e239778991ea3b7dea" dependencies = [ "once_cell", "pest", @@ -1646,9 +1973,9 @@ dependencies = [ [[package]] name = "petgraph" -version = "0.6.4" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1d3afd2628e69da2be385eb6f2fd57c8ac7977ceeff6dc166ff1657b0e386a9" +checksum = "3672b37090dbd86368a4145bc067582552b29c27377cad4e0a306c97f9bd7772" dependencies = [ "fixedbitset", "indexmap", @@ -1665,11 +1992,11 @@ dependencies = [ [[package]] name = "pin-project" -version = "1.1.4" +version = "1.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0302c4a0442c456bd56f841aee5c3bfd17967563f6fadc9ceb9f9c23cf3807e0" +checksum = "dfe2e71e1471fe07709406bf725f710b02927c9c54b2b5b2ec0e8087d97c327d" dependencies = [ - "pin-project-internal 1.1.4", + "pin-project-internal 1.1.9", ] [[package]] @@ -1685,20 +2012,20 @@ dependencies = [ [[package]] name = "pin-project-internal" -version = "1.1.4" +version = "1.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "266c042b60c9c76b8d53061e52b2e0d1116abc57cefc8c5cd671619a56ac3690" +checksum = "f6e859e6e5bd50440ab63c47e3ebabc90f26251f7c73c3d3e837b74a1cc3fa67" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.99", ] [[package]] name = "pin-project-lite" -version = "0.2.13" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" [[package]] name = "pin-utils" @@ -1708,19 +2035,18 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "pkg-config" -version = "0.3.30" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" [[package]] name = "plist" -version = "1.6.0" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5699cc8a63d1aa2b1ee8e12b9ad70ac790d65788cd36101fa37f87ea46c4cef" +checksum = "42cf17e9a1800f5f396bc67d193dc9411b59012a5876445ef450d449881e1016" dependencies = [ - "base64 0.21.7", + "base64 0.22.1", "indexmap", - "line-wrap", "quick-xml", "serde", "time", @@ -1728,18 +2054,18 @@ dependencies = [ [[package]] name = "pom" -version = "3.3.0" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c2d73a5fe10d458e77534589512104e5aa8ac480aa9ac30b74563274235cce4" +checksum = "6c972d8f86e943ad532d0b04e8965a749ad1d18bb981a9c7b3ae72fe7fd7744b" dependencies = [ "bstr", ] [[package]] name = "portable-atomic" -version = "1.6.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7170ef9988bc169ba16dd36a7fa041e5c4cbeb6a35b76d4c03daded371eae7c0" +checksum = "350e9b48cbc6b0e028b0473b114454c6316e57336ee184ceab6e53f72c178b3e" [[package]] name = "powerfmt" @@ -1749,75 +2075,77 @@ checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] name = "ppv-lite86" -version = "0.2.17" +version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +dependencies = [ + "zerocopy 0.7.35", +] [[package]] name = "pq-sys" -version = "0.4.8" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31c0052426df997c0cbd30789eb44ca097e3541717a7b8fa36b1c464ee7edebd" +checksum = "30b51d65ebe1cb1f40641b15abae017fed35ccdda46e3dab1ff8768f625a3222" dependencies = [ + "libc", "vcpkg", ] [[package]] -name = "proc-macro-error" -version = "1.0.4" +name = "proc-macro-error-attr2" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" dependencies = [ - "proc-macro-error-attr", "proc-macro2", "quote", - "syn 1.0.109", - "version_check", ] [[package]] -name = "proc-macro-error-attr" -version = "1.0.4" +name = "proc-macro-error2" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" dependencies = [ + "proc-macro-error-attr2", "proc-macro2", "quote", - "version_check", + "syn 2.0.99", ] [[package]] name = "proc-macro2" -version = "1.0.78" +version = "1.0.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" +checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" dependencies = [ "unicode-ident", ] [[package]] name = "ptree" -version = "0.4.0" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0de80796b316aec75344095a6d2ef68ec9b8f573b9e7adc821149ba3598e270" +checksum = "289cfd20ebec0e7ff2572e370dd7a1c9973ba666d3c38c5e747de0a4ada21f17" dependencies = [ "serde", ] [[package]] name = "quick-xml" -version = "0.31.0" +version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1004a344b30a54e2ee58d66a71b32d2db2feb0a31f9a2d302bf0536f15de2a33" +checksum = "1d3a6e5838b60e0e8fa7a43f22ade549a37d61f8bdbe636d0d7816191de969c2" dependencies = [ "memchr", ] [[package]] name = "quote" -version = "1.0.35" +version = "1.0.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +checksum = "c1f1914ce909e1658d9907913b4b91947430c7d9be598b15a1912935b8c04801" dependencies = [ "proc-macro2", ] @@ -1835,20 +2163,20 @@ dependencies = [ [[package]] name = "rand" -version = "0.8.5" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +checksum = "3779b94aeb87e8bd4e834cee3650289ee9e0d5677f976ecdb6d219e5f4f6cd94" dependencies = [ - "libc", "rand_chacha", "rand_core", + "zerocopy 0.8.21", ] [[package]] name = "rand_chacha" -version = "0.3.1" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" dependencies = [ "ppv-lite86", "rand_core", @@ -1856,18 +2184,18 @@ dependencies = [ [[package]] name = "rand_core" -version = "0.6.4" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" dependencies = [ - "getrandom", + "getrandom 0.3.1", ] [[package]] name = "rayon" -version = "1.9.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4963ed1bc86e4f3ee217022bd855b297cef07fb9eac5dfa1f788b220b49b3bd" +checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" dependencies = [ "either", "rayon-core", @@ -1885,23 +2213,23 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.4.1" +version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +checksum = "82b568323e98e49e2a0899dcee453dd679fae22d69adf9b11dd508d1549b7e2f" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.9.0", ] [[package]] name = "regex" -version = "1.10.3" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.4.5", - "regex-syntax 0.8.2", + "regex-automata 0.4.9", + "regex-syntax 0.8.5", ] [[package]] @@ -1915,13 +2243,13 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.5" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bb987efffd3c6d0d8f5f89510bb458559eab11e4f869acb20bf845e016259cd" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.8.2", + "regex-syntax 0.8.5", ] [[package]] @@ -1932,26 +2260,29 @@ checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] name = "regex-syntax" -version = "0.8.2" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "reqwest" -version = "0.11.24" +version = "0.12.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6920094eb85afde5e4a138be3f2de8bbdf28000f0029e72c45025a56b042251" +checksum = "43e734407157c3c2034e0258f5e4473ddb361b1e85f95a66690d67264d7cd1da" dependencies = [ - "base64 0.21.7", - "bytes 1.5.0", + "base64 0.22.1", + "bytes 1.10.0", "encoding_rs", "futures-core", "futures-util", "h2", - "http", - "http-body", - "hyper", + "http 1.2.0", + "http-body 1.0.1", + "http-body-util", + "hyper 1.6.0", + "hyper-rustls", "hyper-tls", + "hyper-util", "ipnet", "js-sys", "log", @@ -1969,13 +2300,14 @@ dependencies = [ "tokio", "tokio-native-tls", "tokio-util", + "tower", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", "wasm-streams", "web-sys", - "winreg", + "windows-registry", ] [[package]] @@ -1985,65 +2317,97 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cbc95d56eb1865f69288945759cc0879d60ee68168dce676730275804ad2b276" [[package]] -name = "result-inspect" -version = "0.3.0" +name = "ring" +version = "0.17.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7a950f8ecfa2029aec35fed979f0a412e593926f67bf771f7b98509b08db160" +checksum = "da5349ae27d3887ca812fb375b45a4fbb36d8d12d2df394968cd86e35683fe73" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.15", + "libc", + "untrusted", + "windows-sys 0.52.0", +] [[package]] name = "rlimit" -version = "0.10.1" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3560f70f30a0f16d11d01ed078a07740fe6b489667abc7c7b029155d9f21c3d8" +checksum = "7043b63bd0cd1aaa628e476b80e6d4023a3b50eb32789f2728908107bd0c793a" dependencies = [ "libc", ] [[package]] name = "rustc-demangle" -version = "0.1.23" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" [[package]] name = "rustix" -version = "0.38.31" +version = "0.38.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ea3e1a662af26cd7a3ba09c0297a31af215563ecf42817c98df621387f4e949" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.9.0", "errno", "libc", "linux-raw-sys", - "windows-sys 0.52.0", + "windows-sys 0.59.0", +] + +[[package]] +name = "rustls" +version = "0.23.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47796c98c480fce5406ef69d1c76378375492c3b0a0de587be0c1d9feb12f395" +dependencies = [ + "once_cell", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", ] [[package]] name = "rustls-pemfile" -version = "1.0.4" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" +checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" dependencies = [ - "base64 0.21.7", + "rustls-pki-types", ] [[package]] -name = "rustversion" -version = "1.0.14" +name = "rustls-pki-types" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" +checksum = "917ce264624a4b4db1c364dcc35bfca9ded014d0a958cd47ad3e960e988ea51c" [[package]] -name = "ryu" -version = "1.0.17" +name = "rustls-webpki" +version = "0.102.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "rustversion" +version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" +checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4" [[package]] -name = "safemem" -version = "0.3.3" +name = "ryu" +version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef703b7cb59335eae2eb93ceb664c0eb7ea6bf567079d843e09420219668e072" +checksum = "6ea1a2d0a644769cc99faa24c3ad26b379b786fe7c36fd3c546254801650e6dd" [[package]] name = "same-file" @@ -2056,11 +2420,11 @@ dependencies = [ [[package]] name = "schannel" -version = "0.1.23" +version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534" +checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -2080,11 +2444,11 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "security-framework" -version = "2.9.2" +version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.9.0", "core-foundation", "core-foundation-sys", "libc", @@ -2093,9 +2457,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.9.1" +version = "2.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e932934257d3b408ed8f30db49d85ea163bfe74961f017f405b025af298f0c7a" +checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32" dependencies = [ "core-foundation-sys", "libc", @@ -2103,49 +2467,50 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.22" +version = "1.0.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca" +checksum = "f79dfe2d285b0488816f30e700a7438c5a73d816b5b7d3ac72fbc48b0d185e03" dependencies = [ "serde", ] [[package]] name = "serde" -version = "1.0.197" +version = "1.0.218" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2" +checksum = "e8dfc9d19bdbf6d17e22319da49161d5d0108e4188e8b680aef6299eed22df60" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.197" +version = "1.0.218" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" +checksum = "f09503e191f4e797cb8aac08e9a4a4695c5edf6a2e70e376d961ddd5c969f82b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.99", ] [[package]] name = "serde_json" -version = "1.0.114" +version = "1.0.140" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5f09b1bd632ef549eaa9f60a1f8de742bdbc698e6cee2095fc84dde5f549ae0" +checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" dependencies = [ "itoa", + "memchr", "ryu", "serde", ] [[package]] name = "serde_spanned" -version = "0.6.5" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb3622f419d1296904700073ea6cc23ad690adbd66f13ea683df73298736f0c1" +checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" dependencies = [ "serde", ] @@ -2207,18 +2572,18 @@ checksum = "1e468265908f45299c26571dad9a2e5cb3656eceb51cd58f1441cf61aa71aad6" dependencies = [ "base64 0.13.1", "byteorder", - "bytes 1.5.0", + "bytes 1.10.0", "chrono", "flate2", "futures-util", "futures_codec", - "hyper", + "hyper 0.14.32", "hyper-openssl", "hyperlocal", "log", "mime", "openssl", - "pin-project 1.1.4", + "pin-project 1.1.9", "serde", "serde_json", "tar", @@ -2226,11 +2591,17 @@ dependencies = [ "url", ] +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + [[package]] name = "signal-hook-registry" -version = "1.4.1" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" +checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" dependencies = [ "libc", ] @@ -2246,25 +2617,31 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.13.1" +version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7" +checksum = "7fcf8323ef1faaee30a44a340193b1ac6814fd9b7b4e88e9d4519a3e4abe1cfd" [[package]] name = "socket2" -version = "0.5.6" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05ffd9c0a93b7543e062e759284fcf5f5e3b098501104bfbdde4d404db792871" +checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8" dependencies = [ "libc", "windows-sys 0.52.0", ] +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + [[package]] name = "strsim" -version = "0.11.0" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ee073c9e4cd00e28217186dbe12796d692868f432bf2e97ee73bed0c56dfa01" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "structmeta" @@ -2275,7 +2652,7 @@ dependencies = [ "proc-macro2", "quote", "structmeta-derive", - "syn 2.0.52", + "syn 2.0.99", ] [[package]] @@ -2286,9 +2663,15 @@ checksum = "152a0b65a590ff6c3da95cabe2353ee04e6167c896b28e3b14478c2636c922fc" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.99", ] +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + [[package]] name = "syn" version = "1.0.109" @@ -2302,9 +2685,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.52" +version = "2.0.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b699d15b36d1f02c3e7c69f8ffef53de37aefae075d8488d4ba1a7788d574a07" +checksum = "e02e925281e18ffd9d640e234264753c43edc62d64b2d4cf898f1bc5e75f3fc2" dependencies = [ "proc-macro2", "quote", @@ -2313,9 +2696,23 @@ dependencies = [ [[package]] name = "sync_wrapper" -version = "0.1.2" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] + +[[package]] +name = "synstructure" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" +checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.99", +] [[package]] name = "syntect" @@ -2330,31 +2727,31 @@ dependencies = [ "once_cell", "onig", "plist", - "regex-syntax 0.8.2", + "regex-syntax 0.8.5", "serde", "serde_derive", "serde_json", - "thiserror", + "thiserror 1.0.69", "walkdir", "yaml-rust", ] [[package]] name = "system-configuration" -version = "0.5.1" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" +checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.9.0", "core-foundation", "system-configuration-sys", ] [[package]] name = "system-configuration-sys" -version = "0.5.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" dependencies = [ "core-foundation-sys", "libc", @@ -2362,9 +2759,9 @@ dependencies = [ [[package]] name = "tar" -version = "0.4.40" +version = "0.4.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b16afcea1f22891c49a00c751c7b63b2233284064f11a200fc624137c51e2ddb" +checksum = "1d863878d212c87a19c1a610eb53bb01fe12951c0501cf5a0d65f724914a667a" dependencies = [ "filetime", "libc", @@ -2373,44 +2770,66 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.10.1" +version = "3.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" +checksum = "22e5a0acb1f3f55f65cc4a866c361b2fb2a0ff6366785ae6fbb5f85df07ba230" dependencies = [ "cfg-if", "fastrand", + "getrandom 0.3.1", + "once_cell", "rustix", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] name = "terminal_size" -version = "0.3.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21bebf2b7c9e0a515f6e0f8c51dc0f8e4696391e6f1ff30379559f8365fb0df7" +checksum = "5352447f921fda68cf61b4101566c0bdb5104eff6804d0678e5227580ab6a4e9" dependencies = [ "rustix", - "windows-sys 0.48.0", + "windows-sys 0.59.0", ] [[package]] name = "thiserror" -version = "1.0.57" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" +dependencies = [ + "thiserror-impl 2.0.12", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e45bcbe8ed29775f228095caf2cd67af7a4ccf756ebff23a306bf3e8b47b24b" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ - "thiserror-impl", + "proc-macro2", + "quote", + "syn 2.0.99", ] [[package]] name = "thiserror-impl" -version = "1.0.57" +version = "2.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a953cb265bef375dae3de6663da4d3804eee9682ea80d8e2542529b73c531c81" +checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.99", ] [[package]] @@ -2425,9 +2844,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.34" +version = "0.3.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8248b6521bb14bc45b4067159b9b6ad792e2d6d754d6c41fb50e29fefe38749" +checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21" dependencies = [ "deranged", "itoa", @@ -2448,56 +2867,50 @@ checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "time-macros" -version = "0.2.17" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ba3a3ef41e6672a2f0f001392bb5dcd3ff0a9992d618ca761a11c3121547774" +checksum = "2834e6017e3e5e4b9834939793b282bc03b37a3336245fa820e35e233e2a85de" dependencies = [ "num-conv", "time-core", ] [[package]] -name = "tinyvec" -version = "1.6.0" +name = "tinystr" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" dependencies = [ - "tinyvec_macros", + "displaydoc", + "zerovec", ] -[[package]] -name = "tinyvec_macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" - [[package]] name = "tokio" -version = "1.36.0" +version = "1.43.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61285f6515fa018fb2d1e46eb21223fff441ee8db5d0f1435e8ab4f5cdb80931" +checksum = "3d61fa4ffa3de412bfea335c6ecff681de2b609ba3c77ef3e00e521813a9ed9e" dependencies = [ "backtrace", - "bytes 1.5.0", + "bytes 1.10.0", "libc", "mio", - "num_cpus", "pin-project-lite", "signal-hook-registry", "socket2", "tokio-macros", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] name = "tokio-macros" -version = "2.2.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" +checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.99", ] [[package]] @@ -2512,126 +2925,115 @@ dependencies = [ [[package]] name = "tokio-openssl" -version = "0.6.4" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ffab79df67727f6acf57f1ff743091873c24c579b1e2ce4d8f53e47ded4d63d" +checksum = "59df6849caa43bb7567f9a36f863c447d95a11d5903c9cc334ba32576a27eadd" dependencies = [ - "futures-util", "openssl", "openssl-sys", "tokio", ] [[package]] -name = "tokio-stream" -version = "0.1.14" +name = "tokio-rustls" +version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "397c988d37662c7dda6d2208364a706264bf3d6138b11d436cbac0ad38832842" +checksum = "8e727b36a1a0e8b74c376ac2211e40c2c8af09fb4013c60d910495810f008e9b" dependencies = [ - "futures-core", - "pin-project-lite", + "rustls", "tokio", ] [[package]] -name = "tokio-util" -version = "0.7.10" +name = "tokio-stream" +version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15" +checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047" dependencies = [ - "bytes 1.5.0", "futures-core", - "futures-sink", "pin-project-lite", "tokio", - "tracing", ] [[package]] -name = "toml" -version = "0.5.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" -dependencies = [ - "serde", -] - -[[package]] -name = "toml" -version = "0.7.8" +name = "tokio-util" +version = "0.7.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd79e69d3b627db300ff956027cc6c3798cef26d22526befdfcd12feeb6d2257" +checksum = "d7fcaa8d55a2bdd6b83ace262b016eca0d79ee02818c5c1bcdf0305114081078" dependencies = [ - "serde", - "serde_spanned", - "toml_datetime", - "toml_edit 0.19.15", + "bytes 1.10.0", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", ] [[package]] name = "toml" -version = "0.8.10" +version = "0.8.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a9aad4a3066010876e8dcf5a8a06e70a558751117a145c6ce2b82c2e2054290" +checksum = "cd87a5cdd6ffab733b2f74bc4fd7ee5fff6634124999ac278c35fc78c6120148" dependencies = [ "serde", "serde_spanned", "toml_datetime", - "toml_edit 0.22.6", + "toml_edit", ] [[package]] name = "toml_datetime" -version = "0.6.5" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" +checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" dependencies = [ "serde", ] [[package]] name = "toml_edit" -version = "0.19.15" +version = "0.22.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" +checksum = "17b4795ff5edd201c7cd6dca065ae59972ce77d1b80fa0a84d94950ece7d1474" dependencies = [ "indexmap", "serde", "serde_spanned", "toml_datetime", - "winnow 0.5.40", + "winnow", ] [[package]] -name = "toml_edit" -version = "0.22.6" +name = "tower" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c1b5fd4128cc8d3e0cb74d4ed9a9cc7c7284becd4df68f5f940e1ad123606f6" +checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" dependencies = [ - "indexmap", - "serde", - "serde_spanned", - "toml_datetime", - "winnow 0.6.3", + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", ] [[package]] name = "tower-layer" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" [[package]] name = "tower-service" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" [[package]] name = "tracing" -version = "0.1.40" +version = "0.1.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" dependencies = [ "pin-project-lite", "tracing-attributes", @@ -2640,20 +3042,31 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.27" +version = "0.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" +checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.99", +] + +[[package]] +name = "tracing-chrome" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf0a738ed5d6450a9fb96e86a23ad808de2b727fd1394585da5cdd6788ffe724" +dependencies = [ + "serde_json", + "tracing-core", + "tracing-subscriber", ] [[package]] name = "tracing-core" -version = "0.1.32" +version = "0.1.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" dependencies = [ "once_cell", "valuable", @@ -2672,9 +3085,9 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.3.18" +version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" +checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" dependencies = [ "matchers", "nu-ansi-term", @@ -2696,62 +3109,47 @@ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "typed-builder" -version = "0.18.1" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "444d8748011b93cb168770e8092458cb0f8854f931ff82fdf6ddfbd72a9c933e" +checksum = "7e14ed59dc8b7b26cacb2a92bad2e8b1f098806063898ab42a3bd121d7d45e75" dependencies = [ "typed-builder-macro", ] [[package]] name = "typed-builder-macro" -version = "0.18.1" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "563b3b88238ec95680aef36bdece66896eaa7ce3c0f1b4f39d38fb2435261352" +checksum = "560b82d656506509d43abe30e0ba64c56b1953ab3d4fe7ba5902747a7a3cedd5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.99", ] [[package]] name = "typenum" -version = "1.17.0" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" +checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" [[package]] name = "ucd-trie" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9" - -[[package]] -name = "unicode-bidi" -version = "0.3.15" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" +checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" [[package]] name = "unicode-ident" -version = "1.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" - -[[package]] -name = "unicode-normalization" -version = "0.1.23" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" -dependencies = [ - "tinyvec", -] +checksum = "00e2473a93778eb0bad35909dff6a10d28e63f792f16ed15e404fca9d5eeedbe" [[package]] name = "unicode-width" -version = "0.1.11" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" +checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" [[package]] name = "unindent" @@ -2759,11 +3157,17 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c7de7d73e1754487cb58364ee906a499937a0dfabd86bcb980fa99ec8c8fa2ce" +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + [[package]] name = "url" -version = "2.5.0" +version = "2.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" +checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" dependencies = [ "form_urlencoded", "idna", @@ -2771,27 +3175,39 @@ dependencies = [ "serde", ] +[[package]] +name = "utf16_iter" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + [[package]] name = "utf8parse" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.7.0" +version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f00cc9702ca12d3c81455259621e676d0f7251cec66a21e98fe2e9a37db93b2a" +checksum = "e0f540e3240398cce6128b64ba83fdbdd86129c16a3aa1a3a252efd66eb3d587" dependencies = [ - "getrandom", + "getrandom 0.3.1", "serde", ] [[package]] name = "valuable" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" [[package]] name = "vcpkg" @@ -2801,9 +3217,9 @@ checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" [[package]] name = "vergen" -version = "8.3.1" +version = "8.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e27d6bdd219887a9eadd19e1c34f32e47fa332301184935c6d9bca26f3cca525" +checksum = "2990d9ea5967266ea0ccf413a4aa5c42a93dbcfda9cb49a97de6931726b12566" dependencies = [ "anyhow", "cargo_metadata", @@ -2815,15 +3231,15 @@ dependencies = [ [[package]] name = "version_check" -version = "0.9.4" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "walkdir" -version = "2.4.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" dependencies = [ "same-file", "winapi-util", @@ -2844,48 +3260,59 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "wasi" +version = "0.13.3+wasi-0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26816d2e1a4a36a2940b96c5296ce403917633dff8f3440e9b236ed6f6bacad2" +dependencies = [ + "wit-bindgen-rt", +] + [[package]] name = "wasm-bindgen" -version = "0.2.91" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1e124130aee3fb58c5bdd6b639a0509486b0338acaaae0c84a5124b0f588b7f" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" dependencies = [ "cfg-if", + "once_cell", + "rustversion", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" -version = "0.2.91" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9e7e1900c352b609c8488ad12639a311045f40a35491fb69ba8c12f758af70b" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" dependencies = [ "bumpalo", "log", - "once_cell", "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.99", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.41" +version = "0.4.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "877b9c3f61ceea0e56331985743b13f3d25c406a7098d45180fb5f09bc19ed97" +checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" dependencies = [ "cfg-if", "js-sys", + "once_cell", "wasm-bindgen", "web-sys", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.91" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b30af9e2d358182b5c7449424f017eba305ed32a7010509ede96cdc4696c46ed" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2893,28 +3320,31 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.91" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "642f325be6301eb8107a83d12a8ac6c1e1c54345a7ef1a9261962dfefda09e66" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.99", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.91" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f186bd2dcf04330886ce82d6f33dd75a7bfcf69ecf5763b89fcde53b6ac9838" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] [[package]] name = "wasm-streams" -version = "0.4.0" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b65dc4c90b63b118468cf747d8bf3566c1913ef60be765b5730ead9e0a3ba129" +checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" dependencies = [ "futures-util", "js-sys", @@ -2925,9 +3355,19 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.68" +version = "0.3.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96565907687f7aceb35bc5fc03770a8a0471d82e479f25832f54a0e3f4b28446" +checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" dependencies = [ "js-sys", "wasm-bindgen", @@ -2935,15 +3375,14 @@ dependencies = [ [[package]] name = "which" -version = "6.0.0" +version = "7.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fa5e0c10bf77f44aac573e498d1a82d5fbd5e91f6fc0a99e7be4b38e85e101c" +checksum = "2774c861e1f072b3aadc02f8ba886c26ad6321567ecc294c935434cad06f1283" dependencies = [ "either", - "home", - "once_cell", + "env_home", "rustix", - "windows-sys 0.52.0", + "winsafe", ] [[package]] @@ -2964,11 +3403,11 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.6" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "winapi", + "windows-sys 0.59.0", ] [[package]] @@ -2983,174 +3422,168 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" dependencies = [ - "windows-targets 0.52.4", + "windows-targets", ] [[package]] -name = "windows-sys" -version = "0.48.0" +name = "windows-link" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" -dependencies = [ - "windows-targets 0.48.5", -] +checksum = "6dccfd733ce2b1753b03b6d3c65edf020262ea35e20ccdf3e288043e6dd620e3" [[package]] -name = "windows-sys" -version = "0.52.0" +name = "windows-registry" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0" dependencies = [ - "windows-targets 0.52.4", + "windows-result", + "windows-strings", + "windows-targets", ] [[package]] -name = "windows-targets" -version = "0.48.5" +name = "windows-result" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" dependencies = [ - "windows_aarch64_gnullvm 0.48.5", - "windows_aarch64_msvc 0.48.5", - "windows_i686_gnu 0.48.5", - "windows_i686_msvc 0.48.5", - "windows_x86_64_gnu 0.48.5", - "windows_x86_64_gnullvm 0.48.5", - "windows_x86_64_msvc 0.48.5", + "windows-targets", ] [[package]] -name = "windows-targets" -version = "0.52.4" +name = "windows-strings" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b" +checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" dependencies = [ - "windows_aarch64_gnullvm 0.52.4", - "windows_aarch64_msvc 0.52.4", - "windows_i686_gnu 0.52.4", - "windows_i686_msvc 0.52.4", - "windows_x86_64_gnu 0.52.4", - "windows_x86_64_gnullvm 0.52.4", - "windows_x86_64_msvc 0.52.4", + "windows-result", + "windows-targets", ] [[package]] -name = "windows_aarch64_gnullvm" -version = "0.48.5" +name = "windows-sys" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] [[package]] -name = "windows_aarch64_gnullvm" -version = "0.52.4" +name = "windows-sys" +version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets", +] [[package]] -name = "windows_aarch64_msvc" -version = "0.48.5" +name = "windows-targets" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] [[package]] -name = "windows_aarch64_msvc" -version = "0.52.4" +name = "windows_aarch64_gnullvm" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] -name = "windows_i686_gnu" -version = "0.48.5" +name = "windows_aarch64_msvc" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_i686_gnu" -version = "0.52.4" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] -name = "windows_i686_msvc" -version = "0.48.5" +name = "windows_i686_gnullvm" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_msvc" -version = "0.52.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.48.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_x86_64_gnu" -version = "0.52.4" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnullvm" -version = "0.48.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] -name = "windows_x86_64_gnullvm" -version = "0.52.4" +name = "windows_x86_64_msvc" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] -name = "windows_x86_64_msvc" -version = "0.48.5" +name = "winnow" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" +checksum = "0e7f4ea97f6f78012141bcdb6a216b2609f0979ada50b20ca5b52dde2eac2bb1" +dependencies = [ + "memchr", +] [[package]] -name = "windows_x86_64_msvc" -version = "0.52.4" +name = "winsafe" +version = "0.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8" +checksum = "d135d17ab770252ad95e9a872d365cf3090e3be864a34ab46f48555993efc904" [[package]] -name = "winnow" -version = "0.5.40" +name = "wit-bindgen-rt" +version = "0.33.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" +checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c" dependencies = [ - "memchr", + "bitflags 2.9.0", ] [[package]] -name = "winnow" -version = "0.6.3" +name = "write16" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44e19b97e00a4d3db3cdb9b53c8c5f87151b5689b82cc86c2848cbdcccb2689b" -dependencies = [ - "memchr", -] +checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" [[package]] -name = "winreg" -version = "0.50.0" +name = "writeable" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" -dependencies = [ - "cfg-if", - "windows-sys 0.48.0", -] +checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" [[package]] name = "xattr" -version = "1.3.1" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8da84f1a25939b27f6820d92aed108f83ff920fdf11a7b19366c27c4cda81d4f" +checksum = "e105d177a3871454f754b33bb0ee637ecaaac997446375fd3e5d43a2ed00c909" dependencies = [ "libc", "linux-raw-sys", @@ -3172,8 +3605,116 @@ dependencies = [ "linked-hash-map", ] +[[package]] +name = "yoke" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" +dependencies = [ + "serde", + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.99", + "synstructure", +] + +[[package]] +name = "zerocopy" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "byteorder", + "zerocopy-derive 0.7.35", +] + +[[package]] +name = "zerocopy" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcf01143b2dd5d134f11f545cf9f1431b13b749695cb33bcce051e7568f99478" +dependencies = [ + "zerocopy-derive 0.8.21", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.99", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712c8386f4f4299382c9abee219bee7084f78fb939d88b6840fcc1320d5f6da2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.99", +] + +[[package]] +name = "zerofrom" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.99", + "synstructure", +] + [[package]] name = "zeroize" -version = "1.7.0" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" + +[[package]] +name = "zerovec" +version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" +checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.99", +] diff --git a/Cargo.toml b/Cargo.toml index a8e99752..7074101e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "butido" -version = "0.4.0" +version = "0.5.0" authors = [ # Only for the current/active maintainers (sorted alphabetically by the surname) # All other authors are listed in the "Authors" section of README.md @@ -8,7 +8,7 @@ authors = [ "Michael Weiss ", # @primeos-work ] edition = "2021" -rust-version = "1.74.0" # MSRV +rust-version = "1.85.0" # MSRV license = "EPL-2.0" description = "Linux package tool utilizing Docker, PostgreSQL, and TOML" @@ -25,41 +25,42 @@ maintenance = { status = "passively-maintained" } [dependencies] anyhow = "1" -aquamarine = "0.5" -ascii_table = "4" -bytesize = "1" +aquamarine = "0.6" +ascii_table = { version = "4", features = ["color_codes", "wide_characters"] } +bytesize = "2" chrono = "0.4" clap = { version = "4", features = ["cargo"] } clap_complete = "4" -colored = "2" -config = { version = "0.13", default-features = false, features = [ "toml" ] } +colored = "3" +config = { version = "0.15", default-features = false, features = [ "toml" ] } csv = "1" -daggy = { version = "0.8", features = [ "serde" ] } dialoguer = "0.11" diesel = { version = "2", features = ["postgres", "chrono", "uuid", "serde_json", "r2d2"] } diesel_migrations = "2" filters = "0.4" futures = "0.3" getset = "0.1" -git2 = "0.18" -handlebars = { version = "5", features = ["no_logging"] } -human-panic = "1" +git2 = "0.20" +handlebars = { version = "6", features = ["no_logging"] } +human-panic = "2" humantime = "2" indicatif = "0.17" indoc = "2" -itertools = "0.12" +itertools = "0.14" lazy_static = "1" -parse-display = "0.9" +once_cell = "1" +parse-display = "0.10" +petgraph = "0.7" pom = "3" -ptree = { version = "0.4", default-features = false } -rand = "0.8" +ptree = { version = "0.5", default-features = false } +rand = "0.9" rayon = "1" regex = "1" -reqwest = { version = "0.11", features = [ "stream" ] } +reqwest = { version = "0.12", features = [ "stream" ] } resiter = "0.5" -result-inspect = "0.3" rlimit = "0.10" rustversion = "1" +semver = "1" serde = "1" serde_json = "1" sha1 = "0.10" @@ -67,18 +68,20 @@ sha2 = "0.10" shiplift = "0.7" syntect = "5" tar = "0.4" -terminal_size = "0.3" -tokio = { version = "1", features = ["macros", "fs", "process", "io-util", "time"] } +terminal_size = "0.4" +tokio = { version = "1", features = ["macros", "fs", "process", "io-util", "signal", "time"] } +tokio-util = "0.7" tokio-stream = "0.1" toml = "0.8" tracing = "0.1" +tracing-chrome = "0.7" tracing-subscriber = { version = "0.3", features = ["env-filter"] } -typed-builder = "0.18" +typed-builder = "0.20" unindent = "0.2" url = { version = "2", features = ["serde"] } uuid = { version = "1", features = ["serde", "v4"] } walkdir = "2" -which = "6" +which = "7" xdg = "2" [build-dependencies] diff --git a/README.md b/README.md index da92b9d1..895d298f 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,7 @@ Building butido is easy, assuming you have a Rust installation: cargo build --release # (remove --release for a debug build) ``` -Butido is built and tested with Rust 1.74.0 as MSRV. +Butido is built and tested with Rust 1.85.0 as MSRV. ### (Development) Setup @@ -103,6 +103,6 @@ cd /tmp/butido-test-repo # License -butido was developed for science+computing ag (an Atos company). +butido was developed for science + computing AG (an Atos company). License: EPL-2.0 diff --git a/config.toml b/config.toml index a4090ba6..a619a636 100644 --- a/config.toml +++ b/config.toml @@ -12,7 +12,7 @@ compatibility = 1 # fit a 80 or 100 character wide terminal! # # This is also the default if the setting is not present. -progress_format = "[{elapsed_precise}] ({percent:>3}%): {bar:40.cyan/blue} | {msg}" +progress_format = "{elapsed_precise} {percent:>3}% {bar:5.cyan/blue} | {msg}" # The shebang line used when compiling the packaging scripts diff --git a/deny.toml b/deny.toml index 63499975..1e64fb8f 100644 --- a/deny.toml +++ b/deny.toml @@ -1,27 +1,22 @@ -[licenses] -# The lint level for crates which do not have a detectable license -unlicensed = "deny" +# Documentation for this configuration file: +# https://embarkstudios.github.io/cargo-deny/checks/cfg.html -# List of explictly allowed licenses -# See https://spdx.org/licenses/ for list of possible licenses -# [possible values: any SPDX 3.7 short identifier (+ optional exception)]. -allow = ["MPL-2.0"] +# GitHub link: https://github.com/EmbarkStudios/cargo-deny -# List of explictly disallowed licenses +[licenses] +# List of explicitly allowed licenses # See https://spdx.org/licenses/ for list of possible licenses # [possible values: any SPDX 3.7 short identifier (+ optional exception)]. -deny = [] - -# The lint level for licenses considered copyleft -copyleft = "deny" - -# Blanket approval or denial for OSI-approved or FSF Free/Libre licenses -# * both - The license will only be approved if it is both OSI-approved *AND* FSF/Free -# * either - The license will be approved if it is either OSI-approved *OR* FSF/Free -# * osi-only - The license will be approved if is OSI-approved *AND NOT* FSF/Free -# * fsf-only - The license will be approved if is FSF/Free *AND NOT* OSI-approved -# * neither - The license will be denied if is FSF/Free *OR* OSI-approved -allow-osi-fsf-free = "either" +allow = [ + "Apache-2.0", + "BSD-2-Clause", + "BSD-3-Clause", + "EPL-2.0", + "MIT", + "MPL-2.0", + "Unicode-3.0", + "Unicode-DFS-2016" +] # The confidence threshold for detecting a license from license text. # The higher the value, the more closely the license text must be to the @@ -48,20 +43,26 @@ allow = [ deny = [ # Each entry the name of a crate and a version range. If version is # not specified, all versions will be matched. - { name = "fuchsia-cprng" } + { crate = "fuchsia-cprng" } ] # Certain crates/versions that will be skipped when doing duplicate detection. skip = [ ] -# Similarly to `skip` allows you to skip certain crates during duplicate detection, -# unlike skip, it also includes the entire tree of transitive dependencies starting at -# the specified crate, up to a certain depth, which is by default infinite +# Similarly to `skip` allows you to skip certain crates during duplicate +# detection. Unlike skip, it also includes the entire tree of transitive +# dependencies starting at the specified crate, up to a certain depth, which is +# by default infinite. skip-tree = [ ] [advisories] ignore = [ + # Ignore an "INFO Unmaintained" advisory for the yaml-rust crate that the + # "syntect" crate uses. This can be removed once + # https://github.com/trishume/syntect/issues/537 is resolved (replace + # yaml-rust with yaml-rust2): + { id = "RUSTSEC-2024-0320", reason = "Only an informative advisory that the crate is unmaintained and the maintainer unreachable" }, ] diff --git a/doc/containers.md b/doc/containers.md index 52f4b4ae..63ce7ca6 100644 --- a/doc/containers.md +++ b/doc/containers.md @@ -14,10 +14,9 @@ There are some conventions regarding packages, dependencies, sources and so on. Those are listed here. 1. Dependencies are named `/inputs/-.pkg` inside the container -2. Sources are named `/inputs/src-.source` +2. Sources are named `/inputs/src.source` 3. Outputs are expected to be written to the `/outputs` directory The reason for the names lies in the artifact parsing mechanism. If the package is named differently, the artifact parsing mechanism is not able to recognize the package and might fault, which causes butido to stop running. - diff --git a/examples/packages/repo/config.toml b/examples/packages/repo/config.toml index 61883fc9..5a73546c 100644 --- a/examples/packages/repo/config.toml +++ b/examples/packages/repo/config.toml @@ -47,8 +47,7 @@ images = [ [docker.endpoints.testhostname] uri = "http://0.0.0.0:8095" # the URI of the endpoint. Either http or socket path endpoint_type = "http" # either "http" or "socket" -speed = 1 # currently ignored, but required to be present -maxjobs = 1 # currently ignored, but required to be present +maxjobs = 1 # Maximum number of jobs which are allowed on this endpoint [containers] diff --git a/examples/packages/repo/j/pkg.toml b/examples/packages/repo/j/pkg.toml index b9783b21..9835f5d7 100644 --- a/examples/packages/repo/j/pkg.toml +++ b/examples/packages/repo/j/pkg.toml @@ -2,7 +2,7 @@ name = "j" version = "10" [dependencies] -runtime = ["s =19.0", "t =20"] +runtime = ["s =19.0", "s =19.1", "s =19.2", "s =19.3", "t =20"] [sources.src] url = "https://example.com" diff --git a/examples/packages/repo/pkg.toml b/examples/packages/repo/pkg.toml index cdd6a6ce..2909ba44 100644 --- a/examples/packages/repo/pkg.toml +++ b/examples/packages/repo/pkg.toml @@ -7,12 +7,11 @@ runtime = [] [sources.src] hash.type = "sha1" -download_manually = false [phases] sourcecheck.script = ''' - filename="/inputs/src-{{this.sources.src.hash.hash}}.source" + filename="/inputs/src.source" [[ -e $filename ]] || { echo "MISSING: $filename" {{state "ERR" "Missing input"}} @@ -50,4 +49,3 @@ build.script = ''' {{state "OK"}} ''' - diff --git a/examples/packages/sources/a-1/src-e5fa44f2b31c1fb553b6021e7360d07d5d91ff5e.source b/examples/packages/sources/a-1/src.source similarity index 100% rename from examples/packages/sources/a-1/src-e5fa44f2b31c1fb553b6021e7360d07d5d91ff5e.source rename to examples/packages/sources/a-1/src.source diff --git a/examples/packages/sources/b-2/src-7448d8798a4380162d4b56f9b452e2f6f9e24e7a.source b/examples/packages/sources/b-2/src.source similarity index 100% rename from examples/packages/sources/b-2/src-7448d8798a4380162d4b56f9b452e2f6f9e24e7a.source rename to examples/packages/sources/b-2/src.source diff --git a/examples/packages/sources/c-3/src-a3db5c13ff90a36963278c6a39e4ee3c22e2a436.source b/examples/packages/sources/c-3/src.source similarity index 100% rename from examples/packages/sources/c-3/src-a3db5c13ff90a36963278c6a39e4ee3c22e2a436.source rename to examples/packages/sources/c-3/src.source diff --git a/examples/packages/sources/d-4/src-9c6b057a2b9d96a4067a749ee3b3b0158d390cf1.source b/examples/packages/sources/d-4/src.source similarity index 100% rename from examples/packages/sources/d-4/src-9c6b057a2b9d96a4067a749ee3b3b0158d390cf1.source rename to examples/packages/sources/d-4/src.source diff --git a/examples/packages/sources/e-5/src-5d9474c0309b7ca09a182d888f73b37a8fe1362c.source b/examples/packages/sources/e-5/src.source similarity index 100% rename from examples/packages/sources/e-5/src-5d9474c0309b7ca09a182d888f73b37a8fe1362c.source rename to examples/packages/sources/e-5/src.source diff --git a/examples/packages/sources/f-6/src-ccf271b7830882da1791852baeca1737fcbe4b90.source b/examples/packages/sources/f-6/src.source similarity index 100% rename from examples/packages/sources/f-6/src-ccf271b7830882da1791852baeca1737fcbe4b90.source rename to examples/packages/sources/f-6/src.source diff --git a/examples/packages/sources/g-7/src-d3964f9dad9f60363c81b688324d95b4ec7c8038.source b/examples/packages/sources/g-7/src.source similarity index 100% rename from examples/packages/sources/g-7/src-d3964f9dad9f60363c81b688324d95b4ec7c8038.source rename to examples/packages/sources/g-7/src.source diff --git a/examples/packages/sources/h-8/src-136571b41aa14adc10c5f3c987d43c02c8f5d498.source b/examples/packages/sources/h-8/src.source similarity index 100% rename from examples/packages/sources/h-8/src-136571b41aa14adc10c5f3c987d43c02c8f5d498.source rename to examples/packages/sources/h-8/src.source diff --git a/examples/packages/sources/i-9/src-b6abd567fa79cbe0196d093a067271361dc6ca8b.source b/examples/packages/sources/i-9/src.source similarity index 100% rename from examples/packages/sources/i-9/src-b6abd567fa79cbe0196d093a067271361dc6ca8b.source rename to examples/packages/sources/i-9/src.source diff --git a/examples/packages/sources/j-10/src-4143d3a341877154d6e95211464e1df1015b74bd.source b/examples/packages/sources/j-10/src.source similarity index 100% rename from examples/packages/sources/j-10/src-4143d3a341877154d6e95211464e1df1015b74bd.source rename to examples/packages/sources/j-10/src.source diff --git a/examples/packages/sources/k-11/src-dd71038f3463f511ee7403dbcbc87195302d891c.source b/examples/packages/sources/k-11/src.source similarity index 100% rename from examples/packages/sources/k-11/src-dd71038f3463f511ee7403dbcbc87195302d891c.source rename to examples/packages/sources/k-11/src.source diff --git a/examples/packages/sources/l-12/src-ad552e6dc057d1d825bf49df79d6b98eba846ebe.source b/examples/packages/sources/l-12/src.source similarity index 100% rename from examples/packages/sources/l-12/src-ad552e6dc057d1d825bf49df79d6b98eba846ebe.source rename to examples/packages/sources/l-12/src.source diff --git a/examples/packages/sources/m-13/src-feee44ad365b6b1ec75c5621a0ad067371102854.source b/examples/packages/sources/m-13/src.source similarity index 100% rename from examples/packages/sources/m-13/src-feee44ad365b6b1ec75c5621a0ad067371102854.source rename to examples/packages/sources/m-13/src.source diff --git a/examples/packages/sources/n-14/src-030514d80869744a4e2f60d2fd37d6081f5ed01a.source b/examples/packages/sources/n-14/src.source similarity index 100% rename from examples/packages/sources/n-14/src-030514d80869744a4e2f60d2fd37d6081f5ed01a.source rename to examples/packages/sources/n-14/src.source diff --git a/examples/packages/sources/o-15/src-587b596f04f7db9c2cad3d6b87dd2b3a05de4f35.source b/examples/packages/sources/o-15/src.source similarity index 100% rename from examples/packages/sources/o-15/src-587b596f04f7db9c2cad3d6b87dd2b3a05de4f35.source rename to examples/packages/sources/o-15/src.source diff --git a/examples/packages/sources/p-16/src-3596ea087bfdaf52380eae441077572ed289d657.source b/examples/packages/sources/p-16/src.source similarity index 100% rename from examples/packages/sources/p-16/src-3596ea087bfdaf52380eae441077572ed289d657.source rename to examples/packages/sources/p-16/src.source diff --git a/examples/packages/sources/q-17/src-ad48103e4fc71796e9708cafc43adeed0d1076b7.source b/examples/packages/sources/q-17/src.source similarity index 100% rename from examples/packages/sources/q-17/src-ad48103e4fc71796e9708cafc43adeed0d1076b7.source rename to examples/packages/sources/q-17/src.source diff --git a/examples/packages/sources/r-18/src-24b9c1f3fddff79893e5304f998f2f95ebebd149.source b/examples/packages/sources/r-18/src.source similarity index 100% rename from examples/packages/sources/r-18/src-24b9c1f3fddff79893e5304f998f2f95ebebd149.source rename to examples/packages/sources/r-18/src.source diff --git a/examples/packages/sources/s-19.0/src-ba9f376fa71904ccde2a756a24a4e47ec014ee0a.source b/examples/packages/sources/s-19.0/src.source similarity index 100% rename from examples/packages/sources/s-19.0/src-ba9f376fa71904ccde2a756a24a4e47ec014ee0a.source rename to examples/packages/sources/s-19.0/src.source diff --git a/examples/packages/sources/s-19.1/src-ba9f376fa71904ccde2a756a24a4e47ec014ee0a.source b/examples/packages/sources/s-19.1/src.source similarity index 100% rename from examples/packages/sources/s-19.1/src-ba9f376fa71904ccde2a756a24a4e47ec014ee0a.source rename to examples/packages/sources/s-19.1/src.source diff --git a/examples/packages/sources/s-19/src-ba9f376fa71904ccde2a756a24a4e47ec014ee0a.source b/examples/packages/sources/s-19.2/src.source similarity index 100% rename from examples/packages/sources/s-19/src-ba9f376fa71904ccde2a756a24a4e47ec014ee0a.source rename to examples/packages/sources/s-19.2/src.source diff --git a/examples/packages/sources/s-19.3/src.source b/examples/packages/sources/s-19.3/src.source new file mode 100644 index 00000000..d6b24041 --- /dev/null +++ b/examples/packages/sources/s-19.3/src.source @@ -0,0 +1 @@ +19 diff --git a/examples/packages/sources/t-20/src-d0758565fd06c37aa66b071160d156f5628cd518.source b/examples/packages/sources/t-20/src.source similarity index 100% rename from examples/packages/sources/t-20/src-d0758565fd06c37aa66b071160d156f5628cd518.source rename to examples/packages/sources/t-20/src.source diff --git a/examples/packages/sources/u-21/src-8eecbb71d418ef8c7d583dd506a994b1bc1c3f7b.source b/examples/packages/sources/u-21/src.source similarity index 100% rename from examples/packages/sources/u-21/src-8eecbb71d418ef8c7d583dd506a994b1bc1c3f7b.source rename to examples/packages/sources/u-21/src.source diff --git a/examples/packages/sources/v-22/src-a66ca4290ebaf525721fc670ea53476a15957f9e.source b/examples/packages/sources/v-22/src.source similarity index 100% rename from examples/packages/sources/v-22/src-a66ca4290ebaf525721fc670ea53476a15957f9e.source rename to examples/packages/sources/v-22/src.source diff --git a/examples/packages/sources/w-23/src-aec46dc0de48f39f98f9572b6560ca3f0916b715.source b/examples/packages/sources/w-23/src.source similarity index 100% rename from examples/packages/sources/w-23/src-aec46dc0de48f39f98f9572b6560ca3f0916b715.source rename to examples/packages/sources/w-23/src.source diff --git a/examples/packages/sources/x-24/src-b31990eea1cee9f421c933461a2f3c3dd741a58b.source b/examples/packages/sources/x-24/src.source similarity index 100% rename from examples/packages/sources/x-24/src-b31990eea1cee9f421c933461a2f3c3dd741a58b.source rename to examples/packages/sources/x-24/src.source diff --git a/examples/packages/sources/y-25/src-c6e4ffdb7e1f4c736fb7ab897162332b4619d9ca.source b/examples/packages/sources/y-25/src.source similarity index 100% rename from examples/packages/sources/y-25/src-c6e4ffdb7e1f4c736fb7ab897162332b4619d9ca.source rename to examples/packages/sources/y-25/src.source diff --git a/examples/packages/sources/z-26/src-a0361d509d714f50e954ffeb49ac18222609cf2a.source b/examples/packages/sources/z-26/src.source similarity index 100% rename from examples/packages/sources/z-26/src-a0361d509d714f50e954ffeb49ac18222609cf2a.source rename to examples/packages/sources/z-26/src.source diff --git a/src/cli.rs b/src/cli.rs index b923393d..783e31b6 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -9,7 +9,6 @@ // use std::path::PathBuf; -use std::str::FromStr; use clap::crate_authors; use clap::Arg; @@ -48,17 +47,26 @@ pub fn cli() -> Command { .long("package") .short('p') .value_name("PKG") - .help("Only list releases for package PKG"), + .help("List only releases for package PKG"), + ) + .arg( + Arg::new("limit") + .required(false) + .long("limit") + .short('L') + .value_name("LIMIT") + .help("List newest LIMIT releases (0=unlimited)") + .value_parser(clap::value_parser!(usize)), ); Command::new("butido") .author(crate_authors!()) .disable_version_flag(true) - .about("Generic Build Orchestration System for building Linux packages with Docker") + .about("Generic build orchestration system for building Linux packages with Docker") .after_help(indoc::indoc!(r#" The following environment variables can be passed to butido: - RUST_LOG - to enable logging, for exact usage see the rust cookbook + RUST_LOG - to enable logging, for exact usage see the Rust cookbook "#)) .arg(Arg::new("version") @@ -69,6 +77,13 @@ pub fn cli() -> Command { .help("Detailed version output with build information") ) + .arg(Arg::new("tracing-chrome") + .action(ArgAction::SetTrue) + .required(false) + .long("tracing-chrome") + .help("Generate a Chrome compatible trace file (trace-*.json)") + ) + .arg(Arg::new("hide_bars") .action(ArgAction::SetTrue) .required(false) @@ -83,7 +98,7 @@ pub fn cli() -> Command { .help("Override the database host") .long_help(indoc::indoc!(r#" Override the database host set via configuration. - Can also be overriden via environment variable 'BUTIDO_DATABASE_HOST', but this setting has precedence. + Can also be overridden via environment variable 'BUTIDO_DATABASE_HOST', but this setting has precedence. "#)) ) .arg(Arg::new("database_port") @@ -93,8 +108,9 @@ pub fn cli() -> Command { .help("Override the database port") .long_help(indoc::indoc!(r#" Override the database port set via configuration. - Can also be overriden via environment 'BUTIDO_DATABASE_PORT', but this setting has precedence. + Can also be overridden via environment 'BUTIDO_DATABASE_PORT', but this setting has precedence. "#)) + .value_parser(clap::value_parser!(u16)) ) .arg(Arg::new("database_user") .required(false) @@ -103,7 +119,7 @@ pub fn cli() -> Command { .help("Override the database user") .long_help(indoc::indoc!(r#" Override the database user set via configuration. - Can also be overriden via environment 'BUTIDO_DATABASE_USER', but this setting has precedence. + Can also be overridden via environment 'BUTIDO_DATABASE_USER', but this setting has precedence. "#)) ) .arg(Arg::new("database_password") @@ -114,7 +130,7 @@ pub fn cli() -> Command { .help("Override the database password") .long_help(indoc::indoc!(r#" Override the database password set via configuration. - Can also be overriden via environment 'BUTIDO_DATABASE_PASSWORD', but this setting has precedence. + Can also be overridden via environment 'BUTIDO_DATABASE_PASSWORD', but this setting has precedence. "#)) ) .arg(Arg::new("database_name") @@ -124,18 +140,19 @@ pub fn cli() -> Command { .help("Override the database name") .long_help(indoc::indoc!(r#" Override the database name set via configuration. - Can also be overriden via environment 'BUTIDO_DATABASE_NAME', but this setting has precedence. + Can also be overridden via environment 'BUTIDO_DATABASE_NAME', but this setting has precedence. "#)) ) .arg(Arg::new("database_connection_timeout") .required(false) .long("db-timeout") .value_name("TIMEOUT") - .help("Override the database connection timeout") + .help("Override the database connection timeout (in seconds)") .long_help(indoc::indoc!(r#" Override the database connection timeout set via configuration. - Can also be overriden via environment 'BUTIDO_DATABASE_CONNECTION_TIMEOUT', but this setting has precedence. + Can also be overridden via environment 'BUTIDO_DATABASE_CONNECTION_TIMEOUT', but this setting has precedence. "#)) + .value_parser(clap::value_parser!(u16)) ) .subcommand(Command::new("generate-completions") @@ -191,13 +208,15 @@ pub fn cli() -> Command { .short('J') .value_name("JOB UUID") .help("Print only artifacts for a certain job") + .value_parser(uuid::Uuid::parse_str) ) .arg(Arg::new("limit") .required(false) .long("limit") .short('L') .value_name("LIMIT") - .help("Only list LIMIT artifacts") + .help("List newest LIMIT artifacts (0=unlimited)") + .value_parser(clap::value_parser!(usize)) ) ) @@ -227,7 +246,8 @@ pub fn cli() -> Command { .required(true) .index(1) .value_name("SUBMIT") - .help("The Submit to show details about") + .help("The submit to show details about") + .value_parser(uuid::Uuid::parse_str) ) ) @@ -258,7 +278,8 @@ pub fn cli() -> Command { .long("limit") .short('L') .value_name("LIMIT") - .help("Only list LIMIT submits") + .help("List newest LIMIT submits (0=unlimited)") + .value_parser(clap::value_parser!(usize)) ) .arg(Arg::new("for-commit") .required(false) @@ -268,6 +289,7 @@ pub fn cli() -> Command { ) .arg(Arg::new("image") .required(false) + .short('I') .long("image") .value_name("IMAGE") .help("Limit listed submits to submits on IMAGE") @@ -289,6 +311,7 @@ pub fn cli() -> Command { .short('S') .value_name("UUID") .help("Only list jobs of a certain submit") + .value_parser(uuid::Uuid::parse_str) ) .arg(Arg::new("image") @@ -312,7 +335,8 @@ pub fn cli() -> Command { .long("limit") .short('L') .value_name("LIMIT") - .help("Only list newest LIMIT jobs instead of all") + .help("List newest LIMIT jobs (0=unlimited)") + .value_parser(clap::value_parser!(usize)) ) .arg(arg_older_than_date("List only jobs older than DATE")) @@ -350,6 +374,7 @@ pub fn cli() -> Command { .index(1) .value_name("UUID") .help("The job to show") + .value_parser(uuid::Uuid::parse_str) ) .arg(Arg::new("show_log") @@ -387,7 +412,8 @@ pub fn cli() -> Command { .required(true) .index(1) .value_name("UUID") - .help("The id of the Job") + .help("The job to print the log of") + .value_parser(uuid::Uuid::parse_str) ) ) .subcommand(releases_list_command.clone()) @@ -504,7 +530,7 @@ pub fn cli() -> Command { ) .subcommand(Command::new("dependencies-of") .alias("depsof") - .about("List the depenendcies of a package") + .about("List the dependencies of a package") .arg(Arg::new("package_name") .required(true) .index(1) @@ -515,7 +541,7 @@ pub fn cli() -> Command { .required(false) .index(2) .value_name("VERSION_CONSTRAINT") - .help("A version constraint to search for (optional), E.G. '=1.0.0'") + .help("A version constraint to search for (optional), e.g., '=1.0.0'") ) .arg(Arg::new("dependency_type") .required(false) @@ -553,11 +579,11 @@ pub fn cli() -> Command { .value_name("PACKAGE_NAME") .help("The name of the package") ) - .arg(Arg::new("package_version_constraint") + .arg(Arg::new("package_version") .required(true) .index(2) - .value_name("VERSION_CONSTRAINT") - .help("A version constraint to search for (optional), E.G. '=1.0.0'") + .value_name("PACKAGE_VERSION") + .help("The version of the package") ) ) @@ -573,21 +599,21 @@ pub fn cli() -> Command { .required(false) .index(2) .value_name("VERSION_CONSTRAINT") - .help("A version constraint to search for (optional), E.G. '=1.0.0'") + .help("A version constraint to match the package version against (optional), e.g., '=1.0.0'") ) .arg(Arg::new("no_script_filter") .action(ArgAction::SetTrue) .long("no-script-filter") .short('S') .required(false) - .help("Don't check for script equality. Can cause unexact results.") + .help("Don't check for script equality. Can cause inexact results.") ) .arg(Arg::new("staging_dir") .required(false) .long("staging-dir") .value_name("PATH") .value_parser(dir_exists_validator) - .help("Also consider this staging dir when searching for artifacts") + .help("Also consider this staging directory when searching for artifacts") ) .arg(Arg::new("env_filter") .required(false) @@ -619,7 +645,7 @@ pub fn cli() -> Command { .required(false) .index(2) .value_name("VERSION_CONSTRAINT") - .help("A version constraint to search for (optional), E.G. '=1.0.0'") + .help("A version constraint to match the package version against (optional), e.g., '=1.0.0'") ) .arg(Arg::new("terse") @@ -740,11 +766,11 @@ pub fn cli() -> Command { .value_name("PKG") .help("Verify the sources of this package (optional, if left out, all packages are checked)") ) - .arg(Arg::new("package_version") + .arg(Arg::new("package_version_constraint") .required(false) .index(2) - .value_name("VERSION") - .help("Verify the sources of this package version (optional, if left out, all packages are checked)") + .value_name("VERSION_CONSTRAINT") + .help("Verify the sources of matching package versions (optional, if left out, all versions are checked)") ) .arg(Arg::new("matching") @@ -768,13 +794,13 @@ pub fn cli() -> Command { .required(false) .index(1) .value_name("PKG") - .help("Verify the sources of this package (optional, if left out, all packages are checked)") + .help("Show the URL of this package (or all packages, if omitted)") ) - .arg(Arg::new("package_version") + .arg(Arg::new("package_version_constraint") .required(false) .index(2) - .value_name("VERSION") - .help("Verify the sources of this package version (optional, if left out, all packages are checked)") + .value_name("VERSION_CONSTRAINT") + .help("Show the URLs of matching package versions (or all versions, if omitted)") ) ) .subcommand(Command::new("download") @@ -785,24 +811,24 @@ pub fn cli() -> Command { .value_name("PKG") .help("Download the sources of this package") ) - .arg(Arg::new("package_version") + .arg(Arg::new("package_version_constraint") .required(false) .index(2) .value_name("VERSION_CONSTRAINT") - .help("Download the sources of this package version (optional, if left out, all packages are downloaded)") + .help("Download the sources of matching package versions (optional, if left out, all versions are downloaded)") ) .arg(Arg::new("force") .action(ArgAction::SetTrue) .required(false) .long("force") - .help("Overwrite existing cache entry") + .help("Overwrite existing cache entry (the downloaded source file(s) will be deleted before the re-download starts)") ) .arg(Arg::new("matching") .required(false) .long("matching") .value_name("REGEX") - .help("Download all packages matching a regex with their name") + .help("Download all packages where the package name matches REGEX") ) .group(ArgGroup::new("download-one-or-many") @@ -815,21 +841,22 @@ pub fn cli() -> Command { .long("timeout") .value_name("TIMEOUT") .help("Set timeout for download in seconds") + .value_parser(clap::value_parser!(u64)) ) ) .subcommand(Command::new("of") - .about("Get the pathes of the sources of a package") + .about("Get the paths of the sources of a package") .arg(Arg::new("package_name") .required(false) .index(1) .value_name("PKG") - .help("Get the source file pathes for this package") + .help("Get the source file paths for this package (or all packages, if omitted)") ) - .arg(Arg::new("package_version") + .arg(Arg::new("package_version_constraint") .required(false) .index(2) - .value_name("VERSION") - .help("Get the source file pathes for the package in this version") + .value_name("VERSION_CONSTRAINT") + .help("Get the source file paths for the package in matching versions (or all versions, if omitted)") ) ) ) @@ -853,7 +880,7 @@ pub fn cli() -> Command { ) .arg(Arg::new("package_name") - .required(false) + .required(true) .index(1) .value_name("PKG") .help("The name of the package") @@ -861,7 +888,7 @@ pub fn cli() -> Command { ) .arg(Arg::new("package_version") - .required(false) + .required(true) .index(2) .value_name("VERSION") .help("The exact version of the package (string match)") @@ -875,7 +902,8 @@ pub fn cli() -> Command { .required(true) .index(1) .value_name("SUBMIT") - .help("The submit uuid from which to release a package") + .help("The submit UUID from which to release a package") + .value_parser(uuid::Uuid::parse_str) ) .arg(Arg::new("release_store_name") .required(true) @@ -891,18 +919,7 @@ pub fn cli() -> Command { .required(false) .index(2) .value_name("PKG") - .help("The name of the package") - .conflicts_with("all-packages") - ) - .arg(Arg::new("all-packages") - .required(false) - .long("all") - .help("Release all packages") - .conflicts_with("package_name") - ) - .group(ArgGroup::new("package") - .args(["package_name", "all-packages"]) - .required(true) // one of these is required + .help("The name of the package (or, if omitted, release all packages of the submit)") ) .arg(Arg::new("package_version") .required(false) @@ -920,7 +937,7 @@ pub fn cli() -> Command { .action(ArgAction::SetTrue) .required(false) .long("non-interactive") - .help("Dont be interactive (only with --update at the moment)") + .help("Don't be interactive (only with --update at the moment)") .requires("package_do_update") ) .arg(Arg::new("quiet") @@ -928,7 +945,7 @@ pub fn cli() -> Command { .required(false) .long("quiet") .short('q') - .help("Don't print pathes to released filesfiles after releases are complete") + .help("Don't print the paths to released files after releases are complete") ) ) @@ -940,13 +957,13 @@ pub fn cli() -> Command { .required(false) .index(1) .value_name("NAME") - .help("Package name to lint (if not present, every package will be linted") + .help("Package name to lint (if not present, every package will be linted)") ) - .arg(Arg::new("package_version") + .arg(Arg::new("package_version_constraint") .required(false) .index(2) .value_name("VERSION_CONSTRAINT") - .help("A version constraint to search for (optional), E.G. '=1.0.0'") + .help("A version constraint to match the package version against (optional), e.g., '=1.0.0'") ) ) @@ -956,13 +973,13 @@ pub fn cli() -> Command { .required(true) .index(1) .value_name("NAME") - .help("Package name to lint (if not present, every package will be linted") + .help("Package name to print the dependency tree of") ) - .arg(Arg::new("package_version") + .arg(Arg::new("package_version_constraint") .required(false) .index(2) .value_name("VERSION_CONSTRAINT") - .help("A version constraint to search for (optional), E.G. '=1.0.0'") + .help("A version constraint to search for (optional), e.g., '=1.0.0'") ) .arg(Arg::new("image") .required(false) @@ -991,6 +1008,30 @@ pub fn cli() -> Command { conditions on dependencies. "#)) ) + .arg(Arg::new("dot") + .action(ArgAction::SetTrue) + .required(false) + .long("dot") + .help("Output the dependency DAG in the Graphviz DOT format") + .conflicts_with("serial-buildorder") + ) + .arg(Arg::new("serial-buildorder") + .action(ArgAction::SetTrue) + .required(false) + .long("serial-buildorder") + .help("Output the dependencies in a serial build order (flattened tree)") + .long_help(indoc::indoc!(r#" + A serial build order ensures that dependencies are built one at a time + in a specific sequence. This approach follows a reversed topological ordering, + where dependencies (or children) are listed and built first. Each dependency + must be fully built before the next one begins, ensuring all components are + available when needed. + + Keep in mind that the actual build order remains parallel, this serialized + output is mainly useful for debugging purposes. + "#)) + .conflicts_with("dot") + ) ) .subcommand(Command::new("metrics") @@ -998,7 +1039,7 @@ pub fn cli() -> Command { ) .subcommand(Command::new("endpoint") - .about("Endpoint maintentance commands") + .about("Endpoint maintenance commands") .arg(Arg::new("endpoint_name") .required(false) .index(1) @@ -1015,6 +1056,7 @@ pub fn cli() -> Command { .value_name("N") .default_value("10") .help("How often to ping") + .value_parser(clap::value_parser!(u64)) ) .arg(Arg::new("ping_sleep") .required(false) @@ -1022,6 +1064,7 @@ pub fn cli() -> Command { .value_name("N") .default_value("1") .help("How long to sleep between pings") + .value_parser(clap::value_parser!(u64)) ) ) .subcommand(Command::new("stats") @@ -1050,7 +1093,7 @@ pub fn cli() -> Command { .short('t') .value_name("TIMEOUT") .help("Timeout in seconds") - .value_parser(parse_u64) + .value_parser(clap::value_parser!(u64)) ) ) .subcommand(Command::new("list") @@ -1071,6 +1114,7 @@ pub fn cli() -> Command { .arg(Arg::new("filter_image") .required(false) + .short('I') .long("image") .value_name("IMAGE") .help("List only containers of IMAGE") @@ -1092,7 +1136,7 @@ pub fn cli() -> Command { .long("limit") .value_name("LIMIT") .help("Only list LIMIT processes for each container") - .value_parser(parse_usize) + .value_parser(clap::value_parser!(usize)) ) ) ) @@ -1134,7 +1178,8 @@ pub fn cli() -> Command { .required(false) .long("timeout") .value_name("DURATION") - .help("Timeout") + .help("Timeout in seconds") + .value_parser(clap::value_parser!(u64)) ) ) .subcommand(Command::new("exec") @@ -1244,7 +1289,7 @@ fn env_pass_validator(s: &str) -> Result { Err(s) } Ok((k, v)) => { - debug!("Env pass valiation: '{}={}'", k, v); + debug!("Env pass validation: '{}={}'", k, v); Ok(s.to_owned()) } } @@ -1336,18 +1381,6 @@ fn parse_date_from_string(s: &str) -> std::result::Result { .map(|_| s.to_owned()) } -fn parse_usize(s: &str) -> std::result::Result { - usize::from_str(s) - .map_err(|e| e.to_string()) - .map(|_| s.to_owned()) -} - -fn parse_u64(s: &str) -> std::result::Result { - u64::from_str(s) - .map_err(|e| e.to_string()) - .map(|_| s.to_owned()) -} - #[cfg(test)] mod tests { use super::env_pass_validator; diff --git a/src/commands/build.rs b/src/commands/build.rs index 41cc135f..2c5152f2 100644 --- a/src/commands/build.rs +++ b/src/commands/build.rs @@ -31,10 +31,12 @@ use diesel::RunQueryDsl; use itertools::Itertools; use tokio::sync::RwLock; use tokio_stream::StreamExt; +use tracing::Instrument; use tracing::{debug, info, trace, warn}; use uuid::Uuid; use crate::config::*; +use crate::db::models::{EnvVar, GitHash, Image, Job, Package, Submit}; use crate::filestore::path::StoreRoot; use crate::filestore::ReleaseStore; use crate::filestore::StagingStore; @@ -49,6 +51,7 @@ use crate::package::Shebang; use crate::repository::Repository; use crate::schema; use crate::source::SourceCache; +use crate::util::docker::ImageNameLookup; use crate::util::progress::ProgressBars; use crate::util::EnvironmentVariableName; @@ -63,8 +66,11 @@ pub async fn build( repo: Repository, repo_path: &Path, ) -> Result<()> { - use crate::db::models::{EnvVar, GitHash, Image, Job, Package, Submit}; - use crate::util::docker::resolve_image_name; + let command_span = tracing::debug_span!("command-build"); + + let loading_span = tracing::debug_span!(parent: &command_span, "loading"); + // There's no async code for a long time in this function, so this is safe. + let loading_span_guard = loading_span.enter(); let git_repo = git2::Repository::open(repo_path) .with_context(|| anyhow!("Opening repository at {}", repo_path.display()))?; @@ -78,9 +84,10 @@ pub async fn build( .unwrap_or_else(|| config.shebang().clone()) }); + let image_name_lookup = ImageNameLookup::create(config.docker().images())?; let image_name = matches .get_one::("image") - .map(|s| resolve_image_name(s, config.docker().images())) + .map(|s| image_name_lookup.expand(s)) .unwrap()?; // safe by clap debug!("Getting repository HEAD"); @@ -110,10 +117,10 @@ pub async fn build( }) .collect::>(); { - // Because we're loading always sequencially, to have a bit more spread over the endpoints, + // Because we're loading always sequentially, to have a bit more spread over the endpoints, // shuffle the endpoints here. Not a perfect solution, but a working one. use rand::seq::SliceRandom; - let mut rng = rand::thread_rng(); + let mut rng = rand::rng(); endpoint_configurations.shuffle(&mut rng); } info!("Endpoint config build"); @@ -151,10 +158,10 @@ pub async fn build( // We only support building one package per call. // Everything else is invalid if packages.len() > 1 { - return Err(anyhow!( + anyhow::bail!( "Found multiple packages ({}). Cannot decide which one to build", packages.len() - )); + ); } let package = *packages .first() @@ -181,6 +188,8 @@ pub async fn build( }) .collect::>>()?; + drop(loading_span_guard); + let (staging_store, staging_dir, submit_id) = { let bar_staging_loading = progressbars.bar()?; @@ -188,6 +197,7 @@ pub async fn build( matches.get_one::("staging_dir").map(PathBuf::from) { info!( + parent: &loading_span, "Setting staging dir to {} for this run", staging_dir.display() ); @@ -214,10 +224,14 @@ pub async fn build( }; if !p.is_dir() { - tokio::fs::create_dir_all(&p).await?; + tokio::fs::create_dir_all(&p) + .instrument( + tracing::trace_span!(parent: &loading_span, "Creating directories", path = ?p), + ) + .await?; } - debug!("Loading staging directory: {}", p.display()); + debug!(parent: &loading_span, "Loading staging directory: {}", p.display()); let r = StagingStore::load(StoreRoot::new(p.clone())?, &bar_staging_loading); if r.is_ok() { bar_staging_loading.finish_with_message("Loaded staging successfully"); @@ -249,19 +263,20 @@ pub async fn build( let source_cache = SourceCache::new(config.source_cache_root().clone()); if matches.get_flag("no_verification") { - warn!("No hash verification will be performed"); + warn!(parent: &loading_span, "No hash verification will be performed"); } else { crate::commands::source::verify_impl( dag.all_packages().into_iter(), &source_cache, &progressbars, ) + .instrument(tracing::trace_span!(parent: &loading_span, "verify source hashes")) .await?; } // linting the package scripts if matches.get_flag("no_lint") { - warn!("No script linting will be performed!"); + warn!(parent: &loading_span, "No script linting will be performed!"); } else if let Some(linter) = crate::ui::find_linter_command(repo_root, config)? { let all_packages = dag.all_packages(); let bar = progressbars.bar()?; @@ -271,7 +286,7 @@ pub async fn build( let iter = all_packages.into_iter(); crate::commands::util::lint_packages(iter, &linter, config, bar).await?; } else { - warn!("No linter set in configuration, no script linting will be performed!"); + warn!(parent: &loading_span, "No linter set in configuration, no script linting will be performed!"); } // linting dag.all_packages() @@ -279,23 +294,23 @@ pub async fn build( .map(|pkg| { if let Some(allowlist) = pkg.allowed_images() { if !allowlist.contains(&image_name) { - return Err(anyhow!( + anyhow::bail!( "Package {} {} is only allowed on: {}", pkg.name(), pkg.version(), allowlist.iter().join(", ") - )); + ); } } if let Some(deniedlist) = pkg.denied_images() { if deniedlist.iter().any(|denied| image_name == *denied) { - return Err(anyhow!( + anyhow::bail!( "Package {} {} is not allowed to be built on {}", pkg.name(), pkg.version(), image_name - )); + ); } } @@ -303,7 +318,10 @@ pub async fn build( }) .collect::>>()?; - trace!("Setting up database jobs for Package, GitHash, Image"); + drop(loading_span); + let submit_span = tracing::debug_span!(parent: &command_span, "submit"); + + trace!(parent: &submit_span, "Setting up database jobs for Package, GitHash, Image"); let db_package = async { Package::create_or_fetch(&mut database_pool.get().unwrap(), package) }; let db_githash = async { GitHash::create_or_fetch(&mut database_pool.get().unwrap(), &hash_str) }; @@ -322,14 +340,14 @@ pub async fn build( .await }; - trace!("Running database jobs for Package, GitHash, Image"); + trace!(parent: &submit_span, "Running database jobs for Package, GitHash, Image"); let (db_package, db_githash, db_image, db_envs) = tokio::join!(db_package, db_githash, db_image, db_envs); let (db_package, db_githash, db_image, _) = (db_package?, db_githash?, db_image?, db_envs?); - trace!("Database jobs for Package, GitHash, Image finished successfully"); - trace!("Creating Submit in database"); + trace!(parent: &submit_span, "Database jobs for Package, GitHash, Image finished successfully"); + trace!(parent: &submit_span, "Creating Submit in database"); let submit = Submit::create( &mut database_pool.get().unwrap(), &now, @@ -339,6 +357,7 @@ pub async fn build( &db_githash, )?; trace!( + parent: &submit_span, "Creating Submit in database finished successfully: {:?}", submit ); @@ -364,13 +383,16 @@ pub async fn build( writeln!(outlock, "On repo hash: {}", mkgreen(&db_githash.hash))?; } - trace!("Setting up job sets"); + trace!(parent: &submit_span, "Setting up job sets"); let resources: Vec = additional_env.into_iter().map(JobResource::from).collect(); let jobdag = crate::job::Dag::from_package_dag(dag, shebang, image_name, phases.clone(), resources); - trace!("Setting up job sets finished successfully"); + trace!(parent: &submit_span, "Setting up job sets finished successfully"); + drop(submit_span); + + let build_span = tracing::debug_span!(parent: &command_span, "build"); - trace!("Setting up Orchestrator"); + trace!(parent: &build_span, "Setting up Orchestrator"); let orch = OrchestratorSetup::builder() .progress_generator(progressbars) .endpoint_config(endpoint_configurations) @@ -389,11 +411,12 @@ pub async fn build( .repository(git_repo) .build() .setup() + .instrument(build_span.clone()) .await?; - info!("Running orchestrator..."); + info!(parent: &build_span, "Running orchestrator..."); let mut artifacts = vec![]; - let errors = orch.run(&mut artifacts).await?; + let errors = orch.run(&mut artifacts).instrument(build_span).await?; let out = std::io::stdout(); let mut outlock = out.lock(); diff --git a/src/commands/db.rs b/src/commands/db.rs index a3a92b1b..fc9cd54c 100644 --- a/src/commands/db.rs +++ b/src/commands/db.rs @@ -10,7 +10,6 @@ //! Implementation of the 'db' subcommand -use std::collections::HashMap; use std::io::Write; use std::path::PathBuf; use std::process::Command; @@ -32,7 +31,7 @@ use diesel_migrations::EmbeddedMigrations; use diesel_migrations::HarnessWithOutput; use diesel_migrations::MigrationHarness; use itertools::Itertools; -use tracing::{debug, info, trace, warn}; +use tracing::{debug, info, trace}; use crate::commands::util::get_date_filter; use crate::config::Configuration; @@ -41,7 +40,7 @@ use crate::db::DbConnectionConfig; use crate::log::JobResult; use crate::package::Script; use crate::schema; -use crate::util::docker::resolve_image_name; +use crate::util::docker::ImageNameLookup; pub const MIGRATIONS: EmbeddedMigrations = embed_migrations!("migrations"); @@ -51,18 +50,22 @@ pub fn db( config: &Configuration, matches: &ArgMatches, ) -> Result<()> { + let default_limit = config.database_default_query_limit(); + match matches.subcommand() { Some(("cli", matches)) => cli(db_connection_config, matches), Some(("setup", _matches)) => setup(db_connection_config), - Some(("artifacts", matches)) => artifacts(db_connection_config, matches), + Some(("artifacts", matches)) => artifacts(db_connection_config, matches, default_limit), Some(("envvars", matches)) => envvars(db_connection_config, matches), Some(("images", matches)) => images(db_connection_config, matches), - Some(("submit", matches)) => submit(db_connection_config, matches), - Some(("submits", matches)) => submits(db_connection_config, matches), - Some(("jobs", matches)) => jobs(db_connection_config, config, matches), + Some(("submit", matches)) => submit(db_connection_config, config, matches), + Some(("submits", matches)) => submits(db_connection_config, config, matches, default_limit), + Some(("jobs", matches)) => jobs(db_connection_config, config, matches, default_limit), Some(("job", matches)) => job(db_connection_config, config, matches), Some(("log-of", matches)) => log_of(db_connection_config, matches), - Some(("releases", matches)) => releases(db_connection_config, config, matches), + Some(("releases", matches)) => { + releases(db_connection_config, config, matches, default_limit) + } Some((other, _)) => Err(anyhow!("Unknown subcommand: {}", other)), None => Err(anyhow!("No subcommand")), } @@ -92,12 +95,12 @@ fn cli(db_connection_config: DbConnectionConfig<'_>, matches: &ArgMatches) -> Re info!("psql exited successfully"); Ok(()) } else { - Err(anyhow!("psql did not exit successfully")) - .with_context(|| match String::from_utf8(out.stderr) { + Err(anyhow!("psql did not exit successfully")).with_context(|| { + match String::from_utf8(out.stderr) { Ok(log) => anyhow!("{}", log), Err(e) => anyhow!("Cannot parse log into valid UTF-8: {}", e), - }) - .map_err(Error::from) + } + }) } }) } @@ -124,12 +127,12 @@ fn cli(db_connection_config: DbConnectionConfig<'_>, matches: &ArgMatches) -> Re info!("pgcli exited successfully"); Ok(()) } else { - Err(anyhow!("pgcli did not exit successfully")) - .with_context(|| match String::from_utf8(out.stderr) { + Err(anyhow!("pgcli did not exit successfully")).with_context(|| { + match String::from_utf8(out.stderr) { Ok(log) => anyhow!("{}", log), Err(e) => anyhow!("Cannot parse log into valid UTF-8: {}", e), - }) - .map_err(Error::from) + } + }) } }) } @@ -160,19 +163,27 @@ fn setup(conn_cfg: DbConnectionConfig<'_>) -> Result<()> { .map_err(|e| anyhow!(e)) } +/// Helper function to get the LIMIT for DB queries based on the default value and CLI parameters +fn get_limit(matches: &ArgMatches, default_limit: &usize) -> Result { + let limit = *matches.get_one::("limit").unwrap_or(default_limit); + if limit == 0 { + Ok(i64::MAX) + } else { + Ok(i64::try_from(limit)?) + } +} + /// Implementation of the "db artifacts" subcommand -fn artifacts(conn_cfg: DbConnectionConfig<'_>, matches: &ArgMatches) -> Result<()> { +fn artifacts( + conn_cfg: DbConnectionConfig<'_>, + matches: &ArgMatches, + default_limit: &usize, +) -> Result<()> { use crate::schema::artifacts::dsl; let csv = matches.get_flag("csv"); - let job_uuid = matches - .get_one::("job_uuid") - .map(|s| uuid::Uuid::parse_str(s.as_ref())) - .transpose()?; - let limit = matches - .get_one::("limit") - .map(|s| s.parse::()) - .transpose()?; + let job_uuid = matches.get_one::("job_uuid"); + let limit = get_limit(matches, default_limit)?; let hdrs = crate::commands::util::mk_header(vec!["Path", "Released", "Job"]); let mut conn = conn_cfg.establish_connection()?; @@ -180,13 +191,11 @@ fn artifacts(conn_cfg: DbConnectionConfig<'_>, matches: &ArgMatches) -> Result<( .order_by(schema::artifacts::id.desc()) // required for the --limit implementation .inner_join(schema::jobs::table) .left_join(schema::releases::table) - .into_boxed(); + .into_boxed() + .limit(limit); if let Some(job_uuid) = job_uuid { query = query.filter(schema::jobs::dsl::uuid.eq(job_uuid)) }; - if let Some(limit) = limit { - query = query.limit(limit) - }; let data = query .load::<(models::Artifact, models::Job, Option)>(&mut conn)? @@ -254,16 +263,15 @@ fn images(conn_cfg: DbConnectionConfig<'_>, matches: &ArgMatches) -> Result<()> } /// Implementation of the "db submit" subcommand -fn submit(conn_cfg: DbConnectionConfig<'_>, matches: &ArgMatches) -> Result<()> { +fn submit( + conn_cfg: DbConnectionConfig<'_>, + config: &Configuration, + matches: &ArgMatches, +) -> Result<()> { let mut conn = conn_cfg.establish_connection()?; - let submit_id = matches - .get_one::("submit") - .map(|s| uuid::Uuid::from_str(s.as_ref())) - .transpose() - .context("Parsing submit UUID")? - .unwrap(); // safe by clap - - let submit = models::Submit::with_id(&mut conn, &submit_id) + let submit_id = matches.get_one::("submit").unwrap(); // safe by clap + + let submit = models::Submit::with_id(&mut conn, submit_id) .with_context(|| anyhow!("Loading submit '{}' from DB", submit_id))?; let githash = models::GitHash::with_id(&mut conn, submit.repo_hash_id) @@ -317,6 +325,8 @@ fn submit(conn_cfg: DbConnectionConfig<'_>, matches: &ArgMatches) -> Result<()> n_jobs_err = jobs_err.to_string().red(), )?; + let image_name_lookup = ImageNameLookup::create(config.docker().images())?; + let header = crate::commands::util::mk_header( [ "Job", @@ -350,7 +360,7 @@ fn submit(conn_cfg: DbConnectionConfig<'_>, matches: &ArgMatches) -> Result<()> package.version.cyan(), job.container_hash.normal(), endpoint.name.normal(), - image.name.normal(), + image_name_lookup.shorten(&image.name).normal(), ]) }) .collect::>>>()?; @@ -358,12 +368,14 @@ fn submit(conn_cfg: DbConnectionConfig<'_>, matches: &ArgMatches) -> Result<()> } /// Implementation of the "db submits" subcommand -fn submits(conn_cfg: DbConnectionConfig<'_>, matches: &ArgMatches) -> Result<()> { +fn submits( + conn_cfg: DbConnectionConfig<'_>, + config: &Configuration, + matches: &ArgMatches, + default_limit: &usize, +) -> Result<()> { let csv = matches.get_flag("csv"); - let limit = matches - .get_one::("limit") - .map(|s| s.parse::()) - .transpose()?; + let limit = get_limit(matches, default_limit)?; let hdrs = crate::commands::util::mk_header(vec![ "Time", "UUID", @@ -387,7 +399,9 @@ fn submits(conn_cfg: DbConnectionConfig<'_>, matches: &ArgMatches) -> Result<()> }; let query = if let Some(image) = matches.get_one::("image") { - query.filter(schema::images::name.eq(image)) + let image_name_lookup = ImageNameLookup::create(config.docker().images())?; + let image = image_name_lookup.expand(image)?; + query.filter(schema::images::name.eq(image.as_ref().to_string())) } else { query }; @@ -406,13 +420,8 @@ fn submits(conn_cfg: DbConnectionConfig<'_>, matches: &ArgMatches) -> Result<()> .inner_join( schema::packages::table.on(schema::jobs::package_id.eq(schema::packages::id)), ) - .filter(schema::packages::name.eq(&pkgname)); - - let query = if let Some(limit) = limit { - query.limit(limit) - } else { - query - }; + .filter(schema::packages::name.eq(&pkgname)) + .limit(limit); // Only load the IDs of the submits, so we can later use them to filter the submits let submit_ids = query.select(schema::submits::id).load::(&mut conn)?; @@ -428,26 +437,12 @@ fn submits(conn_cfg: DbConnectionConfig<'_>, matches: &ArgMatches) -> Result<()> .load::<(models::Submit, models::Package)>(&mut conn)? } else if let Some(pkgname) = matches.get_one::("for_pkg") { // Get all submits _for_ the package - let query = query - .inner_join({ - schema::packages::table - .on(schema::submits::requested_package_id.eq(schema::packages::id)) - }) - .filter(schema::packages::dsl::name.eq(&pkgname)); - - if let Some(limit) = limit { - query.limit(limit) - } else { - query - } - .select((schema::submits::all_columns, schema::packages::all_columns)) - .load::<(models::Submit, models::Package)>(&mut conn)? - } else if let Some(limit) = limit { query .inner_join({ schema::packages::table .on(schema::submits::requested_package_id.eq(schema::packages::id)) }) + .filter(schema::packages::dsl::name.eq(&pkgname)) .select((schema::submits::all_columns, schema::packages::all_columns)) .limit(limit) .load::<(models::Submit, models::Package)>(&mut conn)? @@ -458,6 +453,7 @@ fn submits(conn_cfg: DbConnectionConfig<'_>, matches: &ArgMatches) -> Result<()> .on(schema::submits::requested_package_id.eq(schema::packages::id)) }) .select((schema::submits::all_columns, schema::packages::all_columns)) + .limit(limit) .load::<(models::Submit, models::Package)>(&mut conn)? }; @@ -491,10 +487,11 @@ fn jobs( conn_cfg: DbConnectionConfig<'_>, config: &Configuration, matches: &ArgMatches, + default_limit: &usize, ) -> Result<()> { let csv = matches.get_flag("csv"); let hdrs = crate::commands::util::mk_header(vec![ - "Submit", "Job", "Time", "Host", "Ok?", "Package", "Version", "Distro", + "Submit", "Job", "Time", "Host", "Ok?", "Package", "Version", "Distro", "Type", ]); let mut conn = conn_cfg.establish_connection()?; let older_than_filter = get_date_filter("older_than", matches)?; @@ -505,19 +502,17 @@ fn jobs( .inner_join(schema::endpoints::table) .inner_join(schema::packages::table) .inner_join(schema::images::table) + .left_outer_join(schema::artifacts::table) .into_boxed(); - if let Some(submit_uuid) = matches - .get_one::("submit_uuid") - .map(|s| uuid::Uuid::parse_str(s.as_ref())) - .transpose()? - { + if let Some(submit_uuid) = matches.get_one::("submit_uuid") { sel = sel.filter(schema::submits::uuid.eq(submit_uuid)) } + let image_name_lookup = ImageNameLookup::create(config.docker().images())?; if let Some(image_name) = matches .get_one::("image") - .map(|s| resolve_image_name(s, config.docker().images())) + .map(|s| image_name_lookup.expand(s)) .transpose()? { sel = sel.filter(schema::images::name.eq(image_name.as_ref().to_string())) @@ -559,14 +554,6 @@ fn jobs( sel = sel.filter(schema::submits::dsl::submit_time.gt(datetime)) } - if let Some(limit) = matches - .get_one::("limit") - .map(|s| s.parse::()) - .transpose()? - { - sel = sel.limit(limit) - } - if let Some(ep_name) = matches.get_one::("endpoint") { sel = sel.filter(schema::endpoints::name.eq(ep_name)) } @@ -575,28 +562,38 @@ fn jobs( sel = sel.filter(schema::packages::name.eq(pkg_name)) } - let mut image_short_name_map = HashMap::new(); - for image in config.docker().images() { - image_short_name_map.insert(image.name.clone(), image.short_name.clone()); - } + let limit = get_limit(matches, default_limit)?; + + let image_name_lookup = ImageNameLookup::create(config.docker().images())?; let data = sel .order_by(schema::jobs::id.desc()) // required for the --limit implementation + .limit(limit) .load::<( models::Job, models::Submit, models::Endpoint, models::Package, models::Image, + Option, )>(&mut conn)? .into_iter() .rev() // required for the --limit implementation - .map(|(job, submit, ep, package, image)| { + .map(|(job, submit, ep, package, image, artifact)| { let success = is_job_successfull(&job)? .map(|b| if b { "yes" } else { "no" }) .map(String::from) .unwrap_or_else(|| String::from("?")); - let image_name = crate::util::docker::ImageName::from(image.name); + let artifact_type = if let Some(artifact) = artifact { + artifact + .path + .split(".") + .last() + .map(str::to_uppercase) + .unwrap_or(String::from("?")) + } else { + String::from("-") + }; Ok(vec![ submit.uuid.to_string(), @@ -606,10 +603,8 @@ fn jobs( success, package.name, package.version, - image_short_name_map - .get(&image_name) - .unwrap_or(&image_name) - .to_string(), + image_name_lookup.shorten(&image.name), + artifact_type, ]) }) .collect::>>()?; @@ -636,11 +631,7 @@ fn job( let show_script = matches.get_flag("show_script"); let csv = matches.get_flag("csv"); let mut conn = conn_cfg.establish_connection()?; - let job_uuid = matches - .get_one::("job_uuid") - .map(|s| uuid::Uuid::parse_str(s.as_ref())) - .transpose()? - .unwrap(); + let job_uuid = matches.get_one::("job_uuid").unwrap(); let data = schema::jobs::table .filter(schema::jobs::dsl::uuid.eq(job_uuid)) @@ -660,7 +651,7 @@ fn job( let parsed_log = crate::log::ParsedLog::from_str(&data.0.log_text)?; trace!("Parsed log = {:?}", parsed_log); let success = parsed_log.is_successfull(); - trace!("log successfull = {:?}", success); + trace!("log successful = {:?}", success); if csv { let hdrs = crate::commands::util::mk_header(vec![ @@ -804,11 +795,7 @@ fn job( /// Implementation of the subcommand "db log-of" fn log_of(conn_cfg: DbConnectionConfig<'_>, matches: &ArgMatches) -> Result<()> { let mut conn = conn_cfg.establish_connection()?; - let job_uuid = matches - .get_one::("job_uuid") - .map(|s| uuid::Uuid::parse_str(s.as_ref())) - .transpose()? - .unwrap(); + let job_uuid = matches.get_one::("job_uuid").unwrap(); let out = std::io::stdout(); let mut lock = out.lock(); @@ -832,9 +819,11 @@ pub fn releases( conn_cfg: DbConnectionConfig<'_>, config: &Configuration, matches: &ArgMatches, + default_limit: &usize, ) -> Result<()> { let csv = matches.get_flag("csv"); let mut conn = conn_cfg.establish_connection()?; + let limit = get_limit(matches, default_limit)?; let header = crate::commands::util::mk_header(["Package", "Version", "Date", "Path"].to_vec()); let mut query = schema::jobs::table .inner_join(schema::packages::table) @@ -846,9 +835,8 @@ pub fn releases( schema::release_stores::table .on(schema::release_stores::id.eq(schema::releases::release_store_id)), ) - .order_by(schema::packages::dsl::name.asc()) - .then_order_by(schema::packages::dsl::version.asc()) - .then_order_by(schema::releases::release_date.asc()) + .order_by(schema::releases::id.desc()) // required for the --limit implementation + .limit(limit) .into_boxed(); if let Some(date) = crate::commands::util::get_date_filter("older_than", matches)? { @@ -882,28 +870,23 @@ pub fn releases( models::ReleaseStore, )>(&mut conn)? .into_iter() - .filter_map(|(art, pack, rel, rstore)| { + .map(|(art, pack, rel, rstore)| { let p = config .releases_directory() - .join(rstore.store_name) - .join(art.path); - - if p.is_file() { - Some(vec![ - pack.name, - pack.version, - rel.release_date.to_string(), - p.display().to_string(), - ]) - } else { - warn!( - "Released file for {} {} not found: {}", - pack.name, - pack.version, - p.display() - ); - None - } + .join(&rstore.store_name) + .join(&art.path); + + vec![ + pack.name, + pack.version, + rel.release_date.to_string(), + if p.is_file() { + p.display().to_string() + } else { + let relative_path = PathBuf::from(rstore.store_name).join(art.path); + format!("{} is not available locally", relative_path.display()) + }, + ] }) .collect::>>(); diff --git a/src/commands/dependencies_of.rs b/src/commands/dependencies_of.rs index 2700054d..ee46024e 100644 --- a/src/commands/dependencies_of.rs +++ b/src/commands/dependencies_of.rs @@ -12,6 +12,7 @@ use std::io::Write; +use anyhow::Context; use anyhow::Result; use clap::ArgMatches; use futures::stream::StreamExt; @@ -21,6 +22,7 @@ use tracing::trace; use crate::commands::util::getbool; use crate::config::*; use crate::package::PackageName; +use crate::package::PackageVersionConstraint; use crate::repository::Repository; use crate::ui::*; @@ -39,8 +41,20 @@ pub async fn dependencies_of( .map(PackageName::from) .unwrap(); trace!("Checking for package with name = {}", name); + let version_constraint = matches + .get_one::("package_version_constraint") + .map(|s| s.to_owned()) + .map(PackageVersionConstraint::try_from) + .transpose() + .context("Parsing package version constraint")?; + trace!( + "Checking for package with version constraint = {:?}", + version_constraint + ); - crate::util::filters::build_package_filter_by_name(name) + crate::util::filters::build_package_filter_by_name(name).and( + crate::util::filters::build_package_filter_by_version_constraint(version_constraint), + ) }; let format = config.package_print_format(); diff --git a/src/commands/endpoint.rs b/src/commands/endpoint.rs index 000bd310..e6705183 100644 --- a/src/commands/endpoint.rs +++ b/src/commands/endpoint.rs @@ -13,7 +13,6 @@ use std::collections::HashMap; use std::io::Write; use std::ops::Deref; -use std::str::FromStr; use std::sync::Arc; use anyhow::anyhow; @@ -28,6 +27,7 @@ use tracing::{debug, info, trace}; use crate::config::Configuration; use crate::config::EndpointName; use crate::endpoint::Endpoint; +use crate::util::docker::ImageNameLookup; use crate::util::progress::ProgressBars; pub async fn endpoint( @@ -71,16 +71,8 @@ async fn ping( config: &Configuration, progress_generator: ProgressBars, ) -> Result<()> { - let n_pings = matches - .get_one::("ping_n") - .map(|s| s.parse::()) - .transpose()? - .unwrap(); // safe by clap - let sleep = matches - .get_one::("ping_sleep") - .map(|s| s.parse::()) - .transpose()? - .unwrap(); // safe by clap + let n_pings = *matches.get_one::("ping_n").unwrap(); // safe by clap + let sleep = *matches.get_one::("ping_sleep").unwrap(); // safe by clap let endpoints = connect_to_endpoints(config, &endpoint_names).await?; let multibar = Arc::new({ let mp = indicatif::MultiProgress::new(); @@ -93,11 +85,10 @@ async fn ping( endpoints .iter() .map(|endpoint| { - let bar = progress_generator.bar().map(|bar| { + let bar = progress_generator.bar().inspect(|bar| { bar.set_length(n_pings); bar.set_message(format!("Pinging {}", endpoint.name())); multibar.add(bar.clone()); - bar }); async move { @@ -140,6 +131,7 @@ async fn stats( "Name", "Containers", "Images", + "Id", "Kernel", "Memory", "Memory limit", @@ -163,9 +155,8 @@ async fn stats( .collect::>() .collect::>>() .await - .map_err(|e| { + .inspect_err(|_| { bar.finish_with_message("Fetching stats errored"); - e })? .into_iter() .map(|stat| { @@ -173,6 +164,7 @@ async fn stats( stat.name, stat.containers.to_string(), stat.images.to_string(), + stat.id.to_string(), stat.kernel_version, bytesize::ByteSize::b(stat.mem_total).to_string(), stat.memory_limit.to_string(), @@ -208,12 +200,25 @@ async fn containers_list( config: &Configuration, ) -> Result<()> { let list_stopped = matches.get_flag("list_stopped"); - let filter_image = matches.get_one::("filter_image"); + let filter_image = if let Some(image) = matches.get_one::("filter_image") { + let image_name_lookup = ImageNameLookup::create(config.docker().images())?; + Some(image_name_lookup.expand(image)?.as_ref().to_string()) + } else { + None + }; let older_than_filter = crate::commands::util::get_date_filter("older_than", matches)?; let newer_than_filter = crate::commands::util::get_date_filter("newer_than", matches)?; let csv = matches.get_flag("csv"); let hdr = crate::commands::util::mk_header( - ["Endpoint", "Container id", "Image", "Created", "Status"].to_vec(), + [ + "Endpoint", + "Container id", + "Image", + "Image id", + "Created", + "Status", + ] + .to_vec(), ); let data = connect_to_endpoints(config, &endpoint_names) @@ -233,7 +238,12 @@ async fn containers_list( tpl.1 .into_iter() .filter(|stat| list_stopped || stat.state != "exited") - .filter(|stat| filter_image.map(|fim| *fim == stat.image).unwrap_or(true)) + .filter(|stat| { + filter_image + .as_ref() + .map(|fim| *fim == stat.image) + .unwrap_or(true) + }) .filter(|stat| { older_than_filter .as_ref() @@ -247,10 +257,12 @@ async fn containers_list( .unwrap_or(true) }) .map(|stat| { + // TODO: The output can become too wide (we should, e.g., try to shorten the IDs): vec![ endpoint_name.as_ref().to_owned(), stat.id, stat.image, + stat.image_id, stat.created.to_string(), stat.status, ] @@ -328,10 +340,7 @@ async fn containers_top( matches: &ArgMatches, config: &Configuration, ) -> Result<()> { - let limit = matches - .get_one::("limit") - .map(|s| usize::from_str(s.as_ref())) - .transpose()?; + let limit = matches.get_one::("limit"); let older_than_filter = crate::commands::util::get_date_filter("older_than", matches)?; let newer_than_filter = crate::commands::util::get_date_filter("newer_than", matches)?; let csv = matches.get_flag("csv"); @@ -376,7 +385,6 @@ async fn containers_top( .top(None) .await .with_context(|| anyhow!("Fetching 'top' for {}", stat.id)) - .map_err(Error::from) .map(|top| (stat.id, top)) }) .collect::>() @@ -386,7 +394,7 @@ async fn containers_top( .inspect(|(cid, _top)| trace!("Processing top of container: {}", cid)) .map(|(container_id, top)| { let processes = if let Some(limit) = limit { - top.processes.into_iter().take(limit).collect() + top.processes.into_iter().take(*limit).collect() } else { top.processes }; @@ -443,10 +451,8 @@ async fn containers_stop( let newer_than_filter = crate::commands::util::get_date_filter("newer_than", matches)?; let stop_timeout = matches - .get_one::("timeout") - .map(|s| s.parse::()) - .transpose()? - .map(std::time::Duration::from_secs); + .get_one::("timeout") + .map(|s| std::time::Duration::from_secs(*s)); let stats = connect_to_endpoints(config, &endpoint_names) .await? diff --git a/src/commands/endpoint_container.rs b/src/commands/endpoint_container.rs index 0e102a45..79243365 100644 --- a/src/commands/endpoint_container.rs +++ b/src/commands/endpoint_container.rs @@ -45,10 +45,7 @@ pub async fn container( .collect::>(); if relevant_endpoints.len() > 1 { - return Err(anyhow!( - "Found more than one container for id {}", - container_id - )); + anyhow::bail!("Found more than one container for id {}", container_id); } let relevant_endpoint = relevant_endpoints @@ -150,10 +147,8 @@ async fn stop(matches: &ArgMatches, container: Container<'_>) -> Result<()> { container .stop({ matches - .get_one::("timeout") - .map(|s| s.parse::()) - .transpose()? - .map(std::time::Duration::from_secs) + .get_one::("timeout") + .map(|t| std::time::Duration::from_secs(*t)) }) .await .map_err(Error::from) diff --git a/src/commands/env_of.rs b/src/commands/env_of.rs index 719eef77..d44f7a2d 100644 --- a/src/commands/env_of.rs +++ b/src/commands/env_of.rs @@ -10,14 +10,12 @@ //! Implementation of the 'env-of' subcommand -use std::convert::TryFrom; - use anyhow::Result; use clap::ArgMatches; use tracing::trace; use crate::package::PackageName; -use crate::package::PackageVersionConstraint; +use crate::package::PackageVersion; use crate::repository::Repository; /// Implementation of the "env_of" subcommand @@ -31,19 +29,20 @@ pub async fn env_of(matches: &ArgMatches, repo: Repository) -> Result<()> { .map(|s| s.to_owned()) .map(PackageName::from) .unwrap(); - let constraint = matches - .get_one::("package_version_constraint") + let version = matches + .get_one::("package_version") .map(|s| s.to_owned()) - .map(PackageVersionConstraint::try_from) + .map(PackageVersion::try_from) .unwrap()?; trace!( "Checking for package with name = {} and version = {:?}", name, - constraint + version ); - crate::util::filters::build_package_filter_by_name(name) - .and(crate::util::filters::build_package_filter_by_version_constraint(constraint)) + crate::util::filters::build_package_filter_by_name(name).and( + crate::util::filters::build_package_filter_by_version(version), + ) }; let mut stdout = std::io::stdout(); diff --git a/src/commands/find_artifact.rs b/src/commands/find_artifact.rs index 84b5a65e..2349291f 100644 --- a/src/commands/find_artifact.rs +++ b/src/commands/find_artifact.rs @@ -10,7 +10,6 @@ //! Implementation of the 'find-artifact' subcommand -use std::convert::TryFrom; use std::io::Write; use std::path::PathBuf; use std::sync::Arc; @@ -31,7 +30,7 @@ use crate::filestore::ReleaseStore; use crate::filestore::StagingStore; use crate::package::PackageVersionConstraint; use crate::repository::Repository; -use crate::util::docker::resolve_image_name; +use crate::util::docker::ImageNameLookup; use crate::util::progress::ProgressBars; /// Implementation of the "find_artifact" subcommand @@ -51,8 +50,7 @@ pub async fn find_artifact( .map(|s| s.to_owned()) .map(PackageVersionConstraint::try_from) .transpose() - .context("Parsing package version constraint") - .context("A valid package version constraint looks like this: '=1.0.0'")?; + .context("Parsing package version constraint")?; let env_filter = matches .get_many::("env_filter") @@ -64,9 +62,10 @@ pub async fn find_artifact( .transpose()? .unwrap_or_default(); + let image_name_lookup = ImageNameLookup::create(config.docker().images())?; let image_name = matches .get_one::("image") - .map(|s| resolve_image_name(s, config.docker().images())) + .map(|s| image_name_lookup.expand(s)) .transpose()?; debug!( @@ -164,7 +163,7 @@ pub async fn find_artifact( ((a, None), (b, None)) => a.cmp(b), } }) - .unique_by(|tpl| tpl.0.clone()) // TODO: Dont clone() + .unique_by(|tpl| tpl.0.clone()) // TODO: Don't clone() .try_for_each(|(path, releasetime)| { if let Some(time) = releasetime { writeln!(std::io::stdout(), "[{}] {}", time, path.display()) diff --git a/src/commands/find_pkg.rs b/src/commands/find_pkg.rs index d5d03ec6..ba157199 100644 --- a/src/commands/find_pkg.rs +++ b/src/commands/find_pkg.rs @@ -10,8 +10,6 @@ //! Implementation of the 'find-pkg' subcommand -use std::convert::TryFrom; - use anyhow::Context; use anyhow::Result; use clap::ArgMatches; @@ -41,8 +39,7 @@ pub async fn find_pkg( .map(|s| s.to_owned()) .map(PackageVersionConstraint::try_from) .transpose() - .context("Parsing package version constraint") - .context("A valid package version constraint looks like this: '=1.0.0'")?; + .context("Parsing package version constraint")?; let iter = repo .packages() diff --git a/src/commands/lint.rs b/src/commands/lint.rs index 4976ce39..ddbb97b4 100644 --- a/src/commands/lint.rs +++ b/src/commands/lint.rs @@ -10,7 +10,6 @@ //! Implementation of the 'lint' subcommand -use std::convert::TryFrom; use std::path::Path; use anyhow::anyhow; @@ -38,7 +37,7 @@ pub async fn lint( .map(|s| s.to_owned()) .map(PackageName::from); let pvers = matches - .get_one::("package_version") + .get_one::("package_version_constraint") .map(|s| s.to_owned()) .map(PackageVersionConstraint::try_from) .transpose()?; diff --git a/src/commands/metrics.rs b/src/commands/metrics.rs index a610aa56..32f9d3b3 100644 --- a/src/commands/metrics.rs +++ b/src/commands/metrics.rs @@ -128,24 +128,27 @@ pub async fn metrics( r#" Butido release {release} - {configured_endpoints} Configured endpoints - {configured_images} Configured images - {configured_release_stores} Configured release stores - {configured_phases} Configures phases + Configuration: + - {configured_endpoints} endpoints + - {configured_images} images + - {configured_release_stores} release stores + - {configured_phases} phases - {nfiles} files in repository - {repo_packages} packages in repository + Repository: + - {nfiles} files + - {repo_packages} packages - {n_artifacts} artifacts in database - {n_endpoints} endpoints in database - {n_envvars} envvars in database - {n_githashes} githashes in database - {n_images} images in database - {n_jobs} jobs in database - {n_packages} packages in database - {n_releasestores} releasestores in database - {n_releases} releases in database - {n_submits} submits in database + Database: + - {n_artifacts} artifacts + - {n_endpoints} endpoints + - {n_envvars} envvars + - {n_githashes} githashes + - {n_images} images + - {n_jobs} jobs + - {n_packages} packages + - {n_releasestores} releasestores + - {n_releases} releases + - {n_submits} submits "#, release = clap::crate_version!(), configured_endpoints = config.docker().endpoints().len(), diff --git a/src/commands/release.rs b/src/commands/release.rs index a8f7b041..d3e866c8 100644 --- a/src/commands/release.rs +++ b/src/commands/release.rs @@ -35,7 +35,8 @@ pub async fn release( ) -> Result<()> { match matches.subcommand() { Some(("list", matches)) => { - crate::commands::db::releases(db_connection_config, config, matches) + let default_limit = config.database_default_query_limit(); + crate::commands::db::releases(db_connection_config, config, matches, default_limit) } Some(("new", matches)) => new_release(db_connection_config, config, matches).await, Some(("rm", matches)) => rm_release(db_connection_config, config, matches).await, @@ -52,10 +53,10 @@ async fn new_release( let print_released_file_pathes = !matches.get_flag("quiet"); let release_store_name = matches.get_one::("release_store_name").unwrap(); // safe by clap if !(config.releases_directory().exists() && config.releases_directory().is_dir()) { - return Err(anyhow!( + anyhow::bail!( "Release directory does not exist or does not point to directory: {}", config.releases_directory().display() - )); + ); } let pname = matches.get_one::("package_name"); @@ -65,11 +66,7 @@ async fn new_release( debug!("Release called for: {:?} {:?}", pname, pvers); let pool = db_connection_config.establish_pool()?; - let submit_uuid = matches - .get_one::("submit_uuid") - .map(|s| uuid::Uuid::parse_str(s.as_ref())) - .transpose()? - .unwrap(); // safe by clap + let submit_uuid = matches.get_one::("submit_uuid").unwrap(); // safe by clap debug!("Release called for submit: {:?}", submit_uuid); let submit = crate::schema::submits::dsl::submits @@ -122,6 +119,10 @@ async fn new_release( }; debug!("Artifacts = {:?}", arts); + if arts.is_empty() { + anyhow::bail!("No matching artifacts found to release"); + } + arts.iter() .filter_map(|art| { art.path_buf() @@ -168,7 +169,7 @@ async fn new_release( Err(anyhow!("Not a file: {}", art_path.display())) } else { if dest_path.exists() && !do_update { - return Err(anyhow!("Does already exist: {}", dest_path.display())); + anyhow::bail!("Does already exist: {}", dest_path.display()); } else if dest_path.exists() && do_update { writeln!( std::io::stderr(), @@ -180,10 +181,10 @@ async fn new_release( .with_prompt("Continue?") .interact()? { - return Err(anyhow!( + anyhow::bail!( "Does already exist: {} and update was denied", dest_path.display() - )); + ); } } @@ -206,7 +207,6 @@ async fn new_release( .with_context(|| { anyhow!("Copying {} to {}", art_path.display(), dest_path.display()) }) - .map_err(Error::from) .and_then(|_| { debug!("Updating {:?} to set released = true", art); let rel = crate::db::models::Release::create( @@ -250,16 +250,13 @@ pub async fn rm_release( ) -> Result<()> { let release_store_name = matches.get_one::("release_store_name").unwrap(); // safe by clap if !(config.releases_directory().exists() && config.releases_directory().is_dir()) { - return Err(anyhow!( + anyhow::bail!( "Release directory does not exist or does not point to directory: {}", config.releases_directory().display() - )); + ); } if !config.release_stores().contains(release_store_name) { - return Err(anyhow!( - "Unknown release store name: {}", - release_store_name - )); + anyhow::bail!("Unknown release store name: {}", release_store_name); } let pname = matches.get_one::("package_name").unwrap(); // safe by clap @@ -297,7 +294,7 @@ pub async fn rm_release( .join(release_store_name) .join(&artifact.path); if !artifact_path.is_file() { - return Err(anyhow!("Not a file: {}", artifact_path.display())); + anyhow::bail!("Not a file: {}", artifact_path.display()); } writeln!( diff --git a/src/commands/source/download.rs b/src/commands/source/download.rs index d5d9025a..5e60aa29 100644 --- a/src/commands/source/download.rs +++ b/src/commands/source/download.rs @@ -8,7 +8,7 @@ // SPDX-License-Identifier: EPL-2.0 // -use std::convert::TryFrom; +use std::concat; use std::path::PathBuf; use std::sync::Arc; @@ -20,7 +20,7 @@ use clap::ArgMatches; use tokio::io::AsyncWriteExt; use tokio::sync::Mutex; use tokio_stream::StreamExt; -use tracing::{debug, info, trace, warn}; +use tracing::{info, trace, warn}; use crate::config::*; use crate::package::PackageName; @@ -30,6 +30,7 @@ use crate::source::*; use crate::util::progress::ProgressBars; const NUMBER_OF_MAX_CONCURRENT_DOWNLOADS: usize = 100; +const APP_USER_AGENT: &str = concat! {env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION")}; /// A wrapper around the indicatif::ProgressBar /// @@ -85,11 +86,15 @@ impl ProgressWrapper { async fn set_message(&self) { let bar = self.bar.lock().await; - bar.set_message(format!("Downloading ({current_bytes}/{sum_bytes} bytes, {dlfinished}/{dlsum} downloads finished)", - current_bytes = self.current_bytes, - sum_bytes = self.sum_bytes, - dlfinished = self.finished_downloads, - dlsum = self.download_count)); + let current_mbytes = (self.current_bytes as f64) / 1_000_000_f64; + let sum_mbytes = (self.sum_bytes as f64) / 1_000_000_f64; + bar.set_message(format!( + "Downloading ({:.2}/{:.2} MB, {dlfinished}/{dlsum} downloads finished)", + current_mbytes, + sum_mbytes, + dlfinished = self.finished_downloads, + dlsum = self.download_count + )); } async fn success(&self) { @@ -116,8 +121,9 @@ async fn perform_download( ) -> Result<()> { trace!("Downloading: {:?}", source); - let client_builder = - reqwest::Client::builder().redirect(reqwest::redirect::Policy::limited(10)); + let client_builder = reqwest::Client::builder() + .user_agent(APP_USER_AGENT) + .redirect(reqwest::redirect::Policy::limited(10)); let client_builder = if let Some(to) = timeout { client_builder.timeout(std::time::Duration::from_secs(to)) @@ -203,11 +209,7 @@ pub async fn download( progressbars: ProgressBars, ) -> Result<()> { let force = matches.get_flag("force"); - let timeout = matches - .get_one::("timeout") - .map(|s| s.parse::()) - .transpose() - .context("Parsing timeout argument to integer")?; + let timeout = matches.get_one::("timeout").copied(); let cache = PathBuf::from(config.source_cache_root()); let sc = SourceCache::new(cache); let pname = matches @@ -215,7 +217,7 @@ pub async fn download( .map(|s| s.to_owned()) .map(PackageName::from); let pvers = matches - .get_one::("package_version") + .get_one::("package_version_constraint") .map(|s| s.to_owned()) .map(PackageVersionConstraint::try_from) .transpose()?; @@ -231,38 +233,8 @@ pub async fn download( NUMBER_OF_MAX_CONCURRENT_DOWNLOADS, )); - let mut r = repo.packages() - .filter(|p| { - match (pname.as_ref(), pvers.as_ref(), matching_regexp.as_ref()) { - (None, None, None) => true, - (Some(pname), None, None) => p.name() == pname, - (Some(pname), Some(vers), None) => p.name() == pname && vers.matches(p.version()), - (None, None, Some(regex)) => regex.is_match(p.name()), - - (_, _, _) => { - panic!("This should not be possible, either we select packages by name and (optionally) version, or by regex.") - }, - } - }).peekable(); - - // check if the iterator is empty - if r.peek().is_none() { - let pname = matches.get_one::("package_name"); - let pvers = matches.get_one::("package_version"); - let matching_regexp = matches.get_one::("matching"); - - match (pname, pvers, matching_regexp) { - (Some(pname), None, None) => return Err(anyhow!("{} not found", pname)), - (Some(pname), Some(vers), None) => return Err(anyhow!("{} {} not found", pname, vers)), - (None, None, Some(regex)) => return Err(anyhow!("{} regex not found", regex)), - - (_, _, _) => { - panic!("This should not be possible, either we select packages by name and (optionally) version, or by regex.") - } - } - } - - let r = r + let r = repo + .search_packages(&pname, &pvers, &matching_regexp)? .flat_map(|p| { sc.sources_for(p).into_iter().map(|source| { let download_sema = download_sema.clone(); @@ -274,8 +246,7 @@ pub async fn download( "Cannot download source that is marked for manual download" )) .context(anyhow!("Creating source: {}", source.path().display())) - .context(anyhow!("Downloading source: {}", source.url())) - .map_err(Error::from); + .context(anyhow!("Downloading source: {}", source.url())); } if source_path_exists && !force { @@ -307,10 +278,12 @@ pub async fn download( if r.is_err() { progressbar.lock().await.error().await; + return r; } else { progressbar.lock().await.success().await; } - debug!("r = {:?}", r); - r + super::verify(matches, config, repo, progressbars).await?; + + Ok(()) } diff --git a/src/commands/source/mod.rs b/src/commands/source/mod.rs index 275a178c..7fa0985f 100644 --- a/src/commands/source/mod.rs +++ b/src/commands/source/mod.rs @@ -10,7 +10,6 @@ //! Implementation of the 'source' subcommand -use std::convert::TryFrom; use std::io::Write; use std::path::PathBuf; @@ -65,7 +64,7 @@ pub async fn verify( .map(|s| s.to_owned()) .map(PackageName::from); let pvers = matches - .get_one::("package_version") + .get_one::("package_version_constraint") .map(|s| s.to_owned()) .map(PackageVersionConstraint::try_from) .transpose()?; @@ -75,21 +74,7 @@ pub async fn verify( .map(|s| crate::commands::util::mk_package_name_regex(s.as_ref())) .transpose()?; - let packages = repo - .packages() - .filter(|p| { - match (pname.as_ref(), pvers.as_ref(), matching_regexp.as_ref()) { - (None, None, None) => true, - (Some(pname), None, None) => p.name() == pname, - (Some(pname), Some(vers), None) => p.name() == pname && vers.matches(p.version()), - (None, None, Some(regex)) => regex.is_match(p.name()), - - (_, _, _) => { - panic!("This should not be possible, either we select packages by name and (optionally) version, or by regex.") - }, - } - }) - .inspect(|p| trace!("Found for verification: {} {}", p.name(), p.version())); + let packages = repo.search_packages(&pname, &pvers, &matching_regexp)?; verify_impl(packages, &sc, &progressbars).await } @@ -195,7 +180,7 @@ pub async fn url(matches: &ArgMatches, repo: Repository) -> Result<()> { .map(|s| s.to_owned()) .map(PackageName::from); let pvers = matches - .get_one::("package_version") + .get_one::("package_version_constraint") .map(|s| s.to_owned()) .map(PackageVersionConstraint::try_from) .transpose()?; @@ -231,7 +216,7 @@ async fn of(matches: &ArgMatches, config: &Configuration, repo: Repository) -> R .map(|s| s.to_owned()) .map(PackageName::from); let pvers = matches - .get_one::("package_version") + .get_one::("package_version_constraint") .map(|s| s.to_owned()) .map(PackageVersionConstraint::try_from) .transpose()?; diff --git a/src/commands/tree_of.rs b/src/commands/tree_of.rs index 81a04b9d..94c144e0 100644 --- a/src/commands/tree_of.rs +++ b/src/commands/tree_of.rs @@ -10,20 +10,20 @@ //! Implementation of the 'tree-of' subcommand -use std::convert::TryFrom; - use anyhow::Error; use anyhow::Result; use clap::ArgMatches; +use petgraph::dot::Dot; use resiter::AndThen; use crate::config::Configuration; use crate::package::condition::ConditionData; use crate::package::Dag; +use crate::package::DependencyType; use crate::package::PackageName; use crate::package::PackageVersionConstraint; use crate::repository::Repository; -use crate::util::docker::resolve_image_name; +use crate::util::docker::ImageNameLookup; use crate::util::EnvironmentVariableName; /// Implementation of the "tree_of" subcommand @@ -33,14 +33,15 @@ pub async fn tree_of(matches: &ArgMatches, repo: Repository, config: &Configurat .map(|s| s.to_owned()) .map(PackageName::from); let pvers = matches - .get_one::("package_version") + .get_one::("package_version_constraint") .map(|s| s.to_owned()) .map(PackageVersionConstraint::try_from) .transpose()?; + let image_name_lookup = ImageNameLookup::create(config.docker().images())?; let image_name = matches .get_one::("image") - .map(|s| resolve_image_name(s, config.docker().images())) + .map(|s| image_name_lookup.expand(s)) .transpose()?; let additional_env = matches @@ -55,6 +56,10 @@ pub async fn tree_of(matches: &ArgMatches, repo: Repository, config: &Configurat env: &additional_env, }; + let dot = matches.get_flag("dot"); + + let serial_buildorder = matches.get_flag("serial-buildorder"); + repo.packages() .filter(|p| pname.as_ref().map(|n| p.name() == n).unwrap_or(true)) .filter(|p| { @@ -64,11 +69,45 @@ pub async fn tree_of(matches: &ArgMatches, repo: Repository, config: &Configurat .unwrap_or(true) }) .map(|package| Dag::for_root_package(package.clone(), &repo, None, &condition_data)) - .and_then_ok(|tree| { - let stdout = std::io::stdout(); - let mut outlock = stdout.lock(); + .and_then_ok(|dag| { + if dot { + let dot = Dot::with_attr_getters( + dag.dag(), + &[ + petgraph::dot::Config::EdgeNoLabel, + petgraph::dot::Config::NodeNoLabel, + ], + &|_, er| { + format!( + "{} ", + match er.weight() { + DependencyType::Build => "style = \"dotted\"", + DependencyType::Runtime => "", + } + ) + }, + &|_, node| format!("label = \"{}\" ", node.1.display_name_version()), + ); + + println!("{:?}", dot); + Ok(()) + } else if serial_buildorder { + let topo_sorted = petgraph::algo::toposort(dag.dag(), None) + .map_err(|_| Error::msg("Cyclic dependency found!"))?; + + for node in topo_sorted.iter().rev() { + let package = dag.dag().node_weight(*node).unwrap(); + println!("{}", package.display_name_version()); + } + println!(); + + Ok(()) + } else { + let stdout = std::io::stdout(); + let mut outlock = stdout.lock(); - ptree::write_tree(&tree.display(), &mut outlock).map_err(Error::from) + ptree::write_tree(&dag.display(), &mut outlock).map_err(Error::from) + } }) .collect::>() } diff --git a/src/commands/util.rs b/src/commands/util.rs index e142da33..8d326a9d 100644 --- a/src/commands/util.rs +++ b/src/commands/util.rs @@ -126,24 +126,24 @@ fn all_phases_available(pkg: &Package, available_phases: &[PhaseName]) -> Result .iter() .find(|name| !available_phases.contains(name)) { - return Err(anyhow!( + anyhow::bail!( "Phase '{}' available in {} {}, but not in config", phase.as_str(), pkg.name(), pkg.version() - )); + ); } if let Some(phase) = available_phases .iter() .find(|name| !package_phasenames.contains(name)) { - return Err(anyhow!( + anyhow::bail!( "Phase '{}' not configured in {} {}", phase.as_str(), pkg.name(), pkg.version() - )); + ); } Ok(()) @@ -159,7 +159,6 @@ pub fn mk_package_name_regex(regex: &str) -> Result { builder .build() .with_context(|| anyhow!("Failed to build regex from '{}'", regex)) - .map_err(Error::from) } /// Make a header column for the ascii_table crate @@ -259,7 +258,6 @@ pub fn get_date_filter( .checked_sub_signed(dur) .ok_or_else(|| anyhow!("Time calculation would overflow")) .with_context(|| anyhow!("Cannot subtract {} from 'now'", dur)) - .map_err(Error::from) }) .transpose() } diff --git a/src/config/container_config.rs b/src/config/container_config.rs index 702d1009..3649012a 100644 --- a/src/config/container_config.rs +++ b/src/config/container_config.rs @@ -16,8 +16,10 @@ use crate::util::EnvironmentVariableName; /// The configuration for the containers #[derive(Debug, CopyGetters, Getters, Deserialize)] +#[serde(deny_unknown_fields)] pub struct ContainerConfig { - /// check environment names whether they're allowed + /// Whether to check if environment variables are allowed (i.e., if their + /// names are listed in `allowed_env`). #[getset(get_copy = "pub")] check_env_names: bool, @@ -25,12 +27,12 @@ pub struct ContainerConfig { #[getset(get = "pub")] allowed_env: Vec, - /// Pass the current git author to the container - /// This can be used to the the "packager" name in a package, for example + /// Pass the current Git author to the container + /// This can be used for the "packager" name in a package, for example #[getset(get = "pub")] git_author: Option, - /// Pass the current git hash to the container + /// Pass the current Git hash to the container #[getset(get = "pub")] git_commit_hash: Option, } diff --git a/src/config/docker_config.rs b/src/config/docker_config.rs index 026dc6c4..ead19bf6 100644 --- a/src/config/docker_config.rs +++ b/src/config/docker_config.rs @@ -19,6 +19,7 @@ use crate::util::docker::ContainerImage; /// Configuration of the Docker daemon interfacing functionality #[derive(Debug, Getters, CopyGetters, Deserialize)] +#[serde(deny_unknown_fields)] pub struct DockerConfig { /// The required Docker version /// @@ -42,9 +43,12 @@ pub struct DockerConfig { #[getset(get = "pub")] docker_api_versions: Option>, + /// List of container images that are allowed for builds. + /// An example: `{ name = "local:debian12-default", short_name ="deb12" }` #[getset(get = "pub")] images: Vec, + /// A map of endpoints (name -> settings) that are used as container hosts to run builds on #[getset(get = "pub")] endpoints: HashMap, } diff --git a/src/config/endpoint_config.rs b/src/config/endpoint_config.rs index 86c9f198..1b67da45 100644 --- a/src/config/endpoint_config.rs +++ b/src/config/endpoint_config.rs @@ -33,14 +33,21 @@ impl AsRef for EndpointName { } } +impl EndpointName { + pub fn len(&self) -> usize { + self.0.len() + } +} + /// Configuration of a single endpoint #[derive(Clone, Debug, Getters, CopyGetters, Deserialize)] +#[serde(deny_unknown_fields)] pub struct Endpoint { /// The URI where the endpoint is reachable #[getset(get = "pub")] uri: String, - /// The type of the endpoint + /// The type of the endpoint (either "socket" or "http") #[getset(get = "pub")] endpoint_type: EndpointType, @@ -48,10 +55,14 @@ pub struct Endpoint { #[getset(get_copy = "pub")] maxjobs: usize, + /// Sets the networking mode for the containers. + /// Supported standard values are: "bridge", "host", "none", and "container:". Any + /// other value is taken as a custom network's name to which this container should connect to. + /// (See https://docs.docker.com/engine/api/v1.45/#tag/Image/operation/ImageBuild) #[getset(get = "pub")] network_mode: Option, - /// Duration length of timeout for connecting endpoint + /// Timeout in seconds for connecting to this endpoint #[getset(get = "pub")] timeout: Option, } diff --git a/src/config/not_validated.rs b/src/config/not_validated.rs index d88baef0..bceb312f 100644 --- a/src/config/not_validated.rs +++ b/src/config/not_validated.rs @@ -41,23 +41,17 @@ pub struct NotValidatedConfiguration { #[getset(get = "pub")] log_dir: PathBuf, - /// Whether the script interpolation feature should be struct, i.e. missing variables result in + /// Whether the script interpolation feature should be strict, i.e. missing variables result in /// a failing interpolation. This should be `true` for most users. #[serde(default = "default_strict_script_interpolation")] #[getset(get = "pub")] strict_script_interpolation: bool, - /// The format of the progress bars + /// The format of the progress status output for various operations #[serde(default = "default_progress_format")] #[getset(get = "pub")] progress_format: String, - /// The format of the spinners in the CLI - #[serde(default = "default_spinner_format")] - #[getset(get = "pub")] - #[allow(unused)] - spinner_format: String, - /// The format used to print a package /// /// This is handlebars syntax @@ -89,7 +83,6 @@ pub struct NotValidatedConfiguration { releases_directory: PathBuf, /// The names of the directories inside the `releases_directory` to store different releases in - #[serde(rename = "release_stores")] #[getset(get = "pub")] release_stores: Vec, @@ -105,36 +98,38 @@ pub struct NotValidatedConfiguration { #[getset(get = "pub")] source_cache_root: PathBuf, - /// The hostname used to connect to the database + /// The hostname/FQDN/IP used to connect to the database #[getset(get = "pub")] - #[serde(rename = "database_host")] database_host: String, - /// The post used to connect to the database + /// The port used to connect to the database #[getset(get = "pub")] - #[serde(rename = "database_port")] database_port: u16, /// The user used to connect to the database #[getset(get = "pub")] - #[serde(rename = "database_user")] database_user: String, /// The password used to connect to the database #[getset(get = "pub")] - #[serde(rename = "database_password")] database_password: String, /// The name of the database #[getset(get = "pub")] - #[serde(rename = "database_name")] database_name: String, - /// The configuration for the Docker endpoints + /// The database connection timeout in seconds + #[getset(get = "pub")] + #[serde(default = "default_database_connection_timeout")] + database_connection_timeout: u16, + + /// The default limit for database queries (when listing tables with the `db` subcommand; + /// 0=unlimited (not recommended as it might result in OOM kills)) + #[serde(default = "default_database_query_limit")] #[getset(get = "pub")] - #[serde(rename = "database_connection_timeout")] - database_connection_timeout: Option, + database_default_query_limit: usize, + /// The configuration for the Docker endpoints and images #[getset(get = "pub")] docker: DockerConfig, @@ -236,9 +231,7 @@ impl NotValidatedConfiguration { // Double-check the compatibility (mainly to avoid "error: field `compatibility` is never read") if self.compatibility != CONFIGURATION_VERSION { - return Err(anyhow!( - "The provided configuration is not compatible with this butido binary" - )); + anyhow::bail!("The provided configuration is not compatible with this butido binary"); } // Error if the configured directories are missing or no directories: @@ -248,14 +241,12 @@ impl NotValidatedConfiguration { check_directory_exists(&self.source_cache_root, "source_cache")?; if self.release_stores.is_empty() { - return Err(anyhow!( - "You need at least one release store in 'release_stores'" - )); + anyhow::bail!("You need at least one release store in 'release_stores'"); } // Error if there are no phases configured if self.available_phases.is_empty() { - return Err(anyhow!("No phases configured")); + anyhow::bail!("No phases configured"); } // Error if script highlighting theme is not valid @@ -274,7 +265,7 @@ impl NotValidatedConfiguration { .any(|allowed_theme| configured_theme == *allowed_theme); if !allowed_theme_present { - return Err(anyhow!("Theme not known: {}", configured_theme)); + anyhow::bail!("Theme not known: {}", configured_theme); } } @@ -298,7 +289,7 @@ mod tests { assert!(changelog.is_ok()); let changelog = changelog.unwrap(); for i in 0..=CONFIGURATION_VERSION { - assert!(changelog.get(&i.to_string()).is_some()); + assert!(changelog.contains_key(&i.to_string())); } } diff --git a/src/config/util.rs b/src/config/util.rs index 5c76fd30..3c51c869 100644 --- a/src/config/util.rs +++ b/src/config/util.rs @@ -12,13 +12,11 @@ //! configuration and having to use default values. /// The default progress bar format +// Ignore a false positive Clippy warning (we pass this format string to +// `indicatif::ProgressBars` as `bar_template` instead of evaluating it here): +#[rustversion::attr(since(1.85), allow(clippy::literal_string_with_formatting_args))] pub fn default_progress_format() -> String { - String::from("[{elapsed_precise}] ({percent:>3}%): {bar:40.cyan/blue} | {msg}") -} - -/// The default spinner format -pub fn default_spinner_format() -> String { - String::from("[{elapsed_precise}] {spinner} | {msg}") + String::from("{elapsed_precise} {percent:>3}% {bar:5.cyan/blue} | {msg}") } /// The default format that is used to print one package @@ -115,3 +113,14 @@ pub fn default_script_shebang() -> String { pub fn default_build_error_lines() -> usize { 10 } + +/// The default value for the database connection timeout (in seconds) +pub fn default_database_connection_timeout() -> u16 { + 30 +} + +/// The default value for the number of results/rows that should be returned for DB queries that +/// list things (LIMIT) +pub fn default_database_query_limit() -> usize { + 10 +} diff --git a/src/db/connection.rs b/src/db/connection.rs index e03c004d..6efad445 100644 --- a/src/db/connection.rs +++ b/src/db/connection.rs @@ -11,7 +11,6 @@ use anyhow::Error; use anyhow::Result; use clap::ArgMatches; -use diesel::pg::PgConnection; use diesel::prelude::*; use diesel::r2d2::ConnectionManager; use diesel::r2d2::Pool; @@ -41,7 +40,7 @@ pub struct DbConnectionConfig<'a> { database_connection_timeout: u16, } -impl<'a> std::fmt::Debug for DbConnectionConfig<'a> { +impl std::fmt::Debug for DbConnectionConfig<'_> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { write!( f, @@ -62,10 +61,8 @@ impl<'a> DbConnectionConfig<'a> { .get_one::("database_host") .unwrap_or_else(|| config.database_host()), database_port: { - cli.get_one::("database_port") - .map(|s| s.parse::()) - .transpose()? - .unwrap_or_else(|| *config.database_port()) + *cli.get_one::("database_port") + .unwrap_or_else(|| config.database_port()) }, database_user: cli .get_one::("database_user") @@ -77,13 +74,8 @@ impl<'a> DbConnectionConfig<'a> { .get_one::("database_name") .unwrap_or_else(|| config.database_name()), database_connection_timeout: { - cli.get_one::("database_connection_timeout") - .map(|s| s.parse::()) - .transpose()? - .unwrap_or_else(|| { - // hardcoded default of 30 seconds database timeout - config.database_connection_timeout().unwrap_or(30) - }) + *cli.get_one::("database_connection_timeout") + .unwrap_or_else(|| config.database_connection_timeout()) }, }) } diff --git a/src/db/find_artifacts.rs b/src/db/find_artifacts.rs index 433570d6..d00bd5c8 100644 --- a/src/db/find_artifacts.rs +++ b/src/db/find_artifacts.rs @@ -50,7 +50,7 @@ use crate::util::EnvironmentVariableName; /// /// If the artifact was released, the return value contains a Some(NaiveDateTime), marking the date /// of the release. -/// Releases are returned prefferably, if multiple equal pathes for an artifact are found. +/// Releases are returned preferably, if multiple equal paths for an artifact are found. #[derive(typed_builder::TypedBuilder)] pub struct FindArtifacts<'a> { config: &'a Configuration, diff --git a/src/db/models/artifact.rs b/src/db/models/artifact.rs index 9df96692..20feccb1 100644 --- a/src/db/models/artifact.rs +++ b/src/db/models/artifact.rs @@ -17,7 +17,6 @@ use anyhow::Error; use anyhow::Result; use chrono::NaiveDateTime; use diesel::prelude::*; -use diesel::PgConnection; use crate::db::models::Job; use crate::db::models::Release; diff --git a/src/db/models/endpoint.rs b/src/db/models/endpoint.rs index 4a26cb73..3ec1b8af 100644 --- a/src/db/models/endpoint.rs +++ b/src/db/models/endpoint.rs @@ -11,7 +11,6 @@ use anyhow::Error; use anyhow::Result; use diesel::prelude::*; -use diesel::PgConnection; use crate::config::EndpointName; use crate::schema::endpoints; diff --git a/src/db/models/envvar.rs b/src/db/models/envvar.rs index a276b193..46d36ede 100644 --- a/src/db/models/envvar.rs +++ b/src/db/models/envvar.rs @@ -11,7 +11,6 @@ use anyhow::Error; use anyhow::Result; use diesel::prelude::*; -use diesel::PgConnection; use crate::schema::envvars; use crate::schema::envvars::*; diff --git a/src/db/models/githash.rs b/src/db/models/githash.rs index d0dc43bb..5b1bbe0e 100644 --- a/src/db/models/githash.rs +++ b/src/db/models/githash.rs @@ -12,7 +12,6 @@ use anyhow::Context; use anyhow::Error; use anyhow::Result; use diesel::prelude::*; -use diesel::PgConnection; use crate::schema::githashes; use crate::schema::githashes::*; @@ -54,6 +53,5 @@ impl GitHash { .find(git_hash_id) .first::<_>(database_connection) .context("Loading GitHash") - .map_err(Error::from) } } diff --git a/src/db/models/image.rs b/src/db/models/image.rs index a701e6c5..8199ed69 100644 --- a/src/db/models/image.rs +++ b/src/db/models/image.rs @@ -11,7 +11,6 @@ use anyhow::Error; use anyhow::Result; use diesel::prelude::*; -use diesel::PgConnection; use crate::schema::images; use crate::schema::images::*; diff --git a/src/db/models/job.rs b/src/db/models/job.rs index 21d4c9be..895d9573 100644 --- a/src/db/models/job.rs +++ b/src/db/models/job.rs @@ -12,7 +12,6 @@ use anyhow::Context; use anyhow::Error; use anyhow::Result; use diesel::prelude::*; -use diesel::PgConnection; use tracing::trace; use crate::db::models::{Endpoint, Image, Package, Submit}; @@ -93,7 +92,6 @@ impl Job { .filter(uuid.eq(job_uuid)) .first::(conn) .with_context(|| format!("Finding created job in database: {job_uuid}")) - .map_err(Error::from) }) } diff --git a/src/db/models/job_env.rs b/src/db/models/job_env.rs index eee7ea12..c1864b31 100644 --- a/src/db/models/job_env.rs +++ b/src/db/models/job_env.rs @@ -10,7 +10,6 @@ use anyhow::Result; use diesel::prelude::*; -use diesel::PgConnection; use crate::db::models::EnvVar; use crate::db::models::Job; diff --git a/src/db/models/package.rs b/src/db/models/package.rs index 4a1d6921..e0b96a84 100644 --- a/src/db/models/package.rs +++ b/src/db/models/package.rs @@ -13,7 +13,6 @@ use std::ops::Deref; use anyhow::Error; use anyhow::Result; use diesel::prelude::*; -use diesel::PgConnection; use crate::schema::packages; use crate::schema::packages::*; diff --git a/src/db/models/releases.rs b/src/db/models/releases.rs index 507ac232..de607dce 100644 --- a/src/db/models/releases.rs +++ b/src/db/models/releases.rs @@ -12,7 +12,6 @@ use anyhow::Error; use anyhow::Result; use chrono::NaiveDateTime; use diesel::prelude::*; -use diesel::PgConnection; use crate::db::models::Artifact; use crate::db::models::ReleaseStore; diff --git a/src/db/models/submit.rs b/src/db/models/submit.rs index 101bb405..82c899d9 100644 --- a/src/db/models/submit.rs +++ b/src/db/models/submit.rs @@ -13,7 +13,6 @@ use anyhow::Error; use anyhow::Result; use chrono::NaiveDateTime; use diesel::prelude::*; -use diesel::PgConnection; use crate::db::models::GitHash; use crate::db::models::Image; @@ -81,6 +80,5 @@ impl Submit { .filter(submits::uuid.eq(submit_id)) .first::(database_connection) .context("Loading submit") - .map_err(Error::from) } } diff --git a/src/endpoint/configured.rs b/src/endpoint/configured.rs index 196be796..92c347da 100644 --- a/src/endpoint/configured.rs +++ b/src/endpoint/configured.rs @@ -19,8 +19,6 @@ use anyhow::Error; use anyhow::Result; use futures::FutureExt; use getset::{CopyGetters, Getters}; -#[rustversion::before(1.76)] -use result_inspect::ResultInspect; use shiplift::Container; use shiplift::Docker; use shiplift::ExecContainerOptions; @@ -125,7 +123,6 @@ impl Endpoint { crate::config::EndpointType::Http => shiplift::Uri::from_str(ep.uri()) .map(shiplift::Docker::host) .with_context(|| anyhow!("Connecting to {}", ep.uri())) - .map_err(Error::from) .map(|docker| { Endpoint::builder() .name(ep_name.clone()) @@ -183,7 +180,7 @@ impl Endpoint { .with_context(|| anyhow!("Getting API version of endpoint: {}", ep.name))?; if !v.contains(&avail.api_version) { - Err(anyhow!("Incompatible Docker API version on endpoint {}: Exepected: {}, Available: [{}]", + Err(anyhow!("Incompatible Docker API version on endpoint {}: Expected: {}, Available: [{}]", ep.name(), avail.api_version, v.join(", "))) } else { Ok(()) @@ -399,6 +396,7 @@ impl From for Image { } } +#[derive(Clone)] pub struct EndpointHandle(Arc); impl EndpointHandle { @@ -545,10 +543,7 @@ impl<'a> PreparedContainer<'a> { Ok(create_info) } - async fn copy_source_to_container<'ca>( - container: &Container<'ca>, - job: &RunnableJob, - ) -> Result<()> { + async fn copy_source_to_container(container: &Container<'_>, job: &RunnableJob) -> Result<()> { use tokio::io::AsyncReadExt; job.package_sources() @@ -601,7 +596,6 @@ impl<'a> PreparedContainer<'a> { container.id() ) }) - .map_err(Error::from) }) .collect::>() .collect::>() @@ -613,13 +607,9 @@ impl<'a> PreparedContainer<'a> { ) }) .with_context(|| anyhow!("Copying sources to container {}", container.id())) - .map_err(Error::from) } - async fn copy_patches_to_container<'ca>( - container: &Container<'ca>, - job: &RunnableJob, - ) -> Result<()> { + async fn copy_patches_to_container(container: &Container<'_>, job: &RunnableJob) -> Result<()> { use tokio::io::AsyncReadExt; debug!( @@ -655,7 +645,7 @@ impl<'a> PreparedContainer<'a> { .copy_file_into(destination, &buf) .await .map_err(Error::from) - .inspect(|_| trace!("Copying patch {} successfull", patch.display())) + .inspect(|_| trace!("Copying patch {} successful", patch.display())) .with_context(|| { anyhow!( "Copying patch {} to container {}", @@ -663,19 +653,16 @@ impl<'a> PreparedContainer<'a> { container.id() ) }) - .map_err(Error::from) }) .collect::>() .collect::>() .await - .map_err(Error::from) .inspect(|_| trace!("Copied all patches")) .with_context(|| anyhow!("Copying patches to container {}", container.id())) - .map_err(Error::from) } - async fn copy_artifacts_to_container<'ca>( - container: &Container<'ca>, + async fn copy_artifacts_to_container( + container: &Container<'_>, job: &RunnableJob, staging_store: Arc>, release_stores: &[Arc], @@ -754,8 +741,7 @@ impl<'a> PreparedContainer<'a> { container.id(), destination.display() ) - }) - .map_err(Error::from); + }); drop(art); // ensure `art` is moved into closure r }); @@ -775,21 +761,16 @@ impl<'a> PreparedContainer<'a> { ) }) .with_context(|| anyhow!("Copying artifacts to container {}", container.id())) - .map_err(Error::from) .map(|_| ()) } - async fn copy_script_to_container<'ca>( - container: &Container<'ca>, - script: &Script, - ) -> Result<()> { + async fn copy_script_to_container(container: &Container<'_>, script: &Script) -> Result<()> { let script_path = PathBuf::from(crate::consts::SCRIPT_PATH); container .copy_file_into(script_path, script.as_ref().as_bytes()) .await .inspect(|_| trace!("Successfully copied script to container {}", container.id())) .with_context(|| anyhow!("Copying the script into container {}", container.id())) - .map_err(Error::from) } pub async fn start(self) -> Result> { @@ -888,7 +869,6 @@ impl<'a> StartedContainer<'a> { .with_context(|| anyhow!("Sending log to log sink")) .map(|_| exited_successfully) }) - .map_err(Error::from) }) .collect::>>() .map(|r| { @@ -934,7 +914,7 @@ pub struct ExecutedContainer<'a> { exit_info: Option<(bool, Option)>, } -impl<'a> ExecutedContainer<'a> { +impl ExecutedContainer<'_> { pub fn container_hash(&self) -> ContainerHash { ContainerHash::from(self.create_info.id.clone()) } @@ -975,7 +955,6 @@ impl<'a> ExecutedContainer<'a> { self.create_info.id ) }) - .map_err(Error::from) }); let mut writelock = staging_store.write().await; diff --git a/src/endpoint/scheduler.rs b/src/endpoint/scheduler.rs index 3e8b0906..52514dfe 100644 --- a/src/endpoint/scheduler.rs +++ b/src/endpoint/scheduler.rs @@ -19,12 +19,13 @@ use colored::Colorize; use diesel::r2d2::ConnectionManager; use diesel::r2d2::Pool; use diesel::PgConnection; +use getset::{CopyGetters, Getters}; use indicatif::ProgressBar; use itertools::Itertools; use tokio::io::AsyncWriteExt; use tokio::sync::mpsc::UnboundedReceiver; use tokio::sync::RwLock; -use tracing::trace; +use tracing::{debug, error, info, trace}; use uuid::Uuid; use crate::db::models as dbmodels; @@ -38,9 +39,12 @@ use crate::job::JobResource; use crate::job::RunnableJob; use crate::log::LogItem; +#[derive(Getters, CopyGetters)] pub struct EndpointScheduler { log_dir: Option, endpoints: Vec>, + #[getset(get = "pub")] + max_endpoint_name_length: usize, staging_store: Arc>, release_stores: Vec>, @@ -58,10 +62,16 @@ impl EndpointScheduler { log_dir: Option, ) -> Result { let endpoints = crate::endpoint::util::setup_endpoints(endpoints).await?; + let max_endpoint_name_length = endpoints + .iter() + .map(|ep| ep.name().len()) + .max() + .unwrap_or(0); Ok(EndpointScheduler { log_dir, endpoints, + max_endpoint_name_length, staging_store, release_stores, db, @@ -85,6 +95,8 @@ impl EndpointScheduler { log_dir: self.log_dir.clone(), bar, endpoint, + container_id: None, + max_endpoint_name_length: self.max_endpoint_name_length, job, staging_store: self.staging_store.clone(), release_stores: self.release_stores.clone(), @@ -125,9 +137,12 @@ impl EndpointScheduler { } } +#[derive(Clone)] pub struct JobHandle { log_dir: Option, endpoint: EndpointHandle, + container_id: Option, + max_endpoint_name_length: usize, job: RunnableJob, bar: ProgressBar, db: Pool>, @@ -143,7 +158,7 @@ impl std::fmt::Debug for JobHandle { } impl JobHandle { - pub async fn run(self) -> Result>> { + pub async fn run(mut self) -> Result>> { let (log_sender, log_receiver) = tokio::sync::mpsc::unbounded_channel::(); let endpoint_uri = self.endpoint.uri().clone(); let endpoint_name = self.endpoint.name().clone(); @@ -169,6 +184,7 @@ impl JobHandle { ) .await?; let container_id = prepared_container.create_info().id.clone(); + self.container_id = Some(container_id.clone()); let running_container = prepared_container .start() .await @@ -185,16 +201,16 @@ impl JobHandle { let logres = LogReceiver { endpoint_name: endpoint_name.as_ref(), + max_endpoint_name_length: &self.max_endpoint_name_length, container_id_chrs: container_id.chars().take(7).collect(), package_name: &package.name, package_version: &package.version, log_dir: self.log_dir.as_ref(), - job: self.job, + job: &self.job, log_receiver, bar: self.bar.clone(), } .join(); - drop(self.bar); let (run_container, logres) = tokio::join!(running_container, logres); let log = @@ -262,8 +278,7 @@ impl JobHandle { &endpoint_uri, &container_id, ) - }) - .map_err(Error::from); + }); if res.is_err() { trace!("Error was returned from script"); @@ -357,21 +372,54 @@ impl JobHandle { } } +impl Drop for JobHandle { + fn drop(&mut self) { + if let Some(container_id) = self.container_id.clone() { + debug!("Cleaning up JobHandle with job UUID: {}", self.job.uuid()); + let docker = self.endpoint.docker().clone(); + + tokio::spawn(async move { + let container = docker.containers().get(&container_id); + debug!("Killing container with ID: {container_id}"); + + let container_info = container.inspect().await.unwrap(); + if container_info.state.running { + // We are killing the container here, since stopping the container can take ages + // and wouldn't bring any benefit here + match container.kill(None).await { + Ok(_) => info!("Killed container with ID: {}", container_id), + Err(e) => { + error!( + "Failed to stop container with ID: {}\nError: {}", + container_id, e + ) + } + } + } else { + debug!("Container with ID {} isn't running anymore", container_id); + } + }); + } else { + debug!("No container ID set, skipping cleanup"); + } + } +} + struct LogReceiver<'a> { endpoint_name: &'a str, + max_endpoint_name_length: &'a usize, container_id_chrs: String, package_name: &'a str, package_version: &'a str, log_dir: Option<&'a PathBuf>, - job: RunnableJob, + job: &'a RunnableJob, log_receiver: UnboundedReceiver, bar: ProgressBar, } -impl<'a> LogReceiver<'a> { +impl LogReceiver<'_> { async fn join(mut self) -> Result { let mut success = None; - // Reserve a reasonable amount of elements. let mut accu = Vec::with_capacity(4096); @@ -389,6 +437,7 @@ impl<'a> LogReceiver<'a> { // Having a timeout of 1 sec proofed to be too long to update the time field in the // progress bar secondly. let timeout_duration = std::time::Duration::from_millis(250); + let max_endpoint_name_length = self.max_endpoint_name_length; loop { // Timeout for receiving from the log receiver channel @@ -422,10 +471,11 @@ impl<'a> LogReceiver<'a> { LogItem::CurrentPhase(ref phasename) => { trace!("Setting bar phase to {}", phasename); self.bar.set_message(format!( - "[{}/{} {} {} {}]: Phase: {}", + "{: LogReceiver<'a> { LogItem::State(Ok(())) => { trace!("Setting bar state to Ok"); self.bar.set_message(format!( - "[{}/{} {} {} {}]: State Ok", + "{: LogReceiver<'a> { LogItem::State(Err(ref e)) => { trace!("Setting bar state to Err: {}", e); self.bar.set_message(format!( - "[{}/{} {} {} {}]: State Err: {}", + "{: LogReceiver<'a> { trace!("Finishing bar = {:?}", success); let finish_msg = match success { - Some(true) => format!( - "[{}/{} {} {} {}]: finished successfully", - self.endpoint_name, - self.container_id_chrs, - self.job.uuid(), - self.package_name, - self.package_version - ), - Some(false) => format!( - "[{}/{} {} {} {}]: finished with error", - self.endpoint_name, - self.container_id_chrs, - self.job.uuid(), - self.package_name, - self.package_version - ), - None => format!( - "[{}/{} {} {} {}]: finished", - self.endpoint_name, - self.container_id_chrs, - self.job.uuid(), - self.package_name, - self.package_version - ), + Some(true) => "\u{2588}\u{2588}".green(), + Some(false) => "\u{2588}\u{2588}".red(), + None => "\u{2588}\u{2588}".blue(), }; + + let finish_msg = format!( + "{: LogReceiver<'a> { .await .map(tokio::io::BufWriter::new) .with_context(|| anyhow!("Opening {}", path.display())) - .map_err(Error::from) }) } else { None diff --git a/src/filestore/path.rs b/src/filestore/path.rs index e8859594..866a4dfd 100644 --- a/src/filestore/path.rs +++ b/src/filestore/path.rs @@ -104,9 +104,9 @@ impl StoreRoot { /// Unpack a tar archive in this location /// /// This function unpacks the provided tar archive "butido-style" in the location pointed to by - /// `self` and returns the written pathes. + /// `self` and returns the written paths. /// - /// The function filteres out the "/output" directory (that's what is meant by "butido-style"). + /// The function filters out the "/output" directory (that's what is meant by "butido-style"). pub(in crate::filestore) fn unpack_archive_here( &self, mut ar: tar::Archive, @@ -206,16 +206,14 @@ impl<'a> FullArtifactPath<'a> { pub async fn read(self) -> Result> { tokio::fs::read(self.joined()) .await - .map(Vec::from) .with_context(|| anyhow!("Reading artifact from path {}", self.0.display())) - .map_err(Error::from) } } #[derive(Debug)] pub struct FullArtifactPathDisplay<'a>(&'a StoreRoot, &'a ArtifactPath); -impl<'a> std::fmt::Display for FullArtifactPathDisplay<'a> { +impl std::fmt::Display for FullArtifactPathDisplay<'_> { fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(fmt, "{}/{}", self.0.display(), self.1.display()) } diff --git a/src/filestore/staging.rs b/src/filestore/staging.rs index 38148881..006afb56 100644 --- a/src/filestore/staging.rs +++ b/src/filestore/staging.rs @@ -12,12 +12,9 @@ use std::fmt::Debug; use anyhow::anyhow; use anyhow::Context; -use anyhow::Error; use anyhow::Result; use futures::stream::Stream; use indicatif::ProgressBar; -#[rustversion::before(1.76)] -use result_inspect::ResultInspect; use tracing::trace; use crate::filestore::path::ArtifactPath; @@ -56,7 +53,6 @@ impl StagingStore { trace!("Unpacking archive to {}", dest.display()); dest.unpack_archive_here(tar::Archive::new(&bytes[..])) .context("Unpacking TAR") - .map_err(Error::from) }) .context("Concatenating the output bytestream")? .into_iter() @@ -65,8 +61,6 @@ impl StagingStore { if self.0.root_path().is_dir(&path) { None } else { - // Clippy doesn't detect this properly - #[allow(clippy::redundant_clone)] ArtifactPath::new(path.to_path_buf()) .inspect(|r| trace!("Loaded from path {} = {:?}", path.display(), r)) .with_context(|| anyhow!("Loading from path: {}", path.display())) diff --git a/src/job/dag.rs b/src/job/dag.rs index 077c9e4e..90977381 100644 --- a/src/job/dag.rs +++ b/src/job/dag.rs @@ -8,9 +8,9 @@ // SPDX-License-Identifier: EPL-2.0 // -use daggy::Dag as DaggyDag; -use daggy::Walker; use getset::Getters; +use petgraph::acyclic::Acyclic; +use petgraph::graph::DiGraph; use uuid::Uuid; use crate::job::Job; @@ -24,7 +24,7 @@ use crate::util::docker::ImageName; #[derive(Debug, Getters)] pub struct Dag { #[getset(get = "pub")] - dag: DaggyDag, + dag: Acyclic>, } impl Dag { @@ -46,17 +46,17 @@ impl Dag { }; Dag { - dag: dag.dag().map(build_job, |_, e| (*e).clone()), + dag: Acyclic::<_>::try_from_graph(dag.dag().map(build_job, |_, e| (*e).clone())) + .unwrap(), // The dag.dag() is already acyclic so this cannot fail } } pub fn iter(&'_ self) -> impl Iterator + '_ { - self.dag.graph().node_indices().map(move |idx| { - let job = self.dag.graph().node_weight(idx).unwrap(); // TODO - let children = self.dag.children(idx); + self.dag.node_indices().map(move |idx| { + let job = self.dag.node_weight(idx).unwrap(); // TODO + let children = self.dag.neighbors_directed(idx, petgraph::Outgoing); let children_uuids = children - .iter(&self.dag) - .filter_map(|(_, node_idx)| self.dag.graph().node_weight(node_idx)) + .filter_map(|node_idx| self.dag.node_weight(node_idx)) .map(Job::uuid) .cloned() .collect(); diff --git a/src/job/runnable.rs b/src/job/runnable.rs index f36feffe..6adf38b6 100644 --- a/src/job/runnable.rs +++ b/src/job/runnable.rs @@ -28,7 +28,7 @@ use crate::util::docker::ImageName; use crate::util::EnvironmentVariableName; /// A job configuration that can be run. All inputs are clear here. -#[derive(Debug, Getters)] +#[derive(Clone, Debug, Getters)] pub struct RunnableJob { #[getset(get = "pub")] uuid: Uuid, @@ -50,9 +50,6 @@ pub struct RunnableJob { } impl RunnableJob { - // TODO: Drop the following clippy exception once we don't need to check against rustc 1.75 - // anymore (a fix was already merged but it likely won't be backported to 1.75): - #[allow(clippy::map_identity)] pub fn build_from_job( job: &Job, source_cache: &SourceCache, diff --git a/src/log/parser.rs b/src/log/parser.rs index 2a54aedb..4b57d3e0 100644 --- a/src/log/parser.rs +++ b/src/log/parser.rs @@ -145,8 +145,6 @@ pub fn parser<'a>() -> PomParser<'a, u8, LogItem> { #[cfg(test)] mod tests { use super::*; - use anyhow::Error; - use anyhow::Result; // Helper function for showing log item in error message in pretty fn prettify_item(e: &LogItem) -> String { diff --git a/src/main.rs b/src/main.rs index a0efb96a..bba6a8b2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -41,7 +41,6 @@ while_true )] #![allow(macro_use_extern_crate)] -#![allow(unstable_name_collisions)] // TODO: Remove me with the next rustc update (probably) #[macro_use] extern crate diesel; @@ -54,10 +53,9 @@ use anyhow::Error; use anyhow::Result; use aquamarine as _; use clap::ArgMatches; -// TODO: Drop the rust-inspect dependency once we bump the MSRV to 1.76: -#[rustversion::since(1.76)] -use result_inspect as _; +use rustversion as _; // This crate is (occasionally) required (e.g., when we need version specific Clippy overrides) use tracing::{debug, error}; +use tracing_subscriber::layer::SubscriberExt; mod cli; mod commands; @@ -91,24 +89,36 @@ pub const VERSION_LONG: &str = concatdoc! {" #[tokio::main] async fn main() -> Result<()> { - human_panic::setup_panic!(Metadata { - name: env!("CARGO_PKG_NAME").into(), - version: env!("CARGO_PKG_VERSION").into(), - authors: "science-computing ag, opensoftware ".into(), - homepage: "atos.net/de/deutschland/sc".into(), - }); - - tracing_subscriber::fmt::fmt() + human_panic::setup_panic!(human_panic::Metadata::new( + env!("CARGO_PKG_NAME"), + env!("CARGO_PKG_VERSION") + ) + .authors("science + computing AG, openSoftware ") + .homepage("https://github.com/science-computing/butido") + .support("- Via https://github.com/science-computing/butido/issues or mail to opensoftware@science-computing.de")); + + let app = cli::cli(); + let cli = app.get_matches(); + + let (chrome_layer, _guard) = match cli + .get_flag("tracing-chrome") + .then(|| tracing_chrome::ChromeLayerBuilder::new().build()) + { + Some((chrome_layer, guard)) => (Some(chrome_layer), Some(guard)), + _ => (None, None), + }; + + let subscriber = tracing_subscriber::fmt::fmt() .with_env_filter( tracing_subscriber::filter::EnvFilter::builder() .with_default_directive(tracing_subscriber::filter::LevelFilter::WARN.into()) .from_env_lossy(), ) - .init(); - debug!("Debugging enabled"); + .finish() + .with(chrome_layer); - let app = cli::cli(); - let cli = app.get_matches(); + tracing::subscriber::set_global_default(subscriber)?; + debug!("Debugging enabled"); // check if the version flag is set if cli.get_flag("version") { @@ -118,7 +128,7 @@ async fn main() -> Result<()> { let repo = git2::Repository::open(PathBuf::from(".")).map_err(|e| match e.code() { git2::ErrorCode::NotFound => { - eprintln!("Butido must be executed in the top-level of the git repository"); + eprintln!("Butido must be executed in the top-level of the Git repository"); std::process::exit(1) } _ => Error::from(e), @@ -281,12 +291,12 @@ async fn main() -> Result<()> { Some((other, _)) => { error!("Unknown subcommand: {}", other); error!("Use --help to find available subcommands"); - return Err(anyhow!("Unknown subcommand: {}", other)); + anyhow::bail!("Unknown subcommand: {}", other); } None => { error!("No subcommand."); error!("Use --help to find available subcommands"); - return Err(anyhow!("No subcommand")); + anyhow::bail!("No subcommand"); } } diff --git a/src/orchestrator/orchestrator.rs b/src/orchestrator/orchestrator.rs index 094f5a60..5c2e959c 100644 --- a/src/orchestrator/orchestrator.rs +++ b/src/orchestrator/orchestrator.rs @@ -13,6 +13,7 @@ use std::borrow::Borrow; use std::collections::HashMap; use std::path::PathBuf; +use std::process::ExitCode; use std::sync::Arc; use std::sync::Mutex; @@ -20,6 +21,7 @@ use anyhow::anyhow; use anyhow::Context; use anyhow::Error; use anyhow::Result; +use colored::Colorize; use diesel::r2d2::ConnectionManager; use diesel::r2d2::Pool; use diesel::PgConnection; @@ -31,7 +33,9 @@ use tokio::sync::mpsc::Receiver; use tokio::sync::mpsc::Sender; use tokio::sync::RwLock; use tokio_stream::StreamExt; -use tracing::{debug, error, trace}; +use tokio_util::sync::CancellationToken; +use tracing::Instrument; +use tracing::{debug, error, info, trace}; use typed_builder::TypedBuilder; use uuid::Uuid; @@ -50,6 +54,8 @@ use crate::source::SourceCache; use crate::util::progress::ProgressBars; use crate::util::EnvironmentVariableName; +const CONTAINER_ID_LENGTH: usize = 7; + #[cfg_attr(doc, aquamarine::aquamarine)] /// The Orchestrator /// @@ -259,14 +265,36 @@ impl Borrow for ProducedArtifact { } } -impl<'a> Orchestrator<'a> { +impl Orchestrator<'_> { pub async fn run(self, output: &mut Vec) -> Result> { - let (results, errors) = self.run_tree().await?; + let cancellation_token = CancellationToken::new(); + let controlc_cancellation_token = cancellation_token.clone(); + + tokio::spawn(async move { + tokio::signal::ctrl_c().await.unwrap(); + info!("Received the Ctrl-c signal, stopping..."); + controlc_cancellation_token.cancel(); + ExitCode::from(1) + }); + + let (results, errors) = self.run_tree(cancellation_token).await?; + output.extend(results); Ok(errors) } - async fn run_tree(self) -> Result<(Vec, HashMap)> { + async fn run_tree( + self, + token: CancellationToken, + ) -> Result<(Vec, HashMap)> { + let prepare_span = tracing::debug_span!("run tree preparation"); + + // There is no async code until we drop this guard, so this is fine + // + // WARNING: If async/await code is added between this and `drop(prepare_span_guard)`, the + // traces from this span _will_ be wrong. + let prepare_span_guard = prepare_span.enter(); + let multibar = Arc::new({ let mp = indicatif::MultiProgress::new(); if self.progress_generator.hide() { @@ -319,8 +347,8 @@ impl<'a> Orchestrator<'a> { let (sender, receiver) = tokio::sync::mpsc::channel(100); trace!( - "Creating TaskPreparation object for job {}", - jobdef.job.uuid() + job_uuid = %jobdef.job.uuid(), + "Creating TaskPreparation object for job" ); let bar = self.progress_generator.bar()?; let bar = multibar.add(bar); @@ -389,6 +417,7 @@ impl<'a> Orchestrator<'a> { .collect::>>>()?; trace!( + job_uuid = %job.1.jobdef.job.uuid(), "{:?} is depending on {}", depending_on_job, job.1.jobdef.job.uuid() @@ -415,7 +444,7 @@ impl<'a> Orchestrator<'a> { .find(|j| j.3.borrow().is_none()) .ok_or_else(|| anyhow!("Failed to find root task"))?; let root_job_id = root_job.1.jobdef.job.uuid(); - trace!("Root job id = {}", root_job_id); + trace!(%root_job_id, "Root job id found"); // Move the progress bar for the root task to the bottom to ensure that it will be visible // without having to scroll up (the MultiProgress implementation doesn't let us modify the // order so we have to remove and re-add it - it works despite the clone because @@ -427,6 +456,10 @@ impl<'a> Orchestrator<'a> { // Create a sender and a receiver for the root of the tree let (root_sender, mut root_receiver) = tokio::sync::mpsc::channel(100); + // preparation ended + drop(prepare_span_guard); + drop(prepare_span); + // Make all prepared jobs into real jobs and run them // // This maps each TaskPreparation with its sender and receiver to a JobTask and calls the @@ -434,35 +467,56 @@ impl<'a> Orchestrator<'a> { // // The JobTask::run implementation handles the rest, we just have to wait for all futures // to succeed. - let running_jobs = jobs - .into_iter() - .map(|prep| { - trace!("Creating JobTask for = {}", prep.1.jobdef.job.uuid()); - // the sender is set or we need to use the root sender - let sender = prep - .3 - .into_inner() - .unwrap_or_else(|| vec![root_sender.clone()]); - JobTask::new(prep.0, prep.1, sender) - }) - .inspect(|task| trace!("Running: {}", task.jobdef.job.uuid())) - .map(|task| task.run()) - .collect::>(); - debug!("Built {} jobs", running_jobs.len()); - - running_jobs.collect::>().await?; - trace!("All jobs finished"); - match root_receiver.recv().await { - None => Err(anyhow!("No result received...")), - Some(Ok(results)) => { - let results = results - .into_iter() - .flat_map(|tpl| tpl.1.into_iter()) - .map(ProducedArtifact::unpack) - .collect(); - Ok((results, HashMap::with_capacity(0))) + let run_span = tracing::debug_span!("run"); + + tokio::select! { + _ = token.cancelled() => { + anyhow::bail!("Received Control-C signal"); + } + r = async { + let running_jobs = jobs + .into_iter() + .map(|prep| { + trace!(parent: &run_span, job_uuid = %prep.1.jobdef.job.uuid(), "Creating JobTask"); + // the sender is set or we need to use the root sender + let sender = prep + .3 + .into_inner() + .unwrap_or_else(|| vec![root_sender.clone()]); + JobTask::new(prep.0, prep.1, sender) + }) + .inspect( + |task| trace!(parent: &run_span, job_uuid = %task.jobdef.job.uuid(), "Running job"), + ) + .map(|task| { + task.run() + .instrument(tracing::debug_span!(parent: &run_span, "JobTask::run")) + }) + .collect::>(); + debug!("Built {} jobs", running_jobs.len()); + + running_jobs + .collect::>() + .instrument(run_span.clone()) + .await?; + trace!(parent: &run_span, "All jobs finished"); + drop(run_span); + + match root_receiver.recv().await { + None => Err(anyhow!("No result received...")), + Some(Ok(results)) => { + let results = results + .into_iter() + .flat_map(|tpl| tpl.1.into_iter()) + .map(ProducedArtifact::unpack) + .collect(); + Ok((results, HashMap::with_capacity(0))) + } + Some(Err(errors)) => Ok((vec![], errors)), + } + } => { + r } - Some(Err(errors)) => Ok((vec![], errors)), } } } @@ -516,7 +570,7 @@ struct JobTask<'a> { /// /// This implementation is a bit of a hack. /// Because all `JobTask`s are `JobTask::run()` in parallel, but there is no IPC _between_ the -/// tasks (there is IPC between childs and parents, but not between all JobTask objects), we never +/// tasks (there is IPC between children and parents, but not between all JobTask objects), we never /// know whether any other task errored when the JobTask object is destructed. /// /// One way to implement this would be to add multi-cast IPC between all `JobTask` objects, with some @@ -529,23 +583,27 @@ struct JobTask<'a> { /// runtime stops running it because some other `JobTask` errored. /// /// In the latter case, we cleanup by telling the progressbar to finish. -impl<'a> Drop for JobTask<'a> { +impl Drop for JobTask<'_> { fn drop(&mut self) { if !self.bar.is_finished() { // If there are dependencies, the error is probably from another task // If there are no dependencies, the error was caused by something else let errmsg = if self.jobdef.dependencies.is_empty() { - "error occured" + "error occurred" } else { - "error on other task" + "inherited" }; + let max_endpoint_name_length = self.scheduler.max_endpoint_name_length(); self.bar.finish_with_message(format!( - "[{} {} {}] Stopped, {msg}", + "{:- JobTask<'a> { sender: Vec>, ) -> Self { let bar = prep.bar.clone(); + let max_endpoint_name_length = prep.scheduler.max_endpoint_name_length(); bar.set_message(format!( - "[{} {} {}]: Booting", + "{:- JobTask<'a> { /// Run the job /// - /// This function runs the job from this object on the scheduler as soon as all dependend jobs + /// This function runs the job from this object on the scheduler as soon as all dependent jobs /// returned successfully. async fn run(mut self) -> Result<()> { - debug!("[{}]: Running", self.jobdef.job.uuid()); + debug!(job_uuid = %self.jobdef.job.uuid(), "Running"); debug!( - "[{}]: Waiting for dependencies = {:?}", - self.jobdef.job.uuid(), + job_uuid = %self.jobdef.job.uuid(), + "Waiting for dependencies = {:?}", { self.jobdef .dependencies @@ -618,51 +680,59 @@ impl<'a> JobTask<'a> { }; // as long as the job definition lists dependencies that are not in the received_dependencies list... + let dependency_receiving_span = tracing::debug_span!("receiving dependencies"); + let max_endpoint_name_length = self.scheduler.max_endpoint_name_length(); while !all_dependencies_are_in(&self.jobdef.dependencies, &received_dependencies) { // Update the status bar message - self.bar.set_message({ - format!( - "[{} {} {}]: Waiting ({}/{})...", - self.jobdef.job.uuid(), - self.jobdef.job.package().name(), - self.jobdef.job.package().version(), - received_dependencies - .iter() - .filter(|(rd_uuid, _)| self.jobdef.dependencies.contains(rd_uuid)) - .count(), - dep_len - ) - }); - trace!("[{}]: Updated bar", self.jobdef.job.uuid()); - - trace!("[{}]: receiving...", self.jobdef.job.uuid()); - // receive from the receiver - let continue_receiving = self - .perform_receive(&mut received_dependencies, &mut received_errors) - .await?; - - trace!( - "[{}]: Received errors = {}", + self.bar.set_message(format!( + "{:- JobTask<'a> { break; } } + drop(dependency_receiving_span); // Check if any of the received dependencies was built (and not reused). // If any dependency was built, we need to build as well. @@ -719,7 +790,7 @@ impl<'a> JobTask<'a> { // subcommand. In this case, there might be an artifact for this job in the // staging store. In this case, we want to use it as a replacement, of course. // - // The fact that released artifacts are returned prefferably from this function + // The fact that released artifacts are returned preferably from this function // call does not change anything, because if there is an artifact that's a released // one that matches this job, we should use it anyways. .staging_store(Some(&staging_store)) @@ -729,14 +800,14 @@ impl<'a> JobTask<'a> { .run()?; debug!( - "[{}]: Found {} replacement artifacts", - self.jobdef.job.uuid(), - replacement_artifacts.len() + job_uuid = %self.jobdef.job.uuid(), + replacement_artifacts_count = replacement_artifacts.len(), + "Found replacement artifacts", ); trace!( - "[{}]: Found replacement artifacts: {:?}", - self.jobdef.job.uuid(), - replacement_artifacts + job_uuid = %self.jobdef.job.uuid(), + ?replacement_artifacts, + "Found replacement artifacts", ); let mut artifacts = replacement_artifacts .into_iter() @@ -749,7 +820,7 @@ impl<'a> JobTask<'a> { }) // We don't need duplicates here, so remove them by making the iterator unique // If we have two artifacts that are the same, the one in the staging store will be - // preffered in the next step + // preferred in the next step .unique_by(|tpl| tpl.0.artifact_path().clone()) // Fetch the artifact from the staging store, if there is one. // If there is none, try the release store. @@ -770,11 +841,7 @@ impl<'a> JobTask<'a> { if !artifacts.is_empty() { received_dependencies.insert(*self.jobdef.job.uuid(), artifacts); - trace!( - "[{}]: Sending to parent: {:?}", - self.jobdef.job.uuid(), - received_dependencies - ); + trace!(job_uuid = %self.jobdef.job.uuid(), "Sending to parent: {:?}", received_dependencies); for s in self.sender.iter() { s.send(Ok(received_dependencies.clone())) .await @@ -789,8 +856,11 @@ impl<'a> JobTask<'a> { })?; } self.bar.finish_with_message(format!( - "[{} {} {}] Reusing artifact", + "{:- JobTask<'a> { .cloned() .collect::>(); trace!( - "[{}]: Dependency artifacts = {:?}", - self.jobdef.job.uuid(), + job_uuid = %self.jobdef.job.uuid(), + "Dependency artifacts = {:?}", dependency_artifacts ); self.bar.set_message(format!( - "[{} {} {}]: Preparing...", + "{:- JobTask<'a> { )?; self.bar.set_message(format!( - "[{} {} {}]: Scheduling...", + "{:- JobTask<'a> { .await? { Err(e) => { - trace!( - "[{}]: Scheduler returned error = {:?}", - self.jobdef.job.uuid(), - e - ); + trace!(job_uuid = %self.jobdef.job.uuid(), "Scheduler returned error = {:?}", e); // ... and we send that to our parent // // We only send to one parent, because it doesn't matter anymore @@ -874,8 +946,8 @@ impl<'a> JobTask<'a> { // it returns the database artifact objects it created! Ok(artifacts) => { trace!( - "[{}]: Scheduler returned artifacts = {:?}", - self.jobdef.job.uuid(), + job_uuid = %self.jobdef.job.uuid(), + "Scheduler returned artifacts = {:?}", artifacts ); @@ -893,7 +965,7 @@ impl<'a> JobTask<'a> { Ok(()) } - /// Performe a recv() call on the receiving side of the channel + /// Perform a recv() call on the receiving side of the channel /// /// Put the dependencies you received into the `received_dependencies`, the errors in the /// `received_errors` @@ -910,22 +982,22 @@ impl<'a> JobTask<'a> { Some(Ok(mut v)) => { // The task we depend on succeeded and returned an // (uuid of the job, [ArtifactPath]) - trace!("[{}]: Received: {:?}", self.jobdef.job.uuid(), v); + trace!(job_uuid = %self.jobdef.job.uuid(), "Received: {:?}", v); received_dependencies.extend(v); Ok(true) } Some(Err(mut e)) => { // The task we depend on failed // we log that error for now - trace!("[{}]: Received: {:?}", self.jobdef.job.uuid(), e); + trace!(job_uuid = %self.jobdef.job.uuid(), "Received: {:?}", e); received_errors.extend(e); Ok(true) } None => { // The task we depend on finished... we must check what we have now... trace!( - "[{}]: Received nothing, channel seems to be empty", - self.jobdef.job.uuid() + job_uuid = %self.jobdef.job.uuid(), + "Received nothing, channel seems to be empty", ); // If the channel was closed and there are already errors in the `received_errors` @@ -933,8 +1005,8 @@ impl<'a> JobTask<'a> { // receiving if !received_errors.is_empty() { trace!( - "[{}]: There are errors, stop receiving", - self.jobdef.job.uuid() + job_uuid = %self.jobdef.job.uuid(), + "There are errors, stop receiving", ); return Ok(false); } @@ -947,9 +1019,8 @@ impl<'a> JobTask<'a> { .iter() .filter(|d| !received.contains(d)) .collect(); - trace!( - "[{}]: Missing dependencies = {:?}", - self.jobdef.job.uuid(), + trace!(job_uuid = %self.jobdef.job.uuid(), + "Missing dependencies = {:?}", missing_deps ); @@ -957,7 +1028,7 @@ impl<'a> JobTask<'a> { if !missing_deps.is_empty() { let missing: Vec = missing_deps.iter().map(|u| u.to_string()).collect(); Err(anyhow!( - "Childs finished, but dependencies still missing: {:?}", + "Children finished, but dependencies still missing: {:?}", missing )) } else { diff --git a/src/orchestrator/util.rs b/src/orchestrator/util.rs index 7efc23c9..a30c7512 100644 --- a/src/orchestrator/util.rs +++ b/src/orchestrator/util.rs @@ -29,7 +29,7 @@ impl AsReceivedErrorDisplay for HashMap { pub struct ReceivedErrorDisplay<'a>(&'a HashMap); -impl<'a> std::fmt::Display for ReceivedErrorDisplay<'a> { +impl std::fmt::Display for ReceivedErrorDisplay<'_> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { self.0 .iter() diff --git a/src/package/dag.rs b/src/package/dag.rs index 97af1975..a1f5645b 100644 --- a/src/package/dag.rs +++ b/src/package/dag.rs @@ -15,12 +15,15 @@ use std::io::Write; use anyhow::anyhow; use anyhow::Context; -use anyhow::Error; use anyhow::Result; -use daggy::Walker; use getset::Getters; use indicatif::ProgressBar; use itertools::Itertools; +use petgraph::acyclic::Acyclic; +use petgraph::data::Build; +use petgraph::graph::DiGraph; +use petgraph::graph::EdgeIndex; +use petgraph::graph::NodeIndex; use ptree::Style; use ptree::TreeItem; use resiter::AndThen; @@ -31,16 +34,16 @@ use crate::package::condition::ConditionData; use crate::package::dependency::ParseDependency; use crate::package::Package; use crate::package::PackageName; -use crate::package::PackageVersionConstraint; +use crate::package::PackageVersion; use crate::repository::Repository; #[derive(Debug, Getters)] pub struct Dag { #[getset(get = "pub")] - dag: daggy::Dag, + dag: Acyclic>, #[getset(get = "pub")] - root_idx: daggy::NodeIndex, + root_idx: NodeIndex, } #[derive(Clone, PartialEq, Eq, Hash, Debug)] @@ -63,12 +66,12 @@ impl Dag { dependency: &D, dependency_type: DependencyType, conditional_data: &ConditionData<'_>, - ) -> Result<(bool, PackageName, PackageVersionConstraint, DependencyType)> { + ) -> Result<(bool, PackageName, PackageVersion, DependencyType)> { // Check whether the condition of the dependency matches our data let take = dependency.check_condition(conditional_data)?; let (name, version) = dependency.parse_as_name_and_version()?; - // (dependency check result, name of the dependency, version constraint of the + // (dependency check result, name of the dependency, version of the // dependency, and type (build/runtime)) Ok((take, name, version, dependency_type)) } @@ -83,7 +86,7 @@ impl Dag { fn get_package_dependencies<'a>( package: &'a Package, conditional_data: &'a ConditionData<'_>, - ) -> impl Iterator> + 'a + ) -> impl Iterator> + 'a { trace!("Collecting the dependencies of {package:?} {conditional_data:?}"); package @@ -114,35 +117,35 @@ impl Dag { /// and adds corresponding nodes to the DAG. The edges are added later in `add_edges()`. fn add_sub_packages<'a>( repo: &'a Repository, - mappings: &mut HashMap<&'a Package, daggy::NodeIndex>, - dag: &mut daggy::Dag<&'a Package, DependencyType>, + mappings: &mut HashMap<&'a Package, NodeIndex>, + dag: &mut Acyclic>, p: &'a Package, progress: Option<&ProgressBar>, conditional_data: &ConditionData<'_>, ) -> Result<()> { get_package_dependencies(p, conditional_data) - .and_then_ok(|(name, constr, kind)| { + .and_then_ok(|(name, version, kind)| { trace!( "Processing the following dependency of {} {}: {} {} {:?}", p.name(), p.version(), name, - constr, + version, kind ); - let packs = repo.find_with_version(&name, &constr); + let packs = repo.find_with_version(&name, &version); trace!( "Found the following matching packages in the repo: {:?}", packs ); if packs.is_empty() { - return Err(anyhow!( + anyhow::bail!( "Couldn't find the following dependency of {} {} in the repo: {} {}", p.name(), p.version(), name, - constr - )); + version + ); } // Check if we already created a DAG node for any of the matching packages and @@ -154,10 +157,11 @@ impl Dag { }) { // TODO: It should be sufficient to process a single package of `packs`. // The `packs` vector contains a list of all packages in the repo that - // match the dependency specification (PackageName and - // PackageVersionConstraint). All packages must have the same name so only - // the version can differ -> we could simply pick the package with the most - // recent version and optionally omit a warning (or even abort with an error). + // match the dependency specification (PackageName and PackageVersion). + // TODO: Support PackageVersionConstraint: All packages must have the same + // name so only the version can differ -> we could simply pick the package + // with the most recent version and optionally omit a warning (or even + // abort with an error). packs.into_iter().try_for_each(|p| { let _ = progress.as_ref().map(|p| p.tick()); @@ -180,22 +184,27 @@ impl Dag { // TODO: It seems easier and more efficient to do this in `add_sub_packages` as well (it // makes that function more complex but doing it separately is weird). fn add_edges( - mappings: &HashMap<&Package, daggy::NodeIndex>, - dag: &mut daggy::Dag<&Package, DependencyType>, + mappings: &HashMap<&Package, NodeIndex>, + dag: &mut Acyclic>, conditional_data: &ConditionData<'_>, ) -> Result<()> { for (package, idx) in mappings { get_package_dependencies(package, conditional_data) - .and_then_ok(|(dep_name, dep_constr, dep_kind)| { + .and_then_ok(|(dep_name, dep_version, dep_kind)| { mappings .iter() .filter(|(pkg, _)| { - *pkg.name() == dep_name && dep_constr.matches(pkg.version()) + *pkg.name() == dep_name && *pkg.version() == dep_version }) .try_for_each(|(dep, dep_idx)| { - dag.add_edge(*idx, *dep_idx, dep_kind.clone()) + dag.try_add_edge(*idx, *dep_idx, dep_kind.clone()) .map(|_| ()) - .map_err(Error::from) + // Only debug formatting is available for the error and for + // cycles it is quite useless (the context below is much more + // helpful than, e.g., "Cycle(Cycle(NodeIndex(0)))") but we'll + // include it for completeness and in case of the other errors + // that could theoretically occur: + .map_err(|e| anyhow!(format!("{e:?}"))) .with_context(|| { anyhow!( "Failed to add package dependency DAG edge \ @@ -215,7 +224,7 @@ impl Dag { } // Create an empty DAG and use the above helper functions to compute the dependency graph: - let mut dag: daggy::Dag<&Package, DependencyType> = daggy::Dag::new(); + let mut dag = Acyclic::>::new(); let mut mappings = HashMap::new(); trace!("Building the package dependency DAG for package {:?}", p); @@ -234,10 +243,11 @@ impl Dag { trace!("Finished building the package DAG"); Ok(Dag { - dag: dag.map( + dag: Acyclic::<_>::try_from_graph(dag.map( |_, p: &&Package| -> Package { (*p).clone() }, |_, e| (*e).clone(), - ), + )) + .unwrap(), // The dag is already acyclic so this cannot fail root_idx, }) } @@ -249,9 +259,8 @@ impl Dag { /// The order of the packages is _NOT_ guaranteed by the implementation pub fn all_packages(&self) -> Vec<&Package> { self.dag - .graph() .node_indices() - .filter_map(|idx| self.dag.graph().node_weight(idx)) + .filter_map(|idx| self.dag.node_weight(idx)) .collect() } @@ -261,16 +270,15 @@ impl Dag { } #[derive(Clone)] -pub struct DagDisplay<'a>(&'a Dag, daggy::NodeIndex, Option); +pub struct DagDisplay<'a>(&'a Dag, NodeIndex, Option); -impl<'a> TreeItem for DagDisplay<'a> { +impl TreeItem for DagDisplay<'_> { type Child = Self; fn write_self(&self, f: &mut W, _: &Style) -> IoResult<()> { let p = self .0 .dag - .graph() .node_weight(self.1) .ok_or_else(|| anyhow!("Error finding node: {:?}", self.1)) .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?; @@ -281,7 +289,6 @@ impl<'a> TreeItem for DagDisplay<'a> { Some(edge_idx) => self .0 .dag - .graph() .edge_weight(edge_idx) .ok_or_else(|| anyhow!("Error finding edge: {:?}", self.2)) .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?, @@ -295,12 +302,16 @@ impl<'a> TreeItem for DagDisplay<'a> { } fn children(&self) -> Cow<[Self::Child]> { - let c = self.0.dag.children(self.1); - Cow::from( - c.iter(&self.0.dag) - .map(|(edge_idx, node_idx)| DagDisplay(self.0, node_idx, Some(edge_idx))) - .collect::>(), - ) + let mut children_walker = self + .0 + .dag + .neighbors_directed(self.1, petgraph::Outgoing) + .detach(); + let mut children = Vec::::new(); + while let Some((edge_idx, node_idx)) = children_walker.next(&self.0.dag) { + children.push(DagDisplay(self.0, node_idx, Some(edge_idx))); + } + Cow::from(children) } } @@ -319,8 +330,6 @@ mod tests { use crate::package::Dependency; use crate::util::docker::ImageName; - use indicatif::ProgressBar; - #[test] fn test_add_package() { let mut btree = BTreeMap::new(); diff --git a/src/package/dependency/build.rs b/src/package/dependency/build.rs index ad2440eb..1b3195ed 100644 --- a/src/package/dependency/build.rs +++ b/src/package/dependency/build.rs @@ -14,9 +14,8 @@ use serde::Serialize; use crate::package::dependency::condition::Condition; use crate::package::dependency::ParseDependency; -use crate::package::dependency::StringEqual; use crate::package::PackageName; -use crate::package::PackageVersionConstraint; +use crate::package::PackageVersion; /// A dependency that is packaged and is only required during build time #[derive(Serialize, Deserialize, Clone, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)] @@ -35,17 +34,8 @@ impl AsRef for BuildDependency { } } -impl StringEqual for BuildDependency { - fn str_equal(&self, s: &str) -> bool { - match self { - BuildDependency::Simple(name) => name == s, - BuildDependency::Conditional { name, .. } => name == s, - } - } -} - impl ParseDependency for BuildDependency { - fn parse_as_name_and_version(&self) -> Result<(PackageName, PackageVersionConstraint)> { + fn parse_as_name_and_version(&self) -> Result<(PackageName, PackageVersion)> { crate::package::dependency::parse_package_dependency_string_into_name_and_version( self.as_ref(), ) diff --git a/src/package/dependency/condition.rs b/src/package/dependency/condition.rs index 5bc046f4..66e5cc78 100644 --- a/src/package/dependency/condition.rs +++ b/src/package/dependency/condition.rs @@ -29,15 +29,15 @@ use crate::util::EnvironmentVariableName; /// #[derive(Serialize, Deserialize, Getters, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)] pub struct Condition { - #[serde(rename = "has_env", skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "Option::is_none")] #[getset(get = "pub")] pub(super) has_env: Option>, - #[serde(rename = "env_eq", skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "Option::is_none")] #[getset(get = "pub")] pub(super) env_eq: Option>, - #[serde(rename = "in_image", skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "Option::is_none")] #[getset(get = "pub")] pub(super) in_image: Option>, } diff --git a/src/package/dependency/mod.rs b/src/package/dependency/mod.rs index 75e97e90..87dbff5c 100644 --- a/src/package/dependency/mod.rs +++ b/src/package/dependency/mod.rs @@ -8,15 +8,13 @@ // SPDX-License-Identifier: EPL-2.0 // -use std::convert::TryFrom; - use anyhow::anyhow; use anyhow::Result; use lazy_static::lazy_static; use regex::Regex; use crate::package::PackageName; -use crate::package::PackageVersionConstraint; +use crate::package::PackageVersion; mod build; pub use build::*; @@ -26,17 +24,18 @@ pub use runtime::*; pub mod condition; -pub trait StringEqual { - fn str_equal(&self, s: &str) -> bool; -} - pub trait ParseDependency { - fn parse_as_name_and_version(&self) -> Result<(PackageName, PackageVersionConstraint)>; + fn parse_as_name_and_version(&self) -> Result<(PackageName, PackageVersion)>; } lazy_static! { + // The following regex could be simplified significantly since we basically only need the space + // (" ") for splitting name and version (and both shouldn't be empty) - the rest of the + // validation could and probably should be done when parsing `name` and `version` (can make the + // errors more precise and we avoid that the regex diverges from the rest of the validation as + // it's already the case): pub(in crate::package::dependency) static ref DEPENDENCY_PARSING_RE: Regex = - Regex::new("^(?P[[:alpha:]]([[[:alnum:]]\\.\\-_])*) (?P([\\*=><])?[[:alnum:]]([[[:alnum:]][[:punct:]]])*)$").unwrap(); + Regex::new("^(?P[[:alnum:]][[:alnum:]._-]*) (?P[*=><]?[[:alnum:]][[:alnum:][:punct:]]*)$").unwrap(); } /// Helper function for the actual implementation of the ParseDependency trait. @@ -44,12 +43,12 @@ lazy_static! { /// TODO: Reimplement using pom crate pub(in crate::package::dependency) fn parse_package_dependency_string_into_name_and_version( s: &str, -) -> Result<(PackageName, PackageVersionConstraint)> { +) -> Result<(PackageName, PackageVersion)> { let caps = crate::package::dependency::DEPENDENCY_PARSING_RE .captures(s) .ok_or_else(|| { anyhow!( - "Could not parse into package name and package version constraint: '{}'", + "Could not parse into package name and package version: '{}'", s ) })?; @@ -64,32 +63,53 @@ pub(in crate::package::dependency) fn parse_package_dependency_string_into_name_ .map(|m| String::from(m.as_str())) .ok_or_else(|| anyhow!("Could not parse version: '{}'", s))?; - let v = PackageVersionConstraint::try_from(vers).map_err(|e| { + // TODO: This is here temporarily to keep the version validation: + let _ = crate::package::PackageVersionConstraint::try_from(vers.clone()).map_err(|e| { e.context(anyhow!( "Could not parse the following package dependency string: {}", s )) })?; - Ok((PackageName::from(name), v)) + Ok((PackageName::from(name), PackageVersion::from(vers))) } #[cfg(test)] mod tests { use super::*; - use crate::package::PackageName; use crate::package::PackageVersion; // // helper functions // - fn name(s: &'static str) -> PackageName { - PackageName::from(String::from(s)) + fn dep_parse_test(name: &'static str, version: &'static str) { + let name = name.to_string(); + let version = version.to_string(); + + let dependency_specification = format!("{name} ={version}"); + let dep = Dependency::from(dependency_specification.clone()); + let (dep_name, dep_version) = dep.parse_as_name_and_version().unwrap(); + + let version = PackageVersion::from(version); + assert_eq!( + dep_name, + PackageName::from(name), + "Name check failed for input: {dependency_specification}" + ); + assert_eq!( + dep_version, version, + "Version check failed for input: {dependency_specification}" + ); } - fn exact(s: &'static str) -> PackageVersion { - PackageVersion::from(String::from(s)) + fn dep_parse_expect_err(dependency_specification: &'static str) { + let dep = Dependency::from(dependency_specification.to_string()); + let result = dep.parse_as_name_and_version(); + assert!( + result.is_err(), + "Should not be able to parse this input: {dependency_specification}" + ); } // @@ -98,43 +118,53 @@ mod tests { #[test] fn test_dependency_conversion_1() { - let s = "vim =8.2"; - let d = Dependency::from(String::from(s)); - - let (n, c) = d.parse_as_name_and_version().unwrap(); - - assert_eq!(n, name("vim")); - assert_eq!( - c, - PackageVersionConstraint::from_version(String::from("="), exact("8.2")) - ); + dep_parse_test("vim", "8.2"); } #[test] fn test_dependency_conversion_2() { - let s = "gtk15 =1b"; - let d = Dependency::from(String::from(s)); + dep_parse_test("gtk15", "1b"); + } - let (n, c) = d.parse_as_name_and_version().unwrap(); + #[test] + fn test_dependency_string_with_punctuation() { + dep_parse_test("foo-bar1.2.3", "0.123"); + } - assert_eq!(n, name("gtk15")); - assert_eq!( - c, - PackageVersionConstraint::from_version(String::from("="), exact("1b")) - ); + #[test] + fn test_dependency_string_where_pkg_starts_with_number() { + dep_parse_test("7z", "42"); } #[test] - fn test_dependency_string_with_punctuation() { - let s = "foo-bar1.2.3 =0.123"; - let d = Dependency::from(String::from(s)); + fn test_dependency_version_with_constraint() { + let name = "foobar"; + let version_constraint = "=1.42.37"; - let (n, c) = d.parse_as_name_and_version().unwrap(); + let dep = Dependency::from(format!("{name} {version_constraint}")); + let (dep_name, dep_version) = dep.parse_as_name_and_version().unwrap(); - assert_eq!(n, name("foo-bar1.2.3")); + assert_eq!(dep_name, PackageName::from(name.to_string())); assert_eq!( - c, - PackageVersionConstraint::from_version(String::from("="), exact("0.123")) + dep_version, + PackageVersion::from(version_constraint.to_string()), ); } + + #[test] + fn test_complex_dependency_parsing() { + dep_parse_test("0ad_", "42"); + dep_parse_test("2048-cli_0.0", "42"); + + dep_parse_expect_err("0] =42"); + dep_parse_expect_err("a\\ =42"); + dep_parse_expect_err("a =.0"); + dep_parse_expect_err("a ="); + dep_parse_expect_err(""); + dep_parse_expect_err(" "); + // Not supported yet: + dep_parse_expect_err("a *"); + dep_parse_expect_err("a >2"); + dep_parse_expect_err("a <2"); + } } diff --git a/src/package/dependency/runtime.rs b/src/package/dependency/runtime.rs index 75708acb..d8395448 100644 --- a/src/package/dependency/runtime.rs +++ b/src/package/dependency/runtime.rs @@ -14,9 +14,8 @@ use serde::Serialize; use crate::package::dependency::condition::Condition; use crate::package::dependency::ParseDependency; -use crate::package::dependency::StringEqual; use crate::package::PackageName; -use crate::package::PackageVersionConstraint; +use crate::package::PackageVersion; /// A dependency that is packaged and is required during runtime #[derive(Serialize, Deserialize, Clone, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)] @@ -42,15 +41,6 @@ impl AsRef for Dependency { } } -impl StringEqual for Dependency { - fn str_equal(&self, s: &str) -> bool { - match self { - Dependency::Simple(name) => name == s, - Dependency::Conditional { name, .. } => name == s, - } - } -} - impl From for Dependency { fn from(s: String) -> Dependency { Dependency::Simple(s) @@ -58,7 +48,7 @@ impl From for Dependency { } impl ParseDependency for Dependency { - fn parse_as_name_and_version(&self) -> Result<(PackageName, PackageVersionConstraint)> { + fn parse_as_name_and_version(&self) -> Result<(PackageName, PackageVersion)> { crate::package::dependency::parse_package_dependency_string_into_name_and_version( self.as_ref(), ) diff --git a/src/package/package.rs b/src/package/package.rs index 614762a1..13c73976 100644 --- a/src/package/package.rs +++ b/src/package/package.rs @@ -9,8 +9,12 @@ // use std::collections::HashMap; +use std::path::Path; use std::path::PathBuf; +use anyhow::anyhow; +use anyhow::Context; +use anyhow::Result; use getset::Getters; use serde::Deserialize; use serde::Serialize; @@ -20,6 +24,7 @@ use crate::package::name::*; use crate::package::source::*; use crate::package::version::*; use crate::package::{Phase, PhaseName}; +use crate::repository::normalize_relative_path; use crate::util::docker::ImageName; use crate::util::EnvironmentVariableName; @@ -100,6 +105,47 @@ impl Package { } } + pub fn display_name_version(&self) -> String { + format!("{} {}", self.name, self.version) + } + + // A function to prepend the path of the origin/base directory (where the `pkg.toml` file that + // defined the "patches" resides in) to the relative paths of the patches (it usually only + // makes sense to call this function once!): + pub fn set_patches_base_dir(&mut self, origin_dir: &Path, root_dir: &Path) -> Result<()> { + // origin_dir: The path to the directory of the pkg.toml file where the patches are declared + // root_dir: The root directory of the packages repository + for patch in self.patches.iter_mut() { + // Prepend the path of the directory of the `pkg.toml` file to the relative path of the + // patch file: + let mut path = origin_dir.join(patch.as_path()); + // Ensure that we use relative paths for the patches (the rest of the code that uses + // the patches doesn't work correctly with absolute paths): + if path.is_absolute() { + path = path + .strip_prefix(root_dir) + .map(|p| p.to_owned()) + .with_context(|| { + anyhow!( + "Cannot strip the prefix {} (root directory) form path {} (origin directory)", + root_dir.display(), + origin_dir.display() + ) + })?; + } + if path.is_absolute() { + // The stripping of root_dir in the previous if branch didn't work: + anyhow::bail!( + "Bug: The path {} is absolute but it should be a relative path.", + path.display() + ); + } else { + *patch = normalize_relative_path(path)?; + } + } + Ok(()) + } + #[cfg(test)] pub fn set_dependencies(&mut self, dependencies: Dependencies) { self.dependencies = dependencies; @@ -134,7 +180,7 @@ impl std::fmt::Debug for Package { pub struct DebugPackage<'a>(&'a Package); #[cfg(debug_assertions)] -impl<'a> std::fmt::Debug for DebugPackage<'a> { +impl std::fmt::Debug for DebugPackage<'_> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> { writeln!( f, @@ -221,9 +267,6 @@ impl<'a> std::fmt::Debug for DebugPackage<'a> { } impl PartialEq for Package { - // Ignore the following lint as it results in a false positive with clippy 0.1.77 - // (TODO: drop this once we bump the MSRV to 1.78): - #[allow(clippy::unconditional_recursion)] fn eq(&self, other: &Package) -> bool { (self.name(), self.version()).eq(&(other.name(), other.version())) } @@ -276,11 +319,7 @@ impl Dependencies { #[cfg(test)] pub mod tests { use super::*; - use crate::package::Dependencies; - use crate::package::HashType; - use crate::package::HashValue; - use crate::package::Source; - use crate::package::SourceHash; + use url::Url; /// helper function for quick object construction diff --git a/src/package/script.rs b/src/package/script.rs index 9375275d..b5dd8fe0 100644 --- a/src/package/script.rs +++ b/src/package/script.rs @@ -8,14 +8,10 @@ // SPDX-License-Identifier: EPL-2.0 // -// TODO: Is this really necessary? -#![allow(clippy::format_push_string)] - use std::process::ExitStatus; use anyhow::anyhow; use anyhow::Context as AnyhowContext; -use anyhow::Error; use anyhow::Result; use handlebars::{ Context, Handlebars, Helper, HelperDef, HelperResult, JsonRender, Output, PathAndJson, @@ -247,15 +243,13 @@ impl<'a> ScriptBuilder<'a> { trace!("Rendering Package: {:?}", package.debug_details()); } - hb.render("script", package) - .with_context(|| { - anyhow!( - "Rendering script for package {} {} failed", - package.name(), - package.version() - ) - }) - .map_err(Error::from) + hb.render("script", package).with_context(|| { + anyhow!( + "Rendering script for package {} {} failed", + package.name(), + package.version() + ) + }) } } diff --git a/src/package/source.rs b/src/package/source.rs index eb62eac4..cfdd6cf2 100644 --- a/src/package/source.rs +++ b/src/package/source.rs @@ -17,12 +17,20 @@ use serde::Serialize; use tracing::trace; use url::Url; +fn default_download_manually() -> bool { + false +} + #[derive(Clone, Debug, Serialize, Deserialize, Getters)] pub struct Source { #[getset(get = "pub")] url: Url, #[getset(get = "pub")] hash: SourceHash, + + // This is only required for some special packages that cannot be downloaded automatically for + // various reasons so it defaults to `false`: + #[serde(default = "default_download_manually")] #[getset(get = "pub")] download_manually: bool, } diff --git a/src/package/version.rs b/src/package/version.rs index 87f5bba4..d04a6423 100644 --- a/src/package/version.rs +++ b/src/package/version.rs @@ -14,9 +14,12 @@ use anyhow::anyhow; use anyhow::Context; use anyhow::Error; use anyhow::Result; +use once_cell::sync::Lazy; use pom::parser::Parser as PomParser; +use regex::Regex; use serde::Deserialize; use serde::Serialize; +use tracing::info; use crate::util::parser::*; @@ -27,10 +30,35 @@ pub struct PackageVersionConstraint { } impl PackageVersionConstraint { + fn get_default_constraint() -> String { + // We default to `Op::Exact` (=) to enable partial version specification + // (e.g., only the major and minor or only the major version): + // https://docs.rs/semver/latest/semver/enum.Op.html#opexact + // Our own implementation of "=" (s. below) allows only a single version to match + // while `semver::Op::Exact` can result in multiple versions matching if only a + // "partial" version is provided. + String::from("=") + } + fn parser<'a>() -> PomParser<'a, u8, Self> { - (pom::parser::sym(b'=') + PackageVersion::parser()) + (pom::parser::sym(b'=').opt() + PackageVersion::parser()) .convert(|(constraint, version)| { - String::from_utf8(vec![constraint]).map(|c| (c, version)) + if let Some(c) = constraint { + String::from_utf8(vec![c]) + .map(|c| (c, version)) + .map_err(Error::from) + } else { + semver::VersionReq::parse(&(Self::get_default_constraint() + &version)) + .map(|_| ("".to_string(), version.clone())) + .map_err(Error::from) + // TODO: Drop this (for backward compatibility, we temporarily fallback to + // the old behaviour (as if the constraint `=` was specified) if the + // provided version cannot be parsed by the semver crate - this is required + // for somewhat "exotic" versions like the old OpenSSL 1.1.1w, web browsers + // with a fourth version number, or (unstable) releases based on the date): + .inspect_err(|e| info!("Couldn't parse version \"{version}\" as SemVer ({e}) -> falling back to strict version matching (={version})")) + .map_or(Ok(("=".to_string(), version)), Ok) + } }) .map(|(constraint, version)| PackageVersionConstraint { constraint, @@ -39,10 +67,35 @@ impl PackageVersionConstraint { } pub fn matches(&self, v: &PackageVersion) -> bool { - self.version == *v + use semver::{Version, VersionReq}; + match self.constraint.as_str() { + "" => { + let constraint = + VersionReq::parse(&(Self::get_default_constraint() + self.version.as_str())) + .unwrap(); + let version = Version::parse(v.as_str()) + .with_context(|| anyhow!("Failed to parse the package version as semver::Version")) + .or_else(|eo| v.clone().try_into().map_err(|e: Error| e.context(eo))) + .with_context(|| anyhow!("Also failed to parse the package version using our own SemVer converter")) + .unwrap_or_else(|e| panic!( + "Failed to parse the package version \"{}\" as SemVer to check if it matches \"{}\". Error: {:#}", + v, + constraint, + e + )); + + constraint.matches(&version) + } + "=" => self.version == *v, + _ => panic!( + "Internal error: Unsupported version constraint: {} (version: {})", + self.constraint, self.version + ), + } } #[cfg(test)] + #[allow(unused)] pub fn from_version(constraint: String, version: PackageVersion) -> Self { PackageVersionConstraint { constraint, @@ -51,7 +104,7 @@ impl PackageVersionConstraint { } } -impl std::convert::TryFrom for PackageVersionConstraint { +impl TryFrom for PackageVersionConstraint { type Error = anyhow::Error; fn try_from(s: String) -> Result { @@ -59,15 +112,14 @@ impl std::convert::TryFrom for PackageVersionConstraint { } } -impl std::convert::TryFrom<&str> for PackageVersionConstraint { +impl TryFrom<&str> for PackageVersionConstraint { type Error = anyhow::Error; fn try_from(s: &str) -> Result { PackageVersionConstraint::parser() .parse(s.as_bytes()) .context(anyhow!("Failed to parse the following package version constraint: {}", s)) - .context("A package version constraint must have a comparator (only `=` is currently supported) and a version string, like so: =0.1.0") - .map_err(Error::from) + .context("A package version constraint must have a version and an optional comparator (only `=` is currently supported, which is also the default), e.g.: =0.1.0") } } @@ -108,7 +160,70 @@ impl AsRef for PackageVersion { impl From for PackageVersion { fn from(s: String) -> Self { - PackageVersion(s) + PackageVersion(s.trim_start_matches('=').to_string()) + } +} + +impl TryInto for PackageVersion { + type Error = anyhow::Error; + + // TODO: Improve or replace this spaghetti code that we only use as a fallback (the unwrap()s + // should be safe as long as the static regex guarantees the assumptions): + fn try_into(self) -> Result { + // Warning: This regex must remain compatible to the one in the PackageVersion::parser below!: + static VERSION_REGEX: Lazy = + Lazy::new(|| Regex::new("^(?:([[:digit:]]+)|[-_.]|[[:alpha:]]+)").unwrap()); + // This regex is based on PackageVersion::parser() below. We use a capture group to extract + // the numbers and the "(?:exp)" syntax is for a non-capturing group. If it matches we'll + // have the entire match in \0 and if it's a number it'll be in \1. + + // Our input (version string): + let mut version_str = self.0.as_str(); + // Our results (extracted version numbers): + let mut versions = Vec::::new(); + + // This loop is dangerous... Ensure that the match gets removed from version_str in every + // iteration to avoid an endless loop! + while let Some(captures) = VERSION_REGEX.captures(version_str) { + // For debugging: println!("{:?}", captures); + + let match_str = captures.get(0).unwrap().as_str(); // Unwrap safe as \0 always exists + version_str = &version_str[match_str.len()..]; + + if let Some(version_match) = captures.get(1) { + // We have a non-empty (version) number match + let version = version_match.as_str().parse()?; + versions.push(version); + } + } + + if version_str.is_empty() { + // Return what is hopefully the corresponding SemVer (the minor and patch version + // default to zero if they couldn't be extracted from PackageVersion): + Ok(semver::Version::new( + *versions.first().unwrap_or(&0), + *versions.get(1).unwrap_or(&0), + *versions.get(2).unwrap_or(&0), + )) + } else { + // We couldn't parse the entire version string -> report an error: + Err(anyhow!( + "The following rest of the package version couldn't be parsed: {}", + version_str + )) + .with_context(|| { + anyhow!( + "The regex \"{}\" for parsing the PackageVersion didn't match", + Lazy::force(&VERSION_REGEX) + ) + }) + .with_context(|| { + anyhow!( + "The PackageVersion \"{}\" couldn't be converted into a semver::Version", + self + ) + }) + } } } @@ -126,6 +241,10 @@ mod tests { #[test] fn test_parse_version_1() { + assert!(PackageVersion::parser().parse(b"1").is_ok()); + assert!(PackageVersion::parser().parse(b"1.42").is_ok()); + assert!(PackageVersion::parser().parse(b"1.42.37").is_ok()); + assert!(PackageVersion::parser().parse(b"").is_err()); assert!(PackageVersion::parser().parse(b"=").is_err()); assert!(PackageVersion::parser().parse(b"*1").is_err()); @@ -137,6 +256,10 @@ mod tests { assert!(PackageVersion::parser().parse(b"=a1").is_err()); assert!(PackageVersion::parser().parse(b"a").is_err()); + assert!(PackageVersionConstraint::parser().parse(b"1").is_ok()); + assert!(PackageVersionConstraint::parser().parse(b"1.42").is_ok()); + assert!(PackageVersionConstraint::parser().parse(b"1.42.37").is_ok()); + assert!(PackageVersionConstraint::parser().parse(b"").is_err()); assert!(PackageVersionConstraint::parser().parse(b"=").is_err()); assert!(PackageVersionConstraint::parser().parse(b"*1").is_err()); @@ -146,7 +269,6 @@ mod tests { assert!(PackageVersionConstraint::parser().parse(b"=.a").is_err()); assert!(PackageVersionConstraint::parser().parse(b"=.1").is_err()); assert!(PackageVersionConstraint::parser().parse(b"=a1").is_err()); - assert!(PackageVersionConstraint::parser().parse(b"1").is_err()); assert!(PackageVersionConstraint::parser().parse(b"a").is_err()); } diff --git a/src/repository/fs/path.rs b/src/repository/fs/path.rs index 83efb7ff..73fa44cd 100644 --- a/src/repository/fs/path.rs +++ b/src/repository/fs/path.rs @@ -8,13 +8,12 @@ // SPDX-License-Identifier: EPL-2.0 // -use std::convert::TryFrom; use std::path::Component; use anyhow::anyhow; use anyhow::Result; -/// Helper type for filtering for pathes we need or dont need +/// Helper type for filtering for paths we need or don't need /// /// We either have a directory, which has a name, or we have a pkg.toml file, which is of interest. /// All other files can be ignored and thus are not represented by this type. diff --git a/src/repository/fs/representation.rs b/src/repository/fs/representation.rs index df74b82e..7bf05f5d 100644 --- a/src/repository/fs/representation.rs +++ b/src/repository/fs/representation.rs @@ -9,8 +9,6 @@ // use std::collections::HashMap; -use std::convert::TryFrom; -use std::convert::TryInto; use std::path::Path; use std::path::PathBuf; @@ -268,7 +266,6 @@ fn load_file(path: &Path) -> Result { trace!("Reading {}", path.display()); std::fs::read_to_string(path) .with_context(|| anyhow!("Reading file from filesystem: {}", path.display())) - .map_err(Error::from) } #[cfg(test)] diff --git a/src/repository/mod.rs b/src/repository/mod.rs index 5167a781..c7d9faeb 100644 --- a/src/repository/mod.rs +++ b/src/repository/mod.rs @@ -13,3 +13,4 @@ mod repository; pub use repository::*; mod fs; +mod pkg_toml_source; diff --git a/src/repository/pkg_toml_source.rs b/src/repository/pkg_toml_source.rs new file mode 100644 index 00000000..9f765dad --- /dev/null +++ b/src/repository/pkg_toml_source.rs @@ -0,0 +1,48 @@ +// Copyright (c) 2020-2024 science+computing ag and other contributors +// +// This program and the accompanying materials are made +// available under the terms of the Eclipse Public License 2.0 +// which is available at https://www.eclipse.org/legal/epl-2.0/ +// +// SPDX-License-Identifier: EPL-2.0 + +// A custom `Source` implementation for the `config` crate to tack the `pkg.toml` file path as URI/origin +// in addition to the content (basically a replacement for `config::File::from_str(str, format)`). + +use std::path::Path; + +use config::ConfigError; +use config::FileFormat; +use config::Format; +use config::Map; +use config::Source; +use config::Value; + +#[derive(Clone, Debug)] +pub struct PkgTomlSource { + content: String, + uri: String, +} + +impl PkgTomlSource { + pub fn new(path: &Path, content: String) -> Self { + // We could also use `path.to_str()` for proper error handling: + let path = path.to_string_lossy().to_string(); + PkgTomlSource { content, uri: path } + } +} + +impl Source for PkgTomlSource { + fn clone_into_box(&self) -> Box { + Box::new((*self).clone()) + } + + fn collect(&self) -> Result, ConfigError> { + FileFormat::Toml + .parse(Some(&self.uri), &self.content) + .map_err(|cause| ConfigError::FileParse { + uri: Some(self.uri.clone()), + cause, + }) + } +} diff --git a/src/repository/repository.rs b/src/repository/repository.rs index 80f7f51e..469c86fd 100644 --- a/src/repository/repository.rs +++ b/src/repository/repository.rs @@ -9,6 +9,7 @@ // use std::collections::BTreeMap; +use std::path::Component; use std::path::Path; use std::path::PathBuf; @@ -16,9 +17,7 @@ use anyhow::anyhow; use anyhow::Context; use anyhow::Error; use anyhow::Result; -use resiter::AndThen; -use resiter::FilterMap; -use resiter::Map; +use regex::Regex; use tracing::trace; use crate::package::Package; @@ -38,6 +37,62 @@ impl From> for Repository { } } +// A helper function to normalize relative Unix paths (ensures that one cannot escape using `..`): +pub fn normalize_relative_path(path: PathBuf) -> Result { + let mut normalized_path = PathBuf::new(); + for component in path.components() { + match component { + Component::Prefix(_) => { + // "A Windows path prefix, e.g., C: or \\server\share." + // "Does not occur on Unix." + anyhow::bail!( + "The relative path \"{}\" starts with a Windows path prefix", + path.display() + ) + } + Component::RootDir => { + // "The root directory component, appears after any prefix and before anything else. + // It represents a separator that designates that a path starts from root." + anyhow::bail!( + "The relative path \"{}\" starts from the root directory", + path.display() + ); + } + Component::CurDir => { + // "A reference to the current directory, i.e., `.`." + // Also (from `Path.components()`): "Occurrences of . are normalized away, except + // if they are at the beginning of the path. For example, a/./b, a/b/, a/b/. and + // a/b all have a and b as components, but ./a/b starts with an additional CurDir + // component." + // -> May only occur as the first path component and we can ignore it / normalize + // it away (we should just ensure that it's not the only path component in which + // case the path would be empty). + } + Component::ParentDir => { + // "A reference to the parent directory, i.e., `..`." + if !normalized_path.pop() { + anyhow::bail!( + "The relative path \"{}\" uses `..` to escape the base directory", + path.display() + ) + } + } + Component::Normal(component) => { + // "A normal component, e.g., a and b in a/b. This variant is the most common one, + // it represents references to files or directories." + normalized_path.push(component); + } + } + } + + if normalized_path.parent().is_none() { + // Optional: Convert "" to ".": + normalized_path.push(Component::CurDir); + } + + Ok(normalized_path) +} + impl Repository { fn new(inner: BTreeMap<(PackageName, PackageVersion), Package>) -> Self { Repository { inner } @@ -52,30 +107,7 @@ impl Repository { trace!("Loading files from filesystem"); let fsr = FileSystemRepresentation::load(path.to_path_buf())?; - // Helper function to extract the `patches` array from a package config/definition: - fn get_patches( - config: &config::ConfigBuilder, - path: &Path, - ) -> Result> { - // TODO: Avoid unnecessary config building (inefficient): - let config = config.build_cloned().context(anyhow!( - "Failed to load the following TOML file: {}", - path.display() - ))?; - match config.get_array("patches") { - Ok(v) => v - .into_iter() - .map(config::Value::into_string) - .map_err(Error::from) - .map_err(|e| e.context("patches must be strings")) - .map_err(Error::from) - .map_ok(PathBuf::from) - .collect(), - Err(config::ConfigError::NotFound(_)) => Ok(Vec::with_capacity(0)), - Err(e) => Err(Error::from(e)), - } - } - + let cwd = std::env::current_dir()?; let leaf_files = fsr .files() .par_iter() @@ -86,74 +118,72 @@ impl Repository { Err(e) => Some(Err(e)), }); progress.set_length(leaf_files.clone().count().try_into()?); - leaf_files.inspect(|r| trace!("Loading files for {:?}", r)) + leaf_files + .inspect(|r| trace!("Loading files for {:?}", r)) .map(|path| { progress.inc(1); let path = path?; - fsr.get_files_for(path)? + let config = fsr.get_files_for(path)? .iter() + // Load all "layers": .inspect(|(path, _)| trace!("Loading layer at {}", path.display())) - .fold(Ok(Config::builder()) as Result<_>, |config, (path, content)| { - let mut config = config?; - - let patches_before_merge = get_patches(&config, path)?; - config = config.add_source(config::File::from_str(content, config::FileFormat::Toml)); - let patches_after_merge = get_patches(&config, path)?; - - // TODO: Get rid of the unnecessarily complex handling of the `patches` configuration setting: - // Ideally this would be handled by the `config` crate (this is - // already the case for all other "settings" but in this case we also need - // to prepend the corresponding directory path). - let patches = if patches_before_merge == patches_after_merge { - patches_before_merge - } else { - // The patches have changed since the `config.merge()` of the next - // `pkg.toml` file so we have to build the paths to the patch files - // by prepending the path to the directory of the `pkg.toml` file since - // `path` is only available in this "iteration". - patches_after_merge - .into_iter() - // Prepend the path of the directory of the `pkg.toml` file to the name of the patch: - .map(|p| if let Some(current_dir) = path.parent() { - Ok(current_dir.join(p)) - } else { - Err(anyhow!("Path should point to path with parent, but doesn't: {}", path.display())) - }) - .inspect(|patch| trace!("Patch: {:?}", patch)) - // If the patch file exists, use it (as config::Value). - // Otherwise we have an error here, because we're referring to a non-existing file: - .and_then_ok(|patch| if patch.exists() { - Ok(Some(patch)) - } else { - Err(anyhow!("Patch does not exist: {}", patch.display())) - .with_context(|| anyhow!("The patch is declared here: {}", path.display())) - }) - .filter_map_ok(|o| o) - .collect::>>()? - }; - - trace!("Patches after postprocessing merge: {:?}", patches); - let patches = patches - .into_iter() - .map(|p| p.display().to_string()) - .map(config::Value::from) - .collect::>(); - { - // Update the `patches` configuration setting: - let mut builder = Config::builder(); - builder = builder.set_default("patches", config::Value::from(patches))?; - config = config.add_source(builder.build()?); - // Ideally we'd use `config.set()` but that is a permanent override (so - // subsequent `config.merge()` merges won't have an effect on - // "patches"). There's also `config.set_once()` but that only lasts - // until the next `config.merge()` and `config.set_default()` only sets - // a default value. - } - Ok(config) + .fold(Config::builder(), |config_builder, (path, content)| { + use crate::repository::pkg_toml_source::PkgTomlSource; + config_builder.add_source(PkgTomlSource::new(path, (*content).to_string())) }) - .and_then(|c| c.build()?.try_deserialize::().map_err(Error::from) - .with_context(|| anyhow!("Could not load package configuration: {}", path.display()))) - .map(|pkg| ((pkg.name().clone(), pkg.version().clone()), pkg)) + .build()?; + + let patches_value = config.get_array("patches"); + let mut pkg = config + .try_deserialize::() + .map_err(Error::from) + .with_context(|| { + anyhow!("Could not load package configuration: {}", path.display()) + })?; + + if !pkg.patches().is_empty() { + // We have to build the full relative paths to the patch files by + // prepending the path to the directory of the `pkg.toml` file they've + // been defined in so that they can be found later. + let patches = patches_value.context(anyhow!( + "Bug: Could not get the \"patches\" value for: {}", + path.display() + ))?; + let first_patch_value = patches.first().ok_or_else(|| anyhow!( + "Bug: Could not get the first \"patches\" entry for: {}", + path.display() + ))?; + // Get the origin (path to the `pkg.toml` file) for the "patches" + // setting (it must currently be the same for all array entries): + let origin_path = first_patch_value.origin().map(PathBuf::from).ok_or_else(|| anyhow!( + "Bug: Could not get the origin of the first \"patches\" entry for: {}", + path.display() + ))?; + // Note: `parent()` only "Returns None if the path terminates in a root + // or prefix, or if it’s the empty string." so this should never happen: + let origin_dir_path = origin_path.parent().ok_or_else(|| anyhow!( + "Bug: Could not get the origin's parent of the first \"patches\" entry for: {}", + path.display() + ))?; + pkg.set_patches_base_dir(origin_dir_path, &cwd) + .with_context(|| { + anyhow!("Could not set the base directory for the patches declared here: {}", path.display()) + })?; + // Check if the patches exist: + for patch in pkg.patches() { + if !patch.exists() { + return Err(anyhow!( + "The following patch does not exist: {}", + patch.display() + )) + .with_context(|| { + anyhow!("Could not process the patches declared here: {}", path.display()) + }); + } + } + } + + Ok(((pkg.name().clone(), pkg.version().clone()), pkg)) }) .collect::>>() .map(Repository::new) @@ -182,11 +212,11 @@ impl Repository { pub fn find_with_version<'a>( &'a self, name: &PackageName, - vc: &PackageVersionConstraint, + version: &PackageVersion, ) -> Vec<&'a Package> { self.inner .iter() - .filter(|((n, v), _)| n == name && vc.matches(v)) + .filter(|((n, v), _)| n == name && v == version) .map(|(_, p)| p) .collect() } @@ -194,6 +224,44 @@ impl Repository { pub fn packages(&self) -> impl Iterator { self.inner.values() } + + pub fn search_packages<'a>( + &'a self, + pname: &'a Option, + pvers: &'a Option, + matching_regexp: &'a Option, + ) -> Result + 'a> { + let mut r = self.inner.values() + .filter(move |p| { + match (pname, pvers, matching_regexp) { + (None, None, None) => true, + (Some(pname), None, None) => p.name() == pname, + (Some(pname), Some(vers), None) => p.name() == pname && vers.matches(p.version()), + (None, None, Some(regex)) => regex.is_match(p.name()), + + (_, _, _) => { + panic!("This should not be possible, either we select packages by name and (optionally) version, or by regex.") + }, + } + }).peekable(); + + // check if the iterator is empty + if r.peek().is_none() { + match (pname, pvers, matching_regexp) { + (Some(pname), None, None) => anyhow::bail!("{} not found", pname), + (Some(pname), Some(vers), None) => { + anyhow::bail!("{} {} not found", pname, vers) + } + (None, None, Some(regex)) => anyhow::bail!("{} regex not found", regex), + + (_, _, _) => { + panic!("This should not be possible, either we select packages by name and (optionally) version, or by regex.") + } + } + } + + Ok(r) + } } #[cfg(test)] @@ -278,9 +346,9 @@ pub mod tests { let repo = Repository::from(btree); - let constraint = PackageVersionConstraint::from_version(String::from("="), pversion("2")); + let version = pversion("=2"); - let ps = repo.find_with_version(&pname("a"), &constraint); + let ps = repo.find_with_version(&pname("a"), &version); assert_eq!(ps.len(), 1); let p = ps.first().unwrap(); @@ -294,9 +362,8 @@ pub mod tests { use crate::package::Package; fn get_pkg(repo: &Repository, name: &str, version: &str) -> Package { - let constraint = - PackageVersionConstraint::from_version(String::from("="), pversion(version)); - let pkgs = repo.find_with_version(&pname(name), &constraint); + let version = pversion(version); + let pkgs = repo.find_with_version(&pname(name), &version); assert_eq!(pkgs.len(), 1, "Failed to find pkg: {name} ={version}"); (*pkgs.first().unwrap()).clone() } @@ -319,7 +386,8 @@ pub mod tests { assert_pkg(&repo, "s", "19.1"); assert_pkg(&repo, "z", "26"); - // Verify the paths of the patches (and "merging"): + // Verify the paths of the patches (and the base directory "merging"/joining logic plus the + // normalization of relative paths): // The patches are defined as follows: // s/pkg.toml: patches = [ "./foo.patch" ] // s/19.0/pkg.toml: patches = ["./foo.patch","s190.patch"] @@ -327,11 +395,10 @@ pub mod tests { // s/19.2/pkg.toml: patches = ["../foo.patch"] // s/19.3/pkg.toml: patches = ["s190.patch"] let p = get_pkg(&repo, "s", "19.0"); - // Ideally we'd normalize the `./` away: assert_eq!( p.patches(), &vec![ - PathBuf::from("examples/packages/repo/s/19.0/./foo.patch"), + PathBuf::from("examples/packages/repo/s/19.0/foo.patch"), PathBuf::from("examples/packages/repo/s/19.0/s190.patch") ] ); @@ -341,10 +408,9 @@ pub mod tests { &vec![PathBuf::from("examples/packages/repo/s/foo.patch")] ); let p = get_pkg(&repo, "s", "19.2"); - // We might want to normalize the `19.2/../` away: assert_eq!( p.patches(), - &vec![PathBuf::from("examples/packages/repo/s/19.2/../foo.patch")] + &vec![PathBuf::from("examples/packages/repo/s/foo.patch")] ); let p = get_pkg(&repo, "s", "19.3"); assert_eq!( @@ -354,4 +420,28 @@ pub mod tests { Ok(()) } + + #[test] + fn test_relative_path_normalization() -> Result<()> { + assert!(normalize_relative_path(PathBuf::from("/root")).is_err()); + assert!(normalize_relative_path(PathBuf::from("a/../../root")).is_err()); + assert_eq!( + normalize_relative_path(PathBuf::from(""))?, + PathBuf::from(".") + ); + assert_eq!( + normalize_relative_path(PathBuf::from("."))?, + PathBuf::from(".") + ); + assert_eq!( + normalize_relative_path(PathBuf::from("./a//b/../b/./c/."))?, + PathBuf::from("a/b/c") + ); + assert_eq!( + normalize_relative_path(PathBuf::from("./a//../b/"))?, + PathBuf::from("b") + ); + + Ok(()) + } } diff --git a/src/source/mod.rs b/src/source/mod.rs index 8e1fa830..d9d002db 100644 --- a/src/source/mod.rs +++ b/src/source/mod.rs @@ -12,7 +12,6 @@ use std::path::PathBuf; use anyhow::anyhow; use anyhow::Context; -use anyhow::Error; use anyhow::Result; use tracing::trace; use url::Url; @@ -110,10 +109,7 @@ impl SourceEntry { if !self.cache_root.is_dir() { trace!("Cache root does not exist: {}", self.cache_root.display()); - return Err(anyhow!( - "Cache root {} does not exist!", - self.cache_root.display() - )); + anyhow::bail!("Cache root {} does not exist!", self.cache_root.display()); } { @@ -140,6 +136,5 @@ impl SourceEntry { .open(&p) .await .with_context(|| anyhow!("Creating file: {}", p.display())) - .map_err(Error::from) } } diff --git a/src/ui/package.rs b/src/ui/package.rs index 5d0fef57..be154ab3 100644 --- a/src/ui/package.rs +++ b/src/ui/package.rs @@ -108,7 +108,7 @@ pub fn handlebars_for_package_printing(format: &str) -> Result { Ok(hb) } -impl<'a, P: Borrow> PreparePrintPackage<'a, P> { +impl> PreparePrintPackage<'_, P> { pub fn into_displayable(self) -> Result { let script = ScriptBuilder::new(&Shebang::from(self.config.shebang().clone())) .build( diff --git a/src/util/docker.rs b/src/util/docker.rs index fde682a8..08667787 100644 --- a/src/util/docker.rs +++ b/src/util/docker.rs @@ -11,10 +11,10 @@ use std::collections::HashMap; use anyhow::anyhow; +use anyhow::Context; use anyhow::Result; use serde::Deserialize; use serde::Serialize; -use tracing::warn; #[derive( parse_display::Display, @@ -51,34 +51,89 @@ impl AsRef for ImageName { } #[derive(Debug, Deserialize)] +#[serde(deny_unknown_fields)] pub struct ContainerImage { pub name: ImageName, pub short_name: ImageName, } -// To convert a user-supplied image name into an expanded image name: -pub fn resolve_image_name(name: &str, available_images: &Vec) -> Result { - let mut images = HashMap::new(); - for image in available_images { - if images.insert(&image.name, &image.name).is_some() { - warn!( - "The image name \"{0}\" is specified multiple times in the configured `images` list", - image.name - ); +pub struct ImageNameLookup { + long2short: HashMap, + short2long: HashMap, +} + +impl ImageNameLookup { + pub fn create(available_images: &[ContainerImage]) -> Result { + let mut long2short = HashMap::new(); + let mut short2long = HashMap::new(); + let mut all_names = HashMap::new(); // Optional (to check for name collisions) + for (idx, image) in available_images.iter().enumerate() { + if let Some(duplicate) = all_names.insert(image.name.clone(), idx) { + return Err(anyhow!( + "The image full name \"{0}\" is specified multiple times in the configured `images` list (either as short or full name)", + image.name + )).with_context(|| anyhow!( + "The configured container image with index {idx} ({:?}) collides with the previous definition at index {duplicate} ({:?})", + available_images.get(idx), + available_images.get(duplicate)) + ); + } + if let Some(duplicate) = all_names.insert(image.short_name.clone(), idx) { + return Err(anyhow!( + "The image short name \"{0}\" is specified multiple times in the configured `images` list (either as short or full name)", + image.short_name + )).with_context(|| anyhow!( + "The configured container image with index {idx} ({:?}) collides with the previous definition at index {duplicate} ({:?})", + available_images.get(idx), + available_images.get(duplicate)) + ); + } + long2short.insert(image.name.clone(), image.short_name.clone()); + short2long.insert(image.short_name.clone(), image.name.clone()); } - if images.insert(&image.short_name, &image.name).is_some() { - warn!( - "The image short name \"{0}\" is specified multiple times in the configured `images` list", - image.short_name - ); + Ok(ImageNameLookup { + long2short, + short2long, + }) + } + + // To convert a user-supplied image name into an expanded image name: + pub fn expand(&self, image_name: &str) -> Result { + let image_name = ImageName::from(image_name.to_string()); + if self.long2short.contains_key(&image_name) { + // It already is a valid long/expanded image name: + Ok(ImageName::from(image_name.to_string())) + } else if let Some(image_name) = self.short2long.get(&image_name) { + Ok(ImageName::from(image_name.to_string())) + } else { + // It is neither a valid short name nor a valid long name: + let available_long_names = self + .long2short + .clone() + .into_keys() + .map(|name| name.0.to_string()); + let available_short_names = self + .short2long + .clone() + .into_keys() + .map(|name| name.0.to_string()); + let mut available_images = available_long_names + .chain(available_short_names) + .collect::>(); + available_images.sort_unstable(); + let available_images = available_images.join(","); + Err(anyhow!("Failed to resolve the requested container image name \"{image_name}\". The available images are: {available_images}")) } } - images.get(&ImageName::from(name.to_string())).cloned().ok_or_else(|| { - let mut available_images = images.into_keys().map(|name| name.0.to_string()).collect::>(); - available_images.sort_unstable(); - let available_images = available_images.join(","); - anyhow!("Failed to resolve the requested container image name \"{name}\". The available images are: {available_images}") - }).cloned() + + // To try to shorten an image name based on the currently configured short names: + pub fn shorten(&self, image_name: &str) -> String { + let image_name = ImageName::from(image_name.to_string()); + self.long2short + .get(&image_name) + .unwrap_or(&image_name) + .to_string() + } } #[derive( diff --git a/src/util/filters.rs b/src/util/filters.rs index dca693ee..3bb4f375 100644 --- a/src/util/filters.rs +++ b/src/util/filters.rs @@ -16,6 +16,7 @@ use tracing::trace; use crate::package::Package; use crate::package::PackageName; +use crate::package::PackageVersion; use crate::package::PackageVersionConstraint; use crate::package::ParseDependency; @@ -27,7 +28,11 @@ pub fn build_package_filter_by_dependency_name( ) -> impl filters::failable::filter::FailableFilter { let n = name.clone(); // clone, so we can move into closure let filter_build_dep = move |p: &Package| -> Result { - trace!("Checking whether any build depenency of {:?} is '{}'", p, n); + trace!( + "Checking whether any build dependency of {:?} is '{}'", + p, + n + ); Ok({ check_build_dep && p.dependencies() @@ -46,7 +51,7 @@ pub fn build_package_filter_by_dependency_name( let n = name.clone(); // clone, so we can move into closure let filter_rt_dep = move |p: &Package| -> Result { trace!( - "Checking whether any runtime depenency of {:?} is '{}'", + "Checking whether any runtime dependency of {:?} is '{}'", p, n ); @@ -75,12 +80,28 @@ pub fn build_package_filter_by_name(name: PackageName) -> impl filters::filter:: } } +pub fn build_package_filter_by_version( + version: PackageVersion, +) -> impl filters::filter::Filter { + move |p: &Package| { + trace!("Checking {:?} -> version == {}", p, version); + *p.version() == version + } +} + pub fn build_package_filter_by_version_constraint( - constraint: PackageVersionConstraint, + version_constraint: Option, ) -> impl filters::filter::Filter { move |p: &Package| { - trace!("Checking {:?} -> version matches {:?}", p, constraint); - constraint.matches(p.version()) + trace!( + "Checking {:?} -> version matches constraint: {:?}", + p, + version_constraint + ); + version_constraint + .as_ref() + .map(|v| v.matches(p.version())) + .unwrap_or(true) } } @@ -91,7 +112,6 @@ mod tests { use std::collections::BTreeMap; use resiter::Filter; - use resiter::Map; use crate::package::tests::package; use crate::package::tests::pname;