cargo-config2-0.1.18/.cargo_vcs_info.json0000644000000001360000000000100135430ustar { "git": { "sha1": "92a851023400d57997014ed725800175a66260ee" }, "path_in_vcs": "" }cargo-config2-0.1.18/CHANGELOG.md000064400000000000000000000067151046102023000141550ustar 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.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 - Implement 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.18...HEAD [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.18/Cargo.lock0000644000000356040000000000100115260ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "anstyle" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87" [[package]] name = "anyhow" version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "080e9890a082662b09c1ad45f567faeeb47f22b5fb23895fbe1e651e718e25ca" [[package]] name = "autocfg" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "bitflags" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" version = "2.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" [[package]] name = "bstr" version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223" dependencies = [ "lazy_static", "memchr", "regex-automata", ] [[package]] name = "build-context" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "50c26f1b3248d94c0526b371757e64ea5fe5d3463b232051622e9eb90db45696" [[package]] name = "cargo-config2" version = "0.1.18" dependencies = [ "anyhow", "build-context", "clap", "duct", "fs-err", "home", "lexopt", "rustversion", "serde", "serde_derive", "serde_json", "shell-escape", "similar-asserts", "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.4.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e578d6ec4194633722ccf9544794b71b1385c3c027efe0c55db226fc880865c" dependencies = [ "clap_builder", "clap_derive", ] [[package]] name = "clap_builder" version = "4.4.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4df4df40ec50c46000231c914968278b1eb05098cf8f1b3a518a95030e71d1c7" dependencies = [ "anstyle", "clap_lex", ] [[package]] name = "clap_derive" version = "4.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442" dependencies = [ "heck", "proc-macro2", "quote", "syn", ] [[package]] name = "clap_lex" version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1" [[package]] name = "console" version = "0.15.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0e1f83fc076bd6dd27517eacdf25fef6c4dfe5f1d7448bafaaf3a26f13b5e4eb" dependencies = [ "encode_unicode", "lazy_static", "libc", "windows-sys", ] [[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 = "encode_unicode" version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" [[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.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" dependencies = [ "libc", "windows-sys", ] [[package]] name = "fastrand" version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" [[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.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" [[package]] name = "heck" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" [[package]] name = "home" version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" dependencies = [ "windows-sys", ] [[package]] name = "indexmap" version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f" dependencies = [ "equivalent", "hashbrown", ] [[package]] name = "itoa" version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" [[package]] name = "lazy_static" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[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.152" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7" [[package]] name = "linux-raw-sys" version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" [[package]] name = "memchr" version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" [[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.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57119c3b893986491ec9aa85056780d3a0f3cf4da7cc09dd3650dbd6c6738fb9" dependencies = [ "libc", "windows-sys", ] [[package]] name = "proc-macro2" version = "1.0.78" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" dependencies = [ "unicode-ident", ] [[package]] name = "quote" version = "1.0.35" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" dependencies = [ "proc-macro2", ] [[package]] name = "redox_syscall" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" dependencies = [ "bitflags 1.3.2", ] [[package]] name = "regex-automata" version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" [[package]] name = "rustix" version = "0.38.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "322394588aaf33c24007e8bb3238ee3e4c5c09c084ab32bc73890b99ff326bca" dependencies = [ "bitflags 2.4.2", "errno", "libc", "linux-raw-sys", "windows-sys", ] [[package]] name = "rustversion" version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" [[package]] name = "ryu" version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c" [[package]] name = "serde" version = "1.0.195" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "63261df402c67811e9ac6def069e4786148c4563f4b50fd4bf30aa370d626b02" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" version = "1.0.195" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46fe8f8603d81ba86327b23a2e9cdf49e1255fb94a4c5f297f6ee0547178ea2c" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "serde_json" version = "1.0.111" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "176e46fa42316f18edd598015a5166857fc835ec732f5215eac6b7bdbf0a84f4" dependencies = [ "itoa", "ryu", "serde", ] [[package]] name = "serde_spanned" version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eb3622f419d1296904700073ea6cc23ad690adbd66f13ea683df73298736f0c1" dependencies = [ "serde", ] [[package]] name = "shared_child" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b0d94659ad3c2137fef23ae75b03d5241d633f8acded53d672decfa0e6e0caef" dependencies = [ "libc", "winapi", ] [[package]] name = "shell-escape" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "45bb67a18fa91266cc7807181f62f9178a6873bfad7dc788c42e6430db40184f" [[package]] name = "similar" version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32fea41aca09ee824cc9724996433064c89f7777e60762749a4170a14abbfa21" dependencies = [ "bstr", "unicode-segmentation", ] [[package]] name = "similar-asserts" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e041bb827d1bfca18f213411d51b665309f1afb37a04a5d1464530e13779fc0f" dependencies = [ "console", "similar", ] [[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.48" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "tempfile" version = "3.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "01ce4141aa927a6d1bd34a041795abd0db1cccba5d5f24b009f694bdf3a1f3fa" dependencies = [ "cfg-if", "fastrand", "redox_syscall", "rustix", "windows-sys", ] [[package]] name = "toml" version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1a195ec8c9da26928f773888e0742ca3ca1040c6cd859c919c9f59c1954ab35" dependencies = [ "serde", "serde_spanned", "toml_datetime", "toml_edit", ] [[package]] name = "toml_datetime" version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" dependencies = [ "serde", ] [[package]] name = "toml_edit" version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d34d383cd00a163b4a5b85053df514d45bc330f6de7737edfe0a93311d1eaa03" 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 = "unicode-segmentation" version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" [[package]] name = "winapi" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" dependencies = [ "winapi-i686-pc-windows-gnu", "winapi-x86_64-pc-windows-gnu", ] [[package]] name = "winapi-i686-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[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-targets" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" dependencies = [ "windows_aarch64_gnullvm", "windows_aarch64_msvc", "windows_i686_gnu", "windows_i686_msvc", "windows_x86_64_gnu", "windows_x86_64_gnullvm", "windows_x86_64_msvc", ] [[package]] name = "windows_aarch64_gnullvm" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" [[package]] name = "windows_aarch64_msvc" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" [[package]] name = "windows_i686_gnu" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" [[package]] name = "windows_i686_msvc" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" [[package]] name = "windows_x86_64_gnu" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" [[package]] name = "windows_x86_64_gnullvm" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" [[package]] name = "windows_x86_64_msvc" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" [[package]] name = "winnow" version = "0.5.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7cf47b659b318dccbd69cc4797a39ae128f533dce7902a1096044d1967b9c16" dependencies = [ "memchr", ] cargo-config2-0.1.18/Cargo.toml0000644000000065220000000000100115460ustar # 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.18" exclude = [ "/.*", "/tools", ] 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] doc-scrape-examples = false [dependencies.serde] version = "1.0.165" [dependencies.serde_derive] version = "1.0.165" [dependencies.toml_edit] version = "0.21" 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.similar-asserts] version = "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" 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.doc_markdown] level = "allow" priority = 1 [lints.clippy.float_cmp] 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.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.rust] improper_ctypes = "warn" improper_ctypes_definitions = "warn" missing_debug_implementations = "warn" non_ascii_idents = "warn" rust_2018_idioms = "warn" single_use_lifetimes = "warn" unreachable_pub = "warn" unsafe_op_in_unsafe_fn = "warn" cargo-config2-0.1.18/Cargo.toml.orig000064400000000000000000000056321046102023000152300ustar 00000000000000[package] name = "cargo-config2" version = "0.1.18" 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.21", 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" similar-asserts = "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 https://github.com/taiki-e. # It is not intended for manual editing. [workspace.lints.rust] improper_ctypes = "warn" improper_ctypes_definitions = "warn" missing_debug_implementations = "warn" non_ascii_idents = "warn" rust_2018_idioms = "warn" single_use_lifetimes = "warn" 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" 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 doc_markdown = { level = "allow", priority = 1 } float_cmp = { level = "allow", priority = 1 } # https://github.com/rust-lang/rust-clippy/issues/7725 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 } 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 } cargo-config2-0.1.18/LICENSE-APACHE000064400000000000000000000236761046102023000142750ustar 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.18/LICENSE-MIT000064400000000000000000000017771046102023000140030ustar 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.18/README.md000064400000000000000000000063631046102023000136220ustar 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) [![rust version](https://img.shields.io/badge/rustc-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" ``` *Compiler support: requires rustc 1.70+* `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.18/examples/get.rs000064400000000000000000000123351046102023000153020ustar 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.18/src/cfg_expr/LICENSE-APACHE000064400000000000000000000251421046102023000166470ustar 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.18/src/cfg_expr/LICENSE-MIT000064400000000000000000000020421046102023000163510ustar 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.18/src/cfg_expr/error.rs000064400000000000000000000064461046102023000164300ustar 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.18/src/cfg_expr/expr/lexer.rs000064400000000000000000000121471046102023000173670ustar 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 { // https://github.com/rust-lang/rust-clippy/issues/3307 #[allow(clippy::range_plus_one)] 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.18/src/cfg_expr/expr/mod.rs000064400000000000000000000162301046102023000170240ustar 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.18/src/cfg_expr/expr/parser.rs000064400000000000000000000251661046102023000175510ustar 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() + usize::from(key.is_some()) + usize::from(top.nest_level); 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.18/src/cfg_expr/mod.rs000064400000000000000000000007121046102023000160440ustar 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.18/src/cfg_expr/tests/eval.rs000064400000000000000000000225301046102023000173600ustar 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-wasi", "wasm64-unknown-unknown", ] { assert!(map.eval_cfg(&wasm, &target.into(), || cmd!("rustc")).unwrap(), "{target}"); } } cargo-config2-0.1.18/src/cfg_expr/tests/lexer.rs000064400000000000000000000024071046102023000175510ustar 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.18/src/cfg_expr/tests/parser.rs000064400000000000000000000122431046102023000177250ustar 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(); similar_asserts::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 }; similar_asserts::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), }; similar_asserts::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.18/src/de.rs000064400000000000000000001140011046102023000140550ustar 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, // TODO: cargo-new // TODO: http // 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)) } /// 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()) } pub(crate) fn _load_with_options( current_dir: &Path, cargo_home: Option, ) -> Result { let mut base = None; for path in crate::Walk::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, from: Self, force: bool) -> Result<()> { crate::merge::Merge::merge(self, from, 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, 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()); 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), } } 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 = build_rustflags.clone(); } 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 = build_rustflags.clone(); } 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, } // https://github.com/rust-lang/cargo/blob/0.67.0/src/cargo/util/config/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, // 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 `[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_BUILD_RUSTDOCFLAGS` /// /// And the following configs: /// /// - `target..rustflags` /// - `target..rustflags` /// - `build.rustflags` /// - `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.67.0/src/cargo/util/config/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 { s.split(' ').map(str::trim).filter(|s| !s.is_empty()) } cargo-config2-0.1.18/src/easy.rs000064400000000000000000001233511046102023000144360ustar 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, When}, error::{Context as _, Result}, process::ProcessBuilder, resolve::{ResolveContext, ResolveOptions, 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, // TODO: cargo-new // TODO: http // 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).clone())?; Self::from_unresolved(de, cx) } pub(crate) 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 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, 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 (`host`) /// /// **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 /// # fn main() -> anyhow::Result<()> { /// 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(()) } /// ``` /// /// With multi-target: /// /// ```no_run /// # fn main() -> anyhow::Result<()> { /// 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(()) } /// ``` 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, 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 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 that method. 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 that method. 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) } // 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, from: Self, force: bool) -> Result<()> { // merge::Merge::merge(self, from, 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) #[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)] pub(crate) de_rustflags: 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 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; Self { jobs, rustc, rustc_wrapper, rustc_workspace_wrapper, rustdoc, target, target_dir, rustflags, rustdocflags, incremental, dep_info_basedir, override_target_rustflags, de_rustflags, } } } // https://github.com/rust-lang/cargo/blob/0.67.0/src/cargo/util/config/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, // TODO: links: https://doc.rust-lang.org/nightly/cargo/reference/config.html#targettriplelinks } impl TargetConfig { pub(crate) 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() }); Self { linker, runner, rustflags } } } /// 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 { pub(crate) 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 { pub(crate) 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 { pub(crate) fn from_unresolved(de: de::FutureIncompatReportConfig) -> Self { let frequency = de.frequency.map(|v| v.val); Self { frequency } } } /// 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 { pub(crate) 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 { pub(crate) 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 { pub(crate) 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 { pub(crate) 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 { pub(crate) 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 and rustdocflags. #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize)] #[serde(transparent)] #[non_exhaustive] pub struct Flags { pub flags: Vec, } 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`](Self::encode). pub fn from_encoded(s: &str) -> Self { Self { flags: split_encoded(s).map(str::to_owned).collect() } } /// 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_BUILD_RUSTDOCFLAGS` /// /// And the following configs: /// /// - `target..rustflags` /// - `target..rustflags` /// - `build.rustflags` /// - `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 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 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 with space (' '). /// /// This is a valid format for the following environment variables: /// /// - `RUSTFLAGS` /// - `CARGO_TARGET__RUSTFLAGS` /// - `CARGO_BUILD_RUSTFLAGS` /// - `RUSTDOCFLAGS` /// - `CARGO_BUILD_RUSTDOCFLAGS` /// /// And the following configs: /// /// - `target..rustflags` /// - `target..rustflags` /// - `build.rustflags` /// - `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. 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 { let mut cmd = Command::new(value.path); cmd.args(value.args); cmd } } impl From 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 { pub(crate) fn from_string(value: &str) -> Self { Self { list: split_space_separated(value).map(str::to_owned).collect() } } pub(crate) fn from_array(list: Vec) -> Self { Self { list } } pub(crate) 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.18/src/env.rs000064400000000000000000000373261046102023000142730ustar 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, Config, DocConfig, Flags, FutureIncompatReportConfig, 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.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. build.rustdocflags (CARGO_BUILD_RUSTDOCFLAGS) // https://doc.rust-lang.org/nightly/cargo/reference/config.html#buildrustdocflags if let Some(rustdocflags) = cx.env("CARGO_ENCODED_RUSTDOCFLAGS")? { self.rustdocflags = Some(Flags::from_encoded(&rustdocflags)); } else if let Some(rustdocflags) = cx.env("RUSTDOCFLAGS")? { self.rustdocflags = Some(Flags::from_space_separated( &rustdocflags.val, rustdocflags.definition.as_ref(), )); } 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.67.0/src/cargo/ops/cargo_doc.rs#L52-L53 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 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_string(), definition: None }); config.rustc_workspace_wrapper = Some(Value { val: "rustc_workspace_wrapper".to_string(), definition: None }); config.apply_env(cx).unwrap(); assert!(config.rustc_wrapper.is_none()); assert!(config.rustc_workspace_wrapper.is_none()); } } cargo-config2-0.1.18/src/error.rs000064400000000000000000000131571046102023000146300ustar 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.18/src/gen/assert_impl.rs000064400000000000000000000313341046102023000165670ustar 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(clippy::std_instead_of_alloc, clippy::std_instead_of_core)] #[allow(dead_code)] fn assert_send() {} #[allow(dead_code)] fn assert_sync() {} #[allow(dead_code)] fn assert_unpin() {} #[allow(dead_code)] fn assert_unwind_safe() {} #[allow(dead_code)] fn assert_ref_unwind_safe() {} #[allow(unused_imports)] use core::marker::PhantomPinned; /// `Send` & `!Sync` #[allow(dead_code)] struct NotSync(core::cell::UnsafeCell<()>); /// `!Send` & `!Sync` #[allow(dead_code)] struct NotSendSync(*const ()); /// `!UnwindSafe` #[allow(dead_code)] struct NotUnwindSafe(&'static mut ()); /// `!RefUnwindSafe` #[allow(dead_code)] struct NotRefUnwindSafe(core::cell::UnsafeCell<()>); #[allow(unused_macros)] macro_rules! assert_not_send { ($ty:ty) => { static_assertions::assert_not_impl_all!($ty : Send); }; } #[allow(unused_macros)] macro_rules! assert_not_sync { ($ty:ty) => { static_assertions::assert_not_impl_all!($ty : Sync); }; } #[allow(unused_macros)] macro_rules! assert_not_unpin { ($ty:ty) => { static_assertions::assert_not_impl_all!($ty : Unpin); }; } #[allow(unused_macros)] macro_rules! assert_not_unwind_safe { ($ty:ty) => { static_assertions::assert_not_impl_all!($ty : std::panic::UnwindSafe); }; } #[allow(unused_macros)] 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_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_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_send::>(); assert_not_send!(crate::value::Value); assert_sync::>(); assert_not_sync!(crate::value::Value); 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.18/src/gen/de.rs000064400000000000000000000163001046102023000146310ustar 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, from: Self, force: bool) -> Result<()> { self.alias.merge(from.alias, force)?; self.build.merge(from.build, force)?; self.doc.merge(from.doc, force)?; self.env.merge(from.env, force)?; self.future_incompat_report.merge(from.future_incompat_report, force)?; self.net.merge(from.net, force)?; self.registries.merge(from.registries, force)?; self.registry.merge(from.registry, force)?; self.target.merge(from.target, force)?; self.term.merge(from.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.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, from: Self, force: bool) -> Result<()> { self.jobs.merge(from.jobs, force)?; self.rustc.merge(from.rustc, force)?; self.rustc_wrapper.merge(from.rustc_wrapper, force)?; self.rustc_workspace_wrapper.merge(from.rustc_workspace_wrapper, force)?; self.rustdoc.merge(from.rustdoc, force)?; self.target.merge(from.target, force)?; self.target_dir.merge(from.target_dir, force)?; self.rustflags.merge(from.rustflags, force)?; self.rustdocflags.merge(from.rustdocflags, force)?; self.incremental.merge(from.incremental, force)?; self.dep_info_basedir.merge(from.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, from: Self, force: bool) -> Result<()> { self.linker.merge(from.linker, force)?; self.runner.merge(from.runner, force)?; self.rustflags.merge(from.rustflags, 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); } } impl Merge for crate::de::DocConfig { fn merge(&mut self, from: Self, force: bool) -> Result<()> { self.browser.merge(from.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, from: Self, force: bool) -> Result<()> { self.frequency.merge(from.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::NetConfig { fn merge(&mut self, from: Self, force: bool) -> Result<()> { self.retry.merge(from.retry, force)?; self.git_fetch_with_cli.merge(from.git_fetch_with_cli, force)?; self.offline.merge(from.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, from: Self, force: bool) -> Result<()> { self.index.merge(from.index, force)?; self.token.merge(from.token, force)?; self.protocol.merge(from.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, from: Self, force: bool) -> Result<()> { self.default.merge(from.default, force)?; self.token.merge(from.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, from: Self, force: bool) -> Result<()> { self.quiet.merge(from.quiet, force)?; self.verbose.merge(from.verbose, force)?; self.color.merge(from.color, force)?; self.progress.merge(from.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, from: Self, force: bool) -> Result<()> { self.when.merge(from.when, force)?; self.width.merge(from.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.18/src/gen/is_none.rs000064400000000000000000000056271046102023000157050ustar 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::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::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.18/src/lib.rs000064400000000000000000000055341046102023000142450ustar 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_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, When}; pub use crate::{ easy::{ BuildConfig, Config, DocConfig, EnvConfigValue, Flags, FutureIncompatReportConfig, NetConfig, PathAndArgs, RegistriesConfigValue, RegistryConfig, StringList, TargetConfig, TermConfig, TermProgressConfig, }, error::Error, resolve::{ResolveOptions, TargetTriple, TargetTripleRef}, walk::Walk, }; cargo-config2-0.1.18/src/merge.rs000064400000000000000000000147631046102023000146020ustar 00000000000000// SPDX-License-Identifier: Apache-2.0 OR MIT use std::collections::{btree_map, BTreeMap}; use crate::{ de::{self, RegistriesProtocol}, error::{Context as _, Result}, value::Value, Color, Frequency, When, }; // https://github.com/rust-lang/cargo/blob/0.67.0/src/cargo/util/config/mod.rs#L1900-L1908 // // > 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, from: Self, force: bool) -> Result<()>; } macro_rules! merge_non_container { ($($ty:tt)*) => { impl Merge for $($ty)* { fn merge(&mut self, from: Self, force: bool) -> Result<()> { if force { *self = from; } Ok(()) } } impl Merge for Value<$($ty)*> { fn merge(&mut self, from: Self, force: bool) -> Result<()> { if force { *self = from; } Ok(()) } } }; } merge_non_container!(bool); merge_non_container!(i32); merge_non_container!(u32); merge_non_container!(String); merge_non_container!(Color); merge_non_container!(Frequency); merge_non_container!(When); merge_non_container!(RegistriesProtocol); impl Merge for Option { fn merge(&mut self, from: Self, force: bool) -> Result<()> { match (self, from) { (_, None) => {} (this @ None, from) => *this = from, (Some(this), Some(from)) => this.merge(from, force)?, } Ok(()) } } impl Merge for de::StringOrArray { fn merge(&mut self, from: Self, force: bool) -> Result<()> { match (self, from) { (this @ de::StringOrArray::String(_), from @ de::StringOrArray::String(_)) => { if force { *this = from; } } (de::StringOrArray::Array(this), de::StringOrArray::Array(mut from)) => { this.append(&mut from); } (expected, actual) => { bail!("expected {}, but found {}", expected.kind(), actual.kind()); } } Ok(()) } } impl Merge for de::PathAndArgs { fn merge(&mut self, mut from: Self, force: bool) -> Result<()> { match (self.deserialized_repr, from.deserialized_repr) { (de::StringListDeserializedRepr::String, de::StringListDeserializedRepr::String) => { if force { *self = from; } } (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(from.path.0); self.args.append(&mut from.args); } (expected, actual) => { bail!("expected {}, but found {}", expected.as_str(), actual.as_str()); } } Ok(()) } } impl Merge for de::StringList { fn merge(&mut self, mut from: Self, force: bool) -> Result<()> { match (self.deserialized_repr, from.deserialized_repr) { (de::StringListDeserializedRepr::String, de::StringListDeserializedRepr::String) => { if force { *self = from; } } (de::StringListDeserializedRepr::Array, de::StringListDeserializedRepr::Array) => { self.list.append(&mut from.list); } (expected, actual) => { bail!("expected {}, but found {}", expected.as_str(), actual.as_str()); } } Ok(()) } } impl Merge for de::EnvConfigValue { fn merge(&mut self, from: Self, force: bool) -> Result<()> { match (self, from) { (Self::Value(this), Self::Value(from)) => { if force { *this = from; } } ( Self::Table { value: this_value, force: this_force, relative: this_relative }, Self::Table { value: from_value, force: from_force, relative: from_relative }, ) => { this_value.merge(from_value, force)?; this_force.merge(from_force, force)?; this_relative.merge(from_relative, force)?; } (expected, actual) => { bail!("expected {}, but found {}", expected.kind(), actual.kind()); } } Ok(()) } } impl Merge for de::Flags { fn merge(&mut self, mut from: Self, force: bool) -> Result<()> { match (self.deserialized_repr, from.deserialized_repr) { (de::StringListDeserializedRepr::String, de::StringListDeserializedRepr::String) => { if force { *self = from; } } (de::StringListDeserializedRepr::Array, de::StringListDeserializedRepr::Array) => { self.flags.append(&mut from.flags); } (expected, actual) => { bail!("expected {}, but found {}", expected.as_str(), actual.as_str()); } } Ok(()) } } impl Merge for BTreeMap { fn merge(&mut self, from: Self, force: bool) -> Result<()> { for (key, value) in from { 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.18/src/process.rs000064400000000000000000000074061046102023000151550ustar 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.18/src/resolve.rs000064400000000000000000000547421046102023000151630ustar 00000000000000// SPDX-License-Identifier: Apache-2.0 OR MIT use core::{ cell::{OnceCell, RefCell}, hash::Hash, 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, 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, cfg: RefCell, pub(crate) current_dir: PathBuf, } impl ResolveContext { pub(crate) fn rustc(&self, build_config: &easy::BuildConfig) -> &PathAndArgs { self.rustc.get_or_init(|| { // TODO: Update comment based on https://github.com/rust-lang/cargo/pull/10896? // The following priorities are not documented, but at as of cargo // 1.63.0-nightly (2022-05-31), `RUSTC_WRAPPER` is preferred over `RUSTC_WORKSPACE_WRAPPER`. // See also https://github.com/taiki-e/cargo-llvm-cov/pull/180#discussion_r887904341. let rustc = build_config.rustc.as_ref().map_or_else(|| rustc_path(&self.cargo), PathBuf::from); match build_config .rustc_wrapper .as_ref() .or(build_config.rustc_workspace_wrapper.as_ref()) { // The wrapper's first argument is supposed to be the path to rustc. Some(wrapper) => { PathAndArgs { path: wrapper.clone(), args: vec![rustc.into_os_string()] } } None => PathAndArgs { path: rustc, args: vec![] }, } }) } 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 host = match host_triple(&self.cargo) { Ok(host) => host, Err(_) => { let rustc = build_config .rustc .as_ref() .map_or_else(|| rustc_path(&self.cargo), PathBuf::from); host_triple(rustc.as_os_str())? } }; Ok(self.host_triple.get_or_init(|| host)) } // 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).clone().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) -> core::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)) } } /// Gets host triple of the given `rustc` or `cargo`. pub(crate) fn host_triple(rustc_or_cargo: &OsStr) -> Result { let mut cmd = cmd!(rustc_or_cargo, "--version", "--verbose"); let verbose_version = cmd.read()?; 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) } pub(crate) 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 fs_err as fs; use super::*; fn fixtures_path() -> &'static Path { Path::new(concat!(env!("CARGO_MANIFEST_DIR"), "/tests/fixtures")) } #[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_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}"); } } } #[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.18/src/value.rs000064400000000000000000000116031046102023000146050ustar 00000000000000// SPDX-License-Identifier: Apache-2.0 OR MIT // Based on https://github.com/rust-lang/cargo/blob/0.67.0/src/cargo/util/config/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) => write!(f, "--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.18/src/walk.rs000064400000000000000000000111551046102023000144310ustar 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 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/b2e1d3b6235c07221dd0fcac54a7b0c754ef8b11/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/b2e1d3b6235c07221dd0fcac54a7b0c754ef8b11/crates/home/src/lib.rs#L77-L86 // https://github.com/rust-lang/cargo/blob/b2e1d3b6235c07221dd0fcac54a7b0c754ef8b11/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 struct Walk<'a> { ancestors: std::path::Ancestors<'a>, cargo_home: Option, } 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 { ancestors: current_dir.ancestors(), cargo_home } } } impl Iterator for Walk<'_> { 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_ref() == Some(&p) { self.cargo_home = None; } if let Some(p) = config_path(&p) { return Some(p); } } config_path(&self.cargo_home.take()?) } } #[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.18/tests/fixtures/target-specs/avr-unknown-gnu-atmega2560.json000064400000000000000000000010311046102023000254620ustar 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.18/tests/helper/mod.rs000064400000000000000000000041061046102023000161020ustar 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.18/tests/test.rs000064400000000000000000000263051046102023000150300ustar 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")); // TODO // 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)); // TODO // [cargo-new] // TODO // [http] // 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.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())); // 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(()) }