cargo-config2-0.1.29/.cargo_vcs_info.json0000644000000001360000000000100135450ustar { "git": { "sha1": "89f671c19caab16fd8e606b94934ff817dce8da7" }, "path_in_vcs": "" }cargo-config2-0.1.29/CHANGELOG.md000064400000000000000000000132651046102023000141550ustar 00000000000000# Changelog All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](https://semver.org). ## [Unreleased] ## [0.1.29] - 2024-09-01 - Support `target..rustdocflags` [that added in Cargo 1.78](https://github.com/rust-lang/cargo/pull/13197). `Config::rustdocflags` is a new recommended interface to get rustdocflags. ## [0.1.28] - 2024-09-01 - Support the `[cargo-new]` table. ([#21](https://github.com/taiki-e/cargo-config2/pull/21), thanks @ranger-ross) ## [0.1.27] - 2024-08-19 - Support the `[http]` table. ([#20](https://github.com/taiki-e/cargo-config2/pull/20), thanks @ranger-ross) ## [0.1.26] - 2024-04-20 - Fix regression [when buggy rustc_workspace_wrapper is set](https://github.com/cuviper/autocfg/issues/58#issuecomment-2067625980), introduced in 0.1.25. ## [0.1.25] - 2024-04-17 - Respect rustc_wrapper and rustc_workspace_wrapper in `Config::{rustc_version, host_triple}` to match the [Cargo's new behavior](https://github.com/rust-lang/cargo/pull/13659). (Other APIs such as `Config::rustc` are already respecting wrappers.) ## [0.1.24] - 2024-04-09 - Fix bug when merging array fields in config. ## [0.1.23] - 2024-03-29 - Fix `Config::rustc` when both rustc_wrapper and rustc_workspace_wrapper are set. ## [0.1.22] - 2024-03-20 - Implement `From<&PathAndArgs>` for `std::process::Command`. ## [0.1.21] - 2024-03-20 - Add `{RustcVersion,CargoVersion}::major_minor`. ## [0.1.20] - 2024-03-20 - Add `Config::{rustc_version, cargo_version}`. ## [0.1.19] - 2024-02-10 - Update `toml_edit` to 0.22. ## [0.1.18] - 2024-01-25 - Make `home` dependency Windows-only dependency. ## [0.1.17] - 2023-12-16 - Remove dependency on `once_cell`. ## [0.1.16] - 2023-11-17 - Support `target.'cfg(..)'.linker` [that added in Cargo 1.74](https://github.com/rust-lang/cargo/pull/12535). - Update `toml_edit` to 0.21. ## [0.1.15] - 2023-10-24 - Improve compile time. ## [0.1.14] - 2023-10-18 - Improve compile time. ## [0.1.13] - 2023-10-17 - Improve compatibility with old Cargo. ## [0.1.12] - 2023-09-14 - Improve robustness when new cfgs are added in the future. - Update `toml` to 0.8. ## [0.1.11] - 2023-09-11 - Remove dependency on `shell-escape`. ## [0.1.10] - 2023-09-08 - Remove dependency on `cfg-expr`. ## [0.1.9] - 2023-08-22 - Recognize unstable `target.cfg(relocation_model = "...")` on nightly. ## [0.1.8] - 2023-07-03 - Fix build error from dependency when built with `-Z minimal-versions`. ## [0.1.7] - 2023-04-05 - Update `cfg-expr` to 0.15. ## [0.1.6] - 2023-03-07 - Support the `[registries]` and `[registry]` tables. ([#8](https://github.com/taiki-e/cargo-config2/pull/8), thanks @yottalogical) ## [0.1.5] - 2023-02-23 - Fix handling of empty string rustc wrapper envs. ([#7](https://github.com/taiki-e/cargo-config2/pull/7), thanks @tofay) ## [0.1.4] - 2023-01-28 - Update `cfg-expr` to 0.14. - Update `toml` to 0.7. ## [0.1.3] - 2023-01-24 - Migrate to `toml` 0.6. ([#6](https://github.com/taiki-e/cargo-config2/pull/6)) ## [0.1.2] - 2023-01-10 - Improve error messages. - Add `Config::cargo` method. - Documentation improvements. ## [0.1.1] - 2023-01-09 - Fix `serde::Serialize` impl of `Config` after target resolved. ## [0.1.0] - 2023-01-09 Initial release [Unreleased]: https://github.com/taiki-e/cargo-config2/compare/v0.1.29...HEAD [0.1.29]: https://github.com/taiki-e/cargo-config2/compare/v0.1.28...v0.1.29 [0.1.28]: https://github.com/taiki-e/cargo-config2/compare/v0.1.27...v0.1.28 [0.1.27]: https://github.com/taiki-e/cargo-config2/compare/v0.1.26...v0.1.27 [0.1.26]: https://github.com/taiki-e/cargo-config2/compare/v0.1.25...v0.1.26 [0.1.25]: https://github.com/taiki-e/cargo-config2/compare/v0.1.24...v0.1.25 [0.1.24]: https://github.com/taiki-e/cargo-config2/compare/v0.1.23...v0.1.24 [0.1.23]: https://github.com/taiki-e/cargo-config2/compare/v0.1.22...v0.1.23 [0.1.22]: https://github.com/taiki-e/cargo-config2/compare/v0.1.21...v0.1.22 [0.1.21]: https://github.com/taiki-e/cargo-config2/compare/v0.1.20...v0.1.21 [0.1.20]: https://github.com/taiki-e/cargo-config2/compare/v0.1.19...v0.1.20 [0.1.19]: https://github.com/taiki-e/cargo-config2/compare/v0.1.18...v0.1.19 [0.1.18]: https://github.com/taiki-e/cargo-config2/compare/v0.1.17...v0.1.18 [0.1.17]: https://github.com/taiki-e/cargo-config2/compare/v0.1.16...v0.1.17 [0.1.16]: https://github.com/taiki-e/cargo-config2/compare/v0.1.15...v0.1.16 [0.1.15]: https://github.com/taiki-e/cargo-config2/compare/v0.1.14...v0.1.15 [0.1.14]: https://github.com/taiki-e/cargo-config2/compare/v0.1.13...v0.1.14 [0.1.13]: https://github.com/taiki-e/cargo-config2/compare/v0.1.12...v0.1.13 [0.1.12]: https://github.com/taiki-e/cargo-config2/compare/v0.1.11...v0.1.12 [0.1.11]: https://github.com/taiki-e/cargo-config2/compare/v0.1.10...v0.1.11 [0.1.10]: https://github.com/taiki-e/cargo-config2/compare/v0.1.9...v0.1.10 [0.1.9]: https://github.com/taiki-e/cargo-config2/compare/v0.1.8...v0.1.9 [0.1.8]: https://github.com/taiki-e/cargo-config2/compare/v0.1.7...v0.1.8 [0.1.7]: https://github.com/taiki-e/cargo-config2/compare/v0.1.6...v0.1.7 [0.1.6]: https://github.com/taiki-e/cargo-config2/compare/v0.1.5...v0.1.6 [0.1.5]: https://github.com/taiki-e/cargo-config2/compare/v0.1.4...v0.1.5 [0.1.4]: https://github.com/taiki-e/cargo-config2/compare/v0.1.3...v0.1.4 [0.1.3]: https://github.com/taiki-e/cargo-config2/compare/v0.1.2...v0.1.3 [0.1.2]: https://github.com/taiki-e/cargo-config2/compare/v0.1.1...v0.1.2 [0.1.1]: https://github.com/taiki-e/cargo-config2/compare/v0.1.0...v0.1.1 [0.1.0]: https://github.com/taiki-e/cargo-config2/releases/tag/v0.1.0 cargo-config2-0.1.29/Cargo.lock0000644000000310030000000000100115150ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "anstyle" version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" [[package]] name = "anyhow" version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" [[package]] name = "autocfg" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" [[package]] name = "bitflags" version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" [[package]] name = "build-context" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53cc09198d5eef1d91e3fd5bafcdea45c5fe44464e8c92dd985d49bc9c563fba" [[package]] name = "cargo-config2" version = "0.1.29" dependencies = [ "anyhow", "build-context", "clap", "duct", "fs-err", "home", "lexopt", "rustversion", "serde", "serde_derive", "serde_json", "shell-escape", "static_assertions", "tempfile", "toml", "toml_edit", ] [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "clap" version = "4.5.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed6719fffa43d0d87e5fd8caeab59be1554fb028cd30edc88fc4369b17971019" dependencies = [ "clap_builder", "clap_derive", ] [[package]] name = "clap_builder" version = "4.5.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "216aec2b177652e3846684cbfe25c9964d18ec45234f0f5da5157b207ed1aab6" dependencies = [ "anstyle", "clap_lex", ] [[package]] name = "clap_derive" version = "4.5.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "501d359d5f3dcaf6ecdeee48833ae73ec6e42723a1e52419c79abf9507eec0a0" dependencies = [ "heck", "proc-macro2", "quote", "syn", ] [[package]] name = "clap_lex" version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" [[package]] name = "duct" version = "0.13.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e4ab5718d1224b63252cd0c6f74f6480f9ffeb117438a2e0f5cf6d9a4798929c" dependencies = [ "libc", "once_cell", "os_pipe", "shared_child", ] [[package]] name = "equivalent" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" dependencies = [ "libc", "windows-sys 0.52.0", ] [[package]] name = "fastrand" version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" [[package]] name = "fs-err" version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "88a41f105fe1d5b6b34b2055e3dc59bb79b46b48b2040b9e6c7b4b5de097aa41" dependencies = [ "autocfg", ] [[package]] name = "hashbrown" version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" [[package]] name = "heck" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "home" version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" dependencies = [ "windows-sys 0.52.0", ] [[package]] name = "indexmap" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68b900aa2f7301e21c36462b170ee99994de34dff39a4a6a528e80e7376d07e5" dependencies = [ "equivalent", "hashbrown", ] [[package]] name = "itoa" version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] name = "lexopt" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baff4b617f7df3d896f97fe922b64817f6cd9a756bb81d40f8883f2f66dcb401" [[package]] name = "libc" version = "0.2.158" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" [[package]] name = "linux-raw-sys" version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" [[package]] name = "memchr" version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "once_cell" version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "os_pipe" version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ffd2b0a5634335b135d5728d84c5e0fd726954b87111f7506a61c502280d982" dependencies = [ "libc", "windows-sys 0.59.0", ] [[package]] name = "proc-macro2" version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" dependencies = [ "unicode-ident", ] [[package]] name = "quote" version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" dependencies = [ "proc-macro2", ] [[package]] name = "rustix" version = "0.38.35" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a85d50532239da68e9addb745ba38ff4612a242c1c7ceea689c4bc7c2f43c36f" dependencies = [ "bitflags", "errno", "libc", "linux-raw-sys", "windows-sys 0.52.0", ] [[package]] name = "rustversion" version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" [[package]] name = "ryu" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" [[package]] name = "serde" version = "1.0.209" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "99fce0ffe7310761ca6bf9faf5115afbc19688edd00171d81b1bb1b116c63e09" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" version = "1.0.209" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a5831b979fd7b5439637af1752d535ff49f4860c0f341d1baeb6faf0f4242170" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "serde_json" version = "1.0.127" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8043c06d9f82bd7271361ed64f415fe5e12a77fdb52e573e7f06a516dea329ad" dependencies = [ "itoa", "memchr", "ryu", "serde", ] [[package]] name = "serde_spanned" version = "0.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eb5b1b31579f3811bf615c144393417496f152e12ac8b7663bf664f4a815306d" dependencies = [ "serde", ] [[package]] name = "shared_child" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09fa9338aed9a1df411814a5b2252f7cd206c55ae9bf2fa763f8de84603aa60c" dependencies = [ "libc", "windows-sys 0.59.0", ] [[package]] name = "shell-escape" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "45bb67a18fa91266cc7807181f62f9178a6873bfad7dc788c42e6430db40184f" [[package]] name = "static_assertions" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] name = "syn" version = "2.0.77" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "tempfile" version = "3.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04cbcdd0c794ebb0d4cf35e88edd2f7d2c4c3e9a5a6dab322839b321c6a87a64" dependencies = [ "cfg-if", "fastrand", "once_cell", "rustix", "windows-sys 0.59.0", ] [[package]] name = "toml" version = "0.8.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e" dependencies = [ "serde", "serde_spanned", "toml_datetime", "toml_edit", ] [[package]] name = "toml_datetime" version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" dependencies = [ "serde", ] [[package]] name = "toml_edit" version = "0.22.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "583c44c02ad26b0c3f3066fe629275e50627026c51ac2e595cca4c230ce1ce1d" dependencies = [ "indexmap", "serde", "serde_spanned", "toml_datetime", "winnow", ] [[package]] name = "unicode-ident" version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] name = "windows-sys" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ "windows-targets", ] [[package]] name = "windows-sys" version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ "windows-targets", ] [[package]] name = "windows-targets" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" 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_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_i686_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" version = "0.6.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68a9bda4691f099d435ad181000724da8e5899daa10713c2d432552b9ccd3a6f" dependencies = [ "memchr", ] cargo-config2-0.1.29/Cargo.toml0000644000000101270000000000100115440ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" rust-version = "1.70" name = "cargo-config2" version = "0.1.29" build = false exclude = [ "/.*", "/tools", ] autobins = false autoexamples = false autotests = false autobenches = false description = """ Load and resolve Cargo configuration. """ readme = "README.md" keywords = [ "cargo", "config", ] categories = [] license = "Apache-2.0 OR MIT" repository = "https://github.com/taiki-e/cargo-config2" [package.metadata.cargo_check_external_types] allowed_external_types = ["serde::*"] [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] [lib] name = "cargo_config2" path = "src/lib.rs" doc-scrape-examples = false [[example]] name = "get" path = "examples/get.rs" [[test]] name = "test" path = "tests/test.rs" [dependencies.serde] version = "1.0.165" [dependencies.serde_derive] version = "1.0.165" [dependencies.toml_edit] version = "0.22" features = [ "parse", "serde", ] default-features = false [dev-dependencies.anyhow] version = "1" [dev-dependencies.build-context] version = "0.1" [dev-dependencies.clap] version = "4" features = [ "std", "derive", ] default-features = false [dev-dependencies.duct] version = "0.13" [dev-dependencies.fs-err] version = "2" [dev-dependencies.lexopt] version = "0.3" [dev-dependencies.rustversion] version = "1" [dev-dependencies.serde_json] version = "1" [dev-dependencies.shell-escape] version = "0.1" [dev-dependencies.static_assertions] version = "1" [dev-dependencies.tempfile] version = "3" [dev-dependencies.toml] version = "0.8" [target."cfg(windows)".dependencies.home] version = "0.5" [lints.clippy] all = "warn" as_ptr_cast_mut = "warn" as_underscore = "warn" default_union_representation = "warn" inline_asm_x86_att_syntax = "warn" pedantic = "warn" trailing_empty_array = "warn" transmute_undefined_repr = "warn" undocumented_unsafe_blocks = "warn" [lints.clippy.bool_assert_comparison] level = "allow" priority = 1 [lints.clippy.borrow_as_ptr] level = "allow" priority = 1 [lints.clippy.cast_lossless] level = "allow" priority = 1 [lints.clippy.declare_interior_mutable_const] level = "allow" priority = 1 [lints.clippy.doc_markdown] level = "allow" priority = 1 [lints.clippy.float_cmp] level = "allow" priority = 1 [lints.clippy.incompatible_msrv] level = "allow" priority = 1 [lints.clippy.lint_groups_priority] level = "allow" priority = 1 [lints.clippy.manual_assert] level = "allow" priority = 1 [lints.clippy.manual_range_contains] level = "allow" priority = 1 [lints.clippy.missing_errors_doc] level = "allow" priority = 1 [lints.clippy.module_name_repetitions] level = "allow" priority = 1 [lints.clippy.naive_bytecount] level = "allow" priority = 1 [lints.clippy.nonminimal_bool] level = "allow" priority = 1 [lints.clippy.range_plus_one] level = "allow" priority = 1 [lints.clippy.similar_names] level = "allow" priority = 1 [lints.clippy.single_match] level = "allow" priority = 1 [lints.clippy.single_match_else] level = "allow" priority = 1 [lints.clippy.struct_excessive_bools] level = "allow" priority = 1 [lints.clippy.struct_field_names] level = "allow" priority = 1 [lints.clippy.too_many_arguments] level = "allow" priority = 1 [lints.clippy.too_many_lines] level = "allow" priority = 1 [lints.clippy.type_complexity] level = "allow" priority = 1 [lints.clippy.unreadable_literal] level = "allow" priority = 1 [lints.rust] deprecated_safe = "warn" improper_ctypes = "warn" improper_ctypes_definitions = "warn" non_ascii_idents = "warn" rust_2018_idioms = "warn" single_use_lifetimes = "warn" unreachable_pub = "warn" unsafe_op_in_unsafe_fn = "warn" [lints.rust.unexpected_cfgs] level = "warn" priority = 0 check-cfg = [] cargo-config2-0.1.29/Cargo.toml.orig000064400000000000000000000077361046102023000152410ustar 00000000000000[package] name = "cargo-config2" version = "0.1.29" #publish:version edition = "2021" rust-version = "1.70" license = "Apache-2.0 OR MIT" repository = "https://github.com/taiki-e/cargo-config2" keywords = ["cargo", "config"] categories = [] exclude = ["/.*", "/tools"] description = """ Load and resolve Cargo configuration. """ [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] [package.metadata.cargo_check_external_types] # The following are external types that are allowed to be exposed in our public API. allowed_external_types = [ "serde::*", ] [lib] doc-scrape-examples = false # Note: serde is public dependencies. [dependencies] serde = "1.0.165" serde_derive = "1.0.165" toml_edit = { version = "0.22", default-features = false, features = ["parse", "serde"] } [target.'cfg(windows)'.dependencies] home = "0.5" [dev-dependencies] anyhow = "1" build-context = "0.1" clap = { version = "4", default-features = false, features = ["std", "derive"] } duct = "0.13" fs-err = "2" lexopt = "0.3" rustversion = "1" serde_json = "1" shell-escape = "0.1" static_assertions = "1" tempfile = "3" toml = "0.8" [lints] workspace = true [workspace] resolver = "2" members = ["bench", "tools/codegen"] # This table is shared by projects under github.com/taiki-e. # It is not intended for manual editing. [workspace.lints.rust] deprecated_safe = "warn" improper_ctypes = "warn" improper_ctypes_definitions = "warn" non_ascii_idents = "warn" rust_2018_idioms = "warn" single_use_lifetimes = "warn" unexpected_cfgs = { level = "warn", check-cfg = [ ] } unreachable_pub = "warn" unsafe_op_in_unsafe_fn = "warn" [workspace.lints.clippy] all = "warn" # Downgrade deny-by-default lints pedantic = "warn" as_ptr_cast_mut = "warn" as_underscore = "warn" default_union_representation = "warn" inline_asm_x86_att_syntax = "warn" trailing_empty_array = "warn" transmute_undefined_repr = "warn" undocumented_unsafe_blocks = "warn" # Suppress buggy or noisy clippy lints bool_assert_comparison = { level = "allow", priority = 1 } borrow_as_ptr = { level = "allow", priority = 1 } # https://github.com/rust-lang/rust-clippy/issues/8286 cast_lossless = { level = "allow", priority = 1 } # https://godbolt.org/z/Pv6vbGG6E declare_interior_mutable_const = { level = "allow", priority = 1 } # https://github.com/rust-lang/rust-clippy/issues/7665 doc_markdown = { level = "allow", priority = 1 } float_cmp = { level = "allow", priority = 1 } # https://github.com/rust-lang/rust-clippy/issues/7725 incompatible_msrv = { level = "allow", priority = 1 } # buggy: doesn't consider cfg, https://github.com/rust-lang/rust-clippy/issues/12280, https://github.com/rust-lang/rust-clippy/issues/12257#issuecomment-2093667187 lint_groups_priority = { level = "allow", priority = 1 } # https://github.com/rust-lang/rust-clippy/issues/12920 manual_assert = { level = "allow", priority = 1 } manual_range_contains = { level = "allow", priority = 1 } # https://github.com/rust-lang/rust-clippy/issues/6455#issuecomment-1225966395 missing_errors_doc = { level = "allow", priority = 1 } module_name_repetitions = { level = "allow", priority = 1 } # buggy: https://github.com/rust-lang/rust-clippy/issues?q=is%3Aissue+is%3Aopen+module_name_repetitions naive_bytecount = { level = "allow", priority = 1 } nonminimal_bool = { level = "allow", priority = 1 } # buggy: https://github.com/rust-lang/rust-clippy/issues?q=is%3Aissue+is%3Aopen+nonminimal_bool range_plus_one = { level = "allow", priority = 1 } # buggy: https://github.com/rust-lang/rust-clippy/issues?q=is%3Aissue+is%3Aopen+range_plus_one similar_names = { level = "allow", priority = 1 } single_match = { level = "allow", priority = 1 } single_match_else = { level = "allow", priority = 1 } struct_excessive_bools = { level = "allow", priority = 1 } struct_field_names = { level = "allow", priority = 1 } too_many_arguments = { level = "allow", priority = 1 } too_many_lines = { level = "allow", priority = 1 } type_complexity = { level = "allow", priority = 1 } unreadable_literal = { level = "allow", priority = 1 } cargo-config2-0.1.29/LICENSE-APACHE000064400000000000000000000236761046102023000142770ustar 00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS cargo-config2-0.1.29/LICENSE-MIT000064400000000000000000000017771046102023000140050ustar 00000000000000Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. cargo-config2-0.1.29/README.md000064400000000000000000000062771046102023000136300ustar 00000000000000# cargo-config2 [![crates.io](https://img.shields.io/crates/v/cargo-config2?style=flat-square&logo=rust)](https://crates.io/crates/cargo-config2) [![docs.rs](https://img.shields.io/badge/docs.rs-cargo--config2-blue?style=flat-square&logo=docs.rs)](https://docs.rs/cargo-config2) [![license](https://img.shields.io/badge/license-Apache--2.0_OR_MIT-blue?style=flat-square)](#license) [![msrv](https://img.shields.io/badge/msrv-1.70-blue?style=flat-square&logo=rust)](https://www.rust-lang.org) [![github actions](https://img.shields.io/github/actions/workflow/status/taiki-e/cargo-config2/ci.yml?branch=main&style=flat-square&logo=github)](https://github.com/taiki-e/cargo-config2/actions) Load and resolve [Cargo configuration](https://doc.rust-lang.org/nightly/cargo/reference/config.html). This library is intended to accurately emulate the actual behavior of Cargo configuration, for example, this supports the following behaviors: - [Hierarchical structure and merge](https://doc.rust-lang.org/nightly/cargo/reference/config.html#hierarchical-structure) - [Environment variables](https://doc.rust-lang.org/nightly/cargo/reference/config.html#environment-variables) and [relative paths](https://doc.rust-lang.org/nightly/cargo/reference/config.html#config-relative-paths) resolution. - `target.` and `target.` resolution. Supported tables and fields are mainly based on [cargo-llvm-cov](https://github.com/taiki-e/cargo-llvm-cov)'s use cases, but feel free to submit an issue if you see something missing in your use case. ## Usage Add this to your `Cargo.toml`: ```toml [dependencies] cargo-config2 = "0.1" ``` `cargo-config2` is usually runnable with Cargo versions older than the Rust version required for build. (e.g., a cargo subcommand using `cargo-config2` could work with older versions such as `cargo +1.59 `.) ## Examples ```rust // Read config files hierarchically from the current directory, merge them, // apply environment variables, and resolve relative paths. let config = cargo_config2::Config::load()?; let target = "x86_64-unknown-linux-gnu"; // Resolve target-specific configuration (`target.` and `target.`), // and returns the resolved rustflags for `target`. let rustflags = config.rustflags(target)?; println!("{rustflags:?}"); ``` See also the [`get` example](https://github.com/taiki-e/cargo-config2/blob/HEAD/examples/get.rs) that partial re-implementation of `cargo config get` using cargo-config2. ## License Licensed under either of [Apache License, Version 2.0](LICENSE-APACHE) or [MIT license](LICENSE-MIT) at your option. Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. ### Third party software This product includes copies and modifications of software developed by third parties: - [`src/cfg_expr`](https://github.com/taiki-e/cargo-config2/tree/HEAD/src/cfg_expr) includes copies and modifications of [`cfg-expr` crate](https://github.com/EmbarkStudios/cfg-expr) by Embark Studios, licensed under "Apache-2.0 OR MIT". See the license files included in these directories for more details. cargo-config2-0.1.29/examples/get.rs000064400000000000000000000123351046102023000153040ustar 00000000000000// SPDX-License-Identifier: Apache-2.0 OR MIT // Partial re-implementation of `cargo config get` using cargo-config2. use std::{ env, io::{self, Write}, str::FromStr, }; use anyhow::{bail, Result}; use cargo_config2::de::Config; use lexopt::{ Arg::{Long, Short}, ValueExt, }; // TODO: --show-origin and --config static USAGE:&str = "cargo-config2-get Usage: cargo run --example get -- [OPTIONS] Options: --format Display format [default: toml] [possible values: toml, json] --merged Whether or not to merge config values [default: yes] [possible values: yes, no] -h, --help Print help information "; fn main() { if let Err(e) = try_main() { eprintln!("error: {e:#}"); std::process::exit(1) } } fn try_main() -> Result<()> { let args = Args::parse()?; let mut stdout = io::stdout().lock(); match args.merged { Merged::Yes => { let config = Config::load()?; print_config(&mut stdout, args.format, &config)?; } Merged::No => { if args.format == Format::Json { bail!( "the `json` format does not support --merged=no, try the `toml` format instead" ); } for path in cargo_config2::Walk::new(&std::env::current_dir()?) { let config = Config::load_file(&path)?; writeln!(stdout, "# {}", path.display())?; print_config(&mut stdout, args.format, &config)?; writeln!(stdout)?; } } } stdout.flush()?; // In toml format, `cargo config get` outputs this in the form of a comment, // but may output toml in an invalid format because it does not handle newlines properly. let mut stderr = io::stderr().lock(); writeln!(stderr, "note: The following environment variables may affect the loaded values.")?; for (k, v) in std::env::vars_os() { if let (Ok(k), Ok(v)) = (k.into_string(), v.into_string()) { if k.starts_with("CARGO_") { writeln!(stderr, "{k}={}", shell_escape::escape(v.into()))?; } } } stderr.flush()?; Ok(()) } fn print_config(writer: &mut dyn Write, format: Format, config: &Config) -> Result<()> { match format { Format::Json => writeln!(writer, "{}", serde_json::to_string(&config)?)?, Format::Toml => { // `cargo config get` displays config with the following format: // // ``` // a.b.c = // a.b.d = // ``` // // Neither toml nor toml_edit supports this output format, so format it manually. fn print_value(writer: &mut dyn Write, path: &str, value: &toml::Value) -> Result<()> { match value { toml::Value::Table(table) => { for (key, item) in table { print_value(writer, &format!("{path}.{key}"), item)?; } } _ => writeln!(writer, "{path} = {value}")?, } Ok(()) } let doc = toml::from_str::(&toml::to_string(&config)?)?; if let Some(table) = doc.as_table() { for (key, value) in table { print_value(writer, key, value)?; } } } } Ok(()) } struct Args { format: Format, merged: Merged, } #[derive(Clone, Copy, Default, PartialEq, Eq)] enum Format { #[default] Toml, Json, } impl FromStr for Format { type Err = anyhow::Error; fn from_str(s: &str) -> Result { match s { "toml" => Ok(Self::Toml), "json" => Ok(Self::Json), other => bail!("must be toml or json, but found `{other}`"), } } } #[derive(Clone, Copy, Default)] enum Merged { #[default] Yes, No, } impl FromStr for Merged { type Err = anyhow::Error; fn from_str(s: &str) -> Result { match s { "yes" => Ok(Self::Yes), "no" => Ok(Self::No), other => bail!("must be yes or no, but found `{other}`"), } } } impl Args { fn parse() -> Result { let mut format: Option = None; let mut merged: Option = None; let mut parser = lexopt::Parser::from_env(); while let Some(arg) = parser.next()? { match arg { Long("format") if format.is_none() => format = Some(parser.value()?.parse()?), Long("merged") if merged.is_none() => merged = Some(parser.value()?.parse()?), Short('h') | Long("help") => { print!("{USAGE}"); std::process::exit(0); } Short('V') | Long("version") => { println!("{} {}", env!("CARGO_PKG_NAME"), env!("CARGO_PKG_VERSION")); std::process::exit(0); } _ => return Err(arg.unexpected().into()), } } Ok(Self { format: format.unwrap_or_default(), merged: merged.unwrap_or_default() }) } } cargo-config2-0.1.29/src/cfg_expr/LICENSE-APACHE000064400000000000000000000251421046102023000166510ustar 00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. cargo-config2-0.1.29/src/cfg_expr/LICENSE-MIT000064400000000000000000000020421046102023000163530ustar 00000000000000Copyright (c) 2019 Embark Studios Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. cargo-config2-0.1.29/src/cfg_expr/error.rs000064400000000000000000000064461046102023000164320ustar 00000000000000// SPDX-License-Identifier: Apache-2.0 OR MIT use core::fmt; /// An error related to parsing of a cfg expression #[derive(Debug, PartialEq, Eq)] pub(crate) struct ParseError { /// The string that was parsed pub(crate) original: String, /// The range of characters in the original string that result /// in this error pub(crate) span: core::ops::Range, /// The specific reason for the error pub(crate) reason: Reason, } /// The particular reason for a `ParseError` #[derive(Debug, PartialEq, Eq)] pub(crate) enum Reason { /// not() takes exactly 1 predicate, unlike all() and any() InvalidNot(usize), /// An opening parens was unmatched with a closing parens UnclosedParens, /// A closing parens was unmatched with an opening parens UnopenedParens, /// An opening quotes was unmatched with a closing quotes UnclosedQuotes, /// The expression does not contain any valid terms Empty, /// Found an unexpected term, which wasn't one of the expected terms that /// is listed Unexpected(&'static [&'static str]), /// The root cfg() may only contain a single predicate MultipleRootPredicates, } impl fmt::Display for ParseError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str(&self.original)?; f.write_str("\n")?; for _ in 0..self.span.start { f.write_str(" ")?; } // Mismatched parens/quotes have a slightly different output // than the other errors match &self.reason { r @ (Reason::UnclosedParens | Reason::UnclosedQuotes) => { f.write_fmt(format_args!("- {r}")) } r @ Reason::UnopenedParens => f.write_fmt(format_args!("^ {r}")), other => { for _ in self.span.start..self.span.end { f.write_str("^")?; } f.write_fmt(format_args!(" {other}")) } } } } impl fmt::Display for Reason { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { use Reason::{ Empty, InvalidNot, MultipleRootPredicates, UnclosedParens, UnclosedQuotes, Unexpected, UnopenedParens, }; match self { UnclosedParens => f.write_str("unclosed parens"), UnopenedParens => f.write_str("unopened parens"), UnclosedQuotes => f.write_str("unclosed quotes"), Empty => f.write_str("empty expression"), Unexpected(expected) => { if expected.len() > 1 { f.write_str("expected one of ")?; for (i, exp) in expected.iter().enumerate() { f.write_fmt(format_args!("{}`{exp}`", if i > 0 { ", " } else { "" }))?; } f.write_str(" here") } else if !expected.is_empty() { f.write_fmt(format_args!("expected a `{}` here", expected[0])) } else { f.write_str("the term was not expected here") } } InvalidNot(np) => f.write_fmt(format_args!("not() takes 1 predicate, found {np}")), MultipleRootPredicates => f.write_str("multiple root predicates"), } } } impl std::error::Error for ParseError {} cargo-config2-0.1.29/src/cfg_expr/expr/lexer.rs000064400000000000000000000117461046102023000173750ustar 00000000000000// SPDX-License-Identifier: Apache-2.0 OR MIT use core::fmt; use crate::cfg_expr::error::{ParseError, Reason}; /// A single token in a cfg expression /// #[derive(Clone, Debug, PartialEq, Eq)] pub(crate) enum Token<'a> { /// A single contiguous term Key(&'a str), /// A single contiguous value, without its surrounding quotes Value(&'a str), /// A '=', joining a key and a value Equals, /// Beginning of an all() predicate list All, /// Beginning of an any() predicate list Any, /// Beginning of a not() predicate Not, /// A `(` for starting a predicate list OpenParen, /// A `)` for ending a predicate list CloseParen, /// A `,` for separating predicates in a predicate list Comma, } impl fmt::Display for Token<'_> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::Debug::fmt(self, f) } } impl Token<'_> { fn len(&self) -> usize { match self { Token::Key(s) => s.len(), Token::Value(s) => s.len() + 2, Token::Equals | Token::OpenParen | Token::CloseParen | Token::Comma => 1, Token::All | Token::Any | Token::Not => 3, } } } /// Allows iteration through a cfg expression, yielding /// a token or a `ParseError`. /// /// Prefer to use `Expression::parse` rather than directly /// using the lexer pub(crate) struct Lexer<'a> { pub(super) inner: &'a str, original: &'a str, offset: usize, } impl<'a> Lexer<'a> { /// Creates a Lexer over a cfg expression, it can either be /// a raw expression eg `key` or in attribute form, eg `cfg(key)` pub(crate) fn new(text: &'a str) -> Self { let text = if text.starts_with("cfg(") && text.ends_with(')') { &text[4..text.len() - 1] } else { text }; Self { inner: text, original: text, offset: 0 } } } /// A wrapper around a particular token that includes the span of the characters /// in the original string, for diagnostic purposes #[derive(Debug)] pub(crate) struct LexerToken<'a> { /// The token that was lexed pub(crate) token: Token<'a>, /// The range of the token characters in the original license expression pub(crate) span: core::ops::Range, } impl<'a> Iterator for Lexer<'a> { type Item = Result, ParseError>; fn next(&mut self) -> Option { #[inline] fn is_ident_start(ch: char) -> bool { ch == '_' || ch.is_ascii_lowercase() || ch.is_ascii_uppercase() } #[inline] fn is_ident_rest(ch: char) -> bool { is_ident_start(ch) || ch.is_ascii_digit() } // Jump over any whitespace, updating `self.inner` and `self.offset` appropriately let non_whitespace_index = match self.inner.find(|c: char| !c.is_whitespace()) { Some(idx) => idx, None => self.inner.len(), }; self.inner = &self.inner[non_whitespace_index..]; self.offset += non_whitespace_index; match self.inner.chars().next() { None => None, Some('=') => Some(Ok(Token::Equals)), Some('(') => Some(Ok(Token::OpenParen)), Some(')') => Some(Ok(Token::CloseParen)), Some(',') => Some(Ok(Token::Comma)), Some(c) => { if c == '"' { match self.inner[1..].find('"') { Some(ind) => Some(Ok(Token::Value(&self.inner[1..=ind]))), None => Some(Err(ParseError { original: self.original.to_owned(), span: self.offset..self.original.len(), reason: Reason::UnclosedQuotes, })), } } else if is_ident_start(c) { let sub_str = match self.inner[1..].find(|c: char| !is_ident_rest(c)) { Some(ind) => &self.inner[..=ind], None => self.inner, }; match sub_str { "all" => Some(Ok(Token::All)), "any" => Some(Ok(Token::Any)), "not" => Some(Ok(Token::Not)), other => Some(Ok(Token::Key(other))), } } else { Some(Err(ParseError { original: self.original.to_owned(), span: self.offset..self.offset + 1, reason: Reason::Unexpected(&["", "all", "any", "not"]), })) } } } .map(|tok| { tok.map(|tok| { let len = tok.len(); let start = self.offset; self.inner = &self.inner[len..]; self.offset += len; LexerToken { token: tok, span: start..self.offset } }) }) } } cargo-config2-0.1.29/src/cfg_expr/expr/mod.rs000064400000000000000000000162301046102023000170260ustar 00000000000000// SPDX-License-Identifier: Apache-2.0 OR MIT pub(crate) mod lexer; mod parser; use core::ops::Range; /// A predicate function, used to combine 1 or more predicates /// into a single value #[derive(Debug, Copy, Clone)] enum Func { /// `not()` with a configuration predicate. It is true if its predicate /// is false and false if its predicate is true. Not, /// `all()` with a comma separated list of configuration predicates. It /// is false if at least one predicate is false. If there are no predicates, /// it is true. /// /// The associated `usize` is the number of predicates inside the `all()`. All(usize), /// `any()` with a comma separated list of configuration predicates. It /// is true if at least one predicate is true. If there are no predicates, /// it is false. /// /// The associated `usize` is the number of predicates inside the `any()`. Any(usize), } /// A single predicate in a `cfg()` expression #[derive(Debug, PartialEq, Eq)] pub(crate) enum Predicate<'a> { /// A generic bare predicate key that doesn't match one of the known options, eg `cfg(bare)` Flag(&'a str), /// A generic key = "value" predicate that doesn't match one of the known options, eg `cfg(foo = "bar")` KeyValue { key: &'a str, val: &'a str }, } #[derive(Clone, Debug)] struct InnerPredicate { identifier: Range, value: Option>, } impl InnerPredicate { fn to_pred<'a>(&self, s: &'a str) -> Predicate<'a> { match &self.value { Some(vs) => { Predicate::KeyValue { key: &s[self.identifier.clone()], val: &s[vs.clone()] } } None => Predicate::Flag(&s[self.identifier.clone()]), } } } #[derive(Clone, Debug)] enum ExprNode { Fn(Func), Predicate(InnerPredicate), } /// A parsed `cfg()` expression that can evaluated #[derive(Clone, Debug)] pub(crate) struct Expression { expr: Vec, // We keep the original string around for providing the arbitrary // strings that can make up an expression original: String, } impl Expression { #[cfg(test)] /// An iterator over each predicate in the expression pub(crate) fn predicates(&self) -> impl Iterator> { self.expr.iter().filter_map(move |item| match item { ExprNode::Predicate(pred) => { let pred = pred.clone().to_pred(&self.original); Some(pred) } ExprNode::Fn(_) => None, }) } /// Evaluates the expression, using the provided closure to determine the value of /// each predicate, which are then combined into a final result depending on the /// functions not(), all(), or any() in the expression. /// /// `eval_predicate` typically returns `bool`, but may return any type that implements /// the `Logic` trait. pub(crate) fn eval(&self, mut eval_predicate: EP) -> T where EP: FnMut(&Predicate<'_>) -> T, T: Logic + core::fmt::Debug, { let mut result_stack = Vec::with_capacity(8); // We store the expression as postfix, so just evaluate each license // requirement in the order it comes, and then combining the previous // results according to each operator as it comes for node in &self.expr { match node { ExprNode::Predicate(pred) => { let pred = pred.to_pred(&self.original); result_stack.push(eval_predicate(&pred)); } ExprNode::Fn(Func::All(count)) => { // all() with a comma separated list of configuration predicates. let mut result = T::top(); for _ in 0..*count { let r = result_stack.pop().unwrap(); result = result.and(r); } result_stack.push(result); } ExprNode::Fn(Func::Any(count)) => { // any() with a comma separated list of configuration predicates. let mut result = T::bottom(); for _ in 0..*count { let r = result_stack.pop().unwrap(); result = result.or(r); } result_stack.push(result); } ExprNode::Fn(Func::Not) => { // not() with a configuration predicate. // It is true if its predicate is false // and false if its predicate is true. let r = result_stack.pop().unwrap(); result_stack.push(r.not()); } } } result_stack.pop().unwrap() } } /// A propositional logic used to evaluate `Expression` instances. /// /// An `Expression` consists of some predicates and the `any`, `all` and `not` operators. An /// implementation of `Logic` defines how the `any`, `all` and `not` operators should be evaluated. pub(crate) trait Logic { /// The result of an `all` operation with no operands, akin to Boolean `true`. fn top() -> Self; /// The result of an `any` operation with no operands, akin to Boolean `false`. fn bottom() -> Self; /// `AND`, which corresponds to the `all` operator. fn and(self, other: Self) -> Self; /// `OR`, which corresponds to the `any` operator. fn or(self, other: Self) -> Self; /// `NOT`, which corresponds to the `not` operator. fn not(self) -> Self; } /// A boolean logic. impl Logic for bool { #[inline] fn top() -> Self { true } #[inline] fn bottom() -> Self { false } #[inline] fn and(self, other: Self) -> Self { self && other } #[inline] fn or(self, other: Self) -> Self { self || other } #[inline] fn not(self) -> Self { !self } } /// A three-valued logic -- `None` stands for the value being unknown. /// /// The truth tables for this logic are described on /// [Wikipedia](https://en.wikipedia.org/wiki/Three-valued_logic#Kleene_and_Priest_logics). impl Logic for Option { #[inline] fn top() -> Self { Some(true) } #[inline] fn bottom() -> Self { Some(false) } #[inline] fn and(self, other: Self) -> Self { match (self, other) { // If either is false, the expression is false. (Some(false), _) | (_, Some(false)) => Some(false), // If both are true, the expression is true. (Some(true), Some(true)) => Some(true), // One or both are unknown -- the result is unknown. _ => None, } } #[inline] fn or(self, other: Self) -> Self { match (self, other) { // If either is true, the expression is true. (Some(true), _) | (_, Some(true)) => Some(true), // If both are false, the expression is false. (Some(false), Some(false)) => Some(false), // One or both are unknown -- the result is unknown. _ => None, } } #[inline] fn not(self) -> Self { self.map(|v| !v) } } cargo-config2-0.1.29/src/cfg_expr/expr/parser.rs000064400000000000000000000251221046102023000175430ustar 00000000000000// SPDX-License-Identifier: Apache-2.0 OR MIT use crate::cfg_expr::{ error::{ParseError, Reason}, expr::{ lexer::{Lexer, Token}, ExprNode, Expression, Func, InnerPredicate, }, }; impl Expression { /// Given a cfg() expression (the cfg( and ) are optional), attempts to /// parse it into a form where it can be evaluated pub(crate) fn parse(original: &str) -> Result { #[derive(Debug)] struct FuncAndSpan { func: Func, parens_index: usize, span: core::ops::Range, predicates: Vec, nest_level: u8, } let lexer = Lexer::new(original); // The lexer automatically trims any cfg( ), so reacquire // the string before we start walking tokens let original = lexer.inner; let mut func_stack: Vec = Vec::with_capacity(5); let mut expr_queue = Vec::with_capacity(5); // Keep track of the last token to simplify validation of the token stream let mut last_token: Option> = None; let parse_predicate = |key: (&str, core::ops::Range), val: Option<(&str, core::ops::Range)>| -> Result { let span = key.1; Ok(InnerPredicate { identifier: span, value: val.map(|(_, span)| span) }) }; macro_rules! token_err { ($span:expr) => {{ let expected: &[&str] = match last_token { None => &["", "all", "any", "not"], Some(Token::All | Token::Any | Token::Not) => &["("], Some(Token::CloseParen) => &[")", ","], Some(Token::Comma) => &[")", ""], Some(Token::Equals) => &["\""], Some(Token::Key(_)) => &["=", ",", ")"], Some(Token::Value(_)) => &[",", ")"], Some(Token::OpenParen) => &["", ")", "all", "any", "not"], }; return Err(ParseError { original: original.to_owned(), span: $span, reason: Reason::Unexpected(&expected), }); }}; } let mut pred_key: Option<(&str, _)> = None; let mut pred_val: Option<(&str, _)> = None; let mut root_predicate_count = 0; // Basic implementation of the https://en.wikipedia.org/wiki/Shunting-yard_algorithm 'outer: for lt in lexer { let lt = lt?; match <.token { Token::Key(k) => { if matches!(last_token, None | Some(Token::OpenParen | Token::Comma)) { pred_key = Some((k, lt.span.clone())); } else { token_err!(lt.span) } } Token::Value(v) => { if matches!(last_token, Some(Token::Equals)) { // We only record the span for keys and values // so that the expression doesn't need a lifetime // but in the value case we need to strip off // the quotes so that the proper raw string is // provided to callers when evaluating the expression pred_val = Some((v, lt.span.start + 1..lt.span.end - 1)); } else { token_err!(lt.span) } } Token::Equals => { if !matches!(last_token, Some(Token::Key(_))) { token_err!(lt.span) } } Token::All | Token::Any | Token::Not => { if matches!(last_token, None | Some(Token::OpenParen | Token::Comma)) { let new_fn = match lt.token { // the 0 is a dummy value -- it will be substituted for the real // number of predicates in the `CloseParen` branch below. Token::All => Func::All(0), Token::Any => Func::Any(0), Token::Not => Func::Not, _ => unreachable!(), }; if let Some(fs) = func_stack.last_mut() { fs.nest_level += 1; } func_stack.push(FuncAndSpan { func: new_fn, span: lt.span, parens_index: 0, predicates: vec![], nest_level: 0, }); } else { token_err!(lt.span) } } Token::OpenParen => { if matches!(last_token, Some(Token::All | Token::Any | Token::Not)) { if let Some(ref mut fs) = func_stack.last_mut() { fs.parens_index = lt.span.start; } } else { token_err!(lt.span) } } Token::CloseParen => { if matches!( last_token, None | Some(Token::All | Token::Any | Token::Not | Token::Equals) ) { token_err!(lt.span) } if let Some(top) = func_stack.pop() { let key = pred_key.take(); let val = pred_val.take(); let num_predicates = top.predicates.len() + key.is_some() as usize + top.nest_level as usize; let func = match top.func { Func::All(_) => Func::All(num_predicates), Func::Any(_) => Func::Any(num_predicates), Func::Not => { // not() doesn't take a predicate list, but only a single predicate, // so ensure we have exactly 1 if num_predicates != 1 { return Err(ParseError { original: original.to_owned(), span: top.span.start..lt.span.end, reason: Reason::InvalidNot(num_predicates), }); } Func::Not } }; for pred in top.predicates { expr_queue.push(ExprNode::Predicate(pred)); } if let Some(key) = key { let inner_pred = parse_predicate(key, val)?; expr_queue.push(ExprNode::Predicate(inner_pred)); } expr_queue.push(ExprNode::Fn(func)); // This is the only place we go back to the top of the outer loop, // so make sure we correctly record this token last_token = Some(Token::CloseParen); continue 'outer; } // We didn't have an opening parentheses if we get here return Err(ParseError { original: original.to_owned(), span: lt.span, reason: Reason::UnopenedParens, }); } Token::Comma => { if matches!( last_token, None | Some( Token::OpenParen | Token::All | Token::Any | Token::Not | Token::Equals ) ) { token_err!(lt.span) } let key = pred_key.take(); let val = pred_val.take(); let inner_pred = key.map(|key| parse_predicate(key, val)).transpose()?; match (inner_pred, func_stack.last_mut()) { (Some(pred), Some(func)) => { func.predicates.push(pred); } (Some(pred), None) => { root_predicate_count += 1; expr_queue.push(ExprNode::Predicate(pred)); } _ => {} } } } last_token = Some(lt.token); } if let Some(Token::Equals) = last_token { return Err(ParseError { original: original.to_owned(), span: original.len()..original.len(), reason: Reason::Unexpected(&["\"\""]), }); } // If we still have functions on the stack, it means we have an unclosed parens if let Some(top) = func_stack.pop() { if top.parens_index == 0 { Err(ParseError { original: original.to_owned(), span: top.span, reason: Reason::Unexpected(&["("]), }) } else { Err(ParseError { original: original.to_owned(), span: top.parens_index..original.len(), reason: Reason::UnclosedParens, }) } } else { let key = pred_key.take(); let val = pred_val.take(); if let Some(key) = key { root_predicate_count += 1; expr_queue.push(ExprNode::Predicate(parse_predicate(key, val)?)); } if expr_queue.is_empty() { Err(ParseError { original: original.to_owned(), span: 0..original.len(), reason: Reason::Empty, }) } else if root_predicate_count > 1 { Err(ParseError { original: original.to_owned(), span: 0..original.len(), reason: Reason::MultipleRootPredicates, }) } else { Ok(Expression { original: original.to_owned(), expr: expr_queue }) } } } } cargo-config2-0.1.29/src/cfg_expr/mod.rs000064400000000000000000000007121046102023000160460ustar 00000000000000// SPDX-License-Identifier: Apache-2.0 OR MIT // This module is a fork of cfg-expr, licensed under "Apache-2.0 OR MIT". // // The code has been simplified and adjusted to match our use cases. // // Source: https://github.com/EmbarkStudios/cfg-expr/tree/0.15.5 // // See the license files included in this directory for copyright & license. pub(crate) mod error; pub(crate) mod expr; #[cfg(test)] mod tests { mod eval; mod lexer; mod parser; } cargo-config2-0.1.29/src/cfg_expr/tests/eval.rs000064400000000000000000000225321046102023000173640ustar 00000000000000// SPDX-License-Identifier: Apache-2.0 OR MIT use crate::{cfg_expr::expr::Expression, resolve::CfgMap}; #[test] #[cfg_attr(miri, ignore)] // Miri doesn't support pipe2 (inside std::process::Command::output) fn target_family() { let matches_any_family = Expression::parse("any(unix, target_family = \"windows\", target_family = \"wasm\")") .unwrap(); let impossible = Expression::parse("all(windows, target_family = \"unix\")").unwrap(); let mut map = CfgMap::default(); for target in [ "aarch64-apple-darwin", "x86_64-pc-windows-msvc", "wasm32-unknown-unknown", "thumbv7m-none-eabi", ] { let t = map.eval_cfg(&matches_any_family, &target.into(), || cmd!("rustc")).unwrap(); if target.contains("-none") { assert!(!t, "{target}"); } else { assert!(t, "{target}"); } assert!(!map.eval_cfg(&impossible, &target.into(), || cmd!("rustc")).unwrap()); } } #[test] fn tiny() { assert!(Expression::parse("all()").unwrap().eval(|_| false)); assert!(!Expression::parse("any()").unwrap().eval(|_| true)); assert!(!Expression::parse("not(all())").unwrap().eval(|_| false)); assert!(Expression::parse("not(any())").unwrap().eval(|_| true)); assert!(Expression::parse("all(not(blah))").unwrap().eval(|_| false)); assert!(!Expression::parse("any(not(blah))").unwrap().eval(|_| true)); } #[test] #[cfg_attr(miri, ignore)] // Miri doesn't support pipe2 (inside std::process::Command::output) fn very_specific() { let specific = Expression::parse( r#"all( target_os = "windows", target_arch = "x86", windows, target_env = "msvc", target_feature = "fxsr", target_feature = "sse", target_feature = "sse2", target_pointer_width = "32", target_endian = "little", not(target_vendor = "uwp"), target_has_atomic = "8", target_has_atomic = "16", target_has_atomic = "32", target_has_atomic = "64", not(target_has_atomic = "128"), target_has_atomic = "ptr", panic = "unwind", not(panic = "abort"), )"#, ) .unwrap(); let mut map = CfgMap::default(); for target in ["i686-pc-windows-msvc", "i586-pc-windows-msvc"] { let t = map.eval_cfg(&specific, &target.into(), || cmd!("rustc")).unwrap(); assert_eq!( target == "i686-pc-windows-msvc", t, "expected true for i686-pc-windows-msvc, but got true for {target}", ); } // for target in all { // let expr = format!( // r#"cfg( // all( // target_arch = "{}", // {} // {} // target_env = "{}" // ) // )"#, // target.arch.0, // if let Some(v) = &target.vendor { // format!(r#"target_vendor = "{}","#, v.0) // } else { // "".to_owned() // }, // if let Some(v) = &target.os { // format!(r#"target_os = "{}","#, v.0) // } else { // "".to_owned() // }, // target.env.as_ref().map_or("", |e| e.as_str()), // ); // let specific = Expression::parse(&expr).unwrap(); // let t = Target::make(target.triple.as_str()); // assert!( // specific.eval(|pred| { // if target.triple.as_str() == "mips64-openwrt-linux-musl" { // if let Predicate::Target(TargetPredicate::Vendor(vendor)) = pred { // // This is a special predicate that doesn't follow the usual rules for // // target-lexicon. // return t.builtin.matches(&TargetPredicate::Vendor(vendor.clone())); // } // } // tg_match!(pred, t) // }), // "failed expression '{}' for {:#?}", // expr, // t.builtin, // ); // } } #[test] #[cfg_attr(miri, ignore)] // Miri doesn't support pipe2 (inside std::process::Command::output) fn complex() { let complex = Expression::parse(r#"cfg(all(unix, not(any(target_os="macos", target_os="android", target_os="emscripten"))))"#).unwrap(); let mut map = CfgMap::default(); // Should match linuxes assert!(map.eval_cfg(&complex, &"x86_64-unknown-linux-gnu".into(), || cmd!("rustc")).unwrap()); assert!(map.eval_cfg(&complex, &"x86_64-unknown-linux-musl".into(), || cmd!("rustc")).unwrap()); // Should *not* match windows or mac or android assert!(!map.eval_cfg(&complex, &"x86_64-pc-windows-msvc".into(), || cmd!("rustc")).unwrap()); assert!(!map.eval_cfg(&complex, &"x86_64-apple-darwin".into(), || cmd!("rustc")).unwrap()); assert!(!map.eval_cfg(&complex, &"aarch64-linux-android".into(), || cmd!("rustc")).unwrap()); let complex = Expression::parse(r#"all(not(target_os = "ios"), not(target_os = "android"))"#).unwrap(); assert!(map.eval_cfg(&complex, &"x86_64-unknown-linux-gnu".into(), || cmd!("rustc")).unwrap()); assert!(map.eval_cfg(&complex, &"x86_64-unknown-linux-musl".into(), || cmd!("rustc")).unwrap()); assert!(map.eval_cfg(&complex, &"x86_64-pc-windows-msvc".into(), || cmd!("rustc")).unwrap()); assert!(map.eval_cfg(&complex, &"x86_64-apple-darwin".into(), || cmd!("rustc")).unwrap()); assert!(!map.eval_cfg(&complex, &"aarch64-linux-android".into(), || cmd!("rustc")).unwrap()); let complex = Expression::parse(r#"all(any(unix, target_arch="x86"), not(any(target_os="android", target_os="emscripten")))"#).unwrap(); // Should match linuxes and mac assert!(map.eval_cfg(&complex, &"x86_64-unknown-linux-gnu".into(), || cmd!("rustc")).unwrap()); assert!(map.eval_cfg(&complex, &"x86_64-unknown-linux-musl".into(), || cmd!("rustc")).unwrap()); assert!(map.eval_cfg(&complex, &"x86_64-apple-darwin".into(), || cmd!("rustc")).unwrap()); // Should *not* match x86_64 windows or android assert!(!map.eval_cfg(&complex, &"x86_64-pc-windows-msvc".into(), || cmd!("rustc")).unwrap()); assert!(!map.eval_cfg(&complex, &"aarch64-linux-android".into(), || cmd!("rustc")).unwrap()); // Ensure that target_os = "none" matches against Os == None. let complex = Expression::parse(r#"all(target_os="none")"#).unwrap(); assert!(!map.eval_cfg(&complex, &"x86_64-unknown-linux-gnu".into(), || cmd!("rustc")).unwrap()); assert!(map.eval_cfg(&complex, &"armebv7r-none-eabi".into(), || cmd!("rustc")).unwrap()); } // #[test] // fn unstable_target_abi() { // let linux_gnu = Target::make("x86_64-unknown-linux-gnu"); // let linux_musl = Target::make("x86_64-unknown-linux-musl"); // let windows_msvc = Target::make("x86_64-pc-windows-msvc"); // let mac = Target::make("x86_64-apple-darwin"); // let android = Target::make("aarch64-linux-android"); // let target_with_abi_that_matches = cfg_expr::targets::TargetInfo { // triple: cfg_expr::targets::Triple::new_const("aarch64-apple-darwin"), // os: None, // abi: Some(cfg_expr::targets::Abi::new_const("eabihf")), // arch: cfg_expr::targets::Arch::aarch64, // env: None, // vendor: None, // families: cfg_expr::targets::Families::unix, // pointer_width: 64, // endian: cfg_expr::targets::Endian::little, // has_atomics: cfg_expr::targets::HasAtomics::atomic_8_16_32_64_128_ptr, // panic: cfg_expr::targets::Panic::unwind, // }; // let target_with_abi_that_does_not_match = cfg_expr::targets::TargetInfo { // abi: Some(cfg_expr::targets::Abi::new_const("ilp32")), // ..target_with_abi_that_matches.clone() // }; // let abi_pred = // Expression::parse(r#"cfg(any(target_arch = "wasm32", target_abi = "eabihf"))"#).unwrap(); // // Should match a specified target_abi that's the same // assert!(abi_pred.eval(|pred| { // match pred { // Predicate::Target(tp) => tp.matches(&target_with_abi_that_matches), // _ => false, // } // })); // // Should *not* match a specified target_abi that isn't the same // assert!(!abi_pred.eval(|pred| { // match pred { // Predicate::Target(tp) => tp.matches(&target_with_abi_that_does_not_match), // _ => false, // } // })); // // Should *not* match any builtins at this point because target_abi isn't stable // assert!(!abi_pred.eval(|pred| tg_match!(pred, linux_gnu))); // assert!(!abi_pred.eval(|pred| tg_match!(pred, linux_musl))); // assert!(!abi_pred.eval(|pred| tg_match!(pred, mac))); // assert!(!abi_pred.eval(|pred| tg_match!(pred, windows_msvc))); // assert!(!abi_pred.eval(|pred| tg_match!(pred, android))); // } #[test] #[cfg_attr(miri, ignore)] // Miri doesn't support pipe2 (inside std::process::Command::output) fn wasm_family() { let wasm = Expression::parse(r#"cfg(target_family = "wasm")"#).unwrap(); let mut map = CfgMap::default(); // All of the above targets match. for target in [ "wasm32-unknown-unknown", "wasm32-unknown-emscripten", "wasm32-wasip1", "wasm64-unknown-unknown", ] { assert!(map.eval_cfg(&wasm, &target.into(), || cmd!("rustc")).unwrap(), "{target}"); } } cargo-config2-0.1.29/src/cfg_expr/tests/lexer.rs000064400000000000000000000024071046102023000175530ustar 00000000000000// SPDX-License-Identifier: Apache-2.0 OR MIT use crate::cfg_expr::expr::lexer::{Lexer, Token}; macro_rules! test_lex { ($text:expr, [$($token:expr),+$(,)?]) => { let lexed: Vec<_> = Lexer::new($text).map(|lt| lt.unwrap().token).collect(); let expected = vec![$($token),+]; assert_eq!(lexed, expected); } } #[test] fn handles_raw() { test_lex!("key", [Token::Key("key")]); } #[test] fn strips_attribute() { test_lex!("cfg(key)", [Token::Key("key")]); } #[test] fn handle_key_value() { test_lex!("key = \"value\"", [Token::Key("key"), Token::Equals, Token::Value("value"),]); } #[test] fn handle_empty_value() { test_lex!("key = \"\"", [Token::Key("key"), Token::Equals, Token::Value(""),]); } #[test] fn handle_short_key() { test_lex!("k", [Token::Key("k"),]); } #[test] fn handle_cfg_keywords() { test_lex!("all(any(not(any_blah,all_nope,not_any)))", [ Token::All, Token::OpenParen, Token::Any, Token::OpenParen, Token::Not, Token::OpenParen, Token::Key("any_blah"), Token::Comma, Token::Key("all_nope"), Token::Comma, Token::Key("not_any"), Token::CloseParen, Token::CloseParen, Token::CloseParen, ]); } cargo-config2-0.1.29/src/cfg_expr/tests/parser.rs000064400000000000000000000121561046102023000177320ustar 00000000000000// SPDX-License-Identifier: Apache-2.0 OR MIT use crate::cfg_expr::{ error::{ParseError, Reason}, expr::{Expression, Predicate as P}, }; macro_rules! test_validate { (ok [$($text:expr => [$($expected:expr),*$(,)?]),+$(,)?]) => { $( let val_expr = Expression::parse($text).unwrap(); let mut predicates = val_expr.predicates().enumerate(); $( let actual = predicates.next().unwrap(); assert_eq!($expected, actual.1, "failed @ index {}", actual.0); )* if let Some((_, additional)) = predicates.next() { assert!(false, "found additional requirement {additional:?}"); } )+ }; } macro_rules! err { ($text:expr => $reason:ident @ $range:expr) => { let act_err = Expression::parse($text).unwrap_err(); let expected = ParseError { original: $text.to_owned(), span: $range, reason: Reason::$reason }; assert_eq!(expected, act_err); }; ($text:expr => $unexpected:expr; $range:expr) => { let act_err = Expression::parse($text).unwrap_err(); let expected = ParseError { original: $text.to_owned(), span: $range, reason: Reason::Unexpected($unexpected), }; assert_eq!(expected, act_err); }; } #[test] fn fails_empty() { err!("" => Empty @ 0..0); err!(" " => Empty @ 0..1); err!("\n\t\n" => Empty @ 0..3); } #[test] fn fails_malformed() { err!("," => &["", "all", "any", "not"]; 0..1); // Keys can't begin with a number err!("8" => &["", "all", "any", "not"]; 0..1); err!("=" => &["", "all", "any", "not"]; 0..1); err!("(" => &["", "all", "any", "not"]; 0..1); err!("key =" => &["\"\""]; 5..5); err!("key1, key2" => MultipleRootPredicates @ 0..10); err!("key1, key2, " => MultipleRootPredicates @ 0..16); err!("key1 = \"v\", key2" => MultipleRootPredicates @ 0..16); } #[test] fn fails_unbalanced_parens() { err!("not(key" => UnclosedParens @ 3..7); err!("key)" => UnopenedParens @ 3..4); err!("foo (" => &["=", ",", ")"]; 4..5); } #[test] fn fails_unbalanced_quotes() { err!("key = \"value" => UnclosedQuotes @ 6..12); err!("key = \"" => UnclosedQuotes @ 6..7); err!("key = \"value, key = \"value\"" => &[",", ")"]; 21..26); err!("all(key = \"value), key = \"value\"" => &[",", ")"]; 26..31); err!("not(key = \"value)" => UnclosedQuotes @ 10..17); } #[test] fn handles_single_predicate() { test_validate!(ok [ "cfg(key)" => [P::Flag("key")], "unix" => [P::Flag("unix")], "target_arch = \"mips\"" => [P::KeyValue{ key: "target_arch", val: "mips" }], "feature = \"awesome\"" => [P::KeyValue{ key: "feature", val: "awesome" }], "_key" => [P::Flag("_key")], " key" => [P::Flag("key")], " key " => [P::Flag("key")], " key = \"val\"" => [P::KeyValue{ key: "key", val: "val" }], "key=\"\"" => [P::KeyValue{ key: "key", val: "" }], " key=\"7\" " => [P::KeyValue{ key: "key", val: "7" }], "key = \"7 q\" " => [P::KeyValue{ key: "key", val: "7 q" }], "target_has_atomic = \"ptr\"" => [P::KeyValue{ key: "target_has_atomic", val: "ptr" }], "target_has_atomic = \"4\"" => [P::KeyValue{ key: "target_has_atomic", val: "4" }], "target_has_atomic = \"64\"" => [P::KeyValue{ key: "target_has_atomic", val: "64" }], "target_has_atomic = \"128\" " => [P::KeyValue{ key: "target_has_atomic", val: "128" }], "panic = \"unwind\"" => [P::KeyValue{ key: "panic", val: "unwind" }], "panic = \"abort\"" => [P::KeyValue{ key: "panic", val: "abort" }], ]); } #[test] fn handles_simple_fns() { test_validate!(ok [ "any()" => [], "all()" => [], "not(key = \"value\")" => [P::KeyValue { key: "key", val: "value" }], ]); } #[test] fn fails_invalid_fns() { err!("nope()" => &["=", ",", ")"]; 4..5); err!("all(nope())" => &["=", ",", ")"]; 8..9); err!("any(,)" => &["", ")", "all", "any", "not"]; 4..5); err!("blah(key)" => &["=", ",", ")"]; 4..5); } #[test] fn ensures_not_has_one_predicate() { assert_eq!(Expression::parse("not()").unwrap_err(), ParseError { original: "not()".to_owned(), span: 0..5, reason: Reason::InvalidNot(0), }); assert_eq!(Expression::parse("not(key_one, key_two)").unwrap_err(), ParseError { original: "not(key_one, key_two)".to_owned(), span: 0..21, reason: Reason::InvalidNot(2), }); assert_eq!(Expression::parse("any(not(not(key_one, key_two)))").unwrap_err(), ParseError { original: "any(not(not(key_one, key_two)))".to_owned(), span: 8..29, reason: Reason::InvalidNot(2), }); test_validate!(ok [ "not(key)" => [P::Flag("key")], "not(key,)" => [P::Flag("key")], "not(key = \"value\")" => [P::KeyValue { key: "key", val: "value" }], "not(key = \"value\",)" => [P::KeyValue { key: "key", val: "value" }], "not(not(not(key = \"value\",)))" => [P::KeyValue { key: "key", val: "value" }], ]); } cargo-config2-0.1.29/src/de.rs000064400000000000000000001333341046102023000140710ustar 00000000000000// SPDX-License-Identifier: Apache-2.0 OR MIT //! Cargo configuration that environment variables, config overrides, and //! target-specific configurations have not been resolved. #[path = "gen/de.rs"] mod gen; use core::{fmt, slice, str::FromStr}; use std::{ borrow::Cow, collections::BTreeMap, ffi::OsStr, fs, path::{Path, PathBuf}, }; use serde::{ de::{self, Deserialize, Deserializer}, ser::{Serialize, Serializer}, }; use serde_derive::{Deserialize, Serialize}; pub use crate::value::{Definition, Value}; use crate::{ easy, error::{Context as _, Error, Result}, resolve::{ResolveContext, TargetTripleRef}, walk, }; /// Cargo configuration that environment variables, config overrides, and /// target-specific configurations have not been resolved. #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "kebab-case")] #[non_exhaustive] pub struct Config { // TODO: paths /// The `[alias]` table. /// /// [reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#alias) #[serde(default)] #[serde(skip_serializing_if = "BTreeMap::is_empty")] pub alias: BTreeMap, /// The `[build]` table. /// /// [reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#build) #[serde(default)] #[serde(skip_serializing_if = "BuildConfig::is_none")] pub build: BuildConfig, /// The `[doc]` table. /// /// [reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#doc) #[serde(default)] #[serde(skip_serializing_if = "DocConfig::is_none")] pub doc: DocConfig, /// The `[env]` table. /// /// [reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#env) #[serde(default)] #[serde(skip_serializing_if = "BTreeMap::is_empty")] pub env: BTreeMap, /// The `[future-incompat-report]` table. /// /// [reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#future-incompat-report) #[serde(default)] #[serde(skip_serializing_if = "FutureIncompatReportConfig::is_none")] pub future_incompat_report: FutureIncompatReportConfig, /// The `[cargo-new]` table. /// /// [reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#cargo-new) #[serde(default)] #[serde(skip_serializing_if = "CargoNewConfig::is_none")] pub cargo_new: CargoNewConfig, /// The `[http]` table. /// /// [reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#http) #[serde(default)] #[serde(skip_serializing_if = "HttpConfig::is_none")] pub http: HttpConfig, // TODO: install /// The `[net]` table. /// /// [reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#net) #[serde(default)] #[serde(skip_serializing_if = "NetConfig::is_none")] pub net: NetConfig, // TODO: patch // TODO: profile /// The `[registries]` table. /// /// [reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#registries) #[serde(default)] #[serde(skip_serializing_if = "BTreeMap::is_empty")] pub registries: BTreeMap, /// The `[registry]` table. /// /// [reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#registry) #[serde(default)] #[serde(skip_serializing_if = "RegistryConfig::is_none")] pub registry: RegistryConfig, // TODO: source /// The `[target]` table. /// /// [reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#target) #[serde(default)] #[serde(skip_serializing_if = "BTreeMap::is_empty")] pub target: BTreeMap, /// The `[term]` table. /// /// [reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#term) #[serde(default)] #[serde(skip_serializing_if = "TermConfig::is_none")] pub term: TermConfig, } impl Config { /// Read config files hierarchically from the current directory and merges them. pub fn load() -> Result { Self::load_with_cwd(std::env::current_dir().context("failed to get current directory")?) } /// Read config files hierarchically from the given directory and merges them. pub fn load_with_cwd>(cwd: P) -> Result { let cwd = cwd.as_ref(); Self::_load_with_options(cwd, walk::cargo_home_with_cwd(cwd).as_deref()) } /// Read config files hierarchically from the given directory and merges them. pub fn load_with_options, Q: Into>>( cwd: P, cargo_home: Q, ) -> Result { Self::_load_with_options(cwd.as_ref(), cargo_home.into().as_deref()) } pub(crate) fn _load_with_options( current_dir: &Path, cargo_home: Option<&Path>, ) -> Result { let mut base = None; for path in crate::walk::WalkInner::with_cargo_home(current_dir, cargo_home) { let config = Self::_load_file(&path)?; match &mut base { None => base = Some((path, config)), Some((base_path, base)) => base.merge(config, false).with_context(|| { format!( "failed to merge config from `{}` into `{}`", path.display(), base_path.display() ) })?, } } Ok(base.map(|(_, c)| c).unwrap_or_default()) } /// Reads cargo config file at the given path. /// /// **Note:** Note: This just reads a file at the given path and does not /// respect the hierarchical structure of the cargo config. pub fn load_file>(path: P) -> Result { Self::_load_file(path.as_ref()) } fn _load_file(path: &Path) -> Result { let buf = fs::read_to_string(path) .with_context(|| format!("failed to read `{}`", path.display()))?; let mut config: Config = toml_edit::de::from_str(&buf).with_context(|| { format!("failed to parse `{}` as cargo configuration", path.display()) })?; config.set_path(path); Ok(config) } /// Merges the given config into this config. /// /// If `force` is `false`, this matches the way cargo [merges configs in the /// parent directories](https://doc.rust-lang.org/nightly/cargo/reference/config.html#hierarchical-structure). /// /// If `force` is `true`, this matches the way cargo's `--config` CLI option /// overrides config. pub(crate) fn merge(&mut self, low: Self, force: bool) -> Result<()> { crate::merge::Merge::merge(self, low, force) } pub(crate) fn set_path(&mut self, path: &Path) { crate::value::SetPath::set_path(self, path); } pub(crate) fn resolve_target( cx: &ResolveContext, target_configs: &BTreeMap, override_target_rustflags: bool, build_rustflags: &Option, override_target_rustdocflags: bool, build_rustdocflags: &Option, target_triple: &TargetTripleRef<'_>, build_config: &easy::BuildConfig, ) -> Result> { let target = target_triple.triple(); if target.starts_with("cfg(") { bail!("'{target}' is not valid target triple"); } let mut target_config = target_configs.get(target).cloned(); let target_u_upper = target_u_upper(target); let mut target_linker = target_config.as_mut().and_then(|c| c.linker.take()); let mut target_runner = target_config.as_mut().and_then(|c| c.runner.take()); let mut target_rustflags: Option = target_config.as_mut().and_then(|c| c.rustflags.take()); let mut target_rustdocflags: Option = target_config.as_mut().and_then(|c| c.rustdocflags.take()); if let Some(linker) = cx.env_dyn(&format!("CARGO_TARGET_{target_u_upper}_LINKER"))? { target_linker = Some(linker); } if let Some(runner) = cx.env_dyn(&format!("CARGO_TARGET_{target_u_upper}_RUNNER"))? { target_runner = Some( PathAndArgs::from_string(&runner.val, runner.definition) .context("invalid length 0, expected at least one element")?, ); } if let Some(rustflags) = cx.env_dyn(&format!("CARGO_TARGET_{target_u_upper}_RUSTFLAGS"))? { let mut rustflags = Flags::from_space_separated(&rustflags.val, rustflags.definition.as_ref()); match &mut target_rustflags { Some(target_rustflags) => { target_rustflags.flags.append(&mut rustflags.flags); } target_rustflags @ None => *target_rustflags = Some(rustflags), } } if let Some(rustdocflags) = cx.env_dyn(&format!("CARGO_TARGET_{target_u_upper}_RUSTDOCFLAGS"))? { let mut rustdocflags = Flags::from_space_separated(&rustdocflags.val, rustdocflags.definition.as_ref()); match &mut target_rustdocflags { Some(target_rustdocflags) => { target_rustdocflags.flags.append(&mut rustdocflags.flags); } target_rustdocflags @ None => *target_rustdocflags = Some(rustdocflags), } } for (k, v) in target_configs { if !k.starts_with("cfg(") { continue; } if cx.eval_cfg(k, target_triple, build_config)? { // https://github.com/rust-lang/cargo/pull/12535 if target_linker.is_none() { if let Some(linker) = v.linker.as_ref() { target_linker = Some(linker.clone()); } } // Priorities (as of 1.68.0-nightly (2022-12-23)): // 1. CARGO_TARGET__RUNNER // 2. target..runner // 3. target..runner if target_runner.is_none() { if let Some(runner) = v.runner.as_ref() { target_runner = Some(runner.clone()); } } // Applied order (as of 1.68.0-nightly (2022-12-23)): // 1. target..rustflags // 2. CARGO_TARGET__RUSTFLAGS // 3. target..rustflags if let Some(rustflags) = v.rustflags.as_ref() { match &mut target_rustflags { Some(target_rustflags) => { target_rustflags.flags.extend_from_slice(&rustflags.flags); } target_rustflags @ None => *target_rustflags = Some(rustflags.clone()), } } } } if let Some(linker) = target_linker { target_config.get_or_insert_with(TargetConfig::default).linker = Some(linker); } if let Some(runner) = target_runner { target_config.get_or_insert_with(TargetConfig::default).runner = Some(runner); } if override_target_rustflags { target_config .get_or_insert_with(TargetConfig::default) .rustflags .clone_from(build_rustflags); } else if let Some(rustflags) = target_rustflags { target_config.get_or_insert_with(TargetConfig::default).rustflags = Some(rustflags); } else { target_config .get_or_insert_with(TargetConfig::default) .rustflags .clone_from(build_rustflags); } if override_target_rustdocflags { target_config .get_or_insert_with(TargetConfig::default) .rustdocflags .clone_from(build_rustdocflags); } else if let Some(rustdocflags) = target_rustdocflags { target_config.get_or_insert_with(TargetConfig::default).rustdocflags = Some(rustdocflags); } else { target_config .get_or_insert_with(TargetConfig::default) .rustdocflags .clone_from(build_rustdocflags); } Ok(target_config) } } /// The `[build]` table. /// /// [reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#build) #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "kebab-case")] #[non_exhaustive] pub struct BuildConfig { /// Sets the maximum number of compiler processes to run in parallel. /// If negative, it sets the maximum number of compiler processes to the /// number of logical CPUs plus provided value. Should not be 0. /// /// [reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#buildjobs) #[serde(skip_serializing_if = "Option::is_none")] pub jobs: Option>, /// Sets the executable to use for `rustc`. /// /// [reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#buildrustc) #[serde(skip_serializing_if = "Option::is_none")] pub rustc: Option>, /// Sets a wrapper to execute instead of `rustc`. /// /// [reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#buildrustc-wrapper) #[serde(skip_serializing_if = "Option::is_none")] pub rustc_wrapper: Option>, /// Sets a wrapper to execute instead of `rustc`, for workspace members only. /// /// [reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#buildrustc-workspace-wrapper) #[serde(skip_serializing_if = "Option::is_none")] pub rustc_workspace_wrapper: Option>, /// Sets the executable to use for `rustdoc`. /// /// [reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#buildrustdoc) #[serde(skip_serializing_if = "Option::is_none")] pub rustdoc: Option>, /// The default target platform triples to compile to. /// /// [reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#buildtarget) #[serde(skip_serializing_if = "Option::is_none")] pub target: Option, /// The path to where all compiler output is placed. The default if not /// specified is a directory named target located at the root of the workspace. /// /// [reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#buildtarget) #[serde(skip_serializing_if = "Option::is_none")] pub target_dir: Option>, /// Extra command-line flags to pass to rustc. The value may be an array /// of strings or a space-separated string. /// /// [reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#buildrustflags) #[serde(skip_serializing_if = "Option::is_none")] pub rustflags: Option, /// Extra command-line flags to pass to `rustdoc`. The value may be an array /// of strings or a space-separated string. /// /// [reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#buildrustdocflags) #[serde(skip_serializing_if = "Option::is_none")] pub rustdocflags: Option, /// Whether or not to perform incremental compilation. /// /// [reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#buildincremental) #[serde(skip_serializing_if = "Option::is_none")] pub incremental: Option>, /// Strips the given path prefix from dep info file paths. /// /// [reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#builddep-info-basedir) #[serde(skip_serializing_if = "Option::is_none")] pub dep_info_basedir: Option>, // Resolve contexts. Completely ignored in serialization and deserialization. #[serde(skip)] pub(crate) override_target_rustflags: bool, #[serde(skip)] pub(crate) override_target_rustdocflags: bool, } // https://github.com/rust-lang/cargo/blob/0.80.0/src/cargo/util/context/target.rs /// A `[target.]` or `[target.]` table. /// /// [reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#target) #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "kebab-case")] #[non_exhaustive] pub struct TargetConfig { /// [reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#targettriplelinker) #[serde(skip_serializing_if = "Option::is_none")] pub linker: Option>, /// [reference (`target..runner`)](https://doc.rust-lang.org/nightly/cargo/reference/config.html#targettriplerunner) /// /// [reference (`target..runner`)](https://doc.rust-lang.org/nightly/cargo/reference/config.html#targetcfgrunner) #[serde(skip_serializing_if = "Option::is_none")] pub runner: Option, /// [reference (`target..rustflags`)](https://doc.rust-lang.org/nightly/cargo/reference/config.html#targettriplerustflags) /// /// [reference (`target..rustflags`)](https://doc.rust-lang.org/nightly/cargo/reference/config.html#targetcfgrustflags) #[serde(skip_serializing_if = "Option::is_none")] pub rustflags: Option, /// [reference (`target..rustdocflags`)](https://doc.rust-lang.org/nightly/cargo/reference/config.html#targettriplerustdocflags) /// /// [reference (`target..rustdocflags`)](https://doc.rust-lang.org/nightly/cargo/reference/config.html#targetcfgrustdocflags) #[serde(skip_serializing_if = "Option::is_none")] pub rustdocflags: Option, // TODO: links: https://doc.rust-lang.org/nightly/cargo/reference/config.html#targettriplelinks } /// The `[doc]` table. /// /// [reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#doc) #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "kebab-case")] #[non_exhaustive] pub struct DocConfig { /// This option sets the browser to be used by `cargo doc`, overriding the /// `BROWSER` environment variable when opening documentation with the `--open` option. /// /// [reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#docbrowser) #[serde(skip_serializing_if = "Option::is_none")] pub browser: Option, } // TODO: hide internal repr, change to struct /// A value of the `[env]` table. /// /// [reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#env) #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(untagged)] #[non_exhaustive] pub enum EnvConfigValue { Value(Value), Table { value: Value, #[serde(skip_serializing_if = "Option::is_none")] force: Option>, #[serde(skip_serializing_if = "Option::is_none")] relative: Option>, }, } impl EnvConfigValue { pub(crate) const fn kind(&self) -> &'static str { match self { Self::Value(..) => "string", Self::Table { .. } => "table", } } pub(crate) fn resolve(&self, current_dir: &Path) -> Cow<'_, OsStr> { match self { Self::Value(v) => OsStr::new(&v.val).into(), Self::Table { value, relative, .. } => { if relative.as_ref().map_or(false, |v| v.val) { if let Some(def) = &value.definition { return def.root(current_dir).join(&value.val).into_os_string().into(); } } OsStr::new(&value.val).into() } } } } /// The `[future-incompat-report]` table. /// /// [reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#future-incompat-report) #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "kebab-case")] #[non_exhaustive] pub struct FutureIncompatReportConfig { /// Controls how often we display a notification to the terminal when a future incompat report is available. /// /// [reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#future-incompat-reportfrequency) #[serde(skip_serializing_if = "Option::is_none")] pub frequency: Option>, } /// The `[cargo-new]` table. /// /// [reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#cargo-new) #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "kebab-case")] #[non_exhaustive] pub struct CargoNewConfig { /// Specifies the source control system to use for initializing a new repository. /// Valid values are git, hg (for Mercurial), pijul, fossil or none to disable this behavior. /// Defaults to git, or none if already inside a VCS repository. Can be overridden with the --vcs CLI option. #[serde(skip_serializing_if = "Option::is_none")] pub vcs: Option>, } #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "kebab-case")] #[non_exhaustive] pub enum VersionControlSoftware { /// Git Git, /// Mercurial #[serde(rename = "hg")] Mercurial, /// Pijul Pijul, /// Fossil Fossil, /// No VCS None, } impl VersionControlSoftware { pub const fn as_str(self) -> &'static str { match self { VersionControlSoftware::Git => "git", VersionControlSoftware::Mercurial => "hg", VersionControlSoftware::Pijul => "pijul", VersionControlSoftware::Fossil => "fossil", VersionControlSoftware::None => "none", } } } impl FromStr for VersionControlSoftware { type Err = Error; fn from_str(vcs: &str) -> Result { match vcs { "git" => Ok(Self::Git), "hg" => Ok(Self::Mercurial), "pijul" => Ok(Self::Pijul), "fossil" => Ok(Self::Fossil), "none" => Ok(Self::None), other => bail!("must be git, hg, pijul, fossil, none, but found `{other}`"), } } } /// The `[http]` table. /// /// [reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#http) #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "kebab-case")] #[non_exhaustive] pub struct HttpConfig { /// If true, enables debugging of HTTP requests. /// The debug information can be seen by setting the `CARGO_LOG=network=debug` environment variable /// (or use `network=trace` for even more information). /// /// [reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#httpdebug) #[serde(skip_serializing_if = "Option::is_none")] pub debug: Option>, /// Sets an HTTP and HTTPS proxy to use. The format is in libcurl format as in `[protocol://]host[:port]`. /// If not set, Cargo will also check the http.proxy setting in your global git configuration. /// If none of those are set, the HTTPS_PROXY or https_proxy environment variables set the proxy for HTTPS requests, /// and http_proxy sets it for HTTP requests. /// /// [reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#httpproxy) #[serde(skip_serializing_if = "Option::is_none")] pub proxy: Option>, /// Sets the timeout for each HTTP request, in seconds. /// /// [reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#httptimeout) #[serde(skip_serializing_if = "Option::is_none")] pub timeout: Option>, /// Path to a Certificate Authority (CA) bundle file, used to verify TLS certificates. /// If not specified, Cargo attempts to use the system certificates. /// /// [reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#httpcainfo) #[serde(skip_serializing_if = "Option::is_none")] pub cainfo: Option>, /// This determines whether or not TLS certificate revocation checks should be performed. /// This only works on Windows. /// /// [reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#httpcheck-revoke) #[serde(skip_serializing_if = "Option::is_none")] pub check_revoke: Option>, // TODO: handle ssl-version /// This setting controls timeout behavior for slow connections. /// If the average transfer speed in bytes per second is below the given value /// for `http.timeout` seconds (default 30 seconds), then the connection is considered too slow and Cargo will abort and retry. /// /// [reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#httplow-speed-limit) #[serde(skip_serializing_if = "Option::is_none")] pub low_speed_limit: Option>, /// When true, Cargo will attempt to use the HTTP2 protocol with multiplexing. /// This allows multiple requests to use the same connection, usually improving performance when fetching multiple files. /// If false, Cargo will use HTTP 1.1 without pipelining. /// /// [reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#httpmultiplexing) #[serde(skip_serializing_if = "Option::is_none")] pub multiplexing: Option>, /// Specifies a custom user-agent header to use. /// The default if not specified is a string that includes Cargo’s version. /// /// [reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#httpuser-agent) #[serde(skip_serializing_if = "Option::is_none")] pub user_agent: Option>, } /// The `[net]` table. /// /// [reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#net) #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "kebab-case")] #[non_exhaustive] pub struct NetConfig { /// Number of times to retry possibly spurious network errors. /// /// [reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#netretry) #[serde(skip_serializing_if = "Option::is_none")] pub retry: Option>, /// If this is `true`, then Cargo will use the `git` executable to fetch /// registry indexes and git dependencies. If `false`, then it uses a /// built-in `git` library. /// /// [reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#netgit-fetch-with-cli) #[serde(skip_serializing_if = "Option::is_none")] pub git_fetch_with_cli: Option>, /// If this is `true`, then Cargo will avoid accessing the network, and /// attempt to proceed with locally cached data. If `false`, Cargo will /// access the network as needed, and generate an error if it encounters a /// network error. /// /// [reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#netoffline) #[serde(skip_serializing_if = "Option::is_none")] pub offline: Option>, } /// A value of the `[registries]` table. /// /// [reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#registries) #[derive(Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "kebab-case")] #[non_exhaustive] pub struct RegistriesConfigValue { /// Specifies the URL of the git index for the registry. /// /// [reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#registriesnameindex) #[serde(skip_serializing_if = "Option::is_none")] pub index: Option>, /// Specifies the authentication token for the given registry. /// /// Note: This library does not read any values in the /// [credentials](https://doc.rust-lang.org/nightly/cargo/reference/config.html#credentials) /// file. /// /// [reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#registriesnametoken) #[serde(skip_serializing_if = "Option::is_none")] pub token: Option>, /// Specifies the protocol used to access crates.io. /// Not allowed for any registries besides crates.io. /// /// [reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#registriescrates-ioprotocol) #[serde(skip_serializing_if = "Option::is_none")] pub protocol: Option>, } impl fmt::Debug for RegistriesConfigValue { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let Self { index, token, protocol } = self; let redacted_token = token .as_ref() .map(|token| Value { val: "[REDACTED]", definition: token.definition.clone() }); f.debug_struct("RegistriesConfigValue") .field("index", &index) .field("token", &redacted_token) .field("protocol", &protocol) .finish() } } /// Specifies the protocol used to access crates.io. /// /// [reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#registriescrates-ioprotocol) #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "kebab-case")] #[non_exhaustive] pub enum RegistriesProtocol { /// Causes Cargo to clone the entire index of all packages ever published to /// [crates.io](https://crates.io/) from . Git, /// A newer protocol which uses HTTPS to download only what is necessary from /// . Sparse, } impl FromStr for RegistriesProtocol { type Err = Error; fn from_str(protocol: &str) -> Result { match protocol { "git" => Ok(Self::Git), "sparse" => Ok(Self::Sparse), other => bail!("must be git or sparse, but found `{other}`"), } } } /// The `[registry]` table. /// /// [reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#registry) #[derive(Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "kebab-case")] #[non_exhaustive] pub struct RegistryConfig { /// The name of the registry (from the /// [`registries` table](https://doc.rust-lang.org/nightly/cargo/reference/config.html#registries)) /// to use by default for registry commands like /// [`cargo publish`](https://doc.rust-lang.org/nightly/cargo/commands/cargo-publish.html). /// /// [reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#registrydefault) #[serde(skip_serializing_if = "Option::is_none")] pub default: Option>, /// Specifies the authentication token for [crates.io](https://crates.io/). /// /// Note: This library does not read any values in the /// [credentials](https://doc.rust-lang.org/nightly/cargo/reference/config.html#credentials) /// file. /// /// [reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#registrytoken) #[serde(skip_serializing_if = "Option::is_none")] pub token: Option>, } impl fmt::Debug for RegistryConfig { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let Self { default, token } = self; let redacted_token = token .as_ref() .map(|token| Value { val: "[REDACTED]", definition: token.definition.clone() }); f.debug_struct("RegistryConfig") .field("default", &default) .field("token", &redacted_token) .finish() } } /// The `[term]` table. /// /// [reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#term) #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "kebab-case")] #[non_exhaustive] pub struct TermConfig { /// Controls whether or not log messages are displayed by Cargo. /// /// [reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#termquiet) #[serde(skip_serializing_if = "Option::is_none")] pub quiet: Option>, /// Controls whether or not extra detailed messages are displayed by Cargo. /// /// [reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#termverbose) #[serde(skip_serializing_if = "Option::is_none")] pub verbose: Option>, /// Controls whether or not colored output is used in the terminal. /// /// [reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#termcolor) #[serde(skip_serializing_if = "Option::is_none")] pub color: Option>, #[serde(default)] #[serde(skip_serializing_if = "TermProgress::is_none")] pub progress: TermProgress, } #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "kebab-case")] #[non_exhaustive] pub struct TermProgress { /// Controls whether or not progress bar is shown in the terminal. /// /// [reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#termprogresswhen) #[serde(skip_serializing_if = "Option::is_none")] pub when: Option>, /// Sets the width for progress bar. /// /// [reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#termprogresswidth) #[serde(skip_serializing_if = "Option::is_none")] pub width: Option>, } #[allow(clippy::exhaustive_enums)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "kebab-case")] pub enum Color { /// (default) Automatically detect if color support is available on the terminal. Auto, /// Always display colors. Always, /// Never display colors. Never, } impl Color { pub const fn as_str(self) -> &'static str { match self { Self::Auto => "auto", Self::Always => "always", Self::Never => "never", } } } impl Default for Color { fn default() -> Self { Self::Auto } } impl FromStr for Color { type Err = Error; fn from_str(color: &str) -> Result { match color { "auto" => Ok(Self::Auto), "always" => Ok(Self::Always), "never" => Ok(Self::Never), other => bail!("must be auto, always, or never, but found `{other}`"), } } } #[allow(clippy::exhaustive_enums)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "kebab-case")] pub enum When { /// (default) Intelligently guess whether to show progress bar. Auto, /// Always show progress bar. Always, /// Never show progress bar. Never, } impl When { pub const fn as_str(self) -> &'static str { match self { Self::Auto => "auto", Self::Always => "always", Self::Never => "never", } } } impl Default for When { fn default() -> Self { Self::Auto } } impl FromStr for When { type Err = Error; fn from_str(color: &str) -> Result { match color { "auto" => Ok(Self::Auto), "always" => Ok(Self::Always), "never" => Ok(Self::Never), other => bail!("must be auto, always, or never, but found `{other}`"), } } } #[allow(clippy::exhaustive_enums)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "kebab-case")] pub enum Frequency { /// (default) Always display a notification when a command (e.g. `cargo build`) /// produces a future incompat report. Always, /// Never display a notification. Never, } impl Frequency { pub const fn as_str(self) -> &'static str { match self { Self::Always => "always", Self::Never => "never", } } } impl Default for Frequency { fn default() -> Self { Self::Always } } impl FromStr for Frequency { type Err = Error; fn from_str(color: &str) -> Result { match color { "always" => Ok(Self::Always), "never" => Ok(Self::Never), other => bail!("must be always or never, but found `{other}`"), } } } /// A representation of rustflags and rustdocflags. #[derive(Debug, Clone, Serialize)] #[serde(transparent)] pub struct Flags { pub flags: Vec>, // for merge #[serde(skip)] pub(crate) deserialized_repr: StringListDeserializedRepr, } impl Flags { /// Creates a rustflags from a string separated with ASCII unit separator ('\x1f'). /// /// This is a valid format for the following environment variables: /// /// - `CARGO_ENCODED_RUSTFLAGS` (Cargo 1.55+) /// - `CARGO_ENCODED_RUSTDOCFLAGS` (Cargo 1.55+) /// /// See also `encode`. pub(crate) fn from_encoded(s: &Value) -> Self { Self { flags: split_encoded(&s.val) .map(|v| Value { val: v.to_owned(), definition: s.definition.clone() }) .collect(), // Encoded rustflags cannot be serialized as a string because they may contain spaces. deserialized_repr: StringListDeserializedRepr::Array, } } /// Creates a rustflags from a string separated with space (' '). /// /// This is a valid format for the following environment variables: /// /// - `RUSTFLAGS` /// - `CARGO_TARGET__RUSTFLAGS` /// - `CARGO_BUILD_RUSTFLAGS` /// - `RUSTDOCFLAGS` /// - `CARGO_TARGET__RUSTDOCFLAGS` /// - `CARGO_BUILD_RUSTDOCFLAGS` /// /// And the following configs: /// /// - `target..rustflags` /// - `target..rustflags` /// - `build.rustflags` /// - `target..rustdocflags` (Cargo 1.78+) /// - `build.rustdocflags` /// /// See also `encode_space_separated`. pub(crate) fn from_space_separated(s: &str, def: Option<&Definition>) -> Self { Self { flags: split_space_separated(s) .map(|v| Value { val: v.to_owned(), definition: def.cloned() }) .collect(), deserialized_repr: StringListDeserializedRepr::String, } } pub(crate) fn from_array(flags: Vec>) -> Self { Self { flags, deserialized_repr: StringListDeserializedRepr::Array } } } impl<'de> Deserialize<'de> for Flags { fn deserialize(deserializer: D) -> Result where D: Deserializer<'de>, { let v: StringOrArray = Deserialize::deserialize(deserializer)?; match v { StringOrArray::String(s) => { Ok(Self::from_space_separated(&s.val, s.definition.as_ref())) } StringOrArray::Array(v) => Ok(Self::from_array(v)), } } } // https://github.com/rust-lang/cargo/blob/0.80.0/src/cargo/util/context/path.rs #[derive(Debug, Deserialize, PartialEq, Clone)] #[serde(transparent)] pub struct ConfigRelativePath(pub(crate) Value); impl ConfigRelativePath { /// Returns the underlying value. pub fn value(&self) -> &Value { &self.0 } /// Returns the raw underlying configuration value for this key. pub fn raw_value(&self) -> &str { &self.0.val } // /// Resolves this configuration-relative path to an absolute path. // /// // /// This will always return an absolute path where it's relative to the // /// location for configuration for this value. // pub(crate) fn resolve_path(&self, current_dir: &Path) -> Cow<'_, Path> { // self.0.resolve_as_path(current_dir) // } /// Resolves this configuration-relative path to either an absolute path or /// something appropriate to execute from `PATH`. /// /// Values which don't look like a filesystem path (don't contain `/` or /// `\`) will be returned as-is, and everything else will fall through to an /// absolute path. pub(crate) fn resolve_program(&self, current_dir: &Path) -> Cow<'_, Path> { self.0.resolve_as_program_path(current_dir) } } /// An executable path with arguments. /// /// [reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#executable-paths-with-arguments) #[derive(Debug, Clone)] #[non_exhaustive] pub struct PathAndArgs { pub path: ConfigRelativePath, pub args: Vec>, // for merge pub(crate) deserialized_repr: StringListDeserializedRepr, } impl PathAndArgs { pub(crate) fn from_string(value: &str, definition: Option) -> Option { let mut s = split_space_separated(value); let path = s.next()?; Some(Self { args: s.map(|v| Value { val: v.to_owned(), definition: definition.clone() }).collect(), path: ConfigRelativePath(Value { val: path.to_owned(), definition }), deserialized_repr: StringListDeserializedRepr::String, }) } pub(crate) fn from_array(mut list: Vec>) -> Option { if list.is_empty() { return None; } let path = list.remove(0); Some(Self { path: ConfigRelativePath(path), args: list, deserialized_repr: StringListDeserializedRepr::Array, }) } } impl Serialize for PathAndArgs { fn serialize(&self, serializer: S) -> Result where S: Serializer, { match self.deserialized_repr { StringListDeserializedRepr::String => { let mut s = self.path.raw_value().to_owned(); for arg in &self.args { s.push(' '); s.push_str(&arg.val); } s.serialize(serializer) } StringListDeserializedRepr::Array => { let mut v = Vec::with_capacity(1 + self.args.len()); v.push(&self.path.0.val); for arg in &self.args { v.push(&arg.val); } v.serialize(serializer) } } } } impl<'de> Deserialize<'de> for PathAndArgs { fn deserialize(deserializer: D) -> Result where D: Deserializer<'de>, { #[derive(Deserialize)] #[serde(untagged)] enum StringOrArray { String(String), Array(Vec>), } let v: StringOrArray = Deserialize::deserialize(deserializer)?; let res = match v { StringOrArray::String(s) => Self::from_string(&s, None), StringOrArray::Array(v) => Self::from_array(v), }; match res { Some(path) => Ok(path), None => Err(de::Error::invalid_length(0, &"at least one element")), } } } #[derive(Debug, Clone)] #[non_exhaustive] pub struct StringList { pub list: Vec>, // for merge pub(crate) deserialized_repr: StringListDeserializedRepr, } #[derive(Debug, Clone, Copy, PartialEq)] pub(crate) enum StringListDeserializedRepr { String, Array, } impl StringListDeserializedRepr { pub(crate) const fn as_str(self) -> &'static str { match self { Self::String => "string", Self::Array => "array", } } } impl StringList { pub(crate) fn from_string(value: &str, definition: Option<&Definition>) -> Self { Self { list: split_space_separated(value) .map(|v| Value { val: v.to_owned(), definition: definition.cloned() }) .collect(), deserialized_repr: StringListDeserializedRepr::String, } } pub(crate) fn from_array(list: Vec>) -> Self { Self { list, deserialized_repr: StringListDeserializedRepr::Array } } } impl Serialize for StringList { fn serialize(&self, serializer: S) -> Result where S: Serializer, { match self.deserialized_repr { StringListDeserializedRepr::String => { let mut s = String::with_capacity( self.list.len().saturating_sub(1) + self.list.iter().map(|v| v.val.len()).sum::(), ); for arg in &self.list { if !s.is_empty() { s.push(' '); } s.push_str(&arg.val); } s.serialize(serializer) } StringListDeserializedRepr::Array => self.list.serialize(serializer), } } } impl<'de> Deserialize<'de> for StringList { fn deserialize(deserializer: D) -> Result where D: Deserializer<'de>, { let v: StringOrArray = Deserialize::deserialize(deserializer)?; match v { StringOrArray::String(s) => Ok(Self::from_string(&s.val, s.definition.as_ref())), StringOrArray::Array(v) => Ok(Self::from_array(v)), } } } /// A string or array of strings. #[allow(clippy::exhaustive_enums)] #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(untagged)] pub enum StringOrArray { String(Value), Array(Vec>), } impl StringOrArray { pub(crate) const fn kind(&self) -> &'static str { match self { Self::String(..) => "string", Self::Array(..) => "array", } } // pub(crate) fn string(&self) -> Option<&Value> { // match self { // Self::String(s) => Some(s), // Self::Array(_) => None, // } // } // pub(crate) fn array(&self) -> Option<&[Value]> { // match self { // Self::String(_) => None, // Self::Array(v) => Some(v), // } // } pub(crate) fn as_array_no_split(&self) -> &[Value] { match self { Self::String(s) => slice::from_ref(s), Self::Array(v) => v, } } } fn target_u_lower(target: &str) -> String { target.replace(['-', '.'], "_") } pub(crate) fn target_u_upper(target: &str) -> String { let mut target = target_u_lower(target); target.make_ascii_uppercase(); target } pub(crate) fn split_encoded(s: &str) -> impl Iterator { s.split('\x1f') } pub(crate) fn split_space_separated(s: &str) -> impl Iterator { // TODO: tab/line? // https://github.com/rust-lang/cargo/blob/0.80.0/src/cargo/util/context/path.rs#L89 s.split(' ').map(str::trim).filter(|s| !s.is_empty()) } cargo-config2-0.1.29/src/easy.rs000064400000000000000000001436101046102023000144400ustar 00000000000000// SPDX-License-Identifier: Apache-2.0 OR MIT use core::{cell::RefCell, fmt, ops}; use std::{ borrow::Cow, collections::BTreeMap, ffi::{OsStr, OsString}, path::{Path, PathBuf}, process::Command, }; use serde::ser::{Serialize, Serializer}; use serde_derive::Serialize; use crate::{ de::{ self, split_encoded, split_space_separated, Color, Frequency, RegistriesProtocol, VersionControlSoftware, When, }, error::{Context as _, Result}, process::ProcessBuilder, resolve::{ CargoVersion, ResolveContext, ResolveOptions, RustcVersion, TargetTriple, TargetTripleBorrow, TargetTripleRef, }, value::Value, }; /// Cargo configuration. #[derive(Debug, Clone, Serialize)] #[serde(rename_all = "kebab-case")] #[non_exhaustive] pub struct Config { // TODO: paths /// The `[alias]` table. /// /// [reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#alias) #[serde(default)] #[serde(skip_serializing_if = "BTreeMap::is_empty")] pub alias: BTreeMap, /// The `[build]` table. /// /// [reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#build) #[serde(default)] #[serde(skip_serializing_if = "BuildConfig::is_none")] pub build: BuildConfig, /// The `[doc]` table. /// /// [reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#doc) #[serde(default)] #[serde(skip_serializing_if = "DocConfig::is_none")] pub doc: DocConfig, /// The `[env]` table. /// /// [reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#env) #[serde(default)] #[serde(skip_serializing_if = "BTreeMap::is_empty")] pub env: BTreeMap, /// The `[future-incompat-report]` table. /// /// [reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#future-incompat-report) #[serde(default)] #[serde(skip_serializing_if = "FutureIncompatReportConfig::is_none")] pub future_incompat_report: FutureIncompatReportConfig, /// The `[cargo-new]` table. /// /// [reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#cargo-new) #[serde(default)] #[serde(skip_serializing_if = "CargoNewConfig::is_none")] pub cargo_new: CargoNewConfig, /// The `[http]` table. /// /// [reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#http) #[serde(default)] #[serde(skip_serializing_if = "HttpConfig::is_none")] pub http: HttpConfig, // TODO: install /// The `[net]` table. /// /// [reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#net) #[serde(default)] #[serde(skip_serializing_if = "NetConfig::is_none")] pub net: NetConfig, // TODO: patch // TODO: profile /// The `[registries]` table. /// /// [reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#registries) #[serde(default)] #[serde(skip_serializing_if = "BTreeMap::is_empty")] pub registries: BTreeMap, /// The `[registry]` table. /// /// [reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#registry) #[serde(default)] #[serde(skip_serializing_if = "RegistryConfig::is_none")] pub registry: RegistryConfig, // TODO: source /// The resolved `[target]` table. #[serde(skip_deserializing)] #[serde(skip_serializing_if = "ref_cell_bree_map_is_empty")] target: RefCell, TargetConfig>>, /// The unresolved `[target]` table. #[serde(default)] #[serde(skip_serializing)] #[serde(rename = "target")] de_target: BTreeMap, /// The `[term]` table. /// /// [reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#term) #[serde(default)] #[serde(skip_serializing_if = "TermConfig::is_none")] pub term: TermConfig, // Resolve contexts. Completely ignored in serialization and deserialization. #[serde(skip)] cx: ResolveContext, } fn ref_cell_bree_map_is_empty(map: &RefCell>) -> bool { map.borrow().is_empty() } impl Config { /// Read config files hierarchically from the current directory and merges them. pub fn load() -> Result { Self::load_with_cwd(std::env::current_dir().context("failed to get current directory")?) } /// Read config files hierarchically from the given directory and merges them. pub fn load_with_cwd>(cwd: P) -> Result { let cwd = cwd.as_ref(); Self::load_with_options(cwd, ResolveOptions::default()) } /// Read config files hierarchically from the given directory and merges them. pub fn load_with_options>(cwd: P, options: ResolveOptions) -> Result { let cwd = cwd.as_ref(); let cx = options.into_context(cwd.to_owned()); let de = de::Config::_load_with_options(&cx.current_dir, cx.cargo_home(cwd).as_deref())?; Self::from_unresolved(de, cx) } fn from_unresolved(mut de: de::Config, cx: ResolveContext) -> Result { de.apply_env(&cx)?; let mut alias = BTreeMap::new(); for (k, v) in de.alias { alias.insert(k, StringList::from_unresolved(v)); } let build = BuildConfig::from_unresolved(de.build, &cx.current_dir); let doc = DocConfig::from_unresolved(de.doc, &cx.current_dir); let mut env = BTreeMap::new(); for (k, v) in de.env { env.insert(k, EnvConfigValue::from_unresolved(v, &cx.current_dir)); } let future_incompat_report = FutureIncompatReportConfig::from_unresolved(de.future_incompat_report); let cargo_new = CargoNewConfig::from_unresolved(de.cargo_new); let http = HttpConfig::from_unresolved(de.http); let net = NetConfig::from_unresolved(de.net); let mut registries = BTreeMap::new(); for (k, v) in de.registries { registries.insert(k, RegistriesConfigValue::from_unresolved(v)); } let registry = RegistryConfig::from_unresolved(de.registry); let term = TermConfig::from_unresolved(de.term); Ok(Self { alias, build, doc, env, future_incompat_report, cargo_new, http, net, registries, registry, target: RefCell::new(BTreeMap::new()), de_target: de.target, term, cx, }) } /// Selects target triples to build. /// /// The targets returned are based on the order of priority in which cargo /// selects the target to be used for the build. /// /// 1. `--target` option (`targets`) /// 2. `CARGO_BUILD_TARGET` environment variable /// 3. `build.target` config /// 4. [host triple](Self::host_triple) /// /// **Note:** The result of this function is intended to handle target-specific /// configurations and is not always appropriate to propagate directly to Cargo. /// See [`build_target_for_cli`](Self::build_target_for_cli) for more. /// /// ## Multi-target support /// /// [Cargo 1.64+ supports multi-target builds](https://blog.rust-lang.org/2022/09/22/Rust-1.64.0.html#cargo-improvements-workspace-inheritance-and-multi-target-builds). /// /// Therefore, this function may return multiple targets if multiple targets /// are specified in `targets` or `build.target` config. /// /// ## Custom target support /// /// rustc allows you to build a custom target by specifying a target-spec file. /// If a target-spec file is specified as the target, rustc considers the /// [file stem](Path::file_stem) of that file to be the target triple name. /// /// Since target-specific configs are referred by target triple name, this /// function also converts the target specified in the path to a target triple name. /// /// ## Examples /// /// With single-target: /// /// ```no_run /// use anyhow::bail; /// use clap::Parser; /// /// #[derive(Parser)] /// struct Args { /// #[clap(long)] /// target: Option, /// } /// /// let args = Args::parse(); /// let config = cargo_config2::Config::load()?; /// /// let mut targets = config.build_target_for_config(args.target.as_ref())?; /// if targets.len() != 1 { /// bail!("multi-target build is not supported: {targets:?}"); /// } /// let target = targets.pop().unwrap(); /// /// println!("{:?}", config.rustflags(target)); /// # Ok::<(), anyhow::Error>(()) /// ``` /// /// With multi-target: /// /// ```no_run /// use clap::Parser; /// /// #[derive(Parser)] /// struct Args { /// #[clap(long)] /// target: Vec, /// } /// /// let args = Args::parse(); /// let config = cargo_config2::Config::load()?; /// /// let targets = config.build_target_for_config(&args.target)?; /// /// for target in targets { /// println!("{:?}", config.rustflags(target)?); /// } /// # Ok::<(), anyhow::Error>(()) /// ``` pub fn build_target_for_config<'a, I: IntoIterator, T: Into>>( &self, targets: I, ) -> Result> { let targets: Vec<_> = targets.into_iter().map(|v| v.into().into_owned()).collect(); if !targets.is_empty() { return Ok(targets); } let config_targets = self.build.target.clone().unwrap_or_default(); if !config_targets.is_empty() { return Ok(config_targets); } Ok(vec![TargetTripleRef::from(self.cx.host_triple(&self.build)?).into_owned()]) } /// Selects target triples to pass to CLI. /// /// The targets returned are based on the order of priority in which cargo /// selects the target to be used for the build. /// /// 1. `--target` option (`targets`) /// 2. `CARGO_BUILD_TARGET` environment variable /// 3. `build.target` config /// /// Unlike [`build_target_for_config`](Self::build_target_for_config), /// host triple is not referenced. This is because the behavior of Cargo /// changes depending on whether or not `--target` option (or one of the /// above) is set. /// Also, Unlike [`build_target_for_config`](Self::build_target_for_config) /// the target name specified in path is preserved. #[allow(clippy::unnecessary_wraps)] // TODO: change in next breaking release? pub fn build_target_for_cli, S: AsRef>( &self, targets: I, ) -> Result> { let targets: Vec<_> = targets.into_iter().map(|t| t.as_ref().to_owned()).collect(); if !targets.is_empty() { return Ok(targets); } let config_targets = self.build.target.as_deref().unwrap_or_default(); if !config_targets.is_empty() { return Ok(config_targets.iter().map(|t| t.cli_target_string().into_owned()).collect()); } Ok(vec![]) } fn init_target_config(&self, target: &TargetTripleRef<'_>) -> Result<()> { let mut target_configs = self.target.borrow_mut(); if !target_configs.contains_key(target.cli_target()) { let target_config = TargetConfig::from_unresolved( de::Config::resolve_target( &self.cx, &self.de_target, self.build.override_target_rustflags, &self.build.de_rustflags, self.build.override_target_rustdocflags, &self.build.de_rustdocflags, target, &self.build, )? .unwrap_or_default(), &self.cx.current_dir, ); target_configs.insert(TargetTripleBorrow(target.clone().into_owned()), target_config); } Ok(()) } /// Returns the resolved `[target]` table for the given target. pub fn target<'a, T: Into>>(&self, target: T) -> Result { let target = target.into(); self.init_target_config(&target)?; Ok(self.target.borrow()[target.cli_target()].clone()) } /// Returns the resolved linker path for the given target. pub fn linker<'a, T: Into>>(&self, target: T) -> Result> { let target = target.into(); self.init_target_config(&target)?; Ok(self.target.borrow()[target.cli_target()].linker.clone()) } /// Returns the resolved runner path and args for the given target. pub fn runner<'a, T: Into>>( &self, target: T, ) -> Result> { let target = target.into(); self.init_target_config(&target)?; Ok(self.target.borrow()[target.cli_target()].runner.clone()) } /// Returns the resolved rustflags for the given target. pub fn rustflags<'a, T: Into>>(&self, target: T) -> Result> { let target = target.into(); self.init_target_config(&target)?; Ok(self.target.borrow()[target.cli_target()].rustflags.clone()) } /// Returns the resolved rustdocflags for the given target. pub fn rustdocflags<'a, T: Into>>( &self, target: T, ) -> Result> { let target = target.into(); self.init_target_config(&target)?; Ok(self.target.borrow()[target.cli_target()].rustdocflags.clone()) } /// Returns the path and args that calls `rustc`. /// /// If [`RUSTC_WRAPPER`](BuildConfig::rustc_wrapper) or /// [`RUSTC_WORKSPACE_WRAPPER`](BuildConfig::rustc_workspace_wrapper) is set, /// the path is the wrapper path and the argument is the rustc path. /// Otherwise, the path is the rustc path. /// /// If you set `rustc` path by [`ResolveOptions::rustc`], this returns the path set by it. pub fn rustc(&self) -> &PathAndArgs { self.cx.rustc(&self.build) } /// Returns the path to `cargo`. /// /// The returned path is the value of the `CARGO` environment variable if it is set. Otherwise, "cargo". /// /// If you set `cargo` path by [`ResolveOptions::cargo`], this returns the path set by it. pub fn cargo(&self) -> &OsStr { &self.cx.cargo } /// Returns the host triple. pub fn host_triple(&self) -> Result<&str> { self.cx.host_triple(&self.build) } /// Returns the version of the [current rustc](Self::rustc). /// /// The result is usually the same as [`cargo_version`](Self::cargo_version), /// but it may differ if a different rustc is specified in config or if the /// [user is manipulating the output of the rustc](https://github.com/taiki-e/cargo-minimal-versions/issues/29). /// /// # rustc_version vs cargo_version /// /// Which is the preferred to use depends on the situation: /// /// - You will need to know the **rustc** version to determine whether options passed to rustc /// via RUSTFLAGS or RUSTDOCFLAGS like `-C instrument-coverage` are available. /// - You will need to know the **cargo** version to determine whether fields in `Cargo.toml` /// or cargo’s CLI options are available. pub fn rustc_version(&self) -> Result { self.cx.rustc_version(&self.build) } /// Returns the version of the [current cargo](Self::cargo). /// /// See also [`rustc_version`](Self::rustc_version). pub fn cargo_version(&self) -> Result { self.cx.cargo_version(&self.build) } // TODO: add override instead? // /// Merges the given config into this config. // /// // /// If `force` is `false`, this matches the way cargo [merges configs in the // /// parent directories](https://doc.rust-lang.org/nightly/cargo/reference/config.html#hierarchical-structure). // /// // /// If `force` is `true`, this matches the way cargo's `--config` CLI option // /// overrides config. // pub fn merge(&mut self, low: Self, force: bool) -> Result<()> { // merge::Merge::merge(self, low, force) // } } /// The `[build]` table. /// /// [reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#build) #[derive(Debug, Clone, Default, Serialize)] #[serde(rename_all = "kebab-case")] #[non_exhaustive] pub struct BuildConfig { /// Sets the maximum number of compiler processes to run in parallel. /// If negative, it sets the maximum number of compiler processes to the /// number of logical CPUs plus provided value. Should not be 0. /// /// [reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#buildjobs) #[serde(skip_serializing_if = "Option::is_none")] pub jobs: Option, /// Sets the executable to use for `rustc`. /// /// [reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#buildrustc) /// /// **Note:** If a wrapper is set, cargo's actual rustc call goes through /// the wrapper, so you may want to use [`Config::rustc`], which respects /// that behavior instead of referencing this value directly. #[serde(skip_serializing_if = "Option::is_none")] pub rustc: Option, /// Sets a wrapper to execute instead of `rustc`. /// /// [reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#buildrustc-wrapper) /// /// **Note:** If a wrapper is set, cargo's actual rustc call goes through /// the wrapper, so you may want to use [`Config::rustc`], which respects /// that behavior instead of referencing this value directly. #[serde(skip_serializing_if = "Option::is_none")] pub rustc_wrapper: Option, /// Sets a wrapper to execute instead of `rustc`, for workspace members only. /// /// [reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#buildrustc-workspace-wrapper) /// /// **Note:** If a wrapper is set, cargo's actual rustc call goes through /// the wrapper, so you may want to use [`Config::rustc`], which respects /// that behavior instead of referencing this value directly. #[serde(skip_serializing_if = "Option::is_none")] pub rustc_workspace_wrapper: Option, /// Sets the executable to use for `rustdoc`. /// /// [reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#buildrustdoc) #[serde(skip_serializing_if = "Option::is_none")] pub rustdoc: Option, /// The default target platform triples to compile to. /// /// [reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#buildtarget) #[serde(skip_serializing_if = "Option::is_none")] pub target: Option>, /// The path to where all compiler output is placed. The default if not /// specified is a directory named target located at the root of the workspace. /// /// [reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#buildtarget) #[serde(skip_serializing_if = "Option::is_none")] pub target_dir: Option, /// Extra command-line flags to pass to rustc. The value may be an array /// of strings or a space-separated string. /// /// [reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#buildrustflags) /// /// **Note:** This field does not reflect the target-specific rustflags /// configuration, so you may want to use [`Config::rustflags`] which respects /// the target-specific rustflags configuration. #[serde(skip_serializing_if = "Option::is_none")] pub rustflags: Option, /// Extra command-line flags to pass to `rustdoc`. The value may be an array /// of strings or a space-separated string. /// /// [reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#buildrustdocflags) /// /// **Note:** This field does not reflect the target-specific rustdocflags /// configuration, so you may want to use [`Config::rustdocflags`] which respects /// the target-specific rustdocflags configuration. #[serde(skip_serializing_if = "Option::is_none")] pub rustdocflags: Option, /// Whether or not to perform incremental compilation. /// /// [reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#buildincremental) #[serde(skip_serializing_if = "Option::is_none")] pub incremental: Option, /// Strips the given path prefix from dep info file paths. /// /// [reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#builddep-info-basedir) #[serde(skip_serializing_if = "Option::is_none")] pub dep_info_basedir: Option, // Resolve contexts. Completely ignored in serialization and deserialization. #[serde(skip)] override_target_rustflags: bool, #[serde(skip)] de_rustflags: Option, #[serde(skip)] override_target_rustdocflags: bool, #[serde(skip)] de_rustdocflags: Option, } impl BuildConfig { pub(crate) fn from_unresolved(de: de::BuildConfig, current_dir: &Path) -> Self { let jobs = de.jobs.map(|v| v.val); let rustc = de.rustc.map(|v| v.resolve_as_program_path(current_dir).into_owned()); let rustc_wrapper = de.rustc_wrapper.map(|v| v.resolve_as_program_path(current_dir).into_owned()); let rustc_workspace_wrapper = de.rustc_workspace_wrapper.map(|v| v.resolve_as_program_path(current_dir).into_owned()); let rustdoc = de.rustdoc.map(|v| v.resolve_as_program_path(current_dir).into_owned()); let target = de.target.map(|t| { t.as_array_no_split() .iter() .map(|v| { TargetTriple::new( v.val.clone().into(), v.definition.as_ref(), Some(current_dir), ) }) .collect() }); let target_dir = de.target_dir.map(|v| v.resolve_as_path(current_dir).into_owned()); let de_rustflags = de.rustflags.clone(); let rustflags = de.rustflags.map(|v| Flags { flags: v.flags.into_iter().map(|v| v.val).collect() }); let de_rustdocflags = de.rustdocflags.clone(); let rustdocflags = de.rustdocflags.map(|v| Flags { flags: v.flags.into_iter().map(|v| v.val).collect() }); let incremental = de.incremental.map(|v| v.val); let dep_info_basedir = de.dep_info_basedir.map(|v| v.resolve_as_path(current_dir).into_owned()); let override_target_rustflags = de.override_target_rustflags; let override_target_rustdocflags = de.override_target_rustdocflags; Self { jobs, rustc, rustc_wrapper, rustc_workspace_wrapper, rustdoc, target, target_dir, rustflags, rustdocflags, incremental, dep_info_basedir, override_target_rustflags, de_rustflags, override_target_rustdocflags, de_rustdocflags, } } } // https://github.com/rust-lang/cargo/blob/0.80.0/src/cargo/util/context/target.rs /// A `[target.]` or `[target.]` table. /// /// [reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#target) #[derive(Debug, Clone, Default, Serialize)] #[serde(rename_all = "kebab-case")] #[non_exhaustive] pub struct TargetConfig { /// [reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#targettriplelinker) #[serde(skip_serializing_if = "Option::is_none")] pub linker: Option, /// [reference (`target..runner`)](https://doc.rust-lang.org/nightly/cargo/reference/config.html#targettriplerunner) /// /// [reference (`target..runner`)](https://doc.rust-lang.org/nightly/cargo/reference/config.html#targetcfgrunner) #[serde(skip_serializing_if = "Option::is_none")] pub runner: Option, /// [reference (`target..rustflags`)](https://doc.rust-lang.org/nightly/cargo/reference/config.html#targettriplerustflags) /// /// [reference (`target..rustflags`)](https://doc.rust-lang.org/nightly/cargo/reference/config.html#targetcfgrustflags) #[serde(skip_serializing_if = "Option::is_none")] pub rustflags: Option, /// [reference (`target..rustdocflags`)](https://doc.rust-lang.org/nightly/cargo/reference/config.html#targettriplerustdocflags) /// /// [reference (`target..rustdocflags`)](https://doc.rust-lang.org/nightly/cargo/reference/config.html#targetcfgrustdocflags) #[serde(skip_serializing_if = "Option::is_none")] pub rustdocflags: Option, // TODO: links: https://doc.rust-lang.org/nightly/cargo/reference/config.html#targettriplelinks } impl TargetConfig { fn from_unresolved(de: de::TargetConfig, current_dir: &Path) -> Self { let linker = de.linker.map(|v| v.resolve_as_program_path(current_dir).into_owned()); let runner = match de.runner { Some(v) => Some(PathAndArgs { path: v.path.resolve_program(current_dir).into_owned(), args: v.args.into_iter().map(|v| v.val.into()).collect(), }), None => None, }; let rustflags = de.rustflags.map(|v| Flags { flags: v.flags.into_iter().map(|v| v.val).collect() }); let rustdocflags = de.rustdocflags.map(|v| Flags { flags: v.flags.into_iter().map(|v| v.val).collect() }); Self { linker, runner, rustflags, rustdocflags } } } /// The `[doc]` table. /// /// [reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#doc) #[derive(Debug, Clone, Default, Serialize)] #[serde(rename_all = "kebab-case")] #[non_exhaustive] pub struct DocConfig { /// This option sets the browser to be used by `cargo doc`, overriding the /// `BROWSER` environment variable when opening documentation with the `--open` option. /// /// [reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#docbrowser) #[serde(skip_serializing_if = "Option::is_none")] pub browser: Option, } impl DocConfig { fn from_unresolved(de: de::DocConfig, current_dir: &Path) -> Self { let browser = de.browser.map(|v| PathAndArgs { path: v.path.resolve_program(current_dir).into_owned(), args: v.args.into_iter().map(|v| v.val.into()).collect(), }); Self { browser } } } /// A value of the `[env]` table. /// /// [reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#env) #[derive(Debug, Clone)] #[non_exhaustive] pub struct EnvConfigValue { pub value: OsString, pub force: bool, pub relative: bool, } impl EnvConfigValue { fn from_unresolved(de: de::EnvConfigValue, current_dir: &Path) -> Self { if let de::EnvConfigValue::Table { force, relative: Some(Value { val: true, .. }), .. } = &de { return Self { value: de.resolve(current_dir).into_owned(), force: force.as_ref().map_or(false, |v| v.val), // Since we resolved the value, it is no longer relative. relative: false, }; } match de { de::EnvConfigValue::Value(value) => { Self { value: value.val.into(), force: false, relative: false } } de::EnvConfigValue::Table { value, force, .. } => Self { value: value.val.into(), force: force.map_or(false, |v| v.val), relative: false, }, } } } impl Serialize for EnvConfigValue { fn serialize(&self, serializer: S) -> Result where S: Serializer, { #[derive(Serialize)] #[serde(untagged)] enum EnvRepr<'a> { Value(Cow<'a, str>), Table { value: Cow<'a, str>, #[serde(skip_serializing_if = "ops::Not::not")] force: bool, #[serde(skip_serializing_if = "ops::Not::not")] relative: bool, }, } match self { Self { value, force: false, relative: false } => { EnvRepr::Value(value.to_string_lossy()).serialize(serializer) } Self { value, force, relative, .. } => EnvRepr::Table { value: value.to_string_lossy(), force: *force, relative: *relative, } .serialize(serializer), } } } /// The `[future-incompat-report]` table. /// /// [reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#future-incompat-report) #[derive(Debug, Clone, Default, Serialize)] #[serde(rename_all = "kebab-case")] #[non_exhaustive] pub struct FutureIncompatReportConfig { /// Controls how often we display a notification to the terminal when a future incompat report is available. /// /// [reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#future-incompat-reportfrequency) #[serde(skip_serializing_if = "Option::is_none")] pub frequency: Option, } impl FutureIncompatReportConfig { fn from_unresolved(de: de::FutureIncompatReportConfig) -> Self { let frequency = de.frequency.map(|v| v.val); Self { frequency } } } /// The `[cargo-new]` table. /// /// [reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#cargo-new) #[derive(Debug, Clone, Default, Serialize)] #[serde(rename_all = "kebab-case")] #[non_exhaustive] pub struct CargoNewConfig { /// Specifies the source control system to use for initializing a new repository. /// Valid values are git, hg (for Mercurial), pijul, fossil or none to disable this behavior. /// Defaults to git, or none if already inside a VCS repository. Can be overridden with the --vcs CLI option. #[serde(skip_serializing_if = "Option::is_none")] pub vcs: Option, } impl CargoNewConfig { fn from_unresolved(de: de::CargoNewConfig) -> Self { Self { vcs: de.vcs.map(|v| v.val) } } } /// The `[http]` table. /// /// [reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#http) #[derive(Debug, Clone, Default, Serialize)] #[serde(rename_all = "kebab-case")] #[non_exhaustive] pub struct HttpConfig { /// If true, enables debugging of HTTP requests. /// The debug information can be seen by setting the `CARGO_LOG=network=debug` environment variable /// (or use `network=trace` for even more information). /// /// [reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#httpdebug) #[serde(skip_serializing_if = "Option::is_none")] pub debug: Option, /// Sets an HTTP and HTTPS proxy to use. The format is in libcurl format as in `[protocol://]host[:port]`. /// If not set, Cargo will also check the http.proxy setting in your global git configuration. /// If none of those are set, the HTTPS_PROXY or https_proxy environment variables set the proxy for HTTPS requests, /// and http_proxy sets it for HTTP requests. /// /// [reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#httpproxy) #[serde(skip_serializing_if = "Option::is_none")] pub proxy: Option, /// Sets the timeout for each HTTP request, in seconds. /// /// [reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#httptimeout) #[serde(skip_serializing_if = "Option::is_none")] pub timeout: Option, /// Path to a Certificate Authority (CA) bundle file, used to verify TLS certificates. /// If not specified, Cargo attempts to use the system certificates. /// /// [reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#httpcainfo) #[serde(skip_serializing_if = "Option::is_none")] pub cainfo: Option, /// This determines whether or not TLS certificate revocation checks should be performed. /// This only works on Windows. /// /// [reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#httpcheck-revoke) #[serde(skip_serializing_if = "Option::is_none")] pub check_revoke: Option, // TODO: Add ssl-version /// This setting controls timeout behavior for slow connections. /// If the average transfer speed in bytes per second is below the given value /// for `http.timeout` seconds (default 30 seconds), then the connection is considered too slow and Cargo will abort and retry. /// /// [reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#httplow-speed-limit) #[serde(skip_serializing_if = "Option::is_none")] pub low_speed_limit: Option, /// When true, Cargo will attempt to use the HTTP2 protocol with multiplexing. /// This allows multiple requests to use the same connection, usually improving performance when fetching multiple files. /// If false, Cargo will use HTTP 1.1 without pipelining. /// /// [reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#httpmultiplexing) #[serde(skip_serializing_if = "Option::is_none")] pub multiplexing: Option, /// Specifies a custom user-agent header to use. /// The default if not specified is a string that includes Cargo’s version. /// /// [reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#httpuser-agent) #[serde(skip_serializing_if = "Option::is_none")] pub user_agent: Option, } impl HttpConfig { fn from_unresolved(de: de::HttpConfig) -> Self { Self { debug: de.debug.map(|v| v.val), proxy: de.proxy.map(|v| v.val), timeout: de.timeout.map(|v| v.val), cainfo: de.cainfo.map(|v| v.val), check_revoke: de.check_revoke.map(|v| v.val), low_speed_limit: de.low_speed_limit.map(|v| v.val), multiplexing: de.multiplexing.map(|v| v.val), user_agent: de.user_agent.map(|v| v.val), } } } /// The `[net]` table. /// /// [reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#net) #[derive(Debug, Clone, Default, Serialize)] #[serde(rename_all = "kebab-case")] #[non_exhaustive] pub struct NetConfig { /// Number of times to retry possibly spurious network errors. /// /// [reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#netretry) #[serde(skip_serializing_if = "Option::is_none")] pub retry: Option, /// If this is `true`, then Cargo will use the `git` executable to fetch /// registry indexes and git dependencies. If `false`, then it uses a /// built-in `git` library. /// /// [reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#netgit-fetch-with-cli) #[serde(skip_serializing_if = "Option::is_none")] pub git_fetch_with_cli: Option, /// If this is `true`, then Cargo will avoid accessing the network, and /// attempt to proceed with locally cached data. If `false`, Cargo will /// access the network as needed, and generate an error if it encounters a /// network error. /// /// [reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#netoffline) #[serde(skip_serializing_if = "Option::is_none")] pub offline: Option, } impl NetConfig { fn from_unresolved(de: de::NetConfig) -> Self { let retry = de.retry.map(|v| v.val); let git_fetch_with_cli = de.git_fetch_with_cli.map(|v| v.val); let offline = de.offline.map(|v| v.val); Self { retry, git_fetch_with_cli, offline } } } /// A value of the `[registries]` table. /// /// [reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#registries) #[derive(Clone, Default, Serialize)] #[serde(rename_all = "kebab-case")] #[non_exhaustive] pub struct RegistriesConfigValue { /// Specifies the URL of the git index for the registry. /// /// [reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#registriesnameindex) #[serde(skip_serializing_if = "Option::is_none")] pub index: Option, /// Specifies the authentication token for the given registry. /// /// Note: This library does not read any values in the /// [credentials](https://doc.rust-lang.org/nightly/cargo/reference/config.html#credentials) /// file. /// /// [reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#registriesnametoken) #[serde(skip_serializing_if = "Option::is_none")] pub token: Option, /// Specifies the protocol used to access crates.io. /// Not allowed for any registries besides crates.io. /// /// [reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#registriescrates-ioprotocol) #[serde(skip_serializing_if = "Option::is_none")] pub protocol: Option, } impl RegistriesConfigValue { fn from_unresolved(de: de::RegistriesConfigValue) -> Self { let index = de.index.map(|v| v.val); let token = de.token.map(|v| v.val); let protocol = de.protocol.map(|v| match v.val { de::RegistriesProtocol::Git => RegistriesProtocol::Git, de::RegistriesProtocol::Sparse => RegistriesProtocol::Sparse, }); Self { index, token, protocol } } } impl fmt::Debug for RegistriesConfigValue { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let Self { index, token, protocol } = self; let redacted_token = token.as_ref().map(|_| "[REDACTED]"); f.debug_struct("RegistriesConfigValue") .field("index", &index) .field("token", &redacted_token) .field("protocol", &protocol) .finish_non_exhaustive() } } /// The `[registry]` table. /// /// [reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#registry) #[derive(Clone, Default, Serialize)] #[serde(rename_all = "kebab-case")] #[non_exhaustive] pub struct RegistryConfig { /// The name of the registry (from the /// [`registries` table](https://doc.rust-lang.org/nightly/cargo/reference/config.html#registries)) /// to use by default for registry commands like /// [`cargo publish`](https://doc.rust-lang.org/nightly/cargo/commands/cargo-publish.html). /// /// [reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#registrydefault) #[serde(skip_serializing_if = "Option::is_none")] pub default: Option, /// Specifies the authentication token for [crates.io](https://crates.io/). /// /// Note: This library does not read any values in the /// [credentials](https://doc.rust-lang.org/nightly/cargo/reference/config.html#credentials) /// file. /// /// [reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#registrytoken) #[serde(skip_serializing_if = "Option::is_none")] pub token: Option, } impl RegistryConfig { fn from_unresolved(de: de::RegistryConfig) -> Self { let default = de.default.map(|v| v.val); let token = de.token.map(|v| v.val); Self { default, token } } } impl fmt::Debug for RegistryConfig { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let Self { default, token } = self; let redacted_token = token.as_ref().map(|_| "[REDACTED]"); f.debug_struct("RegistryConfig") .field("default", &default) .field("token", &redacted_token) .finish() } } /// The `[term]` table. /// /// [reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#term) #[derive(Debug, Clone, Default, Serialize)] #[serde(rename_all = "kebab-case")] #[non_exhaustive] pub struct TermConfig { /// Controls whether or not log messages are displayed by Cargo. /// /// [reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#termquiet) #[serde(skip_serializing_if = "Option::is_none")] pub quiet: Option, /// Controls whether or not extra detailed messages are displayed by Cargo. /// /// [reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#termverbose) #[serde(skip_serializing_if = "Option::is_none")] pub verbose: Option, /// Controls whether or not colored output is used in the terminal. /// /// [reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#termcolor) #[serde(skip_serializing_if = "Option::is_none")] pub color: Option, #[serde(default)] #[serde(skip_serializing_if = "TermProgressConfig::is_none")] pub progress: TermProgressConfig, } impl TermConfig { fn from_unresolved(de: de::TermConfig) -> Self { let quiet = de.quiet.map(|v| v.val); let verbose = de.verbose.map(|v| v.val); let color = de.color.map(|v| v.val); let progress = TermProgressConfig::from_unresolved(de.progress); Self { quiet, verbose, color, progress } } } #[derive(Debug, Clone, Default, Serialize)] #[serde(rename_all = "kebab-case")] #[non_exhaustive] pub struct TermProgressConfig { /// Controls whether or not progress bar is shown in the terminal. /// /// [reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#termprogresswhen) #[serde(skip_serializing_if = "Option::is_none")] pub when: Option, /// Sets the width for progress bar. /// /// [reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#termprogresswidth) #[serde(skip_serializing_if = "Option::is_none")] pub width: Option, } impl TermProgressConfig { fn from_unresolved(de: de::TermProgress) -> Self { let when = de.when.map(|v| v.val); let width = de.width.map(|v| v.val); Self { when, width } } } /// A representation of rustflags or rustdocflags. #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize)] #[serde(transparent)] #[non_exhaustive] pub struct Flags { pub flags: Vec, } impl Flags { /// Creates a rustflags or rustdocflags from a string separated with ASCII unit separator ('\x1f'). /// /// This is a valid format for the following environment variables: /// /// - `CARGO_ENCODED_RUSTFLAGS` (Cargo 1.55+) /// - `CARGO_ENCODED_RUSTDOCFLAGS` (Cargo 1.55+) /// /// See also [`encode`](Self::encode). pub fn from_encoded(s: &str) -> Self { Self { flags: split_encoded(s).map(str::to_owned).collect() } } /// Creates a rustflags or rustdocflags from a string separated with space (' '). /// /// This is a valid format for the following environment variables: /// /// - `RUSTFLAGS` /// - `CARGO_TARGET__RUSTFLAGS` /// - `CARGO_BUILD_RUSTFLAGS` /// - `RUSTDOCFLAGS` /// - `CARGO_TARGET__RUSTDOCFLAGS` /// - `CARGO_BUILD_RUSTDOCFLAGS` /// /// And the following configs: /// /// - `target..rustflags` /// - `target..rustflags` /// - `build.rustflags` /// - `target..rustdocflags` (Cargo 1.78+) /// - `build.rustdocflags` /// /// See also [`encode_space_separated`](Self::encode_space_separated). pub fn from_space_separated(s: &str) -> Self { Self { flags: split_space_separated(s).map(str::to_owned).collect() } } /// Concatenates this rustflags or rustdocflags with ASCII unit separator ('\x1f'). /// /// This is a valid format for the following environment variables: /// /// - `CARGO_ENCODED_RUSTFLAGS` (Cargo 1.55+) /// - `CARGO_ENCODED_RUSTDOCFLAGS` (Cargo 1.55+) /// /// # Errors /// /// This returns an error if any of flag contains ASCII unit separator ('\x1f'). /// /// This is because even if you do not intend it to be interpreted as a /// separator, Cargo will interpret it as a separator. /// /// Since it is not easy to insert an ASCII unit separator in a toml file or /// Shell environment variable, this usually occurs when this rustflags or rustdocflags /// is created in the wrong way ([`from_encoded`](Self::from_encoded) vs /// [`from_space_separated`](Self::from_space_separated)) or when a flag /// containing a separator is written in the rust code ([`push`](Self::push), /// `into`, `from`, etc.). pub fn encode(&self) -> Result { self.encode_internal('\x1f') } /// Concatenates this rustflags or rustdocflags with space (' '). /// /// This is a valid format for the following environment variables: /// /// - `RUSTFLAGS` /// - `CARGO_TARGET__RUSTFLAGS` /// - `CARGO_BUILD_RUSTFLAGS` /// - `RUSTDOCFLAGS` /// - `CARGO_TARGET__RUSTDOCFLAGS` /// - `CARGO_BUILD_RUSTDOCFLAGS` /// /// And the following configs: /// /// - `target..rustflags` /// - `target..rustflags` /// - `build.rustflags` /// - `target..rustdocflags` (Cargo 1.78+) /// - `build.rustdocflags` /// /// # Errors /// /// This returns an error if any of flag contains space (' '). /// /// This is because even if you do not intend it to be interpreted as a /// separator, Cargo will interpret it as a separator. /// /// If you run into this error, consider using a more robust /// [`CARGO_ENCODED_*` flags](Self::encode). pub fn encode_space_separated(&self) -> Result { self.encode_internal(' ') } fn encode_internal(&self, sep: char) -> Result { let mut buf = String::with_capacity( self.flags.len().saturating_sub(1) + self.flags.iter().map(String::len).sum::(), ); for flag in &self.flags { if flag.contains(sep) { bail!("flag in rustflags must not contain its separator ({sep:?})"); } if !buf.is_empty() { buf.push(sep); } buf.push_str(flag); } Ok(buf) } /// Appends a flag to the back of this rustflags or rustdocflags. pub fn push>(&mut self, flag: S) { self.flags.push(flag.into()); } } impl From> for Flags { fn from(value: Vec) -> Self { Self { flags: value } } } impl From<&[String]> for Flags { fn from(value: &[String]) -> Self { Self { flags: value.to_owned() } } } impl From<&[&str]> for Flags { fn from(value: &[&str]) -> Self { Self { flags: value.iter().map(|&v| v.to_owned()).collect() } } } impl From<[String; N]> for Flags { fn from(value: [String; N]) -> Self { Self { flags: value[..].to_owned() } } } impl From<[&str; N]> for Flags { fn from(value: [&str; N]) -> Self { Self { flags: value[..].iter().map(|&v| v.to_owned()).collect() } } } /// An executable path with arguments. /// /// [reference](https://doc.rust-lang.org/nightly/cargo/reference/config.html#executable-paths-with-arguments) #[derive(Debug, Clone, PartialEq, Eq)] #[non_exhaustive] pub struct PathAndArgs { pub path: PathBuf, pub args: Vec, } impl PathAndArgs { /// Creates a new program. pub fn new>(path: P) -> Self { Self { path: path.into(), args: vec![] } } /// Adds an argument to pass to the program. pub fn arg>(&mut self, arg: S) -> &mut Self { self.args.push(arg.into()); self } /// Adds multiple arguments to pass to the program. pub fn args, S: Into>(&mut self, args: I) -> &mut Self { self.args.extend(args.into_iter().map(Into::into)); self } } impl Serialize for PathAndArgs { fn serialize(&self, serializer: S) -> Result where S: Serializer, { let mut v = Vec::with_capacity(1 + self.args.len()); v.push(self.path.to_string_lossy().into_owned()); for arg in &self.args { v.push(arg.to_string_lossy().into_owned()); } v.serialize(serializer) } } impl From for Command { fn from(value: PathAndArgs) -> Self { Self::from(&value) } } impl From<&PathAndArgs> for Command { fn from(value: &PathAndArgs) -> Self { let mut cmd = Command::new(&value.path); cmd.args(&value.args); cmd } } impl From<&PathAndArgs> for ProcessBuilder { fn from(value: &PathAndArgs) -> Self { ProcessBuilder::from_std(Command::from(value)) } } /// A list of string. #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize)] #[serde(transparent)] #[non_exhaustive] pub struct StringList { pub list: Vec, } impl StringList { fn from_string(value: &str) -> Self { Self { list: split_space_separated(value).map(str::to_owned).collect() } } fn from_array(list: Vec) -> Self { Self { list } } fn from_unresolved(value: de::StringList) -> Self { Self { list: value.list.into_iter().map(|v| v.val).collect() } } } impl From for StringList { fn from(value: String) -> Self { Self::from_string(&value) } } impl From<&String> for StringList { fn from(value: &String) -> Self { Self::from_string(value) } } impl From<&str> for StringList { fn from(value: &str) -> Self { Self::from_string(value) } } impl From> for StringList { fn from(value: Vec) -> Self { Self::from_array(value) } } impl From<&[String]> for StringList { fn from(value: &[String]) -> Self { Self::from_array(value.to_owned()) } } impl From<&[&str]> for StringList { fn from(value: &[&str]) -> Self { Self::from_array(value.iter().map(|&v| v.to_owned()).collect()) } } impl From<[String; N]> for StringList { fn from(value: [String; N]) -> Self { Self::from_array(value[..].to_owned()) } } impl From<[&str; N]> for StringList { fn from(value: [&str; N]) -> Self { Self::from_array(value[..].iter().map(|&v| v.to_owned()).collect()) } } cargo-config2-0.1.29/src/env.rs000064400000000000000000000445131046102023000142710ustar 00000000000000// SPDX-License-Identifier: Apache-2.0 OR MIT // Environment variables are prefer over config values. // https://doc.rust-lang.org/nightly/cargo/reference/config.html#environment-variables use crate::{ de::{ BuildConfig, CargoNewConfig, Config, DocConfig, Flags, FutureIncompatReportConfig, HttpConfig, NetConfig, PathAndArgs, RegistriesConfigValue, RegistryConfig, StringList, StringOrArray, TermConfig, TermProgress, }, error::{Context as _, Error, Result}, resolve::ResolveContext, value::{Definition, Value}, }; pub(crate) trait ApplyEnv { /// Applies configuration environment variables. fn apply_env(&mut self, cx: &ResolveContext) -> Result<()>; } impl Config { /// Applies configuration environment variables. /// /// **Note:** This ignores environment variables for target-specific /// configurations ([`self.target`](Self::target). This is because it is /// difficult to determine exactly which target the target-specific /// configuration defined in the environment variables are for. /// (e.g., In environment variables, `-` and `.` in the target triple are replaced by `_`) #[doc(hidden)] // Not public API. pub fn apply_env(&mut self, cx: &ResolveContext) -> Result<()> { for (k, v) in &cx.env { let definition = || Some(Definition::Environment(k.clone().into())); let error_env_not_unicode = || Error::env_not_unicode(k, v.clone()); let error_env_not_unicode_redacted = || Error::env_not_unicode_redacted(k); // https://doc.rust-lang.org/nightly/cargo/reference/config.html#alias if let Some(k) = k.strip_prefix("CARGO_ALIAS_") { self.alias.insert( k.to_owned(), StringList::from_string( v.to_str().ok_or_else(error_env_not_unicode)?, definition().as_ref(), ), ); continue; } // https://doc.rust-lang.org/nightly/cargo/reference/config.html#registries else if let Some(k) = k.strip_prefix("CARGO_REGISTRIES_") { if let Some(k) = k.strip_suffix("_INDEX") { let v = v.to_str().ok_or_else(error_env_not_unicode)?; let index = Some(Value { val: v.to_owned(), definition: definition() }); if let Some(registries_config_value) = self.registries.get_mut(k) { registries_config_value.index = index; } else { self.registries.insert(k.to_owned(), RegistriesConfigValue { index, token: None, protocol: None, }); } continue; } else if let Some(k) = k.strip_suffix("_TOKEN") { let v = v.to_str().ok_or_else(error_env_not_unicode_redacted)?; let token = Some(Value { val: v.to_owned(), definition: definition() }); if let Some(registries_config_value) = self.registries.get_mut(k) { registries_config_value.token = token; } else { self.registries.insert(k.to_owned(), RegistriesConfigValue { index: None, token, protocol: None, }); } continue; } else if k == "CRATES_IO_PROTOCOL" { let k = "crates-io"; let v = v.to_str().ok_or_else(error_env_not_unicode)?; let protocol = Some(Value { val: v.to_owned(), definition: definition() }.parse()?); if let Some(registries_config_value) = self.registries.get_mut(k) { registries_config_value.protocol = protocol; } else { self.registries.insert(k.to_owned(), RegistriesConfigValue { index: None, token: None, protocol, }); } continue; } } } // For self.target, we handle it in de::Config::resolve_target. self.build.apply_env(cx)?; self.doc.apply_env(cx)?; self.future_incompat_report.apply_env(cx)?; self.http.apply_env(cx)?; self.net.apply_env(cx)?; self.registry.apply_env(cx)?; self.term.apply_env(cx)?; Ok(()) } } impl ApplyEnv for BuildConfig { fn apply_env(&mut self, cx: &ResolveContext) -> Result<()> { // https://doc.rust-lang.org/nightly/cargo/reference/config.html#buildjobs if let Some(jobs) = cx.env_parse("CARGO_BUILD_JOBS")? { self.jobs = Some(jobs); } // The following priorities are not documented, but at as of cargo // 1.63.0-nightly (2022-05-31), `RUSTC*` are preferred over `CARGO_BUILD_RUSTC*`. // 1. RUSTC // 2. build.rustc (CARGO_BUILD_RUSTC) // https://doc.rust-lang.org/nightly/cargo/reference/config.html#buildrustc // See also https://github.com/taiki-e/cargo-llvm-cov/pull/180#discussion_r887904341. if let Some(rustc) = cx.env("RUSTC")? { self.rustc = Some(rustc); } else if let Some(rustc) = cx.env("CARGO_BUILD_RUSTC")? { self.rustc = Some(rustc); } // 1. RUSTC_WRAPPER // 2. build.rustc-wrapper (CARGO_BUILD_RUSTC_WRAPPER) // https://doc.rust-lang.org/nightly/cargo/reference/config.html#buildrustc-wrapper // Setting this to an empty string instructs cargo to not use a wrapper. // https://doc.rust-lang.org/cargo/reference/environment-variables.html#environment-variables-cargo-reads if let Some(rustc_wrapper) = cx.env("RUSTC_WRAPPER")? { if rustc_wrapper.val.is_empty() { self.rustc_wrapper = None; } else { self.rustc_wrapper = Some(rustc_wrapper); } } else if let Some(rustc_wrapper) = cx.env("CARGO_BUILD_RUSTC_WRAPPER")? { if rustc_wrapper.val.is_empty() { self.rustc_wrapper = None; } else { self.rustc_wrapper = Some(rustc_wrapper); } } // 1. RUSTC_WORKSPACE_WRAPPER // 2. build.rustc-workspace-wrapper (CARGO_BUILD_RUSTC_WORKSPACE_WRAPPER) // https://doc.rust-lang.org/nightly/cargo/reference/config.html#buildrustc-workspace-wrapper // Setting this to an empty string instructs cargo to not use a wrapper. // https://doc.rust-lang.org/cargo/reference/environment-variables.html#environment-variables-cargo-reads if let Some(rustc_workspace_wrapper) = cx.env("RUSTC_WORKSPACE_WRAPPER")? { if rustc_workspace_wrapper.val.is_empty() { self.rustc_workspace_wrapper = None; } else { self.rustc_workspace_wrapper = Some(rustc_workspace_wrapper); } } else if let Some(rustc_workspace_wrapper) = cx.env("CARGO_BUILD_RUSTC_WORKSPACE_WRAPPER")? { if rustc_workspace_wrapper.val.is_empty() { self.rustc_workspace_wrapper = None; } else { self.rustc_workspace_wrapper = Some(rustc_workspace_wrapper); } } // 1. RUSTDOC // 2. build.rustdoc (CARGO_BUILD_RUSTDOC) // https://doc.rust-lang.org/nightly/cargo/reference/config.html#buildrustdoc if let Some(rustdoc) = cx.env("RUSTDOC")? { self.rustdoc = Some(rustdoc); } else if let Some(rustdoc) = cx.env("CARGO_BUILD_RUSTDOC")? { self.rustdoc = Some(rustdoc); } // https://doc.rust-lang.org/nightly/cargo/reference/config.html#buildtarget if let Some(target) = cx.env("CARGO_BUILD_TARGET")? { self.target = Some(StringOrArray::String(target)); } // The following priorities are not documented, but at as of cargo // 1.68.0-nightly (2022-12-23), `CARGO_*` are preferred over `CARGO_BUILD_*`. // 1. CARGO_TARGET_DIR // 2. CARGO_BUILD_TARGET_DIR // https://doc.rust-lang.org/nightly/cargo/reference/config.html#buildtarget if let Some(target_dir) = cx.env("CARGO_TARGET_DIR")? { self.target_dir = Some(target_dir); } else if let Some(target_dir) = cx.env("CARGO_BUILD_TARGET_DIR")? { self.target_dir = Some(target_dir); } // 1. CARGO_ENCODED_RUSTFLAGS // 2. RUSTFLAGS // 3. target..rustflags (CARGO_TARGET__RUSTFLAGS) and target..rustflags // 4. build.rustflags (CARGO_BUILD_RUSTFLAGS) // For 3, we handle it in de::Config::resolve_target. // https://doc.rust-lang.org/nightly/cargo/reference/config.html#buildrustflags self.override_target_rustflags = false; if let Some(rustflags) = cx.env("CARGO_ENCODED_RUSTFLAGS")? { self.rustflags = Some(Flags::from_encoded(&rustflags)); self.override_target_rustflags = true; } else if let Some(rustflags) = cx.env("RUSTFLAGS")? { self.rustflags = Some(Flags::from_space_separated(&rustflags.val, rustflags.definition.as_ref())); self.override_target_rustflags = true; } else if let Some(rustflags) = cx.env("CARGO_BUILD_RUSTFLAGS")? { self.rustflags = Some(Flags::from_space_separated(&rustflags.val, rustflags.definition.as_ref())); } // 1. CARGO_ENCODED_RUSTDOCFLAGS // 2. RUSTDOCFLAGS // 3. target..rustdocflags (CARGO_TARGET__RUSTDOCFLAGS) // 4. build.rustdocflags (CARGO_BUILD_RUSTDOCFLAGS) // For 3, we handle it in de::Config::resolve_target. // https://doc.rust-lang.org/nightly/cargo/reference/config.html#buildrustdocflags self.override_target_rustdocflags = false; if let Some(rustdocflags) = cx.env("CARGO_ENCODED_RUSTDOCFLAGS")? { self.rustdocflags = Some(Flags::from_encoded(&rustdocflags)); self.override_target_rustdocflags = true; } else if let Some(rustdocflags) = cx.env("RUSTDOCFLAGS")? { self.rustdocflags = Some(Flags::from_space_separated( &rustdocflags.val, rustdocflags.definition.as_ref(), )); self.override_target_rustdocflags = true; } else if let Some(rustdocflags) = cx.env("CARGO_BUILD_RUSTDOCFLAGS")? { self.rustdocflags = Some(Flags::from_space_separated( &rustdocflags.val, rustdocflags.definition.as_ref(), )); } // The following priorities are not documented, but at as of cargo // 1.68.0-nightly (2022-12-23), `CARGO_*` are preferred over `CARGO_BUILD_*`. // 1. CARGO_INCREMENTAL // 2. CARGO_BUILD_INCREMENTAL // https://doc.rust-lang.org/nightly/cargo/reference/config.html#buildincremental if let Some(incremental) = cx.env("CARGO_INCREMENTAL")? { // As of cargo 1.68.0-nightly (2022-12-23), cargo handles invalid value like 0. self.incremental = Some(Value { val: incremental.val == "1", definition: incremental.definition }); } else if let Some(incremental) = cx.env_parse("CARGO_BUILD_INCREMENTAL")? { self.incremental = Some(incremental); } // https://doc.rust-lang.org/nightly/cargo/reference/config.html#builddep-info-basedir if let Some(dep_info_basedir) = cx.env("CARGO_BUILD_DEP_INFO_BASEDIR")? { self.dep_info_basedir = Some(dep_info_basedir); } Ok(()) } } impl ApplyEnv for DocConfig { fn apply_env(&mut self, cx: &ResolveContext) -> Result<()> { // doc.browser config value is prefer over BROWSER environment variable. // https://github.com/rust-lang/cargo/blob/0.80.0/src/cargo/ops/cargo_doc.rs#L143-L144 if self.browser.is_none() { if let Some(browser) = cx.env("BROWSER")? { self.browser = Some( PathAndArgs::from_string(&browser.val, browser.definition) .context("invalid length 0, expected at least one element")?, ); } } Ok(()) } } impl ApplyEnv for FutureIncompatReportConfig { fn apply_env(&mut self, cx: &ResolveContext) -> Result<()> { // https://doc.rust-lang.org/nightly/cargo/reference/config.html#future-incompat-reportfrequency if let Some(frequency) = cx.env_parse("CARGO_FUTURE_INCOMPAT_REPORT_FREQUENCY")? { self.frequency = Some(frequency); } Ok(()) } } impl ApplyEnv for CargoNewConfig { fn apply_env(&mut self, cx: &ResolveContext) -> Result<()> { // https://doc.rust-lang.org/nightly/cargo/reference/config.html#cargo-newvcs if let Some(vcs) = cx.env_parse("CARGO_CARGO_NEW_VCS")? { self.vcs = Some(vcs); } Ok(()) } } impl ApplyEnv for HttpConfig { fn apply_env(&mut self, cx: &ResolveContext) -> Result<()> { // https://doc.rust-lang.org/nightly/cargo/reference/config.html#httpdebug if let Some(debug) = cx.env_parse("CARGO_HTTP_DEBUG")? { self.debug = Some(debug); } // https://doc.rust-lang.org/nightly/cargo/reference/config.html#httpproxy // TODO: // > CARGO_HTTP_PROXY or HTTPS_PROXY or https_proxy or http_proxy if let Some(proxy) = cx.env("CARGO_HTTP_PROXY")? { self.proxy = Some(proxy); } // https://doc.rust-lang.org/nightly/cargo/reference/config.html#httptimeout // TODO: // > CARGO_HTTP_TIMEOUT or HTTP_TIMEOUT if let Some(timeout) = cx.env_parse("CARGO_HTTP_TIMEOUT")? { self.timeout = Some(timeout); } // https://doc.rust-lang.org/nightly/cargo/reference/config.html#httpcainfo if let Some(cainfo) = cx.env("CARGO_HTTP_CAINFO")? { self.cainfo = Some(cainfo); } // https://doc.rust-lang.org/nightly/cargo/reference/config.html#httpcheck-revoke if let Some(check_revoke) = cx.env_parse("CARGO_HTTP_CHECK_REVOKE")? { self.check_revoke = Some(check_revoke); } // https://doc.rust-lang.org/nightly/cargo/reference/config.html#httplow-speed-limit if let Some(low_speed_limit) = cx.env_parse("CARGO_HTTP_LOW_SPEED_LIMIT")? { self.low_speed_limit = Some(low_speed_limit); } // https://doc.rust-lang.org/nightly/cargo/reference/config.html#httpmultiplexing if let Some(multiplexing) = cx.env_parse("CARGO_HTTP_MULTIPLEXING")? { self.multiplexing = Some(multiplexing); } // https://doc.rust-lang.org/nightly/cargo/reference/config.html#httpuser-agent if let Some(user_agent) = cx.env("CARGO_HTTP_USER_AGENT")? { self.user_agent = Some(user_agent); } Ok(()) } } impl ApplyEnv for NetConfig { fn apply_env(&mut self, cx: &ResolveContext) -> Result<()> { // https://doc.rust-lang.org/nightly/cargo/reference/config.html#netretry if let Some(retry) = cx.env_parse("CARGO_NET_RETRY")? { self.retry = Some(retry); } // https://doc.rust-lang.org/nightly/cargo/reference/config.html#netgit-fetch-with-cli if let Some(git_fetch_with_cli) = cx.env_parse("CARGO_NET_GIT_FETCH_WITH_CLI")? { self.git_fetch_with_cli = Some(git_fetch_with_cli); } // https://doc.rust-lang.org/nightly/cargo/reference/config.html#netoffline if let Some(offline) = cx.env_parse("CARGO_NET_OFFLINE")? { self.offline = Some(offline); } Ok(()) } } impl ApplyEnv for RegistryConfig { fn apply_env(&mut self, cx: &ResolveContext) -> Result<()> { // https://doc.rust-lang.org/nightly/cargo/reference/config.html#registrydefault if let Some(default) = cx.env("CARGO_REGISTRY_DEFAULT")? { self.default = Some(default); } // https://doc.rust-lang.org/nightly/cargo/reference/config.html#registrytoken if let Some(token) = cx.env_redacted("CARGO_REGISTRY_TOKEN")? { self.token = Some(token); } Ok(()) } } impl ApplyEnv for TermConfig { fn apply_env(&mut self, cx: &ResolveContext) -> Result<()> { // https://doc.rust-lang.org/nightly/cargo/reference/config.html#termquiet if let Some(quiet) = cx.env_parse("CARGO_TERM_QUIET")? { self.quiet = Some(quiet); } // https://doc.rust-lang.org/nightly/cargo/reference/config.html#termverbose if let Some(verbose) = cx.env_parse("CARGO_TERM_VERBOSE")? { self.verbose = Some(verbose); } // https://doc.rust-lang.org/nightly/cargo/reference/config.html#termcolor if let Some(color) = cx.env_parse("CARGO_TERM_COLOR")? { self.color = Some(color); } self.progress.apply_env(cx)?; Ok(()) } } impl ApplyEnv for TermProgress { fn apply_env(&mut self, cx: &ResolveContext) -> Result<()> { // https://doc.rust-lang.org/nightly/cargo/reference/config.html#termprogresswhen if let Some(when) = cx.env_parse("CARGO_TERM_PROGRESS_WHEN")? { self.when = Some(when); } // https://doc.rust-lang.org/nightly/cargo/reference/config.html#termprogresswidth if let Some(width) = cx.env_parse("CARGO_TERM_PROGRESS_WIDTH")? { self.width = Some(width); } Ok(()) } } #[cfg(test)] mod tests { use super::ApplyEnv; use crate::{value::Value, ResolveOptions}; #[test] fn empty_string_wrapper_envs() { let env_list = [("RUSTC_WRAPPER", ""), ("RUSTC_WORKSPACE_WRAPPER", "")]; let mut config = crate::de::BuildConfig::default(); let cx = &ResolveOptions::default().env(env_list).into_context(std::env::current_dir().unwrap()); config.rustc_wrapper = Some(Value { val: "rustc_wrapper".to_owned(), definition: None }); config.rustc_workspace_wrapper = Some(Value { val: "rustc_workspace_wrapper".to_owned(), definition: None }); config.apply_env(cx).unwrap(); assert!(config.rustc_wrapper.is_none()); assert!(config.rustc_workspace_wrapper.is_none()); } } cargo-config2-0.1.29/src/error.rs000064400000000000000000000131571046102023000146320ustar 00000000000000// SPDX-License-Identifier: Apache-2.0 OR MIT use std::{ffi::OsString, fmt, io}; macro_rules! format_err { ($($tt:tt)*) => { crate::Error::new(format!($($tt)*)) }; } macro_rules! bail { ($($tt:tt)*) => { return Err(format_err!($($tt)*)) }; } pub(crate) type Result = core::result::Result; /// An error that occurred during loading or resolving the Cargo configuration. #[derive(Debug)] pub struct Error(ErrorKind); // Hiding error variants from a library's public error type to prevent // dependency updates from becoming breaking changes. // We can add `is_*` methods that indicate the kind of error if needed, but // don't expose dependencies' types directly in the public API. #[derive(Debug)] pub(crate) enum ErrorKind { Io(io::Error), CfgExprParse(crate::cfg_expr::error::ParseError), Other(String), WithContext(String, Option>), } impl Error { pub(crate) fn new(e: impl Into) -> Self { Self(e.into()) } pub(crate) fn env_not_unicode(name: &str, var: OsString) -> Self { Self(ErrorKind::WithContext( format!("failed to parse environment variable `{name}`"), Some(Box::new(std::env::VarError::NotUnicode(var))), )) } pub(crate) fn env_not_unicode_redacted(name: &str) -> Self { Self(ErrorKind::WithContext( format!("failed to parse environment variable `{name}`"), Some("environment variable was not valid unicode: [REDACTED]".into()), )) } } impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match &self.0 { ErrorKind::Io(e) => fmt::Display::fmt(e, f), ErrorKind::CfgExprParse(e) => fmt::Display::fmt(e, f), ErrorKind::Other(e) | ErrorKind::WithContext(e, ..) => fmt::Display::fmt(e, f), } } } impl std::error::Error for Error { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { match &self.0 { ErrorKind::Io(e) => e.source(), ErrorKind::CfgExprParse(e) => e.source(), ErrorKind::Other(_) => None, ErrorKind::WithContext(_, e) => Some(&**e.as_ref()?), } } } // TODO: consider removing this in the next breaking release impl From for io::Error { fn from(e: Error) -> Self { match e.0 { ErrorKind::Io(e) => e, ErrorKind::CfgExprParse(e) => Self::new(io::ErrorKind::Other, e), ErrorKind::Other(e) | ErrorKind::WithContext(e, None) => { Self::new(io::ErrorKind::Other, e) } ErrorKind::WithContext(msg, Some(source)) => { let kind = if let Some(e) = source.downcast_ref::() { e.kind() } else if source.downcast_ref::().is_some() { io::ErrorKind::InvalidData } else { io::ErrorKind::Other }; Self::new(kind, Error(ErrorKind::WithContext(msg, Some(source)))) } } } } impl From for ErrorKind { fn from(s: String) -> Self { Self::Other(s) } } impl From for ErrorKind { fn from(e: crate::cfg_expr::error::ParseError) -> Self { Self::CfgExprParse(e) } } // Note: Do not implement From to prevent dependency // updates from becoming breaking changes. // Implementing `From` should also be avoided whenever possible, // as it would be a breaking change to remove the implementation if the // conversion is no longer needed due to changes in the internal implementation. // TODO: consider removing them in the next breaking release impl From for Error { fn from(e: io::Error) -> Self { Self(ErrorKind::Io(e)) } } // TODO: this is no longer used in our code; remove in the next breaking release impl From for Error { fn from(e: std::env::VarError) -> Self { Self(ErrorKind::Other(e.to_string())) } } // Inspired by anyhow::Context. pub(crate) trait Context { fn context(self, context: C) -> Result where C: fmt::Display; fn with_context(self, context: F) -> Result where C: fmt::Display, F: FnOnce() -> C; } impl Context for Result where E: std::error::Error + Send + Sync + 'static, { fn context(self, context: C) -> Result where C: fmt::Display, { match self { Ok(ok) => Ok(ok), Err(e) => Err(Error(ErrorKind::WithContext(context.to_string(), Some(Box::new(e))))), } } fn with_context(self, context: F) -> Result where C: fmt::Display, F: FnOnce() -> C, { match self { Ok(ok) => Ok(ok), Err(e) => Err(Error(ErrorKind::WithContext(context().to_string(), Some(Box::new(e))))), } } } impl Context for Option { fn context(self, context: C) -> Result where C: fmt::Display, { match self { Some(ok) => Ok(ok), None => Err(Error(ErrorKind::WithContext(context.to_string(), None))), } } fn with_context(self, context: F) -> Result where C: fmt::Display, F: FnOnce() -> C, { match self { Some(ok) => Ok(ok), None => Err(Error(ErrorKind::WithContext(context().to_string(), None))), } } } cargo-config2-0.1.29/src/gen/assert_impl.rs000064400000000000000000000345401046102023000165730ustar 00000000000000// SPDX-License-Identifier: Apache-2.0 OR MIT // This file is @generated by cargo-config2-internal-codegen // (gen_assert_impl function at tools/codegen/src/main.rs). // It is not intended for manual editing. #![cfg_attr(rustfmt, rustfmt::skip)] #![allow( dead_code, unused_macros, clippy::std_instead_of_alloc, clippy::std_instead_of_core, )] fn assert_send() {} fn assert_sync() {} fn assert_unpin() {} fn assert_unwind_safe() {} fn assert_ref_unwind_safe() {} /// `Send` & `!Sync` struct NotSync(core::cell::UnsafeCell<()>); /// `!Send` & `Sync` struct NotSend(std::sync::MutexGuard<'static, ()>); /// `!Send` & `!Sync` struct NotSendSync(*const ()); /// `!Unpin` struct NotUnpin(core::marker::PhantomPinned); /// `!UnwindSafe` struct NotUnwindSafe(&'static mut ()); /// `!RefUnwindSafe` struct NotRefUnwindSafe(core::cell::UnsafeCell<()>); macro_rules! assert_not_send { ($ty:ty) => { static_assertions::assert_not_impl_all!($ty : Send); }; } macro_rules! assert_not_sync { ($ty:ty) => { static_assertions::assert_not_impl_all!($ty : Sync); }; } macro_rules! assert_not_unpin { ($ty:ty) => { static_assertions::assert_not_impl_all!($ty : Unpin); }; } macro_rules! assert_not_unwind_safe { ($ty:ty) => { static_assertions::assert_not_impl_all!($ty : std::panic::UnwindSafe); }; } macro_rules! assert_not_ref_unwind_safe { ($ty:ty) => { static_assertions::assert_not_impl_all!($ty : std::panic::RefUnwindSafe); }; } const _: fn() = || { assert_send::(); assert_sync::(); assert_unpin::(); assert_unwind_safe::(); assert_ref_unwind_safe::(); assert_send::(); assert_sync::(); assert_unpin::(); assert_unwind_safe::(); assert_ref_unwind_safe::(); assert_send::(); assert_sync::(); assert_unpin::(); assert_unwind_safe::(); assert_ref_unwind_safe::(); assert_send::(); assert_sync::(); assert_unpin::(); assert_unwind_safe::(); assert_ref_unwind_safe::(); assert_send::(); assert_sync::(); assert_unpin::(); assert_unwind_safe::(); assert_ref_unwind_safe::(); assert_send::(); assert_sync::(); assert_unpin::(); assert_unwind_safe::(); assert_ref_unwind_safe::(); assert_send::(); assert_sync::(); assert_unpin::(); assert_unwind_safe::(); assert_ref_unwind_safe::(); assert_send::(); assert_sync::(); assert_unpin::(); assert_unwind_safe::(); assert_ref_unwind_safe::(); assert_send::(); assert_sync::(); assert_unpin::(); assert_unwind_safe::(); assert_ref_unwind_safe::(); assert_send::(); assert_sync::(); assert_unpin::(); assert_unwind_safe::(); assert_ref_unwind_safe::(); assert_send::(); assert_sync::(); assert_unpin::(); assert_unwind_safe::(); assert_ref_unwind_safe::(); assert_send::(); assert_sync::(); assert_unpin::(); assert_unwind_safe::(); assert_ref_unwind_safe::(); assert_send::(); assert_sync::(); assert_unpin::(); assert_unwind_safe::(); assert_ref_unwind_safe::(); assert_send::(); assert_sync::(); assert_unpin::(); assert_unwind_safe::(); assert_ref_unwind_safe::(); assert_send::(); assert_sync::(); assert_unpin::(); assert_unwind_safe::(); assert_ref_unwind_safe::(); assert_send::(); assert_sync::(); assert_unpin::(); assert_unwind_safe::(); assert_ref_unwind_safe::(); assert_send::(); assert_sync::(); assert_unpin::(); assert_unwind_safe::(); assert_ref_unwind_safe::(); assert_send::(); assert_sync::(); assert_unpin::(); assert_unwind_safe::(); assert_ref_unwind_safe::(); assert_send::(); assert_sync::(); assert_unpin::(); assert_unwind_safe::(); assert_ref_unwind_safe::(); assert_send::(); assert_sync::(); assert_unpin::(); assert_unwind_safe::(); assert_ref_unwind_safe::(); assert_send::(); assert_sync::(); assert_unpin::(); assert_unwind_safe::(); assert_ref_unwind_safe::(); assert_send::(); assert_sync::(); assert_unpin::(); assert_unwind_safe::(); assert_ref_unwind_safe::(); assert_send::(); assert_sync::(); assert_unpin::(); assert_unwind_safe::(); assert_ref_unwind_safe::(); assert_send::(); assert_not_sync!(crate::easy::Config); assert_unpin::(); assert_unwind_safe::(); assert_not_ref_unwind_safe!(crate::easy::Config); assert_send::(); assert_sync::(); assert_unpin::(); assert_unwind_safe::(); assert_ref_unwind_safe::(); assert_send::(); assert_sync::(); assert_unpin::(); assert_unwind_safe::(); assert_ref_unwind_safe::(); assert_send::(); assert_sync::(); assert_unpin::(); assert_unwind_safe::(); assert_ref_unwind_safe::(); assert_send::(); assert_sync::(); assert_unpin::(); assert_unwind_safe::(); assert_ref_unwind_safe::(); assert_send::(); assert_sync::(); assert_unpin::(); assert_unwind_safe::(); assert_ref_unwind_safe::(); assert_send::(); assert_sync::(); assert_unpin::(); assert_unwind_safe::(); assert_ref_unwind_safe::(); assert_send::(); assert_sync::(); assert_unpin::(); assert_unwind_safe::(); assert_ref_unwind_safe::(); assert_send::(); assert_sync::(); assert_unpin::(); assert_unwind_safe::(); assert_ref_unwind_safe::(); assert_send::(); assert_sync::(); assert_unpin::(); assert_unwind_safe::(); assert_ref_unwind_safe::(); assert_send::(); assert_sync::(); assert_unpin::(); assert_unwind_safe::(); assert_ref_unwind_safe::(); assert_send::(); assert_sync::(); assert_unpin::(); assert_unwind_safe::(); assert_ref_unwind_safe::(); assert_send::(); assert_sync::(); assert_unpin::(); assert_unwind_safe::(); assert_ref_unwind_safe::(); assert_send::(); assert_sync::(); assert_unpin::(); assert_unwind_safe::(); assert_ref_unwind_safe::(); assert_send::(); assert_sync::(); assert_unpin::(); assert_unwind_safe::(); assert_ref_unwind_safe::(); assert_send::(); assert_sync::(); assert_unpin::(); assert_unwind_safe::(); assert_ref_unwind_safe::(); assert_send::(); assert_sync::(); assert_unpin::(); assert_not_unwind_safe!(crate::error::Error); assert_not_ref_unwind_safe!(crate::error::Error); assert_send::(); assert_sync::(); assert_unpin::(); assert_unwind_safe::(); assert_ref_unwind_safe::(); assert_send::(); assert_not_sync!(crate::resolve::ResolveContext); assert_unpin::(); assert_unwind_safe::(); assert_not_ref_unwind_safe!(crate::resolve::ResolveContext); assert_send::>(); assert_sync::>(); assert_unpin::>(); assert_unwind_safe::>(); assert_ref_unwind_safe::>(); assert_send::(); assert_sync::(); assert_unpin::(); assert_unwind_safe::(); assert_ref_unwind_safe::(); assert_send::(); assert_sync::(); assert_unpin::(); assert_unwind_safe::(); assert_ref_unwind_safe::(); assert_send::(); assert_sync::(); assert_unpin::(); assert_unwind_safe::(); assert_ref_unwind_safe::(); assert_send::>(); assert_send::>(); assert_not_send!(crate::value::Value); assert_sync::>(); assert_sync::>(); assert_not_sync!(crate::value::Value); assert_unpin::>(); assert_not_unpin!(crate::value::Value); assert_unwind_safe::>(); assert_not_unwind_safe!(crate::value::Value); assert_ref_unwind_safe::>(); assert_not_ref_unwind_safe!(crate::value::Value); assert_send::(); assert_sync::(); assert_unpin::(); assert_unwind_safe::(); assert_ref_unwind_safe::(); assert_send::>(); assert_sync::>(); assert_unpin::>(); assert_unwind_safe::>(); assert_ref_unwind_safe::>(); }; cargo-config2-0.1.29/src/gen/de.rs000064400000000000000000000212001046102023000146260ustar 00000000000000// SPDX-License-Identifier: Apache-2.0 OR MIT // This file is @generated by cargo-config2-internal-codegen // (gen_de function at tools/codegen/src/main.rs). // It is not intended for manual editing. #![cfg_attr(rustfmt, rustfmt::skip)] use std::path::Path; use crate::{error::Result, merge::Merge, value::SetPath}; impl Merge for crate::de::Config { fn merge(&mut self, low: Self, force: bool) -> Result<()> { self.alias.merge(low.alias, force)?; self.build.merge(low.build, force)?; self.doc.merge(low.doc, force)?; self.env.merge(low.env, force)?; self.future_incompat_report.merge(low.future_incompat_report, force)?; self.cargo_new.merge(low.cargo_new, force)?; self.http.merge(low.http, force)?; self.net.merge(low.net, force)?; self.registries.merge(low.registries, force)?; self.registry.merge(low.registry, force)?; self.target.merge(low.target, force)?; self.term.merge(low.term, force)?; Ok(()) } } impl SetPath for crate::de::Config { fn set_path(&mut self, path: &Path) { self.alias.set_path(path); self.build.set_path(path); self.doc.set_path(path); self.env.set_path(path); self.future_incompat_report.set_path(path); self.cargo_new.set_path(path); self.http.set_path(path); self.net.set_path(path); self.registries.set_path(path); self.registry.set_path(path); self.target.set_path(path); self.term.set_path(path); } } impl Merge for crate::de::BuildConfig { fn merge(&mut self, low: Self, force: bool) -> Result<()> { self.jobs.merge(low.jobs, force)?; self.rustc.merge(low.rustc, force)?; self.rustc_wrapper.merge(low.rustc_wrapper, force)?; self.rustc_workspace_wrapper.merge(low.rustc_workspace_wrapper, force)?; self.rustdoc.merge(low.rustdoc, force)?; self.target.merge(low.target, force)?; self.target_dir.merge(low.target_dir, force)?; self.rustflags.merge(low.rustflags, force)?; self.rustdocflags.merge(low.rustdocflags, force)?; self.incremental.merge(low.incremental, force)?; self.dep_info_basedir.merge(low.dep_info_basedir, force)?; Ok(()) } } impl SetPath for crate::de::BuildConfig { fn set_path(&mut self, path: &Path) { self.jobs.set_path(path); self.rustc.set_path(path); self.rustc_wrapper.set_path(path); self.rustc_workspace_wrapper.set_path(path); self.rustdoc.set_path(path); self.target.set_path(path); self.target_dir.set_path(path); self.rustflags.set_path(path); self.rustdocflags.set_path(path); self.incremental.set_path(path); self.dep_info_basedir.set_path(path); } } impl Merge for crate::de::TargetConfig { fn merge(&mut self, low: Self, force: bool) -> Result<()> { self.linker.merge(low.linker, force)?; self.runner.merge(low.runner, force)?; self.rustflags.merge(low.rustflags, force)?; self.rustdocflags.merge(low.rustdocflags, force)?; Ok(()) } } impl SetPath for crate::de::TargetConfig { fn set_path(&mut self, path: &Path) { self.linker.set_path(path); self.runner.set_path(path); self.rustflags.set_path(path); self.rustdocflags.set_path(path); } } impl Merge for crate::de::DocConfig { fn merge(&mut self, low: Self, force: bool) -> Result<()> { self.browser.merge(low.browser, force)?; Ok(()) } } impl SetPath for crate::de::DocConfig { fn set_path(&mut self, path: &Path) { self.browser.set_path(path); } } impl SetPath for crate::de::EnvConfigValue { fn set_path(&mut self, path: &Path) { match self { Self::Value(v) => { v.set_path(path); } Self::Table { value, force, relative } => { value.set_path(path); force.set_path(path); relative.set_path(path); } } } } impl Merge for crate::de::FutureIncompatReportConfig { fn merge(&mut self, low: Self, force: bool) -> Result<()> { self.frequency.merge(low.frequency, force)?; Ok(()) } } impl SetPath for crate::de::FutureIncompatReportConfig { fn set_path(&mut self, path: &Path) { self.frequency.set_path(path); } } impl Merge for crate::de::CargoNewConfig { fn merge(&mut self, low: Self, force: bool) -> Result<()> { self.vcs.merge(low.vcs, force)?; Ok(()) } } impl SetPath for crate::de::CargoNewConfig { fn set_path(&mut self, path: &Path) { self.vcs.set_path(path); } } impl Merge for crate::de::HttpConfig { fn merge(&mut self, low: Self, force: bool) -> Result<()> { self.debug.merge(low.debug, force)?; self.proxy.merge(low.proxy, force)?; self.timeout.merge(low.timeout, force)?; self.cainfo.merge(low.cainfo, force)?; self.check_revoke.merge(low.check_revoke, force)?; self.low_speed_limit.merge(low.low_speed_limit, force)?; self.multiplexing.merge(low.multiplexing, force)?; self.user_agent.merge(low.user_agent, force)?; Ok(()) } } impl SetPath for crate::de::HttpConfig { fn set_path(&mut self, path: &Path) { self.debug.set_path(path); self.proxy.set_path(path); self.timeout.set_path(path); self.cainfo.set_path(path); self.check_revoke.set_path(path); self.low_speed_limit.set_path(path); self.multiplexing.set_path(path); self.user_agent.set_path(path); } } impl Merge for crate::de::NetConfig { fn merge(&mut self, low: Self, force: bool) -> Result<()> { self.retry.merge(low.retry, force)?; self.git_fetch_with_cli.merge(low.git_fetch_with_cli, force)?; self.offline.merge(low.offline, force)?; Ok(()) } } impl SetPath for crate::de::NetConfig { fn set_path(&mut self, path: &Path) { self.retry.set_path(path); self.git_fetch_with_cli.set_path(path); self.offline.set_path(path); } } impl Merge for crate::de::RegistriesConfigValue { fn merge(&mut self, low: Self, force: bool) -> Result<()> { self.index.merge(low.index, force)?; self.token.merge(low.token, force)?; self.protocol.merge(low.protocol, force)?; Ok(()) } } impl SetPath for crate::de::RegistriesConfigValue { fn set_path(&mut self, path: &Path) { self.index.set_path(path); self.token.set_path(path); self.protocol.set_path(path); } } impl Merge for crate::de::RegistryConfig { fn merge(&mut self, low: Self, force: bool) -> Result<()> { self.default.merge(low.default, force)?; self.token.merge(low.token, force)?; Ok(()) } } impl SetPath for crate::de::RegistryConfig { fn set_path(&mut self, path: &Path) { self.default.set_path(path); self.token.set_path(path); } } impl Merge for crate::de::TermConfig { fn merge(&mut self, low: Self, force: bool) -> Result<()> { self.quiet.merge(low.quiet, force)?; self.verbose.merge(low.verbose, force)?; self.color.merge(low.color, force)?; self.progress.merge(low.progress, force)?; Ok(()) } } impl SetPath for crate::de::TermConfig { fn set_path(&mut self, path: &Path) { self.quiet.set_path(path); self.verbose.set_path(path); self.color.set_path(path); self.progress.set_path(path); } } impl Merge for crate::de::TermProgress { fn merge(&mut self, low: Self, force: bool) -> Result<()> { self.when.merge(low.when, force)?; self.width.merge(low.width, force)?; Ok(()) } } impl SetPath for crate::de::TermProgress { fn set_path(&mut self, path: &Path) { self.when.set_path(path); self.width.set_path(path); } } impl SetPath for crate::de::Flags { fn set_path(&mut self, path: &Path) { self.flags.set_path(path); } } impl SetPath for crate::de::ConfigRelativePath { fn set_path(&mut self, path: &Path) { self.0.set_path(path); } } impl SetPath for crate::de::PathAndArgs { fn set_path(&mut self, path: &Path) { self.path.set_path(path); self.args.set_path(path); } } impl SetPath for crate::de::StringList { fn set_path(&mut self, path: &Path) { self.list.set_path(path); } } impl SetPath for crate::de::StringOrArray { fn set_path(&mut self, path: &Path) { match self { Self::String(v) => { v.set_path(path); } Self::Array(v) => { v.set_path(path); } } } } cargo-config2-0.1.29/src/gen/is_none.rs000064400000000000000000000074531046102023000157060ustar 00000000000000// SPDX-License-Identifier: Apache-2.0 OR MIT // This file is @generated by cargo-config2-internal-codegen // (gen_is_none function at tools/codegen/src/main.rs). // It is not intended for manual editing. #![cfg_attr(rustfmt, rustfmt::skip)] impl crate::easy::BuildConfig { pub(crate) fn is_none(&self) -> bool { self.jobs.is_none() && self.rustc.is_none() && self.rustc_wrapper.is_none() && self.rustc_workspace_wrapper.is_none() && self.rustdoc.is_none() && self.target.is_none() && self.target_dir.is_none() && self.rustflags.is_none() && self.rustdocflags.is_none() && self.incremental.is_none() && self.dep_info_basedir.is_none() } } impl crate::easy::DocConfig { pub(crate) fn is_none(&self) -> bool { self.browser.is_none() } } impl crate::easy::FutureIncompatReportConfig { pub(crate) fn is_none(&self) -> bool { self.frequency.is_none() } } impl crate::easy::CargoNewConfig { pub(crate) fn is_none(&self) -> bool { self.vcs.is_none() } } impl crate::easy::HttpConfig { pub(crate) fn is_none(&self) -> bool { self.debug.is_none() && self.proxy.is_none() && self.timeout.is_none() && self.cainfo.is_none() && self.check_revoke.is_none() && self.low_speed_limit.is_none() && self.multiplexing.is_none() && self.user_agent.is_none() } } impl crate::easy::NetConfig { pub(crate) fn is_none(&self) -> bool { self.retry.is_none() && self.git_fetch_with_cli.is_none() && self.offline.is_none() } } impl crate::easy::RegistryConfig { pub(crate) fn is_none(&self) -> bool { self.default.is_none() && self.token.is_none() } } impl crate::easy::TermConfig { pub(crate) fn is_none(&self) -> bool { self.quiet.is_none() && self.verbose.is_none() && self.color.is_none() && self.progress.is_none() } } impl crate::easy::TermProgressConfig { pub(crate) fn is_none(&self) -> bool { self.when.is_none() && self.width.is_none() } } impl crate::de::BuildConfig { pub(crate) fn is_none(&self) -> bool { self.jobs.is_none() && self.rustc.is_none() && self.rustc_wrapper.is_none() && self.rustc_workspace_wrapper.is_none() && self.rustdoc.is_none() && self.target.is_none() && self.target_dir.is_none() && self.rustflags.is_none() && self.rustdocflags.is_none() && self.incremental.is_none() && self.dep_info_basedir.is_none() } } impl crate::de::DocConfig { pub(crate) fn is_none(&self) -> bool { self.browser.is_none() } } impl crate::de::FutureIncompatReportConfig { pub(crate) fn is_none(&self) -> bool { self.frequency.is_none() } } impl crate::de::CargoNewConfig { pub(crate) fn is_none(&self) -> bool { self.vcs.is_none() } } impl crate::de::HttpConfig { pub(crate) fn is_none(&self) -> bool { self.debug.is_none() && self.proxy.is_none() && self.timeout.is_none() && self.cainfo.is_none() && self.check_revoke.is_none() && self.low_speed_limit.is_none() && self.multiplexing.is_none() && self.user_agent.is_none() } } impl crate::de::NetConfig { pub(crate) fn is_none(&self) -> bool { self.retry.is_none() && self.git_fetch_with_cli.is_none() && self.offline.is_none() } } impl crate::de::RegistryConfig { pub(crate) fn is_none(&self) -> bool { self.default.is_none() && self.token.is_none() } } impl crate::de::TermConfig { pub(crate) fn is_none(&self) -> bool { self.quiet.is_none() && self.verbose.is_none() && self.color.is_none() && self.progress.is_none() } } impl crate::de::TermProgress { pub(crate) fn is_none(&self) -> bool { self.when.is_none() && self.width.is_none() } } cargo-config2-0.1.29/src/lib.rs000064400000000000000000000056661046102023000142550ustar 00000000000000// SPDX-License-Identifier: Apache-2.0 OR MIT /*! Load and resolve [Cargo configuration](https://doc.rust-lang.org/nightly/cargo/reference/config.html). This library is intended to accurately emulate the actual behavior of Cargo configuration, for example, this supports the following behaviors: - [Hierarchical structure and merge](https://doc.rust-lang.org/nightly/cargo/reference/config.html#hierarchical-structure) - [Environment variables](https://doc.rust-lang.org/nightly/cargo/reference/config.html#environment-variables) and [relative paths](https://doc.rust-lang.org/nightly/cargo/reference/config.html#config-relative-paths) resolution. - `target.` and `target.` resolution. Supported tables and fields are mainly based on [cargo-llvm-cov](https://github.com/taiki-e/cargo-llvm-cov)'s use cases, but feel free to submit an issue if you see something missing in your use case. ## Examples ``` # fn main() -> anyhow::Result<()> { // Read config files hierarchically from the current directory, merge them, // apply environment variables, and resolve relative paths. let config = cargo_config2::Config::load()?; let target = "x86_64-unknown-linux-gnu"; // Resolve target-specific configuration (`target.` and `target.`), // and returns the resolved rustflags for `target`. let rustflags = config.rustflags(target)?; println!("{rustflags:?}"); # Ok(()) } ``` See also the [`get` example](https://github.com/taiki-e/cargo-config2/blob/HEAD/examples/get.rs) that partial re-implementation of `cargo config get` using cargo-config2. */ #![doc(test( no_crate_inject, attr( deny(warnings, rust_2018_idioms, single_use_lifetimes), allow(dead_code, unused_variables) ) ))] #![forbid(unsafe_code)] #![warn( // Lints that may help when writing public library. missing_debug_implementations, // missing_docs, clippy::alloc_instead_of_core, clippy::exhaustive_enums, clippy::exhaustive_structs, clippy::impl_trait_in_params, // clippy::missing_inline_in_public_items, // clippy::std_instead_of_alloc, // clippy::std_instead_of_core, )] #![allow(clippy::must_use_candidate)] // Refs: // - https://doc.rust-lang.org/nightly/cargo/reference/config.html #[cfg(test)] #[path = "gen/assert_impl.rs"] mod assert_impl; #[path = "gen/is_none.rs"] mod is_none_impl; #[macro_use] mod error; #[macro_use] mod process; mod cfg_expr; pub mod de; mod easy; mod env; mod merge; mod resolve; mod value; mod walk; #[doc(no_inline)] pub use crate::de::{Color, Frequency, RegistriesProtocol, VersionControlSoftware, When}; pub use crate::{ easy::{ BuildConfig, Config, DocConfig, EnvConfigValue, Flags, FutureIncompatReportConfig, NetConfig, PathAndArgs, RegistriesConfigValue, RegistryConfig, StringList, TargetConfig, TermConfig, TermProgressConfig, }, error::Error, resolve::{CargoVersion, ResolveOptions, RustcVersion, TargetTriple, TargetTripleRef}, walk::Walk, }; cargo-config2-0.1.29/src/merge.rs000064400000000000000000000166621046102023000146040ustar 00000000000000// SPDX-License-Identifier: Apache-2.0 OR MIT use std::collections::{btree_map, BTreeMap}; use crate::{ de::{self, RegistriesProtocol, VersionControlSoftware}, error::{Context as _, Result}, value::Value, Color, Frequency, When, }; // https://github.com/rust-lang/cargo/blob/0.74.0/src/cargo/util/config/mod.rs#L2107-L2115 // TODO: // - https://github.com/rust-lang/cargo/pull/12515 // - https://github.com/rust-lang/cargo/blob/0.80.0/src/cargo/util/context/mod.rs#L2159-L2167 // // > If `force` is true, primitive (non-container) types will override existing values. // > If false, the original will be kept and the new value ignored. // > // > Container types (tables and arrays) are merged with existing values. // > // > Container and non-container types cannot be mixed. pub(crate) trait Merge { /// Merges given config into this config. fn merge(&mut self, low: Self, force: bool) -> Result<()>; } macro_rules! merge_non_container { ($($ty:tt)*) => { impl Merge for $($ty)* { fn merge(&mut self, low: Self, force: bool) -> Result<()> { if force { *self = low; } Ok(()) } } impl Merge for Value<$($ty)*> { fn merge(&mut self, low: Self, force: bool) -> Result<()> { if force { *self = low; } Ok(()) } } }; } merge_non_container!(bool); merge_non_container!(i32); merge_non_container!(u32); merge_non_container!(String); merge_non_container!(Color); merge_non_container!(VersionControlSoftware); merge_non_container!(Frequency); merge_non_container!(When); merge_non_container!(RegistriesProtocol); impl Merge for Option { fn merge(&mut self, low: Self, force: bool) -> Result<()> { match (self, low) { (_, None) => {} (this @ None, low) => *this = low, (Some(this), Some(low)) => this.merge(low, force)?, } Ok(()) } } impl Merge for de::StringOrArray { fn merge(&mut self, low: Self, force: bool) -> Result<()> { match (self, low) { (this @ de::StringOrArray::String(_), low @ de::StringOrArray::String(_)) => { if force { *this = low; } } (de::StringOrArray::Array(this), de::StringOrArray::Array(mut low)) => { // https://doc.rust-lang.org/nightly/cargo/reference/config.html#hierarchical-structure // > Arrays will be joined together with higher precedence items being placed later in the merged array. low.append(this); *this = low; } (expected, actual) => { bail!("expected {}, but found {}", expected.kind(), actual.kind()); } } Ok(()) } } impl Merge for de::PathAndArgs { fn merge(&mut self, mut low: Self, force: bool) -> Result<()> { match (self.deserialized_repr, low.deserialized_repr) { (de::StringListDeserializedRepr::String, de::StringListDeserializedRepr::String) => { if force { *self = low; } } (de::StringListDeserializedRepr::Array, de::StringListDeserializedRepr::Array) => { // This is a bit non-intuitive, but e.g., "echo a /index.html" // is called in the following case because they are arrays. // // # a/b/.cargo/config // [doc] // browser = ["echo"] // // # a/.cargo/config // [doc] // browser = ["a"] self.args.push(low.path.0); self.args.append(&mut low.args); } (expected, actual) => { bail!("expected {}, but found {}", expected.as_str(), actual.as_str()); } } Ok(()) } } impl Merge for de::StringList { fn merge(&mut self, mut low: Self, force: bool) -> Result<()> { match (self.deserialized_repr, low.deserialized_repr) { (de::StringListDeserializedRepr::String, de::StringListDeserializedRepr::String) => { if force { *self = low; } } (de::StringListDeserializedRepr::Array, de::StringListDeserializedRepr::Array) => { // https://doc.rust-lang.org/nightly/cargo/reference/config.html#hierarchical-structure // > Arrays will be joined together with higher precedence items being placed later in the merged array. low.list.append(&mut self.list); self.list = low.list; } (expected, actual) => { bail!("expected {}, but found {}", expected.as_str(), actual.as_str()); } } Ok(()) } } impl Merge for de::EnvConfigValue { fn merge(&mut self, low: Self, force: bool) -> Result<()> { match (self, low) { (Self::Value(this), Self::Value(low)) => { if force { *this = low; } } ( Self::Table { value: this_value, force: this_force, relative: this_relative }, Self::Table { value: low_value, force: low_force, relative: low_relative }, ) => { this_value.merge(low_value, force)?; this_force.merge(low_force, force)?; this_relative.merge(low_relative, force)?; } (expected, actual) => { bail!("expected {}, but found {}", expected.kind(), actual.kind()); } } Ok(()) } } impl Merge for de::Flags { fn merge(&mut self, mut low: Self, force: bool) -> Result<()> { match (self.deserialized_repr, low.deserialized_repr) { (de::StringListDeserializedRepr::String, de::StringListDeserializedRepr::String) => { if force { *self = low; } } (de::StringListDeserializedRepr::Array, de::StringListDeserializedRepr::Array) => { // https://doc.rust-lang.org/nightly/cargo/reference/config.html#hierarchical-structure // > Arrays will be joined together with higher precedence items being placed later in the merged array. low.flags.append(&mut self.flags); self.flags = low.flags; } (expected, actual) => { bail!("expected {}, but found {}", expected.as_str(), actual.as_str()); } } Ok(()) } } impl Merge for BTreeMap { fn merge(&mut self, low: Self, force: bool) -> Result<()> { for (key, value) in low { match self.entry(key.clone()) { btree_map::Entry::Occupied(mut entry) => { let entry = entry.get_mut(); entry.merge(value.clone(), force).with_context(|| { format!( "failed to merge key `{key}` between \ {entry:?} and {value:?}", /* TODO: do not use debug output */ ) })?; } btree_map::Entry::Vacant(entry) => { entry.insert(value); } }; } Ok(()) } } cargo-config2-0.1.29/src/process.rs000064400000000000000000000073671046102023000151650ustar 00000000000000// SPDX-License-Identifier: Apache-2.0 OR MIT use std::{ ffi::OsStr, fmt, process::{Command, ExitStatus, Output}, str, }; use crate::{ error::{Context as _, Result}, Error, }; macro_rules! cmd { ($program:expr $(, $arg:expr)* $(,)?) => {{ let mut _cmd = std::process::Command::new($program); $( _cmd.arg($arg); )* $crate::process::ProcessBuilder::from_std(_cmd) }}; } // A builder for an external process, inspired by https://github.com/rust-lang/cargo/blob/0.47.0/src/cargo/util/process_builder.rs #[must_use] pub(crate) struct ProcessBuilder { cmd: Command, } impl ProcessBuilder { pub(crate) fn from_std(cmd: Command) -> Self { Self { cmd } } /// Adds an argument to pass to the program. pub(crate) fn arg(&mut self, arg: impl AsRef) -> &mut Self { self.cmd.arg(arg.as_ref()); self } /// Adds multiple arguments to pass to the program. pub(crate) fn args(&mut self, args: impl IntoIterator>) -> &mut Self { self.cmd.args(args); self } /// Executes a process, captures its stdio output, returning the captured /// output, or an error if non-zero exit status. pub(crate) fn run_with_output(&mut self) -> Result { let output = self.cmd.output().with_context(|| { process_error(format!("could not execute process {self}"), None, None) })?; if output.status.success() { Ok(output) } else { Err(process_error( format!("process didn't exit successfully: {self}"), Some(output.status), Some(&output), )) } } /// Executes a process, captures its stdio output, returning the captured /// standard output as a `String`. pub(crate) fn read(&mut self) -> Result { let mut output = String::from_utf8(self.run_with_output()?.stdout) .with_context(|| format!("failed to parse output from {self}"))?; while output.ends_with('\n') || output.ends_with('\r') { output.pop(); } Ok(output) } } // Based on https://github.com/rust-lang/cargo/blob/0.47.0/src/cargo/util/process_builder.rs impl fmt::Display for ProcessBuilder { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { if !f.alternate() { f.write_str("`")?; } f.write_str(&self.cmd.get_program().to_string_lossy())?; for arg in self.cmd.get_args() { write!(f, " {}", arg.to_string_lossy())?; } if !f.alternate() { f.write_str("`")?; } Ok(()) } } // Based on https://github.com/rust-lang/cargo/blob/0.47.0/src/cargo/util/errors.rs /// Creates a new process error. /// /// `status` can be `None` if the process did not launch. /// `output` can be `None` if the process did not launch, or output was not captured. fn process_error(mut msg: String, status: Option, output: Option<&Output>) -> Error { match status { Some(s) => { msg.push_str(" ("); msg.push_str(&s.to_string()); msg.push(')'); } None => msg.push_str(" (never executed)"), } if let Some(out) = output { match str::from_utf8(&out.stdout) { Ok(s) if !s.trim().is_empty() => { msg.push_str("\n--- stdout\n"); msg.push_str(s); } Ok(_) | Err(_) => {} } match str::from_utf8(&out.stderr) { Ok(s) if !s.trim().is_empty() => { msg.push_str("\n--- stderr\n"); msg.push_str(s); } Ok(_) | Err(_) => {} } } Error::new(msg) } cargo-config2-0.1.29/src/resolve.rs000064400000000000000000001026411046102023000151550ustar 00000000000000// SPDX-License-Identifier: Apache-2.0 OR MIT use core::{ cell::{OnceCell, RefCell}, cmp, hash::Hash, iter, str::FromStr, }; use std::{ borrow::Cow, collections::{HashMap, HashSet}, ffi::{OsStr, OsString}, path::{Path, PathBuf}, }; use serde::{ de::{Deserialize, Deserializer}, ser::{Serialize, Serializer}, }; use serde_derive::{Deserialize, Serialize}; use crate::{ cfg_expr::expr::{Expression, Predicate}, easy, error::{Context as _, Error, Result}, process::ProcessBuilder, value::{Definition, Value}, walk, PathAndArgs, }; #[derive(Debug, Clone, Default)] #[must_use] pub struct ResolveOptions { env: Option>, rustc: Option, cargo: Option, #[allow(clippy::option_option)] cargo_home: Option>, host_triple: Option, } impl ResolveOptions { /// Sets `rustc` path and args. /// /// # Default value /// /// [`Config::rustc`](crate::Config::rustc) pub fn rustc>(mut self, rustc: P) -> Self { self.rustc = Some(rustc.into()); self } /// Sets `cargo` path. /// /// # Default value /// /// The value of the `CARGO` environment variable if it is set. Otherwise, "cargo". pub fn cargo>(mut self, cargo: S) -> Self { self.cargo = Some(cargo.into()); self } /// Sets `CARGO_HOME` path. /// /// # Default value /// /// [`home::cargo_home_with_cwd`] if the current directory was specified when /// loading config. Otherwise, [`home::cargo_home`]. /// /// [`home::cargo_home_with_cwd`]: https://docs.rs/home/latest/home/fn.cargo_home_with_cwd.html /// [`home::cargo_home`]: https://docs.rs/home/latest/home/fn.cargo_home.html pub fn cargo_home>>(mut self, cargo_home: P) -> Self { self.cargo_home = Some(cargo_home.into()); self } /// Sets host target triple. /// /// # Default value /// /// Parse the version output of `cargo` specified by [`Self::cargo`]. pub fn host_triple>(mut self, triple: S) -> Self { self.host_triple = Some(triple.into()); self } /// Sets the specified key-values as environment variables to be read during /// config resolution. /// /// This is mainly intended for use in tests where it is necessary to adjust /// the kinds of environment variables that are referenced. /// /// # Default value /// /// [`std::env::vars_os`] pub fn env, K: Into, V: Into>( mut self, vars: I, ) -> Self { let mut env = HashMap::default(); for (k, v) in vars { if let Ok(k) = k.into().into_string() { if k.starts_with("CARGO") || k.starts_with("RUST") || k == "BROWSER" { env.insert(k, v.into()); } } } self.env = Some(env); self } #[doc(hidden)] // Not public API. pub fn into_context(mut self, current_dir: PathBuf) -> ResolveContext { if self.env.is_none() { self = self.env(std::env::vars_os()); } let env = self.env.unwrap(); let rustc = match self.rustc { Some(rustc) => OnceCell::from(rustc), None => OnceCell::new(), }; let cargo = match self.cargo { Some(cargo) => cargo, None => env.get("CARGO").cloned().unwrap_or_else(|| "cargo".into()), }; let cargo_home = match self.cargo_home { Some(cargo_home) => OnceCell::from(cargo_home), None => OnceCell::new(), }; let host_triple = match self.host_triple { Some(host_triple) => OnceCell::from(host_triple), None => OnceCell::new(), }; ResolveContext { env, rustc, cargo, cargo_home, host_triple, rustc_version: OnceCell::new(), cargo_version: OnceCell::new(), cfg: RefCell::default(), current_dir, } } } #[doc(hidden)] // Not public API. #[derive(Debug, Clone)] #[must_use] pub struct ResolveContext { pub(crate) env: HashMap, rustc: OnceCell, pub(crate) cargo: OsString, cargo_home: OnceCell>, host_triple: OnceCell, rustc_version: OnceCell, cargo_version: OnceCell, cfg: RefCell, pub(crate) current_dir: PathBuf, } impl ResolveContext { pub(crate) fn rustc(&self, build_config: &easy::BuildConfig) -> &PathAndArgs { self.rustc.get_or_init(|| { // https://github.com/rust-lang/cargo/pull/10896 // https://github.com/rust-lang/cargo/pull/13648 let rustc = build_config.rustc.as_ref().map_or_else(|| rustc_path(&self.cargo), PathBuf::from); let rustc_wrapper = build_config.rustc_wrapper.clone(); let rustc_workspace_wrapper = build_config.rustc_workspace_wrapper.clone(); let mut rustc = rustc_wrapper.into_iter().chain(rustc_workspace_wrapper).chain(iter::once(rustc)); PathAndArgs { path: rustc.next().unwrap(), args: rustc.map(PathBuf::into_os_string).collect(), } }) } pub(crate) fn rustc_for_version(&self, build_config: &easy::BuildConfig) -> PathAndArgs { // Do not apply RUSTC_WORKSPACE_WRAPPER: https://github.com/cuviper/autocfg/issues/58#issuecomment-2067625980 let rustc = build_config.rustc.as_ref().map_or_else(|| rustc_path(&self.cargo), PathBuf::from); let rustc_wrapper = build_config.rustc_wrapper.clone(); let mut rustc = rustc_wrapper.into_iter().chain(iter::once(rustc)); PathAndArgs { path: rustc.next().unwrap(), args: rustc.map(PathBuf::into_os_string).collect(), } } pub(crate) fn cargo_home(&self, cwd: &Path) -> &Option { self.cargo_home.get_or_init(|| walk::cargo_home_with_cwd(cwd)) } pub(crate) fn host_triple(&self, build_config: &easy::BuildConfig) -> Result<&str> { if let Some(host) = self.host_triple.get() { return Ok(host); } let cargo_host = verbose_version(cmd!(&self.cargo)).and_then(|ref vv| { let r = self.cargo_version.set(cargo_version(vv)?); debug_assert!(r.is_ok()); host_triple(vv) }); let host = match cargo_host { Ok(host) => host, Err(_) => { let vv = &verbose_version((&self.rustc_for_version(build_config)).into())?; let r = self.rustc_version.set(rustc_version(vv)?); debug_assert!(r.is_ok()); host_triple(vv)? } }; Ok(self.host_triple.get_or_init(|| host)) } pub(crate) fn rustc_version(&self, build_config: &easy::BuildConfig) -> Result { if let Some(&rustc_version) = self.rustc_version.get() { return Ok(rustc_version); } let _ = self.host_triple(build_config); if let Some(&rustc_version) = self.rustc_version.get() { return Ok(rustc_version); } let vv = &verbose_version((&self.rustc_for_version(build_config)).into())?; let rustc_version = rustc_version(vv)?; Ok(*self.rustc_version.get_or_init(|| rustc_version)) } pub(crate) fn cargo_version(&self, build_config: &easy::BuildConfig) -> Result { if let Some(&cargo_version) = self.cargo_version.get() { return Ok(cargo_version); } let _ = self.host_triple(build_config); if let Some(&cargo_version) = self.cargo_version.get() { return Ok(cargo_version); } let vv = &verbose_version(cmd!(&self.cargo))?; let cargo_version = cargo_version(vv)?; Ok(*self.cargo_version.get_or_init(|| cargo_version)) } // micro-optimization for static name -- avoiding name allocation can speed up // de::Config::apply_env by up to 40% because most env var names we fetch are static. pub(crate) fn env(&self, name: &'static str) -> Result>> { match self.env.get(name) { None => Ok(None), Some(v) => Ok(Some(Value { val: v.clone().into_string().map_err(|var| Error::env_not_unicode(name, var))?, definition: Some(Definition::Environment(name.into())), })), } } pub(crate) fn env_redacted(&self, name: &'static str) -> Result>> { match self.env.get(name) { None => Ok(None), Some(v) => Ok(Some(Value { val: v .clone() .into_string() .map_err(|_var| Error::env_not_unicode_redacted(name))?, definition: Some(Definition::Environment(name.into())), })), } } pub(crate) fn env_parse(&self, name: &'static str) -> Result>> where T: FromStr, T::Err: std::error::Error + Send + Sync + 'static, { match self.env(name)? { Some(v) => Ok(Some( v.parse() .with_context(|| format!("failed to parse environment variable `{name}`"))?, )), None => Ok(None), } } pub(crate) fn env_dyn(&self, name: &str) -> Result>> { match self.env.get(name) { None => Ok(None), Some(v) => Ok(Some(Value { val: v.clone().into_string().map_err(|var| Error::env_not_unicode(name, var))?, definition: Some(Definition::Environment(name.to_owned().into())), })), } } pub(crate) fn eval_cfg( &self, expr: &str, target: &TargetTripleRef<'_>, build_config: &easy::BuildConfig, ) -> Result { let expr = Expression::parse(expr).map_err(Error::new)?; let mut cfg_map = self.cfg.borrow_mut(); cfg_map.eval_cfg(&expr, target, || self.rustc(build_config).into()) } } #[derive(Debug, Clone, Default)] pub(crate) struct CfgMap { map: HashMap, Cfg>, } impl CfgMap { pub(crate) fn eval_cfg( &mut self, expr: &Expression, target: &TargetTripleRef<'_>, rustc: impl FnOnce() -> ProcessBuilder, ) -> Result { let cfg = match self.map.get(target.cli_target()) { Some(cfg) => cfg, None => { let cfg = Cfg::from_rustc(rustc(), target)?; self.map.insert(TargetTripleBorrow(target.clone().into_owned()), cfg); &self.map[target.cli_target()] } }; Ok(expr.eval(|pred| match pred { Predicate::Flag(flag) => { match *flag { // https://github.com/rust-lang/cargo/pull/7660 "test" | "debug_assertions" | "proc_macro" => false, flag => cfg.flags.contains(flag), } } Predicate::KeyValue { key, val } => { match *key { // https://github.com/rust-lang/cargo/pull/7660 "feature" => false, key => cfg.key_values.get(key).map_or(false, |values| values.contains(*val)), } } })) } } #[derive(Debug, Clone)] struct Cfg { flags: HashSet, key_values: HashMap>, } impl Cfg { fn from_rustc(mut rustc: ProcessBuilder, target: &TargetTripleRef<'_>) -> Result { let list = rustc.args(["--print", "cfg", "--target", &*target.cli_target_string()]).read()?; Ok(Self::parse(&list)) } fn parse(list: &str) -> Self { let mut flags = HashSet::default(); let mut key_values = HashMap::>::default(); for line in list.lines() { let line = line.trim(); if line.is_empty() { continue; } match line.split_once('=') { None => { flags.insert(line.to_owned()); } Some((name, value)) => { if value.len() < 2 || !value.starts_with('"') || !value.ends_with('"') { #[cfg(test)] panic!("invalid value '{value}'"); #[cfg(not(test))] continue; } let value = &value[1..value.len() - 1]; if value.is_empty() { continue; } if let Some(values) = key_values.get_mut(name) { values.insert(value.to_owned()); } else { let mut values = HashSet::default(); values.insert(value.to_owned()); key_values.insert(name.to_owned(), values); } } } } Self { flags, key_values } } } #[derive(Debug, Clone)] pub struct TargetTripleRef<'a> { triple: Cow<'a, str>, spec_path: Option>, } pub type TargetTriple = TargetTripleRef<'static>; impl PartialEq for TargetTripleRef<'_> { fn eq(&self, other: &Self) -> bool { self.cli_target() == other.cli_target() } } impl Eq for TargetTripleRef<'_> {} impl PartialOrd for TargetTripleRef<'_> { fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } } impl Ord for TargetTripleRef<'_> { fn cmp(&self, other: &Self) -> cmp::Ordering { self.cli_target().cmp(other.cli_target()) } } impl Hash for TargetTripleRef<'_> { fn hash(&self, state: &mut H) { self.cli_target().hash(state); } } // This wrapper is needed to support pre-1.63 Rust. // In pre-1.63 Rust you can't use TargetTripleRef<'non_static> as an index of // HashMap, _> without this trick. #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] #[serde(transparent)] pub(crate) struct TargetTripleBorrow<'a>(pub(crate) TargetTripleRef<'a>); impl core::borrow::Borrow for TargetTripleBorrow<'_> { fn borrow(&self) -> &OsStr { self.0.cli_target() } } fn is_spec_path(triple_or_spec_path: &str) -> bool { Path::new(triple_or_spec_path).extension() == Some(OsStr::new("json")) || triple_or_spec_path.contains('/') || triple_or_spec_path.contains('\\') } fn resolve_spec_path( spec_path: &str, def: Option<&Definition>, current_dir: Option<&Path>, ) -> Option { if let Some(def) = def { if let Some(root) = def.root_opt(current_dir) { return Some(root.join(spec_path)); } } None } impl<'a> TargetTripleRef<'a> { pub(crate) fn new( triple_or_spec_path: Cow<'a, str>, def: Option<&Definition>, current_dir: Option<&Path>, ) -> Self { // Handles custom target if is_spec_path(&triple_or_spec_path) { let triple = match &triple_or_spec_path { // `triple_or_spec_path` is valid UTF-8, so unwrap here will never panic. &Cow::Borrowed(v) => Path::new(v).file_stem().unwrap().to_str().unwrap().into(), Cow::Owned(v) => { Path::new(v).file_stem().unwrap().to_str().unwrap().to_owned().into() } }; Self { triple, spec_path: Some(match resolve_spec_path(&triple_or_spec_path, def, current_dir) { Some(v) => v.into(), None => match triple_or_spec_path { Cow::Borrowed(v) => Path::new(v).into(), Cow::Owned(v) => PathBuf::from(v).into(), }, }), } } else { Self { triple: triple_or_spec_path, spec_path: None } } } pub fn into_owned(self) -> TargetTriple { TargetTripleRef { triple: self.triple.into_owned().into(), spec_path: self.spec_path.map(|v| v.into_owned().into()), } } pub fn triple(&self) -> &str { &self.triple } pub fn spec_path(&self) -> Option<&Path> { self.spec_path.as_deref() } pub(crate) fn cli_target_string(&self) -> Cow<'_, str> { // Cargo converts spec path containing non-UTF8 byte to string with // to_string_lossy before passing it to rustc. // This is not good behavior but we just follow the behavior of cargo for now. // // ``` // $ pwd // /tmp/��/a // $ cat .cargo/config.toml // [build] // target = "avr-unknown-gnu-atmega2560.json" // ``` // $ cargo build // error: target path "/tmp/��/a/avr-unknown-gnu-atmega2560.json" is not a valid file // // Caused by: // No such file or directory (os error 2) // ``` self.cli_target().to_string_lossy() } pub(crate) fn cli_target(&self) -> &OsStr { match self.spec_path() { Some(v) => v.as_os_str(), None => OsStr::new(self.triple()), } } } impl<'a> From<&'a TargetTripleRef<'_>> for TargetTripleRef<'a> { fn from(value: &'a TargetTripleRef<'_>) -> Self { TargetTripleRef { triple: value.triple().into(), spec_path: value.spec_path().map(Into::into), } } } impl From for TargetTripleRef<'static> { fn from(value: String) -> Self { Self::new(value.into(), None, None) } } impl<'a> From<&'a String> for TargetTripleRef<'a> { fn from(value: &'a String) -> Self { Self::new(value.into(), None, None) } } impl<'a> From<&'a str> for TargetTripleRef<'a> { fn from(value: &'a str) -> Self { Self::new(value.into(), None, None) } } impl Serialize for TargetTripleRef<'_> { fn serialize(&self, serializer: S) -> Result where S: Serializer, { self.cli_target_string().serialize(serializer) } } impl<'de> Deserialize<'de> for TargetTripleRef<'static> { fn deserialize(deserializer: D) -> Result where D: Deserializer<'de>, { Ok(Self::new(String::deserialize(deserializer)?.into(), None, None)) } } #[derive(Debug, Copy, Clone, PartialEq, Eq)] #[non_exhaustive] pub struct RustcVersion { pub major: u32, pub minor: u32, pub patch: Option, pub nightly: bool, } #[derive(Debug, Copy, Clone, PartialEq, Eq)] #[non_exhaustive] pub struct CargoVersion { pub major: u32, pub minor: u32, pub patch: u32, pub nightly: bool, } impl RustcVersion { /// Returns the pair of the major and minor versions. /// /// This is useful for comparing versions: `version.major_minor() < (1, 70)` pub fn major_minor(&self) -> (u32, u32) { (self.major, self.minor) } } impl CargoVersion { /// Returns the pair of the major and minor versions. /// /// This is useful for comparing versions: `version.major_minor() < (1, 70)` pub fn major_minor(&self) -> (u32, u32) { (self.major, self.minor) } } fn verbose_version(mut rustc_or_cargo: ProcessBuilder) -> Result<(String, ProcessBuilder)> { // Use verbose version output because the packagers add extra strings to the normal version output. // Do not use long flags (--version --verbose) because clippy-deriver doesn't handle them properly. // -vV is also matched with that cargo internally uses: https://github.com/rust-lang/cargo/blob/0.80.0/src/cargo/util/rustc.rs#L65 rustc_or_cargo.arg("-vV"); let verbose_version = rustc_or_cargo.read()?; Ok((verbose_version, rustc_or_cargo)) } fn parse_version(verbose_version: &str) -> Option<(u32, u32, Option, bool)> { let release = verbose_version.lines().find_map(|line| line.strip_prefix("release: "))?; let (version, channel) = release.split_once('-').unwrap_or((release, "")); let mut digits = version.splitn(3, '.'); let major = digits.next()?.parse::().ok()?; let minor = digits.next()?.parse::().ok()?; let patch = match digits.next() { Some(p) => Some(p.parse::().ok()?), None => None, }; let nightly = channel == "nightly" || channel == "dev"; Some((major, minor, patch, nightly)) } fn rustc_version((verbose_version, cmd): &(String, ProcessBuilder)) -> Result { let (major, minor, patch, nightly) = parse_version(verbose_version) .ok_or_else(|| format_err!("unexpected version output from {cmd}: {verbose_version}"))?; Ok(RustcVersion { major, minor, patch, nightly }) } fn cargo_version((verbose_version, cmd): &(String, ProcessBuilder)) -> Result { let (major, minor, patch, nightly) = parse_version(verbose_version) .and_then(|(major, minor, patch, nightly)| Some((major, minor, patch?, nightly))) .ok_or_else(|| format_err!("unexpected version output from {cmd}: {verbose_version}"))?; Ok(CargoVersion { major, minor, patch, nightly }) } /// Gets host triple of the given `rustc` or `cargo`. fn host_triple((verbose_version, cmd): &(String, ProcessBuilder)) -> Result { let host = verbose_version .lines() .find_map(|line| line.strip_prefix("host: ")) .ok_or_else(|| format_err!("unexpected version output from {cmd}: {verbose_version}"))? .to_owned(); Ok(host) } fn rustc_path(cargo: &OsStr) -> PathBuf { // When toolchain override shorthand (`+toolchain`) is used, `rustc` in // PATH and `CARGO` environment variable may be different toolchains. // When Rust was installed using rustup, the same toolchain's rustc // binary is in the same directory as the cargo binary, so we use it. let mut rustc = PathBuf::from(cargo); rustc.pop(); // cargo rustc.push(format!("rustc{}", std::env::consts::EXE_SUFFIX)); if rustc.exists() { rustc } else { "rustc".into() } } #[cfg(test)] mod tests { use std::io::{self, Write}; use fs_err as fs; use super::*; fn fixtures_path() -> &'static Path { Path::new(concat!(env!("CARGO_MANIFEST_DIR"), "/tests/fixtures")) } #[test] #[cfg_attr(miri, ignore)] // Miri doesn't support pipe2 (inside std::process::Command::output) fn version_and_host() { let rustc_vv = &verbose_version(cmd!("rustc")).unwrap(); let cargo_vv = &verbose_version(cmd!("cargo")).unwrap(); let rustc_version = rustc_version(rustc_vv).unwrap(); let cargo_version = cargo_version(cargo_vv).unwrap(); let mut stderr = io::stdout().lock(); let _ = writeln!(stderr, "rustc version: {rustc_version:?}"); let _ = writeln!(stderr, "rustc host: {:?}", host_triple(rustc_vv).unwrap()); let _ = writeln!(stderr, "cargo version: {cargo_version:?}"); let _ = writeln!(stderr, "cargo host: {:?}", host_triple(cargo_vv).unwrap()); let _ = stderr.flush(); assert_eq!(rustc_version.major_minor(), (rustc_version.major, rustc_version.minor)); assert!(rustc_version.major_minor() < (2, 0)); assert!(rustc_version.major_minor() < (1, u32::MAX)); assert!(rustc_version.major_minor() >= (1, 70)); assert!(rustc_version.major_minor() > (1, 0)); assert!(rustc_version.major_minor() > (0, u32::MAX)); assert_eq!(cargo_version.major_minor(), (cargo_version.major, cargo_version.minor)); assert!(cargo_version.major_minor() < (2, 0)); assert!(cargo_version.major_minor() < (1, u32::MAX)); assert!(cargo_version.major_minor() >= (1, 70)); assert!(cargo_version.major_minor() > (1, 0)); assert!(cargo_version.major_minor() > (0, u32::MAX)); } #[test] fn target_triple() { let t = TargetTripleRef::from("x86_64-unknown-linux-gnu"); assert_eq!(t.triple, "x86_64-unknown-linux-gnu"); assert!(matches!(t.triple, Cow::Borrowed(..))); assert!(t.spec_path.is_none()); } #[rustversion::attr(not(nightly), ignore)] #[test] #[cfg_attr(miri, ignore)] // Miri doesn't support pipe2 (inside std::process::Command::output) fn parse_cfg_list() { // builtin targets for target in cmd!("rustc", "--print", "target-list").read().unwrap().lines() { let _cfg = Cfg::from_rustc(cmd!("rustc"), &target.into()).unwrap(); } // custom targets for spec_path in fs::read_dir(fixtures_path().join("target-specs")) .unwrap() .filter_map(Result::ok) .map(|e| e.path()) { let _cfg = Cfg::from_rustc(cmd!("rustc"), &spec_path.to_str().unwrap().into()).unwrap(); } } #[test] fn env_filter() { // NB: sync with bench in bench/benches/bench.rs let env_list = [ ("CARGO_BUILD_JOBS", "-1"), ("RUSTC", "rustc"), ("CARGO_BUILD_RUSTC", "rustc"), ("RUSTC_WRAPPER", "rustc_wrapper"), ("CARGO_BUILD_RUSTC_WRAPPER", "rustc_wrapper"), ("RUSTC_WORKSPACE_WRAPPER", "rustc_workspace_wrapper"), ("CARGO_BUILD_RUSTC_WORKSPACE_WRAPPER", "rustc_workspace_wrapper"), ("RUSTDOC", "rustdoc"), ("CARGO_BUILD_RUSTDOC", "rustdoc"), ("CARGO_BUILD_TARGET", "triple"), ("CARGO_TARGET_DIR", "target"), ("CARGO_BUILD_TARGET_DIR", "target"), ("CARGO_ENCODED_RUSTFLAGS", "1"), ("RUSTFLAGS", "1"), ("CARGO_BUILD_RUSTFLAGS", "1"), ("CARGO_ENCODED_RUSTDOCFLAGS", "1"), ("RUSTDOCFLAGS", "1"), ("CARGO_BUILD_RUSTDOCFLAGS", "1"), ("CARGO_INCREMENTAL", "false"), ("CARGO_BUILD_INCREMENTAL", "1"), ("CARGO_BUILD_DEP_INFO_BASEDIR", "1"), ("BROWSER", "1"), ("CARGO_FUTURE_INCOMPAT_REPORT_FREQUENCY", "always"), ("CARGO_CARGO_NEW_VCS", "git"), ("CARGO_HTTP_DEBUG", "true"), ("CARGO_HTTP_PROXY", "-"), ("CARGO_HTTP_TIMEOUT", "1"), ("CARGO_HTTP_CAINFO", "-"), ("CARGO_HTTP_CHECK_REVOKE", "true"), ("CARGO_HTTP_LOW_SPEED_LIMIT", "1"), ("CARGO_HTTP_MULTIPLEXING", "true"), ("CARGO_HTTP_USER_AGENT", "-"), ("CARGO_NET_RETRY", "1"), ("CARGO_NET_GIT_FETCH_WITH_CLI", "false"), ("CARGO_NET_OFFLINE", "false"), ("CARGO_REGISTRIES_crates-io_INDEX", "https://github.com/rust-lang/crates.io-index"), ("CARGO_REGISTRIES_crates-io_TOKEN", "00000000000000000000000000000000000"), ("CARGO_REGISTRY_DEFAULT", "crates-io"), ("CARGO_REGISTRY_TOKEN", "00000000000000000000000000000000000"), ("CARGO_REGISTRIES_CRATES_IO_PROTOCOL", "git"), ("CARGO_TERM_QUIET", "false"), ("CARGO_TERM_VERBOSE", "false"), ("CARGO_TERM_COLOR", "auto"), ("CARGO_TERM_PROGRESS_WHEN", "auto"), ("CARGO_TERM_PROGRESS_WIDTH", "100"), ]; let mut config = crate::de::Config::default(); let cx = &ResolveOptions::default().env(env_list).into_context(std::env::current_dir().unwrap()); config.apply_env(cx).unwrap(); // ResolveOptions::env attempts to avoid pushing unrelated envs. let mut env_list = env_list.to_vec(); env_list.push(("A", "B")); let cx = &ResolveOptions::default() .env(env_list.iter().copied()) .into_context(std::env::current_dir().unwrap()); for (k, v) in env_list { if k == "A" { assert!(!cx.env.contains_key(k)); } else { assert_eq!(cx.env[k], v, "key={k},value={v}"); } } } #[test] fn rustc_wrapper() { for (env_list, expected) in [ ( &[ ("RUSTC", "rustc"), ("CARGO_BUILD_RUSTC", "cargo_build_rustc"), ("RUSTC_WRAPPER", "rustc_wrapper"), ("CARGO_BUILD_RUSTC_WRAPPER", "cargo_build_rustc_wrapper"), ("RUSTC_WORKSPACE_WRAPPER", "rustc_workspace_wrapper"), ("CARGO_BUILD_RUSTC_WORKSPACE_WRAPPER", "cargo_build_rustc_workspace_wrapper"), ][..], PathAndArgs { path: "rustc_wrapper".into(), args: vec!["rustc_workspace_wrapper".into(), "rustc".into()], }, ), ( &[ ("RUSTC", "rustc"), ("CARGO_BUILD_RUSTC", "cargo_build_rustc"), ("RUSTC_WRAPPER", ""), ("CARGO_BUILD_RUSTC_WRAPPER", "cargo_build_rustc_wrapper"), ("RUSTC_WORKSPACE_WRAPPER", "rustc_workspace_wrapper"), ("CARGO_BUILD_RUSTC_WORKSPACE_WRAPPER", "cargo_build_rustc_workspace_wrapper"), ][..], PathAndArgs { path: "rustc_workspace_wrapper".into(), args: vec!["rustc".into()] }, ), ( &[ ("RUSTC", "rustc"), ("CARGO_BUILD_RUSTC", "cargo_build_rustc"), ("RUSTC_WRAPPER", "rustc_wrapper"), ("CARGO_BUILD_RUSTC_WRAPPER", "cargo_build_rustc_wrapper"), ("RUSTC_WORKSPACE_WRAPPER", ""), ("CARGO_BUILD_RUSTC_WORKSPACE_WRAPPER", "cargo_build_rustc_workspace_wrapper"), ][..], PathAndArgs { path: "rustc_wrapper".into(), args: vec!["rustc".into()] }, ), ( &[ ("CARGO_BUILD_RUSTC", "cargo_build_rustc"), ("CARGO_BUILD_RUSTC_WRAPPER", "cargo_build_rustc_wrapper"), ("CARGO_BUILD_RUSTC_WORKSPACE_WRAPPER", "cargo_build_rustc_workspace_wrapper"), ], PathAndArgs { path: "cargo_build_rustc_wrapper".into(), args: vec![ "cargo_build_rustc_workspace_wrapper".into(), "cargo_build_rustc".into(), ], }, ), ( &[ ("RUSTC", "rustc"), ("RUSTC_WRAPPER", "rustc_wrapper"), ("RUSTC_WORKSPACE_WRAPPER", "rustc_workspace_wrapper"), ], PathAndArgs { path: "rustc_wrapper".into(), args: vec!["rustc_workspace_wrapper".into(), "rustc".into()], }, ), ( &[ ("RUSTC", "rustc"), ("RUSTC_WRAPPER", "rustc_wrapper"), ("RUSTC_WORKSPACE_WRAPPER", ""), ], PathAndArgs { path: "rustc_wrapper".into(), args: vec!["rustc".into()] }, ), ( &[ ("RUSTC", "rustc"), ("RUSTC_WRAPPER", ""), ("RUSTC_WORKSPACE_WRAPPER", "rustc_workspace_wrapper"), ], PathAndArgs { path: "rustc_workspace_wrapper".into(), args: vec!["rustc".into()] }, ), (&[("RUSTC", "rustc"), ("RUSTC_WRAPPER", "rustc_wrapper")], PathAndArgs { path: "rustc_wrapper".into(), args: vec!["rustc".into()], }), ( &[("RUSTC", "rustc"), ("RUSTC_WORKSPACE_WRAPPER", "rustc_workspace_wrapper")], PathAndArgs { path: "rustc_workspace_wrapper".into(), args: vec!["rustc".into()] }, ), (&[("RUSTC", "rustc"), ("RUSTC_WRAPPER", "")], PathAndArgs { path: "rustc".into(), args: vec![], }), (&[("RUSTC", "rustc"), ("RUSTC_WORKSPACE_WRAPPER", "")], PathAndArgs { path: "rustc".into(), args: vec![], }), ] { let mut config = crate::de::Config::default(); let cx = &ResolveOptions::default() .env(env_list.iter().copied()) .into_context(std::env::current_dir().unwrap()); config.apply_env(cx).unwrap(); let build = crate::easy::BuildConfig::from_unresolved(config.build, &cx.current_dir); assert_eq!(*cx.rustc(&build), expected); } } #[cfg(unix)] #[test] fn env_no_utf8() { use std::{ffi::OsStr, os::unix::prelude::OsStrExt}; let cx = &ResolveOptions::default() .env([("CARGO_ALIAS_a", OsStr::from_bytes(&[b'f', b'o', 0x80, b'o']))]) .cargo_home(None) .rustc(PathAndArgs::new("rustc")) .into_context(std::env::current_dir().unwrap()); assert_eq!( cx.env("CARGO_ALIAS_a").unwrap_err().to_string(), "failed to parse environment variable `CARGO_ALIAS_a`" ); assert_eq!( format!("{:#}", anyhow::Error::from(cx.env("CARGO_ALIAS_a").unwrap_err())), "failed to parse environment variable `CARGO_ALIAS_a`: environment variable was not valid unicode: \"fo\\x80o\"" ); } // #[test] // fn dump_all_env() { // let mut config = crate::de::Config::default(); // let cx = &mut ResolveContext::no_env(); // config.apply_env(cx).unwrap(); // } } cargo-config2-0.1.29/src/value.rs000064400000000000000000000116101046102023000146050ustar 00000000000000// SPDX-License-Identifier: Apache-2.0 OR MIT // Based on https://github.com/rust-lang/cargo/blob/0.80.0/src/cargo/util/context/value.rs. use core::{fmt, mem, str::FromStr}; use std::{ borrow::Cow, collections::BTreeMap, path::{Path, PathBuf}, }; use serde_derive::{Deserialize, Serialize}; use crate::error::Result; #[allow(clippy::exhaustive_structs)] #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(transparent)] pub struct Value { /// The inner value that was deserialized. pub val: T, /// The location where `val` was defined in configuration (e.g. file it was /// defined in, env var etc). #[serde(skip)] pub definition: Option, } impl Value { pub(crate) fn parse(self) -> Result, T::Err> { Ok(Value { val: self.val.parse()?, definition: self.definition }) } // https://doc.rust-lang.org/nightly/cargo/reference/config.html#config-relative-paths pub(crate) fn resolve_as_program_path(&self, current_dir: &Path) -> Cow<'_, Path> { match &self.definition { Some(def) if !Path::new(&self.val).is_absolute() && (self.val.contains('/') || self.val.contains('\\')) => { def.root(current_dir).join(&self.val).into() } _ => Path::new(&self.val).into(), } } pub(crate) fn resolve_as_path(&self, current_dir: &Path) -> Cow<'_, Path> { match &self.definition { Some(def) if !Path::new(&self.val).is_absolute() => { def.root(current_dir).join(&self.val).into() } _ => Path::new(&self.val).into(), } } } /// Location where a config value is defined. #[derive(Clone, Debug)] #[non_exhaustive] pub enum Definition { /// Defined in a `.cargo/config`, includes the path to the file. Path(PathBuf), /// Defined in an environment variable, includes the environment key. Environment(Cow<'static, str>), /// Passed in on the command line. /// A path is attached when the config value is a path to a config file. Cli(Option), } impl Definition { /// Root directory where this is defined. /// /// If from a file, it is the directory above `.cargo/config`. /// CLI and env are the current working directory. pub(crate) fn root<'a>(&'a self, current_dir: &'a Path) -> &'a Path { match self { Definition::Path(p) | Definition::Cli(Some(p)) => p.parent().unwrap().parent().unwrap(), Definition::Environment(_) | Definition::Cli(None) => current_dir, } } pub(crate) fn root_opt<'a>(&'a self, current_dir: Option<&'a Path>) -> Option<&'a Path> { match self { Definition::Path(p) | Definition::Cli(Some(p)) => { Some(p.parent().unwrap().parent().unwrap()) } Definition::Environment(_) | Definition::Cli(None) => current_dir, } } // /// Returns `true` if self is a higher priority to other. // /// // /// CLI is preferred over environment, which is preferred over files. // pub(crate) fn is_higher_priority(&self, other: &Definition) -> bool { // matches!( // (self, other), // (Definition::Cli(_), Definition::Environment(_) | Definition::Path(_)) // | (Definition::Environment(_), Definition::Path(_)) // ) // } } impl fmt::Display for Definition { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Definition::Path(p) | Definition::Cli(Some(p)) => p.display().fmt(f), Definition::Environment(key) => write!(f, "environment variable `{key}`"), Definition::Cli(None) => f.write_str("--config cli option"), } } } impl PartialEq for Definition { fn eq(&self, other: &Definition) -> bool { // configuration values are equivalent no matter where they're defined, // but they need to be defined in the same location. For example if // they're defined in the environment that's different than being // defined in a file due to path interpretations. mem::discriminant(self) == mem::discriminant(other) } } pub(crate) trait SetPath { fn set_path(&mut self, path: &Path); } impl SetPath for Option { fn set_path(&mut self, path: &Path) { if let Some(v) = self { v.set_path(path); } } } impl SetPath for Vec { fn set_path(&mut self, path: &Path) { for v in self { v.set_path(path); } } } impl SetPath for BTreeMap { fn set_path(&mut self, path: &Path) { for v in self.values_mut() { v.set_path(path); } } } impl SetPath for Value { fn set_path(&mut self, path: &Path) { self.definition = Some(Definition::Path(path.to_owned())); } } cargo-config2-0.1.29/src/walk.rs000064400000000000000000000124241046102023000144330ustar 00000000000000// SPDX-License-Identifier: Apache-2.0 OR MIT // https://doc.rust-lang.org/nightly/cargo/reference/config.html#hierarchical-structure // // > Cargo allows local configuration for a particular package as well as global // > configuration. It looks for configuration files in the current directory // > and all parent directories. If, for example, Cargo were invoked in // > `/projects/foo/bar/baz`, then the following configuration files would be // > probed for and unified in this order: // > // > - `/projects/foo/bar/baz/.cargo/config.toml` // > - `/projects/foo/bar/.cargo/config.toml` // > - `/projects/foo/.cargo/config.toml` // > - `/projects/.cargo/config.toml` // > - `/.cargo/config.toml` // > - `$CARGO_HOME/config.toml` which defaults to: // > - Windows: `%USERPROFILE%\.cargo\config.toml` // > - Unix: `$HOME/.cargo/config.toml` use core::ops; use std::path::{Path, PathBuf}; fn config_path(path: &Path) -> Option { // https://doc.rust-lang.org/nightly/cargo/reference/config.html#hierarchical-structure // // > Cargo also reads config files without the `.toml` extension, // > such as `.cargo/config`. Support for the `.toml` extension was // > added in version 1.39 and is the preferred form. If both files // > exist, Cargo will use the file without the extension. let config = path.join("config"); if config.exists() { return Some(config); } let config = path.join("config.toml"); if config.exists() { return Some(config); } None } // Use the home crate only on Windows which std::env::home_dir is not correct. // https://github.com/rust-lang/cargo/blob/0.80.0/crates/home/src/lib.rs#L65-L72 #[cfg(windows)] use home::home_dir; #[cfg(not(windows))] fn home_dir() -> Option { #[allow(deprecated)] std::env::home_dir() } pub(crate) fn cargo_home_with_cwd(cwd: &Path) -> Option { // Follow the cargo's behavior. // https://github.com/rust-lang/cargo/blob/0.80.0/crates/home/src/lib.rs#L77-L86 // https://github.com/rust-lang/cargo/blob/0.80.0/crates/home/src/env.rs#L63-L77 match std::env::var_os("CARGO_HOME").filter(|h| !h.is_empty()).map(PathBuf::from) { Some(home) => { if home.is_absolute() { Some(home) } else { Some(cwd.join(home)) } } _ => Some(home_dir()?.join(".cargo")), } } /// An iterator over Cargo configuration file paths. #[derive(Debug)] #[must_use = "iterators are lazy and do nothing unless consumed"] pub(crate) struct WalkInner<'a, P> { ancestors: std::path::Ancestors<'a>, cargo_home: Option

, } impl<'a, P: ops::Deref> WalkInner<'a, P> { /// Creates an iterator over Cargo configuration file paths from the given path /// and `CARGO_HOME` path. pub(crate) fn with_cargo_home(current_dir: &'a Path, cargo_home: Option

) -> Self { Self { ancestors: current_dir.ancestors(), cargo_home } } } impl> Iterator for WalkInner<'_, P> { type Item = PathBuf; fn next(&mut self) -> Option { for p in self.ancestors.by_ref() { let p = p.join(".cargo"); // dedup CARGO_HOME if self.cargo_home.as_deref() == Some(&p) { self.cargo_home = None; } if let Some(p) = config_path(&p) { return Some(p); } } config_path(&self.cargo_home.take()?) } } // TODO: Remove in next breaking release? (if no one using this) /// An iterator over Cargo configuration file paths. #[derive(Debug)] #[must_use = "iterators are lazy and do nothing unless consumed"] pub struct Walk<'a>(WalkInner<'a, PathBuf>); impl<'a> Walk<'a> { /// Creates an iterator over Cargo configuration file paths from the given path. pub fn new(current_dir: &'a Path) -> Self { Self::with_cargo_home(current_dir, cargo_home_with_cwd(current_dir)) } /// Creates an iterator over Cargo configuration file paths from the given path /// and `CARGO_HOME` path. pub fn with_cargo_home(current_dir: &'a Path, cargo_home: Option) -> Self { Self(WalkInner::with_cargo_home(current_dir, cargo_home)) } } impl Iterator for Walk<'_> { type Item = PathBuf; fn next(&mut self) -> Option { self.0.next() } } #[cfg(test)] mod tests { use fs_err as fs; use super::*; #[test] fn walk() { let tmp = tempfile::tempdir().unwrap(); let p = tmp.path(); let home = &p.join("a/.cargo"); let cwd = &p.join("a/b/c"); fs::create_dir_all(home).unwrap(); fs::write(p.join("a/.cargo/config"), "").unwrap(); fs::create_dir_all(p.join("a/b/.cargo")).unwrap(); fs::write(p.join("a/b/.cargo/config"), "").unwrap(); fs::write(p.join("a/b/.cargo/config.toml"), "").unwrap(); fs::create_dir_all(p.join("a/b/c/.cargo")).unwrap(); fs::write(p.join("a/b/c/.cargo/config.toml"), "").unwrap(); let mut w = Walk::with_cargo_home(cwd, Some(home.clone())); assert_eq!(w.next(), Some(p.join("a/b/c/.cargo/config.toml"))); assert_eq!(w.next(), Some(p.join("a/b/.cargo/config"))); assert_eq!(w.next(), Some(p.join("a/.cargo/config"))); assert_eq!(w.next(), None); } } cargo-config2-0.1.29/tests/fixtures/target-specs/avr-unknown-gnu-atmega2560.json000064400000000000000000000010311046102023000254640ustar 00000000000000{ "arch": "avr", "atomic-cas": false, "cpu": "atmega2560", "data-layout": "e-P1-p:16:8-i8:8-i16:8-i32:8-i64:8-f32:8-f64:8-n8-a:8", "eh-frame-header": false, "exe-suffix": ".elf", "executables": true, "late-link-args": { "gcc": ["-lgcc"] }, "linker": "avr-gcc", "llvm-target": "avr-unknown-unknown", "max-atomic-width": 16, "no-default-libraries": false, "pre-link-args": { "gcc": ["-mmcu=atmega2560"] }, "relocation-model": "static", "target-c-int-width": "16", "target-pointer-width": "16" } cargo-config2-0.1.29/tests/helper/mod.rs000064400000000000000000000041061046102023000161040ustar 00000000000000// SPDX-License-Identifier: Apache-2.0 OR MIT use std::{ path::{Path, PathBuf}, process::Command, str, }; use anyhow::{bail, Context as _, Result}; pub(crate) use fs_err as fs; pub(crate) fn fixtures_path() -> &'static Path { Path::new(concat!(env!("CARGO_MANIFEST_DIR"), "/tests/fixtures")) } pub(crate) fn test_project(model: &str) -> Result<(tempfile::TempDir, PathBuf)> { let tmpdir = tempfile::tempdir()?; let tmpdir_path = tmpdir.path(); let model_path; let workspace_root; if model.contains('/') { let mut model = model.splitn(2, '/'); model_path = fixtures_path().join(model.next().unwrap()); workspace_root = tmpdir_path.join(model.next().unwrap()); assert!(model.next().is_none()); } else { model_path = fixtures_path().join(model); workspace_root = tmpdir_path.to_path_buf(); } for (file_name, from) in git_ls_files(&model_path, &[])? { let to = &tmpdir_path.join(file_name); if !to.parent().unwrap().is_dir() { fs::create_dir_all(to.parent().unwrap())?; } fs::copy(from, to)?; } Ok((tmpdir, workspace_root)) } fn git_ls_files(dir: &Path, filters: &[&str]) -> Result> { let mut cmd = Command::new("git"); cmd.arg("ls-files").args(filters).current_dir(dir); let output = cmd.output().with_context(|| format!("could not execute process `{cmd:?}`"))?; if !output.status.success() { bail!( "process didn't exit successfully: `{cmd:?}`:\n\nSTDOUT:\n{0}\n{1}\n{0}\n\nSTDERR:\n{0}\n{2}\n{0}\n", "-".repeat(60), String::from_utf8_lossy(&output.stdout), String::from_utf8_lossy(&output.stderr), ); } Ok(str::from_utf8(&output.stdout)? .lines() .map(str::trim) .filter_map(|f| { if f.is_empty() { return None; } let p = dir.join(f); if !p.exists() { return None; } Some((f.to_owned(), p)) }) .collect()) } cargo-config2-0.1.29/tests/test.rs000064400000000000000000000275631046102023000150410ustar 00000000000000// SPDX-License-Identifier: Apache-2.0 OR MIT #![allow(clippy::needless_pass_by_value)] mod helper; use std::{collections::HashMap, path::Path, str}; use anyhow::{Context as _, Result}; use build_context::TARGET; use cargo_config2::*; use helper::*; fn test_options() -> ResolveOptions { ResolveOptions::default() .env(HashMap::::default()) .cargo_home(None) .rustc(PathAndArgs::new("rustc")) } fn assert_reference_example(de: fn(&Path, ResolveOptions) -> Result) -> Result<()> { let (_tmp, root) = test_project("reference")?; let dir = &root; let base_config = &de(dir, test_options())?; let config = base_config.clone(); // [alias] for (k, v) in &config.alias { match k.as_str() { "b" => assert_eq!(*v, "build".into()), "c" => assert_eq!(*v, "check".into()), "t" => assert_eq!(*v, "test".into()), "r" => assert_eq!(*v, "run".into()), "rr" => assert_eq!(*v, "run --release".into()), "recursive_example" => assert_eq!(*v, "rr --example recursions".into()), "space_example" => { assert_eq!(*v, ["run", "--release", "--", "\"command list\""].into()); } _ => panic!("unexpected alias: name={k}, value={v:?}"), } } // [build] assert_eq!(config.build.rustc.as_ref().unwrap().as_os_str(), "rustc"); assert_eq!(config.build.rustc_wrapper.as_ref().unwrap().as_os_str(), "…"); assert_eq!(config.build.rustc_workspace_wrapper.as_ref().unwrap().as_os_str(), "…"); assert_eq!(config.build.rustdoc.as_ref().unwrap().as_os_str(), "rustdoc"); assert_eq!(config.build.target.as_ref().unwrap(), &vec!["triple".into()]); assert_eq!(config.build.target_dir.as_ref().unwrap(), &dir.join("target")); assert_eq!(config.build.rustflags, Some(["…", "…"].into())); assert_eq!(config.build.rustdocflags, Some(["…", "…"].into())); assert_eq!(config.build.incremental, Some(true)); assert_eq!(config.build.dep_info_basedir.as_ref().unwrap(), &dir.join("…")); // [doc] assert_eq!(config.doc.browser.as_ref().unwrap().path.as_os_str(), "chromium"); assert!(config.doc.browser.as_ref().unwrap().args.is_empty()); // [env] assert_eq!(config.env["ENV_VAR_NAME"].value, "value"); assert_eq!(config.env["ENV_VAR_NAME"].force, true); assert_eq!(config.env["ENV_VAR_NAME"].relative, false); assert_eq!(config.env["ENV_VAR_NAME_2"].value, "value"); assert_eq!(config.env["ENV_VAR_NAME_2"].force, false); assert_eq!(config.env["ENV_VAR_NAME_2"].relative, false); assert_eq!(config.env["ENV_VAR_NAME_3"].value, dir.join("relative/path")); assert_eq!(config.env["ENV_VAR_NAME_3"].force, false); assert_eq!(config.env["ENV_VAR_NAME_3"].relative, false); // false because it has been resolved // [future-incompat-report] assert_eq!(config.future_incompat_report.frequency, Some(Frequency::Always)); // [cargo-new] assert_eq!(config.cargo_new.vcs, Some(VersionControlSoftware::None)); // [http] assert_eq!(config.http.debug, Some(false)); assert_eq!(config.http.proxy.as_deref(), Some("host:port")); assert_eq!(config.http.timeout, Some(30)); assert_eq!(config.http.low_speed_limit, Some(10)); assert_eq!(config.http.cainfo.as_deref(), Some("cert.pem")); assert_eq!(config.http.check_revoke, Some(true)); assert_eq!(config.http.multiplexing, Some(true)); assert_eq!(config.http.user_agent.as_deref(), Some("foo-usr-agt")); // TODO // [install] // [net] assert_eq!(config.net.retry, Some(2)); assert_eq!(config.net.git_fetch_with_cli, Some(true)); assert_eq!(config.net.offline, Some(true)); // TODO // [patch.] // [profile.] // [registries.] assert_eq!(config.registries.len(), 1); assert_eq!( config.registries["crates-io"].index.as_deref(), Some("https://github.com/rust-lang/crates.io-index") ); assert_eq!( config.registries["crates-io"].token.as_deref(), Some("00000000000000000000000000000000000") ); assert_eq!(config.registries["crates-io"].protocol, Some(RegistriesProtocol::Git)); // [registry] assert_eq!(config.registry.default.as_deref(), Some("crates-io")); assert_eq!(config.registry.token.as_deref(), Some("00000000000000000000000000000000000")); // TODO // [source.] // [target.] and [target.] assert_eq!(config.target("x86_64-unknown-linux-gnu")?.linker.unwrap().as_os_str(), "b"); assert_eq!(config.target("x86_64-unknown-linux-gnu")?.runner.unwrap().path.as_os_str(), "b"); assert!(config.target("x86_64-unknown-linux-gnu")?.runner.unwrap().args.is_empty()); assert_eq!( config.target("x86_64-unknown-linux-gnu")?.rustflags, Some(["b", "bb", "c", "cc"].into()) ); assert_eq!(config.target("x86_64-unknown-linux-gnu")?.rustdocflags, Some(["d", "dd"].into())); assert_eq!(config.linker("x86_64-unknown-linux-gnu")?.unwrap().as_os_str(), "b"); assert_eq!(config.runner("x86_64-unknown-linux-gnu")?.unwrap().path.as_os_str(), "b"); assert!(config.runner("x86_64-unknown-linux-gnu")?.unwrap().args.is_empty()); assert_eq!(config.rustflags("x86_64-unknown-linux-gnu")?, Some(["b", "bb", "c", "cc"].into())); assert_eq!(config.rustdocflags("x86_64-unknown-linux-gnu")?, Some(["d", "dd"].into())); // TODO: [target..] // resolved target config cannot be accessed by cfg(...) assert!(config .target("cfg(target_arch = \"x86_64\")") .unwrap_err() .to_string() .contains("not valid target triple")); assert!(config .linker("cfg(target_arch = \"x86_64\")") .unwrap_err() .to_string() .contains("not valid target triple")); assert!(config .runner("cfg(target_arch = \"x86_64\")") .unwrap_err() .to_string() .contains("not valid target triple")); assert!(config .rustflags("cfg(target_arch = \"x86_64\")") .unwrap_err() .to_string() .contains("not valid target triple")); // [term] assert_eq!(config.term.quiet, Some(false)); assert_eq!(config.term.verbose, Some(false)); assert_eq!(config.term.color, Some(Color::Auto)); assert_eq!(config.term.progress.when, Some(When::Auto)); assert_eq!(config.term.progress.width, Some(80)); let _config = toml::to_string(&config).unwrap(); Ok(()) } fn easy_load(dir: &Path, options: ResolveOptions) -> Result { Ok(Config::load_with_options(dir, options)?) } #[test] #[cfg_attr(miri, ignore)] // Miri doesn't support file with non-default mode: https://github.com/rust-lang/miri/pull/2720 fn easy() { use easy_load as de; assert_reference_example(de).unwrap(); } #[test] fn no_manifest_dir() { let tmpdir = tempfile::tempdir().unwrap(); assert_eq!( "", toml::to_string(&Config::load_with_options(tmpdir.path(), test_options()).unwrap()) .unwrap() ); } fn de_load(dir: &Path, _cx: ResolveOptions) -> Result { Ok(de::Config::load_with_options(dir, None)?) } #[test] #[cfg_attr(miri, ignore)] // Miri doesn't support file with non-default mode: https://github.com/rust-lang/miri/pull/2720 fn de() { use de_load as de; let (_tmp, root) = test_project("reference").unwrap(); let dir = &root; let base_config = &de(dir, test_options()).unwrap(); let config = base_config.clone(); let _config = toml::to_string(&config).unwrap(); assert_eq!("", toml::to_string(&de::Config::default()).unwrap()); } #[test] #[cfg_attr(miri, ignore)] // Miri doesn't support file with non-default mode: https://github.com/rust-lang/miri/pull/2720 fn custom_target() { use easy_load as de; struct IsBuiltin(bool); #[track_caller] fn t(target: &str, IsBuiltin(is_builtin): IsBuiltin) -> Result<()> { let (_tmp, root) = test_project("empty")?; let dir = &root; fs::write( root.join(".cargo/config.toml"), r#" target.'cfg(target_arch = "avr")'.linker = "avr-gcc" target.'cfg(target_arch = "avr")'.rustflags = "-C opt-level=s" "#, )?; let spec_path = fixtures_path().join(format!("target-specs/{target}.json")); assert_eq!(spec_path.exists(), !is_builtin); let cli_target = if spec_path.exists() { spec_path.to_str().unwrap() } else { target }; let config = de(dir, test_options())?; assert_eq!( config .build_target_for_config([cli_target])? .iter() .map(|t| t.triple().to_owned()) .collect::>(), vec![target.to_owned()] ); assert_eq!(config.build_target_for_cli([cli_target])?, vec![cli_target.to_owned()]); assert_eq!(config.linker(cli_target)?.unwrap().as_os_str(), "avr-gcc"); assert_eq!(config.rustflags(cli_target)?, Some(["-C", "opt-level=s"].into())); // only resolve relative path from config or environment variables let spec_file_name = spec_path.file_name().unwrap().to_str().unwrap(); assert_eq!( config.build_target_for_config([spec_file_name])?[0].spec_path().unwrap().as_os_str(), spec_file_name ); assert_eq!(config.build_target_for_cli([spec_file_name])?, vec![spec_file_name.to_owned()]); let _config = toml::to_string(&config).unwrap(); Ok(()) } t("avr-unknown-gnu-atmega328", IsBuiltin(true)).unwrap(); t("avr-unknown-gnu-atmega2560", IsBuiltin(false)).unwrap(); } #[rustversion::attr(not(nightly), ignore)] #[cfg_attr(miri, ignore)] // Miri doesn't support pipe2 (inside duct::Expression::read) #[test] fn cargo_config_toml() { fn de(dir: &Path) -> Result { // remove CARGO_PKG_DESCRIPTION -- if field in Cargo.toml contains newline, --format=toml display invalid toml let s = duct::cmd!("cargo", "-Z", "unstable-options", "config", "get", "--format=toml") .dir(dir) .env_remove("CARGO_PKG_DESCRIPTION") .stderr_capture() .read()?; toml::from_str(&s).context("failed to parse output from cargo") } let _config = de(&fixtures_path().join("reference")).unwrap(); } #[rustversion::attr(not(nightly), ignore)] #[cfg_attr(miri, ignore)] // Miri doesn't support pipe2 (inside duct::Expression::read) #[test] fn cargo_config_json() { fn de(dir: &Path) -> Result { let s = duct::cmd!("cargo", "-Z", "unstable-options", "config", "get", "--format=json") .dir(dir) .stderr_capture() .read()?; serde_json::from_str(&s).context("failed to parse output from cargo") } let _config = de(&fixtures_path().join("reference")).unwrap(); } #[test] #[cfg_attr(miri, ignore)] // Miri doesn't support pipe2 (inside duct::Expression::read) fn test_cargo_behavior() -> Result<()> { let (_tmp, root) = test_project("empty").unwrap(); let dir = &root; // [env] table doesn't affect config resolution // https://github.com/taiki-e/cargo-config2/issues/2 fs::write( root.join(".cargo/config.toml"), r#" [env] RUSTFLAGS = "--cfg a" [build] rustflags = "--cfg b" "#, )?; let output = duct::cmd!("cargo", "build", "-v") .dir(dir) .env("CARGO_HOME", root.join(".cargo")) .env_remove("CARGO_ENCODED_RUSTFLAGS") .env_remove("RUSTFLAGS") .env_remove(format!("CARGO_TARGET_{}_RUSTFLAGS", TARGET.replace(['-', '.'], "_"))) .env_remove("CARGO_BUILD_RUSTFLAGS") .stdout_capture() .stderr_capture() .run()?; let stderr = str::from_utf8(&output.stderr)?; assert!(!stderr.contains("--cfg a"), "actual:\n---\n{stderr}\n---\n"); assert!(stderr.contains("--cfg b"), "actual:\n---\n{stderr}\n---\n"); Ok(()) }