diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6dbcbf68..1daade7f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -169,7 +169,7 @@ jobs: # # Also make sure to update the MSRV in the cargo-semver-checks-action CI: # https://github.com/obi1kenobi/cargo-semver-checks-action/blob/main/.github/workflows/test-action.yml#L18 - toolchain: ["1.80", "1.81", "stable", "beta"] + toolchain: ["1.81", "1.82", "stable", "beta"] experimental: [false] include: - toolchain: "nightly" @@ -194,7 +194,7 @@ jobs: uses: actions/cache@v4 with: path: semver/localdata/test_data/ - key: test-rustdocs-${{ runner.os }}-${{ steps.toolchain.outputs.cachekey }}-${{ hashFiles('semver/test_crates/**/*.rs') }} + key: test-rustdocs-and-meta-${{ runner.os }}-${{ steps.toolchain.outputs.cachekey }}-${{ hashFiles('semver/test_crates/**/*.rs') }} - name: Regenerate test data if: steps.cache-test-rustdocs.outputs.cache-hit != 'true' @@ -406,11 +406,17 @@ jobs: # Show what's in the cache. ls subject-new/target/semver-checks/cache/ - EXPECTED_CACHED_RUSTDOC='subject-new/target/semver-checks/cache/libp2p_core-0_37_0-ccce455725cbab73.json' + EXPECTED_PREFIX='subject-new/target/semver-checks/cache/libp2p_core-0_37_0-ccce455725cbab73' + EXPECTED_CACHED_METADATA="${EXPECTED_PREFIX}.metadata.json" + EXPECTED_CACHED_RUSTDOC="${EXPECTED_PREFIX}.json" - # Ensure the previous cached rustdoc file still exists. + # Ensure the previous cached rustdoc and metadata files still exist. + [ -f "$EXPECTED_CACHED_METADATA" ] || { \ + echo "could not find libp2p-core metadata cache file"; \ + exit 1; + } [ -f "$EXPECTED_CACHED_RUSTDOC" ] || { \ - echo "could not find libp2p-core cache file"; \ + echo "could not find libp2p-core rustdoc cache file"; \ exit 1; } @@ -527,20 +533,30 @@ jobs: ls subject/target/semver-checks/cache/ # The previous cached rustdoc should continue to exist. - if [ -f subject/target/semver-checks/cache/serde-1_0_162-5900ebf8bb9b9f8b.json ]; then - exit 0 - else + PREVIOUS_PREFIX='subject/target/semver-checks/cache/serde-1_0_162-5900ebf8bb9b9f8b' + PREVIOUS_RUSTDOC="${PREVIOUS_PREFIX}.json" + PREVIOUS_METADATA="${PREVIOUS_PREFIX}.metadata.json" + if [ ! -f "$PREVIOUS_RUSTDOC" ]; then echo "Older rustdoc JSON not found in cache!" exit 1 fi + if [ ! -f "$PREVIOUS_METADATA" ]; then + echo "Older metadata file not found in cache!" + exit 1 + fi # There should also be a new cached rustdoc file for the new feature settings. - if [ -f subject/target/semver-checks/cache/serde-1_0_162-6999ae87ca463ab3.json ]; then - exit 0 - else + NEW_PREFIX='subject/target/semver-checks/cache/serde-1_0_162-6999ae87ca463ab3' + NEW_RUSTDOC="${NEW_PREFIX}.json" + NEW_METADATA="${NEW_PREFIX}.metadata.json" + if [ ! -f "$NEW_RUSTDOC" ]; then echo "New rustdoc JSON for new feature combo not found in cache!" exit 1 fi + if [ ! -f "$NEW_METADATA" ]; then + echo "New metadata file for new feature combo not found in cache!" + exit 1 + fi - name: Cleanup run: | @@ -1767,7 +1783,7 @@ jobs: id: toolchain uses: actions-rust-lang/setup-rust-toolchain@v1 with: - toolchain: "1.79" + toolchain: "1.80" rustflags: "" cache: false @@ -1810,7 +1826,7 @@ jobs: - name: Check output run: | cd semver - EXPECTED="$(echo -e "error: rustc version is not high enough: >=1.80.0 needed, got 1.79.0")" + EXPECTED="$(echo -e "error: rustc version is not high enough: >=1.81.0 needed, got 1.80.1")" RESULT="$(cat output | grep 'error: rustc version')" diff <(echo "$RESULT") <(echo "$EXPECTED") diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9bdfebb3..81c4e8eb 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -111,7 +111,7 @@ To generate this data, please run `./scripts/regenerate_test_rustdocs.sh`. To use a specific toolchain, like beta or nightly, pass it as an argument: `./scripts/regenerate_test_rustdocs.sh +nightly`. -## What are those `.snap` or `.snap.new` files generated via `cargo test` +## What are those `.snap` or `.snap.new` files generated via `cargo test` As part of our overall testing strategy, we use a technique called "snapshot testing." The tool we use for this ([`insta`](https://insta.rs/docs/)) is user friendly and has mutliple ways to interact with it: @@ -138,7 +138,7 @@ Reviewing them is possible via these options: ``` 3. **Manually**: If you can't (or don't want to) use `cargo-insta`, you can verify the snapshot file manually. There should be a file called `test_outputs//.snap.new`. - Open it, and verify that its contents match what you expected: all expected data is present, and no unexpected data is included. + Open it, and verify that its contents match what you expected: all expected data is present, and no unexpected data is included. Once you've checked it, remove the `.new` suffix so that the file's new path is `test_outputs//.snap` @@ -172,14 +172,16 @@ We'll use the [`scripts/make_new_lint.sh`](https://github.com/obi1kenobi/cargo-s Now it's time to fill in these files! - Define the lint in `src/lints/.ron`. -- Make sure your lint outputs `span_filename` and `span_begin_line` for it - to be a valid lint. The pattern we commonly use is: +- For almost all lints, make sure your lint outputs `span_filename` and `span_begin_line` + in order to be a valid lint. The pattern we commonly use is: ``` span_: span @optional { filename @output begin_line @output } ``` +
Exception: lints over Cargo.toml information (click to expand)If you are writing a lint over manifest (Cargo.toml) information such as "a feature was deleted," you won't be able to find span data from the manifest file. To proceed, pick a value unique to each result produced by your lint query and output it as ordering_key instead.
+ - Demonstrate the semver issue your lint is looking for by adding suitable code in the `test_crates//old` and `test_crates//new` crates. - Add code to the test crates that aims to catch for false-positives @@ -269,8 +271,8 @@ Inspect the generated "actual" output in the `.snap.new` file: If so, ensure the reported code is indeed violating semver and is not being flagged by any other lint. -If everything looks okay, either run `cargo insta review` (see -the [snapshot instructions](#what-are-those-snap-or-snapnew-files-generated-via-cargo-test) +If everything looks okay, either run `cargo insta review` (see +the [snapshot instructions](#what-are-those-snap-or-snapnew-files-generated-via-cargo-test) for context) or manually move `test_outputs/query_execution/.snap.new` to `test_outputs/query_execution/.snap`. Then re-run `cargo test` and make sure everything passes. @@ -342,7 +344,7 @@ to update the test outputs. > It may contain output for other test crates — this is not necessarily an error: > See the [troubleshooting section](#troubleshooting) for more info. -To update the output, please refer to the section +To update the output, please refer to the section on [snapshot testing](#what-are-those-snap-or-snapnew-files-generated-via-cargo-test) Once you've update the test output, run `cargo test` again and the `` test should pass! @@ -356,14 +358,14 @@ otherwise the test will fail in CI. ### Troubleshooting -
A valid query must output span_filename and/or span_begin_line (click to expand) - + If your lint fails with an error similar to the following: ``` ---- query::tests_lints::enum_missing stdout ---- thread 'query::tests_lints::enum_missing' panicked at 'A valid query must output both `span_filename` and `span_begin_line`. See https://github.com/obi1kenobi/cargo-semver-checks/blob/main/CONTRIBUTING.md for how to do this.', src/query.rs:395:26 note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace ``` - + It likely means that your lint does not specify the `span_filename` and `span_begin_line` of where the error occurs. To fix this, add the following to the part of query that catches the error: ``` span_: span @optional { @@ -371,25 +373,25 @@ otherwise the test will fail in CI. begin_line @output } ``` - +
-
Other lints' tests failed too (click to expand) This is not always a problem! In process of testing a lint, it's frequently desirable to include test code that contains a related semver issue in order to ensure the lint differentiates between them. - + For example, say one is testing a lint for pub field removals from a struct. Its test crate code may then include removals of the entire struct, in order to make sure that the lint *does not* report those. But those struct removals *will* get reported by the lint that looks for semver violations due to struct removal! - + So if you added code to a test crate, and it caused other lints to report new findings, consider: - whether your code indeed contains the reported semver issue; - whether the same semver issue is being reported only once, and not multiple times by different lints, - and whether the new reported lint result points to the correct item and span information. - + If the answer to all is yes, then everything is fine! Just edit those other lints' expected output files to include the new items, and you can get back on track.
diff --git a/Cargo.lock b/Cargo.lock index d5d3c39c..6a5da65f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -40,9 +40,9 @@ dependencies = [ [[package]] name = "allocator-api2" -version = "0.2.20" +version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45862d1c77f2228b9e10bc609d5bc203d86ebc9b87ad8d5d5167a6c9abf739d9" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" [[package]] name = "anstream" @@ -129,9 +129,9 @@ dependencies = [ [[package]] name = "async-compression" -version = "0.4.17" +version = "0.4.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cb8f1d480b0ea3783ab015936d2a55c87e219676f0c0b7dec61494043f21857" +checksum = "df895a515f70646414f4b45c0b79082783b80552b373a68283012928df56f522" dependencies = [ "flate2", "futures-core", @@ -176,7 +176,7 @@ version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" dependencies = [ - "hermit-abi 0.1.19", + "hermit-abi", "libc", "winapi", ] @@ -289,9 +289,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.8.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ac0150caa2ae65ca5bd83f25c7de183dea78d4d366469f148435e2acfbad0da" +checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" dependencies = [ "serde", ] @@ -319,16 +319,16 @@ dependencies = [ [[package]] name = "cargo-platform" -version = "0.1.8" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24b1f0365a6c6bb4020cd05806fd0d33c44d38046b8bd7f0e40814b9763cabfc" +checksum = "e35af189006b9c0f00a064685c727031e3ed2d8020f7ba284d78cc2671bd36ea" dependencies = [ "serde", ] [[package]] name = "cargo-semver-checks" -version = "0.36.0" +version = "0.37.0" dependencies = [ "anstream", "anstyle", @@ -395,9 +395,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.1" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd9de9f2205d5ef3fd67e685b0df337994ddd4495e2a28d185500d0e1edfea47" +checksum = "f34d93e62b03caf570cccc334cbc6c2fceca82f39211051345108adcba3eebdc" dependencies = [ "shlex", ] @@ -517,9 +517,9 @@ checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "cpufeatures" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ca741a962e1b0bff6d724a1a0958b686406e853bb14061f218562e1896f95e6" +checksum = "16b80225097f2e5ae4e7179dd2266824648f3e2f49d9134d584b76389d31c4c3" dependencies = [ "libc", ] @@ -696,12 +696,12 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" -version = "0.3.9" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" +checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -954,27 +954,27 @@ dependencies = [ [[package]] name = "gix-bitmap" -version = "0.2.12" +version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10f78312288bd02052be5dbc2ecbc342c9f4eb791986d86c0a5c06b92dc72efa" +checksum = "d48b897b4bbc881aea994b4a5bbb340a04979d7be9089791304e04a9fbc66b53" dependencies = [ - "thiserror 1.0.69", + "thiserror 2.0.3", ] [[package]] name = "gix-chunk" -version = "0.4.9" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c28b58ba04f0c004722344390af9dbc85888fbb84be1981afb934da4114d4cf" +checksum = "c6ffbeb3a5c0b8b84c3fe4133a6f8c82fa962f4caefe8d0762eced025d3eb4f7" dependencies = [ - "thiserror 1.0.69", + "thiserror 2.0.3", ] [[package]] name = "gix-command" -version = "0.3.10" +version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c201d2b9e9cce2365a6638fd0a966f751ed92d74be5c0727ac331e6a29ef5846" +checksum = "6d7d6b8f3a64453fd7e8191eb80b351eb7ac0839b40a1237cd2c137d5079fe53" dependencies = [ "bstr", "gix-path", @@ -1019,15 +1019,15 @@ dependencies = [ [[package]] name = "gix-config-value" -version = "0.14.9" +version = "0.14.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3de3fdca9c75fa4b83a76583d265fa49b1de6b088ebcd210749c24ceeb74660" +checksum = "49aaeef5d98390a3bcf9dbc6440b520b793d1bf3ed99317dc407b02be995b28e" dependencies = [ "bitflags 2.6.0", "bstr", "gix-path", "libc", - "thiserror 1.0.69", + "thiserror 2.0.3", ] [[package]] @@ -1049,14 +1049,14 @@ dependencies = [ [[package]] name = "gix-date" -version = "0.9.1" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d10d543ac13c97292a15e8e8b7889cd006faf739777437ed95362504b8fe81a0" +checksum = "691142b1a34d18e8ed6e6114bc1a2736516c5ad60ef3aa9bd1b694886e3ca92d" dependencies = [ "bstr", "itoa", "jiff", - "thiserror 1.0.69", + "thiserror 2.0.3", ] [[package]] @@ -1328,15 +1328,15 @@ dependencies = [ [[package]] name = "gix-path" -version = "0.10.12" +version = "0.10.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c04e5a94fdb56b1e91eb7df2658ad16832428b8eeda24ff1a0f0288de2bce554" +checksum = "afc292ef1a51e340aeb0e720800338c805975724c1dfbd243185452efd8645b7" dependencies = [ "bstr", "gix-trace", "home", "once_cell", - "thiserror 1.0.69", + "thiserror 2.0.3", ] [[package]] @@ -1356,15 +1356,15 @@ dependencies = [ [[package]] name = "gix-prompt" -version = "0.8.8" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57944bbdb87f7a9893907032276e99ff4eba3640d8db1bdfb1eba8c07edfd006" +checksum = "7a7822afc4bc9c5fbbc6ce80b00f41c129306b7685cac3248dbfa14784960594" dependencies = [ "gix-command", "gix-config-value", "parking_lot", "rustix", - "thiserror 1.0.69", + "thiserror 2.0.3", ] [[package]] @@ -1387,13 +1387,13 @@ dependencies = [ [[package]] name = "gix-quote" -version = "0.4.13" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f89f9a1525dcfd9639e282ea939f5ab0d09d93cf2b90c1fc6104f1b9582a8e49" +checksum = "64a1e282216ec2ab2816cd57e6ed88f8009e634aec47562883c05ac8a7009a63" dependencies = [ "bstr", "gix-utils", - "thiserror 1.0.69", + "thiserror 2.0.3", ] [[package]] @@ -1464,9 +1464,9 @@ dependencies = [ [[package]] name = "gix-sec" -version = "0.10.9" +version = "0.10.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2007538eda296445c07949cf04f4a767307d887184d6b3e83e2d636533ddc6e" +checksum = "a8b876ef997a955397809a2ec398d6a45b7a55b4918f2446344330f778d14fd6" dependencies = [ "bitflags 2.6.0", "gix-path", @@ -1571,12 +1571,12 @@ dependencies = [ [[package]] name = "gix-validate" -version = "0.9.1" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e187b263461bc36cea17650141567753bc6207d036cedd1de6e81a52f277ff68" +checksum = "cd520d09f9f585b34b32aba1d0b36ada89ab7fefb54a8ca3fe37fc482a750937" dependencies = [ "bstr", - "thiserror 1.0.69", + "thiserror 2.0.3", ] [[package]] @@ -1613,9 +1613,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.4.6" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "524e8ac6999421f49a846c2d4411f337e53497d8ec55d67753beffa43c5d9205" +checksum = "ccae279728d634d083c00f6099cb58f01cc99c145b84b8be2f6c74618d79922e" dependencies = [ "atomic-waker", "bytes", @@ -1657,9 +1657,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.15.1" +version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a9bfc1af68b1726ea47d3d5109de126281def866b33970e10fbab11b5dafab3" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" [[package]] name = "heck" @@ -1676,12 +1676,6 @@ dependencies = [ "libc", ] -[[package]] -name = "hermit-abi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" - [[package]] name = "home" version = "0.5.9" @@ -1749,9 +1743,9 @@ dependencies = [ [[package]] name = "hyper" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbbff0a806a4728c99295b254c8838933b5b082d75e3cb70c8dab21fdfbcfa9a" +checksum = "97818827ef4f364230e16705d4706e2897df2bb60617d6ca15d598025a3c481f" dependencies = [ "bytes", "futures-channel", @@ -1961,12 +1955,12 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.6.0" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" +checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f" dependencies = [ "equivalent", - "hashbrown 0.15.1", + "hashbrown 0.15.2", "serde", ] @@ -2020,15 +2014,15 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.11" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" +checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" [[package]] name = "jiff" -version = "0.1.14" +version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9d9d414fc817d3e3d62b2598616733f76c4cc74fbac96069674739b881295c8" +checksum = "db69f08d4fb10524cacdb074c10b296299d71274ddbc830a8ee65666867002e9" dependencies = [ "jiff-tzdb-platform", "windows-sys 0.59.0", @@ -2051,10 +2045,11 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.72" +version = "0.3.74" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a88f1bda2bd75b0452a14784937d796722fdebfe50df998aeb3f0b7603019a9" +checksum = "a865e038f7f6ed956f788f0d7d60c541fff74c7bd74272c5d4cf15c63743e705" dependencies = [ + "once_cell", "wasm-bindgen", ] @@ -2075,9 +2070,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.164" +version = "0.2.167" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "433bfe06b8c75da9b2e3fbea6e5329ff87748f0b144ef75306e674c3f6f7c13f" +checksum = "09d6582e104315a817dff97f75133544b2e094ee22447d2acf4a74e189ba06fc" [[package]] name = "libredox" @@ -2116,9 +2111,9 @@ checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" [[package]] name = "litemap" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "643cb0b8d4fcc284004d5fd0d67ccf61dfffadb7f75e1e71bc420f4688a3a704" +checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104" [[package]] name = "lock_api" @@ -2185,11 +2180,10 @@ dependencies = [ [[package]] name = "mio" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" +checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" dependencies = [ - "hermit-abi 0.3.9", "libc", "wasi", "windows-sys 0.52.0", @@ -2266,9 +2260,9 @@ checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" [[package]] name = "os_info" -version = "3.8.2" +version = "3.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae99c7fa6dd38c7cafe1ec085e804f8f555a2f8659b0dbe03f1f9963a9b51092" +checksum = "e5ca711d8b83edbb00b44d504503cd247c9c0bd8b0fa2694f2a1a3d8165379ce" dependencies = [ "log", "serde", @@ -2408,9 +2402,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.89" +version = "1.0.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e" +checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" dependencies = [ "unicode-ident", ] @@ -2675,9 +2669,9 @@ checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" [[package]] name = "rustc-hash" -version = "2.0.0" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "583034fd73374156e66797ed8e5b0d5690409c9226b22d87cb7f19821c05d152" +checksum = "c7fb8039b3032c191086b10f11f319a6e99e1e82889c5cc6046f515c9db1d497" [[package]] name = "rustc_version" @@ -2742,11 +2736,20 @@ dependencies = [ "serde", ] +[[package]] +name = "rustdoc-types" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e33060dbec9e1d13d285c4cddc150a431569be97f33bf0b6c1ec6eea934c31ca" +dependencies = [ + "serde", +] + [[package]] name = "rustix" -version = "0.38.40" +version = "0.38.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99e4ea3e1cdc4b559b8e5650f9c8e5998e3e5c1343b4eaf034565f32318d63c0" +checksum = "d7f649912bc1495e167a6edee79151c84b1bad49748cb4f1f1167f459f6224f6" dependencies = [ "bitflags 2.6.0", "errno", @@ -2757,9 +2760,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.17" +version = "0.23.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f1a745511c54ba6d4465e8d5dfbd81b45791756de28d4981af70d6dca128f1e" +checksum = "934b404430bb06b3fae2cba809eb45a1ab1aecd64491213d7c3301b88393f8d1" dependencies = [ "once_cell", "ring", @@ -2815,9 +2818,9 @@ dependencies = [ [[package]] name = "schannel" -version = "0.1.26" +version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01227be5826fa0690321a2ba6c5cd57a19cf3f6a09e76973b58e61de6ab9d1c1" +checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" dependencies = [ "windows-sys 0.59.0", ] @@ -2976,9 +2979,9 @@ dependencies = [ [[package]] name = "socket2" -version = "0.5.7" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" +checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8" dependencies = [ "libc", "windows-sys 0.52.0", @@ -3016,9 +3019,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" -version = "2.0.87" +version = "2.0.90" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d" +checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31" dependencies = [ "proc-macro2", "quote", @@ -3027,9 +3030,9 @@ dependencies = [ [[package]] name = "sync_wrapper" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" dependencies = [ "futures-core", ] @@ -3284,9 +3287,9 @@ checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" [[package]] name = "tracing" -version = "0.1.40" +version = "0.1.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" dependencies = [ "pin-project-lite", "tracing-core", @@ -3294,9 +3297,9 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.32" +version = "0.1.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" dependencies = [ "once_cell", ] @@ -3314,10 +3317,12 @@ dependencies = [ [[package]] name = "trustfall-rustdoc-adapter" -version = "30.2.1" +version = "30.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3c4c364305bfcfaf34b91265ccd953bf78695efa5320f5159a3f59509af9595" +checksum = "8d5cbd647bc404a1c04abcc5ab673a61b7f996da0c3a65bf282e6dffc00fed7d" dependencies = [ + "cargo_metadata", + "cargo_toml", "rayon", "rustc-hash", "rustdoc-types 0.26.0", @@ -3326,10 +3331,12 @@ dependencies = [ [[package]] name = "trustfall-rustdoc-adapter" -version = "32.2.1" +version = "32.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63180270e2426595e6b7903cc37e80d3921130c576ccc2698cf69cee3cf17dd2" +checksum = "fb4a6d11d915cb39265771c547a0e2d5d446ce26345dfc3d2832f6d4f26f229d" dependencies = [ + "cargo_metadata", + "cargo_toml", "rayon", "rustc-hash", "rustdoc-types 0.28.1", @@ -3338,10 +3345,12 @@ dependencies = [ [[package]] name = "trustfall-rustdoc-adapter" -version = "33.2.1" +version = "33.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75fdf1c0bd46e0e5a8401b7fd665ab54e74a24336b4ac18ca655363020b901d6" +checksum = "9339448bef1aaf543b26acd1aa15d9a979f81e96b7acafae7c189f94f176bdb4" dependencies = [ + "cargo_metadata", + "cargo_toml", "rayon", "rustc-hash", "rustdoc-types 0.29.1", @@ -3350,10 +3359,12 @@ dependencies = [ [[package]] name = "trustfall-rustdoc-adapter" -version = "34.1.1" +version = "34.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81dd0771f37413b05c3c38a5741cc4ac62516923a27e49425d4bf3f2081db4a8" +checksum = "f1ee1cc32494489876a0ab7c242889e79150a0a189e4b1d471993a74ad82ab8a" dependencies = [ + "cargo_metadata", + "cargo_toml", "rayon", "rustc-hash", "rustdoc-types 0.30.0", @@ -3362,10 +3373,12 @@ dependencies = [ [[package]] name = "trustfall-rustdoc-adapter" -version = "35.1.1" +version = "35.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1a7b50e069c411a8840c95a97ded728fe64d07b06247f66cbeb9e0fe507ca95" +checksum = "499001e412ad753a1a344d751b5c99f53a3fdeaed2a5b91357174c2b4360470f" dependencies = [ + "cargo_metadata", + "cargo_toml", "rayon", "rustc-hash", "rustdoc-types 0.31.0", @@ -3374,16 +3387,32 @@ dependencies = [ [[package]] name = "trustfall-rustdoc-adapter" -version = "36.1.1" +version = "36.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "331b183e6bbd654d46dcacb1184cee4d00b14e6547625492e84c21c8e8f36f6e" +checksum = "643f0d9ea888721de62eac48f7838da7c7f6933e92c4152c5ffd8e785c88574f" dependencies = [ + "cargo_metadata", + "cargo_toml", "rayon", "rustc-hash", "rustdoc-types 0.32.2", "trustfall", ] +[[package]] +name = "trustfall-rustdoc-adapter" +version = "37.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3881686448ceb86978de52ed8da1989439df3289eda04c841a6fdb7b6f7b7b2" +dependencies = [ + "cargo_metadata", + "cargo_toml", + "rayon", + "rustc-hash", + "rustdoc-types 0.33.0", + "trustfall", +] + [[package]] name = "trustfall_core" version = "0.8.0" @@ -3414,20 +3443,23 @@ dependencies = [ [[package]] name = "trustfall_rustdoc" -version = "0.17.0" +version = "0.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8821319f597e497392fbedff6309aba078fcb4bd5f0530a67e722c3212bf4546" +checksum = "dd5ed08e834dc55a2837376b7093868b4f5dea37972368c2db658f332d1f07e5" dependencies = [ "anyhow", + "cargo_metadata", "serde", "serde_json", + "thiserror 2.0.3", "trustfall", - "trustfall-rustdoc-adapter 30.2.1", - "trustfall-rustdoc-adapter 32.2.1", - "trustfall-rustdoc-adapter 33.2.1", - "trustfall-rustdoc-adapter 34.1.1", - "trustfall-rustdoc-adapter 35.1.1", - "trustfall-rustdoc-adapter 36.1.1", + "trustfall-rustdoc-adapter 30.3.0", + "trustfall-rustdoc-adapter 32.3.0", + "trustfall-rustdoc-adapter 33.3.0", + "trustfall-rustdoc-adapter 34.2.0", + "trustfall-rustdoc-adapter 35.2.0", + "trustfall-rustdoc-adapter 36.2.0", + "trustfall-rustdoc-adapter 37.0.0", ] [[package]] @@ -3475,9 +3507,9 @@ checksum = "7eec5d1121208364f6793f7d2e222bf75a915c19557537745b195b253dd64217" [[package]] name = "unicode-ident" -version = "1.0.13" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" +checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" [[package]] name = "unicode-normalization" @@ -3502,9 +3534,9 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "url" -version = "2.5.3" +version = "2.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d157f1b96d14500ffdc1f10ba712e780825526c03d9a49b4d0324b0d9113ada" +checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" dependencies = [ "form_urlencoded", "idna", @@ -3592,9 +3624,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.95" +version = "0.2.97" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "128d1e363af62632b8eb57219c8fd7877144af57558fb2ef0368d0087bddeb2e" +checksum = "d15e63b4482863c109d70a7b8706c1e364eb6ea449b201a76c5b89cedcec2d5c" dependencies = [ "cfg-if", "once_cell", @@ -3603,9 +3635,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.95" +version = "0.2.97" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb6dd4d3ca0ddffd1dd1c9c04f94b868c37ff5fac97c30b97cff2d74fce3a358" +checksum = "8d36ef12e3aaca16ddd3f67922bc63e48e953f126de60bd33ccc0101ef9998cd" dependencies = [ "bumpalo", "log", @@ -3618,21 +3650,22 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.45" +version = "0.4.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc7ec4f8827a71586374db3e87abdb5a2bb3a15afed140221307c3ec06b1f63b" +checksum = "9dfaf8f50e5f293737ee323940c7d8b08a66a95a419223d9f41610ca08b0833d" dependencies = [ "cfg-if", "js-sys", + "once_cell", "wasm-bindgen", "web-sys", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.95" +version = "0.2.97" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e79384be7f8f5a9dd5d7167216f022090cf1f9ec128e6e6a482a2cb5c5422c56" +checksum = "705440e08b42d3e4b36de7d66c944be628d579796b8090bfa3471478a2260051" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -3640,9 +3673,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.95" +version = "0.2.97" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68" +checksum = "98c9ae5a76e46f4deecd0f0255cc223cfa18dc9b261213b8aa0c7b36f61b3f1d" dependencies = [ "proc-macro2", "quote", @@ -3653,15 +3686,15 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.95" +version = "0.2.97" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d" +checksum = "6ee99da9c5ba11bd675621338ef6fa52296b76b83305e9b6e5c77d4c286d6d49" [[package]] name = "web-sys" -version = "0.3.72" +version = "0.3.74" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6488b90108c040df0fe62fa815cbdee25124641df01814dd7282749234c6112" +checksum = "a98bc3c33f0fe7e59ad7cd041b89034fa82a7c2d4365ca538dda6cdaf513863c" dependencies = [ "js-sys", "wasm-bindgen", @@ -3679,9 +3712,9 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "0.26.6" +version = "0.26.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "841c67bff177718f1d4dfefde8d8f0e78f9b6589319ba88312f567fc5841a958" +checksum = "5d642ff16b7e79272ae451b7322067cdc17cadf68c23264be9d94a32319efe7e" dependencies = [ "rustls-pki-types", ] @@ -3918,9 +3951,9 @@ checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" [[package]] name = "yoke" -version = "0.7.4" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c5b1314b079b0930c31e3af543d8ee1757b1951ae1e1565ec704403a7240ca5" +checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" dependencies = [ "serde", "stable_deref_trait", @@ -3930,9 +3963,9 @@ dependencies = [ [[package]] name = "yoke-derive" -version = "0.7.4" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28cc31741b18cb6f1d5ff12f5b7523e3d6eb0852bbbad19d73905511d9849b95" +checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" dependencies = [ "proc-macro2", "quote", @@ -3963,18 +3996,18 @@ dependencies = [ [[package]] name = "zerofrom" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91ec111ce797d0e0784a1116d0ddcdbea84322cd79e5d5ad173daeba4f93ab55" +checksum = "cff3ee08c995dee1859d998dea82f7374f2826091dd9cd47def953cae446cd2e" dependencies = [ "zerofrom-derive", ] [[package]] name = "zerofrom-derive" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ea7b4a3637ea8669cedf0f1fd5c286a17f3de97b8dd5a70a6c167a1730e63a5" +checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 170ca477..9ace53d3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cargo-semver-checks" -version = "0.36.0" +version = "0.37.0" edition = "2021" authors = ["Predrag Gruevski "] license = "Apache-2.0 OR MIT" @@ -9,14 +9,19 @@ repository = "https://github.com/obi1kenobi/cargo-semver-checks" readme = "./README.md" keywords = ["semver", "linter", "check", "crate", "cargo"] categories = ["command-line-utilities", "development-tools::cargo-plugins"] -rust-version = "1.80" +rust-version = "1.81" exclude = [".github/", "brand/", "scripts/", "test_crates/", "test_outputs/", "tests/"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] trustfall = "0.8.0" -trustfall_rustdoc = { version = "0.17.0", default-features = false, features = ["v30", "v32", "v33", "v34", "v35", "v36", "rayon", "rustc-hash"] } +# `cargo_metadata` is used at the API boundary of `trustfall_rustdoc`, +# so ensure the version we use for `cargo_metadata` here matches what `trustfall_rustdoc` uses too. +trustfall_rustdoc = { version = "0.18.1", default-features = false, features = ["v32", "v33", "v35", "v36", "v37", "rayon", "rustc-hash"] } +cargo_metadata = "0.18.1" +# End of dependency block + clap = { version = "4.5.17", features = ["derive", "cargo"] } serde_json = "1.0.128" anyhow = "1.0.89" @@ -25,7 +30,6 @@ serde = { version = "1.0.210", features = ["derive"] } semver = "1.0.23" handlebars = "6.1.0" atty = "0.2.14" -cargo_metadata = "0.18.1" clap-cargo = { version = "0.14.1", features = ["cargo_metadata"] } ignore = "0.4.23" clap-verbosity-flag = "2.2.1" @@ -57,6 +61,7 @@ predicates = "3.1.2" insta = { version = "1.40.0", features = ["ron", "filters", "toml"] } regex = "1.10.6" insta-cmd = "0.6.0" +rayon = "1.10.0" # In dev and test profiles, compile all dependencies with optimizations enabled, # but still checking debug assertions and overflows. diff --git a/README.md b/README.md index 572f5ff1..4db288e5 100644 --- a/README.md +++ b/README.md @@ -46,6 +46,7 @@ to the implementation of that query in the current version of the tool. - [What if my project needs stronger guarantees around supported Rust versions?](#what-if-my-project-needs-stronger-guarantees-around-supported-rust-versions) - [Does the crate I'm checking have to be published on crates.io?](#does-the-crate-im-checking-have-to-be-published-on-cratesio) - [What features does `cargo-semver-checks` enable in the tested crates?](#what-features-does-cargo-semver-checks-enable-in-the-tested-crates) +- [My crate uses `--cfg` conditional compilation. Can `cargo-semver-checks` scan it?](#my-crate-uses---cfg-conditional-compilation-can-cargo-semver-checks-scan-it) - [Does `cargo-semver-checks` have false positives?](#does-cargo-semver-checks-have-false-positives) - [Will `cargo-semver-checks` catch every semver violation?](#will-cargo-semver-checks-catch-every-semver-violation) - [Can I configure individual lints?](#can-i-configure-individual-lints) @@ -154,6 +155,17 @@ For example, consider crate [serde](https://github.com/serde-rs/serde), with the | `--only-explicit-features` | none | No explicit features are passed. | | `--only-explicit-features --features unstable` | `unstable` | All features can be added explicitly, regardless of their name. | +### My crate uses `--cfg` conditional compilation. Can `cargo-semver-checks` scan it? + +Yes! You can configure the `--cfg` options that `cargo-semver-checks` will use +when scanning your crate by setting them in the `RUSTDOCFLAGS` environment variable. + +For example, you can ask `cargo-semver-checks` to enable the `some-option` config +by invoking it as: +``` +RUSTDOCFLAGS="--cfg some-option" cargo semver-checks +``` + ### Does `cargo-semver-checks` have false positives? "False positive" means that `cargo-semver-checks` reported a semver violation incorrectly. diff --git a/scripts/regenerate_test_rustdocs.sh b/scripts/regenerate_test_rustdocs.sh index 8bc8dc10..1ae67e7c 100755 --- a/scripts/regenerate_test_rustdocs.sh +++ b/scripts/regenerate_test_rustdocs.sh @@ -36,14 +36,30 @@ fi set -u echo "Generating rustdoc with: $(cargo $TOOLCHAIN --version)" RUSTDOC_CMD="cargo $TOOLCHAIN rustdoc" +METADATA_CMD="cargo $TOOLCHAIN metadata --format-version 1" -# Run rustdoc on test_crates/*/{new,old}/ if [[ $# -eq 0 ]]; then + # Run rustdoc on test_crates/*/{new,old}/ set -- "$TOPLEVEL/test_crates/"*/ always_update= else + # Run on whichever paths the user specified. + if [[ $1 == '*' ]]; then + # As a special case, run on everything if the user specified a literal (escaped) asterisk. + set -- "$TOPLEVEL/test_crates/"*/ + fi always_update=1 fi + +PLACEHOLDER_DIR="$TARGET_DIR/placeholder" +rm -rf "$PLACEHOLDER_DIR" +mkdir -p "$TARGET_DIR" +pushd "$TARGET_DIR" +cargo new --lib placeholder +cd placeholder +cargo add --path ../../../test_crates/template/old/ +popd + for crate_pair; do # Strip all but last path component from crate_pair crate_pair=${crate_pair%/} @@ -55,6 +71,7 @@ for crate_pair; do crate="$crate_pair/$crate_version" crate_dir=$TOPLEVEL/test_crates/$crate target=$TARGET_DIR/$crate/rustdoc.json + metadata=$TARGET_DIR/$crate/metadata.json if [[ -z $always_update ]] && ! dir_is_newer_than_file "$crate_dir" "$target"; then printf 'No updates needed for %s.\n' "$crate" @@ -77,6 +94,13 @@ for crate_pair; do mkdir -p "$TARGET_DIR/$crate" mv "$RUSTDOC_OUTPUT_DIR/$crate_pair.json" "$target" popd + + pushd "$PLACEHOLDER_DIR" + head -n -1 Cargo.toml >Cargo.toml.trunc + mv Cargo.toml.trunc Cargo.toml + cargo add --path "../../../test_crates/$crate" + $METADATA_CMD >"$metadata" + popd done else echo >&2 "WARNING: $crate_pair/new/Cargo.toml exists but $crate_pair/old/Cargo.toml does not; skipping $crate_pair." diff --git a/src/config.rs b/src/config.rs index be99b071..860120f8 100644 --- a/src/config.rs +++ b/src/config.rs @@ -37,7 +37,7 @@ impl GlobalConfig { Self { level: None, handlebars: make_handlebars_registry(), - minimum_rustc_version: semver::Version::new(1, 80, 0), + minimum_rustc_version: semver::Version::new(1, 81, 0), stdout: AutoStream::new(Box::new(std::io::stdout()), stdout_choice), stderr: AutoStream::new(Box::new(std::io::stderr()), stderr_choice), feature_flags: HashSet::new(), diff --git a/src/data_generation/generate.rs b/src/data_generation/generate.rs index 85859934..d854eb73 100644 --- a/src/data_generation/generate.rs +++ b/src/data_generation/generate.rs @@ -47,7 +47,7 @@ pub(super) fn generate_rustdoc( build_dir: &Path, settings: GenerationSettings, callbacks: &mut CallbackHandler<'_>, -) -> Result { +) -> Result<(PathBuf, cargo_metadata::Metadata), TerminalError> { let crate_name = request.kind.name().into_terminal_result()?; let version = request.kind.version().into_terminal_result()?; @@ -107,16 +107,18 @@ pub(super) fn generate_rustdoc( let placeholder_target_directory = metadata.target_directory.as_path().as_std_path().to_owned(); let target_dir = placeholder_target_directory.as_path(); - run_cargo_doc( + let rustdoc_data = run_cargo_doc( request, - metadata, + &metadata, &placeholder_manifest_path, target_dir, crate_name, version, &settings, callbacks, - ) + )?; + + Ok((rustdoc_data, metadata)) } fn produce_repro_workspace_shell_commands(request: &CrateDataRequest<'_>) -> String { @@ -231,7 +233,7 @@ fn run_cargo_update( #[allow(clippy::too_many_arguments)] fn run_cargo_doc( request: &CrateDataRequest<'_>, - metadata: cargo_metadata::Metadata, + metadata: &cargo_metadata::Metadata, placeholder_manifest_path: &Path, target_dir: &Path, crate_name: &str, @@ -242,8 +244,8 @@ fn run_cargo_doc( let pkg_spec = format!("{crate_name}@{version}"); // Generating rustdoc JSON for a crate also involves checking that crate's dependencies. - // The check is done by rustc, not rustdoc, so it's subject to RUSTFLAGS not RUSTDOCFLAGS. - // We don't want rustc to fail that check if the user has set RUSTFLAGS="-Dwarnings" here. + // The check is done by rustc, not rustdoc, so it's subject to `RUSTFLAGS` not `RUSTDOCFLAGS`. + // We don't want rustc to fail that check if the user has set `RUSTFLAGS="-Dwarnings"` here. // This fixes: https://github.com/obi1kenobi/cargo-semver-checks/issues/589 let rustflags = match std::env::var("RUSTFLAGS") { Ok(mut prior_rustflags) => { @@ -253,6 +255,20 @@ fn run_cargo_doc( Err(_) => std::borrow::Cow::Borrowed("--cap-lints=allow"), }; + // Ensure we preserve `RUSTDOCFLAGS` if they are set. + // This allows users to supply `--cfg ` settings in `RUSTDOCFLAGS` + // in order to toggle what functionality is compiled into the scanned crate. + // Suggested in: https://github.com/obi1kenobi/cargo-semver-checks/discussions/1012 + let extra_rustdocflags = "-Z unstable-options --document-private-items --document-hidden-items --output-format=json --cap-lints=allow"; + let rustdocflags = match std::env::var("RUSTDOCFLAGS") { + Ok(mut prior_rustdocflags) => { + prior_rustdocflags.push(' '); + prior_rustdocflags.push_str(extra_rustdocflags); + std::borrow::Cow::Owned(prior_rustdocflags) + } + Err(_) => std::borrow::Cow::Borrowed(extra_rustdocflags), + }; + // Run the rustdoc generation command on the placeholder crate, // specifically requesting the rustdoc of *only* the crate specified in `pkg_spec`. // @@ -264,10 +280,7 @@ fn run_cargo_doc( callbacks.generate_rustdoc_start(); let mut cmd = std::process::Command::new("cargo"); cmd.env("RUSTC_BOOTSTRAP", "1") - .env( - "RUSTDOCFLAGS", - "-Z unstable-options --document-private-items --document-hidden-items --output-format=json --cap-lints=allow", - ) + .env("RUSTDOCFLAGS", rustdocflags.as_ref()) .env("RUSTFLAGS", rustflags.as_ref()) .stdout(std::process::Stdio::null()) // Don't pollute output .stderr(settings.stderr()) diff --git a/src/data_generation/mod.rs b/src/data_generation/mod.rs index 44e11087..8f6f1b0e 100644 --- a/src/data_generation/mod.rs +++ b/src/data_generation/mod.rs @@ -3,7 +3,7 @@ mod generate; mod progress; mod request; -use trustfall_rustdoc::{VersionedCrate, VersionedIndexedCrate, VersionedRustdocAdapter}; +use trustfall_rustdoc::{VersionedIndex, VersionedRustdocAdapter, VersionedStorage}; pub(crate) use error::{IntoTerminalResult, TerminalError}; pub(crate) use generate::GenerationSettings; @@ -12,40 +12,37 @@ pub(crate) use request::{CacheSettings, CrateDataRequest}; #[derive(Debug)] pub(crate) struct DataStorage { - current_crate: VersionedCrate, - baseline_crate: VersionedCrate, + current: VersionedStorage, + baseline: VersionedStorage, } impl DataStorage { - pub(crate) fn new(current_crate: VersionedCrate, baseline_crate: VersionedCrate) -> Self { - Self { - current_crate, - baseline_crate, - } + pub(crate) fn new(current: VersionedStorage, baseline: VersionedStorage) -> Self { + Self { current, baseline } } - pub(crate) fn current_crate(&self) -> &VersionedCrate { - &self.current_crate + pub(crate) fn current_crate(&self) -> &VersionedStorage { + &self.current } - pub(crate) fn baseline_crate(&self) -> &VersionedCrate { - &self.baseline_crate + pub(crate) fn baseline_crate(&self) -> &VersionedStorage { + &self.baseline } } impl DataStorage { pub(crate) fn create_indexes(&self) -> IndexStorage<'_> { IndexStorage { - current_crate: VersionedIndexedCrate::new(&self.current_crate), - baseline_crate: VersionedIndexedCrate::new(&self.baseline_crate), + current_crate: VersionedIndex::from_storage(&self.current), + baseline_crate: VersionedIndex::from_storage(&self.baseline), } } } #[derive(Debug)] pub(crate) struct IndexStorage<'a> { - current_crate: VersionedIndexedCrate<'a>, - baseline_crate: VersionedIndexedCrate<'a>, + current_crate: VersionedIndex<'a>, + baseline_crate: VersionedIndex<'a>, } impl IndexStorage<'_> { diff --git a/src/data_generation/request.rs b/src/data_generation/request.rs index dc8cb7c6..1d47d3c9 100644 --- a/src/data_generation/request.rs +++ b/src/data_generation/request.rs @@ -3,7 +3,7 @@ use std::{borrow::Cow, collections::BTreeSet, path::Path}; use anyhow::Context; use sha2::Digest as _; -use trustfall_rustdoc::VersionedCrate; +use trustfall_rustdoc::{LoadingError, VersionedStorage}; use crate::manifest::Manifest; use crate::util::slugify; @@ -28,7 +28,7 @@ pub(super) enum RequestKind<'a> { LocalProject(ProjectRequest<'a>), } -impl<'a> RequestKind<'a> { +impl RequestKind<'_> { pub(super) fn name(&self) -> anyhow::Result<&str> { Ok(match self { Self::Registry(RegistryRequest { index_entry }) => &index_entry.name, @@ -80,11 +80,21 @@ impl CacheSettings<()> { pub(crate) struct CacheUse<'a> { /// Invariant: always `None` if the cache settings are [`CacheSettings::None`], /// and always `Some` otherwise. - cache_location: Option, + json_cache_location: Option, + + /// Invariant: always `None` if the cache settings are [`CacheSettings::None`], + /// and always `Some` otherwise. + metadata_cache_location: Option, settings: CacheSettings<&'a Path>, } +#[derive(Debug, Clone)] +struct CacheEntry<'a> { + json: &'a Path, + metadata: &'a Path, +} + impl<'a> CacheUse<'a> { fn new( request: &CrateDataRequest<'a>, @@ -108,31 +118,42 @@ impl<'a> CacheUse<'a> { } }; - let cache_location = { + let (json_cache_location, metadata_cache_location) = { match settings { - CacheSettings::None => None, + CacheSettings::None => (None, None), CacheSettings::ReadOnly(path) | CacheSettings::ReadWrite(path) - | CacheSettings::WriteOnly(path) => Some(path.join(format!("{key}.json"))), + | CacheSettings::WriteOnly(path) => ( + Some(path.join(format!("{key}.json"))), + Some(path.join(format!("{key}.metadata.json"))), + ), } }; Ok(Self { - cache_location, + json_cache_location, + metadata_cache_location, settings, }) } - fn read(&self) -> anyhow::Result> { + fn read(&self) -> anyhow::Result>> { match self.settings { CacheSettings::ReadWrite(..) | CacheSettings::ReadOnly(..) => { - let cache_path = self - .cache_location + let json_path = self + .json_cache_location .as_ref() .expect("invariant violation: no cache path for readable cache"); + let metadata_path = self + .metadata_cache_location + .as_ref() + .expect("invariant violation: no metadata path for readable cache"); - if cache_path.exists() { - return Ok(Some(cache_path)); + if json_path.exists() && metadata_path.exists() { + return Ok(Some(CacheEntry { + json: json_path, + metadata: metadata_path, + })); } } CacheSettings::WriteOnly(..) | CacheSettings::None => {} @@ -145,15 +166,25 @@ impl<'a> CacheUse<'a> { /// /// If the cache policy does not allow writing, this returns `Ok(..)`. /// Errors are genuine failures to write to the cache, such as I/O errors. - fn populate(&self, data_path: &Path) -> anyhow::Result { + fn populate( + &self, + rustdoc_json: &Path, + metadata: &cargo_metadata::Metadata, + ) -> anyhow::Result { match self.settings { CacheSettings::ReadWrite(path) | CacheSettings::WriteOnly(path) => { - let cache_path = self - .cache_location + let json_path = self + .json_cache_location .as_ref() - .expect("invariant violation: no cache path for writeable cache"); + .expect("invariant violation: no cache path for readable cache"); + let metadata_path = self + .metadata_cache_location + .as_ref() + .expect("invariant violation: no metadata path for readable cache"); + fs_err::create_dir_all(path)?; - fs_err::copy(data_path, cache_path)?; + fs_err::copy(rustdoc_json, json_path)?; + fs_err::write(metadata_path, serde_json::to_string(metadata)?)?; Ok(true) } CacheSettings::None | CacheSettings::ReadOnly(..) => Ok(false), @@ -229,7 +260,7 @@ impl<'a> CrateDataRequest<'a> { cache_settings: CacheSettings<&'a Path>, generation_settings: GenerationSettings, callbacks: &'slf mut dyn ProgressCallbacks<'slf>, - ) -> Result { + ) -> Result { let mut callbacks = CallbackHandler::new( self.kind .name() @@ -252,17 +283,38 @@ impl<'a> CrateDataRequest<'a> { // Can we satisfy the request from cache? match cache.read() { // Cache hit! - Ok(Some(path)) => { + Ok(Some(entry)) => { callbacks.rustdoc_cache_hit(); callbacks.parse_rustdoc_start(true); - match trustfall_rustdoc::load_rustdoc(path) { - Ok(data) => { - callbacks.parse_rustdoc_success(true); - return Ok(data); - } - Err(e) => { - callbacks.non_fatal_error(e.context("failed to load cached rustdoc JSON")); - } + + match std::fs::read_to_string(entry.metadata) { + Ok(text) => match serde_json::from_str(&text) { + Ok(metadata) => { + match load_rustdoc_with_optional_metadata( + entry.json, + metadata, + &mut callbacks, + ) { + Ok(data) => { + callbacks.parse_rustdoc_success(true); + return Ok(data); + } + Err(e) => { + callbacks.non_fatal_error( + e.context("failed to load cached rustdoc JSON"), + ); + } + } + } + Err(e) => { + callbacks.non_fatal_error( + anyhow::anyhow!(e).context("failed to parse cached metadata"), + ); + } + }, + Err(e) => callbacks.non_fatal_error( + anyhow::anyhow!(e).context("failed to read cached metadata file"), + ), } } @@ -279,7 +331,7 @@ impl<'a> CrateDataRequest<'a> { // Generate the data we need. let build_dir = target_root.join(self.build_path_slug().into_terminal_result()?); - let data_path = super::generate::generate_rustdoc( + let (data_path, metadata) = super::generate::generate_rustdoc( self, &build_dir, generation_settings, @@ -290,7 +342,7 @@ impl<'a> CrateDataRequest<'a> { // If the cache doesn't need to be populated, this returns `Ok(false)`. // Errors are genuine failures to populate the cache, such as I/O problems. let mut clean_up_build_dir = false; - match cache.populate(data_path.as_path()) { + match cache.populate(data_path.as_path(), &metadata) { // Populated the cache. Ok(true) => { callbacks.rustdoc_cache_populated(); @@ -310,7 +362,8 @@ impl<'a> CrateDataRequest<'a> { // This time, failure to read the rustdoc is fatal. callbacks.parse_rustdoc_start(false); - let data = trustfall_rustdoc::load_rustdoc(data_path.as_path()).into_terminal_result()?; + let data = load_rustdoc_with_optional_metadata(&data_path, metadata, &mut callbacks) + .into_terminal_result()?; callbacks.parse_rustdoc_success(false); if clean_up_build_dir { @@ -349,6 +402,25 @@ impl<'a> CrateDataRequest<'a> { } } +fn load_rustdoc_with_optional_metadata( + json_path: &Path, + metadata: cargo_metadata::Metadata, + callbacks: &mut CallbackHandler<'_>, +) -> anyhow::Result { + match trustfall_rustdoc::load_rustdoc(json_path, Some(metadata)) { + Ok(data) => Ok(data), + Err(e @ LoadingError::MetadataParsing(..)) => { + // Metadata parsing is brand new and might have unforeseen issues. + // Be resilient: report the problem but don't crash -- + // instead, drop back to not checking manifest data. + callbacks.non_fatal_error(anyhow::Error::from(e).context("skipping package metadata due to failure to load it; package manifest checks will not discover any breakage")); + + trustfall_rustdoc::load_rustdoc(json_path, None).map_err(anyhow::Error::from) + } + Err(e) => Err(anyhow::Error::from(e)), + } +} + fn make_features_hash(default_features: bool, extra_features: &BTreeSet>) -> String { // Use newlines as the record separator, since newlines are not valid in feature names. let mut hasher = sha2::Sha256::new(); diff --git a/src/lints/feature_missing.ron b/src/lints/feature_missing.ron new file mode 100644 index 00000000..f10e3568 --- /dev/null +++ b/src/lints/feature_missing.ron @@ -0,0 +1,53 @@ +SemverQuery( + id: "feature_missing", + human_readable_name: "package feature removed or renamed", + description: "A feature has been removed from this package's Cargo.toml.", + required_update: Major, + lint_level: Deny, + reference_link: Some("https://doc.rust-lang.org/cargo/reference/semver.html#cargo-feature-remove"), + query: r#" + { + CrateDiff { + baseline { + feature { + # Until cargo ships with support for private and/or unstable feature names, + # we'll rely on feature names to detect whether to flag feature removals. + # + # This lint will ignore features that match any of the following: + # - start with an underscore (`_`) character + # - are named `unstable`, `nightly`, or `bench` + # - have a prefix of `unstable`, `nightly`, or `bench` followed by + # a dash (`-`) or underscore (`_`) character. + # + # Cargo tracking issues: + # - unstable/nightly features: https://github.com/rust-lang/cargo/issues/10881 + # - private/hidden features: https://github.com/rust-lang/cargo/issues/10882 + name @tag + @filter(op: "not_regex", value: ["$unstable_feature_pattern"]) + @filter(op: "not_has_prefix", value: ["$underscore"]) + @output + + # An explicit ordering key is needed since we don't have span information, + # which what we usually use to order results in tests. + name @output(name: "ordering_key") + } + } + current { + feature @fold @transform(op: "count") @filter(op: "=", value: ["$zero"]) { + name @filter(op: "=", value: ["%name"]) + } + } + } + }"#, + arguments: { + "zero": 0, + "unstable_feature_pattern": "^(?:unstable|nightly|bench)(?:[-_].*)?$", + "underscore": "_", + }, + error_message: "A feature has been removed from this package's Cargo.toml. This will break downstream crates which enable that feature.", + per_result_error_template: Some("feature {{name}} in the package's Cargo.toml"), + // TODO: It's currently not possible to write witnesses for manifest lints, + // since we'd need to generate a *Cargo.toml* witness instead of a Rust code witness. + // Issue: https://github.com/obi1kenobi/cargo-semver-checks/issues/1008 + witness: None, +) diff --git a/src/query.rs b/src/query.rs index cd09e8ad..cba71674 100644 --- a/src/query.rs +++ b/src/query.rs @@ -329,14 +329,15 @@ mod tests { use std::borrow::Cow; use std::collections::BTreeSet; use std::path::PathBuf; - use std::sync::OnceLock; + use std::sync::{Arc, OnceLock}; use std::{collections::BTreeMap, path::Path}; use anyhow::Context; + use rayon::prelude::*; use serde::{Deserialize, Serialize}; use trustfall::{FieldValue, TransparentValue}; use trustfall_rustdoc::{ - load_rustdoc, VersionedCrate, VersionedIndexedCrate, VersionedRustdocAdapter, + load_rustdoc, VersionedIndex, VersionedRustdocAdapter, VersionedStorage, }; use crate::query::{ @@ -348,15 +349,31 @@ mod tests { static TEST_CRATE_NAMES: OnceLock> = OnceLock::new(); /// Mapping test crate (pair) name -> (old rustdoc, new rustdoc). - static TEST_CRATE_RUSTDOCS: OnceLock> = + static TEST_CRATE_RUSTDOCS: OnceLock> = OnceLock::new(); + /// Mapping test crate (pair) name -> (old index, new index). + static TEST_CRATE_INDEXES: OnceLock< + BTreeMap, VersionedIndex<'static>)>, + > = OnceLock::new(); + fn get_test_crate_names() -> &'static [String] { TEST_CRATE_NAMES.get_or_init(initialize_test_crate_names) } - fn get_test_crate_rustdocs(test_crate: &str) -> &'static (VersionedCrate, VersionedCrate) { - &TEST_CRATE_RUSTDOCS.get_or_init(initialize_test_crate_rustdocs)[test_crate] + fn get_all_test_crates() -> &'static BTreeMap { + TEST_CRATE_RUSTDOCS.get_or_init(initialize_test_crate_rustdocs) + } + + fn get_all_test_crate_indexes( + ) -> &'static BTreeMap, VersionedIndex<'static>)> { + TEST_CRATE_INDEXES.get_or_init(initialize_test_crate_indexes) + } + + fn get_test_crate_indexes( + test_crate: &str, + ) -> &'static (VersionedIndex<'static>, VersionedIndex<'static>) { + &get_all_test_crate_indexes()[test_crate] } fn initialize_test_crate_names() -> Vec { @@ -400,9 +417,9 @@ mod tests { .collect() } - fn initialize_test_crate_rustdocs() -> BTreeMap { + fn initialize_test_crate_rustdocs() -> BTreeMap { get_test_crate_names() - .iter() + .par_iter() .map(|crate_pair| { let old_rustdoc = load_pregenerated_rustdoc(crate_pair.as_str(), "old"); let new_rustdoc = load_pregenerated_rustdoc(crate_pair, "new"); @@ -412,20 +429,37 @@ mod tests { .collect() } - fn load_pregenerated_rustdoc(crate_pair: &str, crate_version: &str) -> VersionedCrate { - let path = format!("./localdata/test_data/{crate_pair}/{crate_version}/rustdoc.json"); - load_rustdoc(Path::new(&path)) - .with_context(|| format!("Could not load {path} file, did you forget to run ./scripts/regenerate_test_rustdocs.sh ?")) - .expect("failed to load baseline rustdoc") + fn initialize_test_crate_indexes( + ) -> BTreeMap, VersionedIndex<'static>)> { + get_all_test_crates() + .par_iter() + .map(|(key, (old_crate, new_crate))| { + let old_index = VersionedIndex::from_storage(old_crate); + let new_index = VersionedIndex::from_storage(new_crate); + (key.clone(), (old_index, new_index)) + }) + .collect() + } + + fn load_pregenerated_rustdoc(crate_pair: &str, crate_version: &str) -> VersionedStorage { + let rustdoc_path = + format!("./localdata/test_data/{crate_pair}/{crate_version}/rustdoc.json"); + let metadata_path = + format!("./localdata/test_data/{crate_pair}/{crate_version}/metadata.json"); + let metadata_text = std::fs::read_to_string(&metadata_path).map_err(|e| anyhow::anyhow!(e).context( + format!("Could not load {metadata_path} file. These files are newly required as of PR#1007. Please re-run ./scripts/regenerate_test_rustdocs.sh"))).expect("failed to load metadata"); + let metadata = serde_json::from_str(&metadata_text).expect("failed to parse metadata file"); + load_rustdoc(Path::new(&rustdoc_path), Some(metadata)) + .with_context(|| format!("Could not load {rustdoc_path} file, did you forget to run ./scripts/regenerate_test_rustdocs.sh ?")) + .expect("failed to load rustdoc") } #[test] fn all_queries_are_valid() { - let (_baseline_crate, current_crate) = get_test_crate_rustdocs("template"); - let indexed_crate = VersionedIndexedCrate::new(current_crate); + let (_baseline, current) = get_test_crate_indexes("template"); - let adapter = VersionedRustdocAdapter::new(&indexed_crate, Some(&indexed_crate)) - .expect("failed to create adapter"); + let adapter = + VersionedRustdocAdapter::new(current, Some(current)).expect("failed to create adapter"); for semver_query in SemverQuery::all_queries().into_values() { let _ = adapter .run_query(&semver_query.query, semver_query.arguments) @@ -435,8 +469,7 @@ mod tests { #[test] fn pub_use_handling() { - let (_baseline_crate, current_crate) = get_test_crate_rustdocs("pub_use_handling"); - let current = VersionedIndexedCrate::new(current_crate); + let (_baseline, current) = get_test_crate_indexes("pub_use_handling"); let query = r#" { @@ -460,7 +493,7 @@ mod tests { arguments.insert("struct", "CheckPubUseHandling"); let adapter = - VersionedRustdocAdapter::new(¤t, None).expect("could not create adapter"); + VersionedRustdocAdapter::new(current, None).expect("could not create adapter"); let results_iter = adapter .run_query(query, arguments) @@ -542,8 +575,8 @@ mod tests { fn run_query_on_crate_pair( semver_query: &SemverQuery, crate_pair_name: &String, - indexed_crate_new: &VersionedIndexedCrate, - indexed_crate_old: &VersionedIndexedCrate, + indexed_crate_new: &VersionedIndex<'_>, + indexed_crate_old: &VersionedIndex<'_>, ) -> (String, Vec>) { let adapter = VersionedRustdocAdapter::new(indexed_crate_new, Some(indexed_crate_old)) .expect("could not create adapter"); @@ -561,7 +594,7 @@ mod tests { fn assert_no_false_positives_in_nonchanged_crate( query_name: &str, semver_query: &SemverQuery, - indexed_crate: &VersionedIndexedCrate, + indexed_crate: &VersionedIndex<'_>, crate_pair_name: &String, crate_version: &str, ) { @@ -593,31 +626,24 @@ mod tests { let mut query_execution_results: TestOutput = get_test_crate_names() .iter() .map(|crate_pair_name| { - let (crate_old, crate_new) = get_test_crate_rustdocs(crate_pair_name); - let indexed_crate_old = VersionedIndexedCrate::new(crate_old); - let indexed_crate_new = VersionedIndexedCrate::new(crate_new); + let (baseline, current) = get_test_crate_indexes(crate_pair_name); assert_no_false_positives_in_nonchanged_crate( query_name, &semver_query, - &indexed_crate_new, + current, crate_pair_name, "new", ); assert_no_false_positives_in_nonchanged_crate( query_name, &semver_query, - &indexed_crate_old, + baseline, crate_pair_name, "old", ); - run_query_on_crate_pair( - &semver_query, - crate_pair_name, - &indexed_crate_new, - &indexed_crate_old, - ) + run_query_on_crate_pair(&semver_query, crate_pair_name, current, baseline) }) .filter(|(_crate_pair_name, output)| !output.is_empty()) .collect(); @@ -625,14 +651,31 @@ mod tests { // Reorder vector of results into a deterministic order that will compensate for // nondeterminism in how the results are ordered. let key_func = |elem: &BTreeMap| { - let filename = elem.get("span_filename").and_then(|value| value.as_str()); - let line = elem.get("span_begin_line"); - - match (filename, line) { - (Some(filename), Some(line)) => (filename.to_owned(), line.as_usize()), - (Some(_filename), None) => panic!("A valid query must output `span_filename`. See https://github.com/obi1kenobi/cargo-semver-checks/blob/main/CONTRIBUTING.md for details."), - (None, Some(_line)) => panic!("A valid query must output `span_begin_line`. See https://github.com/obi1kenobi/cargo-semver-checks/blob/main/CONTRIBUTING.md for details."), - (None, None) => panic!("A valid query must output both `span_filename` and `span_begin_line`. See https://github.com/obi1kenobi/cargo-semver-checks/blob/main/CONTRIBUTING.md for details."), + // Queries should either: + // - define an explicit `ordering_key` string value sufficient to establish + // a total order of results for each crate, or + // - define `span_filename` and `span_begin_line` values where the lint is being raised, + // which will then define a total order of results for that query on that crate. + let ordering_key = elem + .get("ordering_key") + .and_then(|value| value.as_arc_str()); + if let Some(key) = ordering_key { + (Arc::clone(key), 0) + } else { + let filename = elem.get("span_filename").map(|value| { + value + .as_arc_str() + .expect("`span_filename` was not a string") + }); + let line = elem + .get("span_begin_line") + .map(|value: &FieldValue| value.as_usize().expect("begin line was not an int")); + match (filename, line) { + (Some(filename), Some(line)) => (Arc::clone(filename), line), + (Some(_filename), None) => panic!("No `span_begin_line` was returned by the query, even though `span_filename` was present. A valid query must either output an explicit `ordering_key`, or output both `span_filename` and `span_begin_line`. See https://github.com/obi1kenobi/cargo-semver-checks/blob/main/CONTRIBUTING.md for details."), + (None, Some(_line)) => panic!("No `span_filename` was returned by the query, even though `span_begin_line` was present. A valid query must either output an explicit `ordering_key`, or output both `span_filename` and `span_begin_line`. See https://github.com/obi1kenobi/cargo-semver-checks/blob/main/CONTRIBUTING.md for details."), + (None, None) => panic!("A valid query must either output an explicit `ordering_key`, or output both `span_filename` and `span_begin_line`. See https://github.com/obi1kenobi/cargo-semver-checks/blob/main/CONTRIBUTING.md for details."), + } } }; for value in query_execution_results.values_mut() { @@ -1031,6 +1074,7 @@ add_lints!( enum_variant_marked_non_exhaustive, enum_variant_missing, exported_function_changed_abi, + feature_missing, function_abi_no_longer_unwind, function_changed_abi, function_const_removed, diff --git a/src/rustdoc_gen.rs b/src/rustdoc_gen.rs index f0b06d86..eadb1f75 100644 --- a/src/rustdoc_gen.rs +++ b/src/rustdoc_gen.rs @@ -6,7 +6,7 @@ use anyhow::{bail, Context as _}; use itertools::Itertools; use serde::Serialize; use tame_index::IndexKrate; -use trustfall_rustdoc::VersionedCrate; +use trustfall_rustdoc::VersionedStorage; use crate::data_generation::{CrateDataRequest, IntoTerminalResult as _, TerminalError}; use crate::manifest::Manifest; @@ -253,7 +253,7 @@ fn generate_rustdoc( target_root: PathBuf, crate_source: CrateSource, crate_data: CrateDataForRustdoc, -) -> Result { +) -> Result { let extra_features: BTreeSet> = crate_source .feature_list_from_config(config, crate_data.feature_config) .into_iter() @@ -306,7 +306,7 @@ pub(crate) trait RustdocGenerator { generation_settings: super::data_generation::GenerationSettings, cache_settings: super::data_generation::CacheSettings<()>, crate_data: CrateDataForRustdoc, - ) -> Result; + ) -> Result; } #[derive(Debug)] @@ -327,8 +327,10 @@ impl RustdocGenerator for RustdocFromFile { _generation_settings: super::data_generation::GenerationSettings, _cache_settings: super::data_generation::CacheSettings<()>, _crate_data: CrateDataForRustdoc, - ) -> Result { - trustfall_rustdoc::load_rustdoc(&self.path).into_terminal_result() + ) -> Result { + trustfall_rustdoc::load_rustdoc(&self.path, None) + .map_err(anyhow::Error::from) + .into_terminal_result() } } @@ -424,7 +426,7 @@ impl RustdocGenerator for RustdocFromProjectRoot { generation_settings: super::data_generation::GenerationSettings, cache_settings: super::data_generation::CacheSettings<()>, crate_data: CrateDataForRustdoc, - ) -> Result { + ) -> Result { let manifest: &Manifest = self.manifests.get(crate_data.name).ok_or_else(|| { if let Some(duplicates) = self.duplicate_packages.get(crate_data.name) { let duplicates = duplicates.iter().map(|p| p.display()).join("\n "); @@ -522,7 +524,7 @@ impl RustdocGenerator for RustdocFromGitRevision { generation_settings: super::data_generation::GenerationSettings, cache_settings: super::data_generation::CacheSettings<()>, crate_data: CrateDataForRustdoc, - ) -> Result { + ) -> Result { self.path .load_rustdoc(config, generation_settings, cache_settings, crate_data) } @@ -681,7 +683,7 @@ impl RustdocGenerator for RustdocFromRegistry { generation_settings: super::data_generation::GenerationSettings, cache_settings: super::data_generation::CacheSettings<()>, crate_data: CrateDataForRustdoc, - ) -> Result { + ) -> Result { let lock = acquire_cargo_global_package_lock(config).into_terminal_result()?; let crate_ = self.index.krate(crate_data.name.try_into().expect("this should be impossible"), false, &lock) .with_context(|| { diff --git a/test_crates/cfg_conditional_compilation/new/Cargo.toml b/test_crates/cfg_conditional_compilation/new/Cargo.toml new file mode 100644 index 00000000..80d9aadc --- /dev/null +++ b/test_crates/cfg_conditional_compilation/new/Cargo.toml @@ -0,0 +1,7 @@ +[package] +publish = false +name = "cfg_conditional_compilation" +version = "0.1.0" +edition = "2021" + +[dependencies] diff --git a/test_crates/cfg_conditional_compilation/new/build.rs b/test_crates/cfg_conditional_compilation/new/build.rs new file mode 100644 index 00000000..6d858e6f --- /dev/null +++ b/test_crates/cfg_conditional_compilation/new/build.rs @@ -0,0 +1,3 @@ +fn main() { + println!("cargo::rustc-check-cfg=cfg(custom)"); +} diff --git a/test_crates/cfg_conditional_compilation/new/src/lib.rs b/test_crates/cfg_conditional_compilation/new/src/lib.rs new file mode 100644 index 00000000..986de8e0 --- /dev/null +++ b/test_crates/cfg_conditional_compilation/new/src/lib.rs @@ -0,0 +1,6 @@ +pub enum Data { + Int(i64), + String(String), + #[cfg(custom)] + Bool(bool), +} diff --git a/test_crates/cfg_conditional_compilation/old/Cargo.toml b/test_crates/cfg_conditional_compilation/old/Cargo.toml new file mode 100644 index 00000000..80d9aadc --- /dev/null +++ b/test_crates/cfg_conditional_compilation/old/Cargo.toml @@ -0,0 +1,7 @@ +[package] +publish = false +name = "cfg_conditional_compilation" +version = "0.1.0" +edition = "2021" + +[dependencies] diff --git a/test_crates/cfg_conditional_compilation/old/build.rs b/test_crates/cfg_conditional_compilation/old/build.rs new file mode 100644 index 00000000..6d858e6f --- /dev/null +++ b/test_crates/cfg_conditional_compilation/old/build.rs @@ -0,0 +1,3 @@ +fn main() { + println!("cargo::rustc-check-cfg=cfg(custom)"); +} diff --git a/test_crates/cfg_conditional_compilation/old/src/lib.rs b/test_crates/cfg_conditional_compilation/old/src/lib.rs new file mode 100644 index 00000000..83ba06fb --- /dev/null +++ b/test_crates/cfg_conditional_compilation/old/src/lib.rs @@ -0,0 +1,7 @@ +#[cfg(custom)] +pub struct Example; + +pub enum Data { + Int(i64), + String(String), +} diff --git a/test_crates/feature_missing/new/Cargo.toml b/test_crates/feature_missing/new/Cargo.toml new file mode 100644 index 00000000..fb23397f --- /dev/null +++ b/test_crates/feature_missing/new/Cargo.toml @@ -0,0 +1,17 @@ +[package] +publish = false +name = "feature_missing" +version = "0.1.0" +edition = "2021" + +[dependencies] +# Adding a `dep:` on an optional dependency will remove its implicit feature, +# making it a breaking change. +rand_pcg = { version = "*", optional = true} + +[features] +still_present = [] +# Explicitly-added feature, to replace the implicit feature defined by +# the `optional = true` dependency in the previous crate version. +rand_core = [] +pcg = ["dep:rand_pcg"] diff --git a/test_crates/feature_missing/new/src/lib.rs b/test_crates/feature_missing/new/src/lib.rs new file mode 100644 index 00000000..e69de29b diff --git a/test_crates/feature_missing/old/Cargo.toml b/test_crates/feature_missing/old/Cargo.toml new file mode 100644 index 00000000..88623c09 --- /dev/null +++ b/test_crates/feature_missing/old/Cargo.toml @@ -0,0 +1,34 @@ +[package] +publish = false +name = "feature_missing" +version = "0.1.0" +edition = "2021" + +[dependencies] +# Since `rand` isn't used in a feature with `dep:rand` syntax, +# it defines an implicit feature by that name. +# Removing that implicit feature is a breaking change. +rand = { version = "*", optional = true } +# However, re-adding an explicit feature after removing the implicit one +# will avoid the breakage. +rand_core = { version = "*", optional = true} +# Adding a `dep:` on an optional dependency will remove its implicit feature, +# making it a breaking change. +rand_pcg = { version = "*", optional = true} + +[features] +still_present = [] +going_missing = [] + +# We ignore unstable-looking feature names. +# All of the following will be removed, and none of them should be flagged. +unstable = [] +nightly = [] +bench = [] +unstable-dash = [] +unstable_underscore = [] +nightly-dash = [] +nightly_underscore = [] +bench-dash = [] +bench_underscore = [] +_underscore_prefix = [] diff --git a/test_crates/feature_missing/old/src/lib.rs b/test_crates/feature_missing/old/src/lib.rs new file mode 100644 index 00000000..e69de29b diff --git a/test_crates/function_feature_changed/new/Cargo.toml b/test_crates/function_feature_changed/new/Cargo.toml index b673ed4e..757b4b57 100644 --- a/test_crates/function_feature_changed/new/Cargo.toml +++ b/test_crates/function_feature_changed/new/Cargo.toml @@ -4,6 +4,19 @@ name = "function_feature_changed" version = "0.1.0" edition = "2021" +# The purpose of this test crate is to ensure that cargo-semver-checks' feature selection +# is robust to changes in which features exist and are enabled. +# +# We want cargo-semver-checks runs to succeed and fail on the basis of +# what they find inside the crate, not just always fail because a feature has been deleted. +# Hence, we disable this lint. +# +# This *only* affects directly running cargo-semver-checks against this crate. +# It does not affect the lint query runs against all crates, since those run over +# pre-generated rustdoc JSON and `cargo metadata` output and ignore this configuration. +[package.metadata.cargo-semver-checks.lints] +feature_missing = "allow" + [dependencies] [features] diff --git a/test_outputs/integration_snapshots__cfg_conditional_compilation.snap b/test_outputs/integration_snapshots__cfg_conditional_compilation.snap new file mode 100644 index 00000000..98a3bbfd --- /dev/null +++ b/test_outputs/integration_snapshots__cfg_conditional_compilation.snap @@ -0,0 +1,54 @@ +--- +source: tests/integration_snapshots.rs +info: + program: cargo-semver-checks + args: + - semver-checks + - "--manifest-path" + - test_crates/cfg_conditional_compilation/new + - "--baseline-root" + - test_crates/cfg_conditional_compilation/old + env: + CARGO_TERM_COLOR: never + RUSTDOCFLAGS: "--cfg custom" + RUSTFLAGS: "--cfg custom" + RUST_BACKTRACE: "0" +--- +success: false +exit_code: 1 +----- stdout ----- + +--- failure enum_variant_added: enum variant added on exhaustive enum --- + +Description: +A publicly-visible enum without #[non_exhaustive] has a new variant. + ref: https://doc.rust-lang.org/cargo/reference/semver.html#enum-variant-new + impl: https://github.com/obi1kenobi/cargo-semver-checks/tree/[VERSION]/src/lints/enum_variant_added.ron + +Failed in: + variant Data:Bool in [ROOT]/test_crates/cfg_conditional_compilation/new/src/lib.rs:5 + +--- failure struct_missing: pub struct removed or renamed --- + +Description: +A publicly-visible struct cannot be imported by its prior path. A `pub use` may have been removed, or the struct itself may have been renamed or removed entirely. + ref: https://doc.rust-lang.org/cargo/reference/semver.html#item-remove + impl: https://github.com/obi1kenobi/cargo-semver-checks/tree/[VERSION]/src/lints/struct_missing.ron + +Failed in: + struct cfg_conditional_compilation::Example, previously in file [ROOT]/test_crates/cfg_conditional_compilation/old/src/lib.rs:2 + +----- stderr ----- + Building cfg_conditional_compilation v0.1.0 (current) + Built [TIME] (current) + Parsing cfg_conditional_compilation v0.1.0 (current) + Parsed [TIME] (current) + Building cfg_conditional_compilation v0.1.0 (baseline) + Built [TIME] (baseline) + Parsing cfg_conditional_compilation v0.1.0 (baseline) + Parsed [TIME] (baseline) + Checking cfg_conditional_compilation v0.1.0 -> v0.1.0 (no change) + Checked [TIME] [TOTAL] checks: [PASS] pass, 2 fail, 0 warn, 0 skip + + Summary semver requires new major version: 2 major and 0 minor checks failed + Finished [TIME] cfg_conditional_compilation diff --git a/test_outputs/integration_snapshots__cfg_conditional_compilation_without_cfg_set.snap b/test_outputs/integration_snapshots__cfg_conditional_compilation_without_cfg_set.snap new file mode 100644 index 00000000..04c51f9c --- /dev/null +++ b/test_outputs/integration_snapshots__cfg_conditional_compilation_without_cfg_set.snap @@ -0,0 +1,31 @@ +--- +source: tests/integration_snapshots.rs +info: + program: cargo-semver-checks + args: + - semver-checks + - "--manifest-path" + - test_crates/cfg_conditional_compilation/new + - "--baseline-root" + - test_crates/cfg_conditional_compilation/old + env: + CARGO_TERM_COLOR: never + RUST_BACKTRACE: "0" +--- +success: true +exit_code: 0 +----- stdout ----- + +----- stderr ----- + Building cfg_conditional_compilation v0.1.0 (current) + Built [TIME] (current) + Parsing cfg_conditional_compilation v0.1.0 (current) + Parsed [TIME] (current) + Building cfg_conditional_compilation v0.1.0 (baseline) + Built [TIME] (baseline) + Parsing cfg_conditional_compilation v0.1.0 (baseline) + Parsed [TIME] (baseline) + Checking cfg_conditional_compilation v0.1.0 -> v0.1.0 (no change) + Checked [TIME] [TOTAL] checks: [PASS] pass, 0 skip + Summary no semver update required + Finished [TIME] cfg_conditional_compilation diff --git a/test_outputs/query_execution/feature_missing.snap b/test_outputs/query_execution/feature_missing.snap new file mode 100644 index 00000000..80dbcb6e --- /dev/null +++ b/test_outputs/query_execution/feature_missing.snap @@ -0,0 +1,26 @@ +--- +source: src/query.rs +expression: "&query_execution_results" +--- +{ + "./test_crates/feature_missing/": [ + { + "name": String("going_missing"), + "ordering_key": String("going_missing"), + }, + { + "name": String("rand"), + "ordering_key": String("rand"), + }, + { + "name": String("rand_pcg"), + "ordering_key": String("rand_pcg"), + }, + ], + "./test_crates/function_feature_changed/": [ + { + "name": String("feature_to_be_removed"), + "ordering_key": String("feature_to_be_removed"), + }, + ], +} diff --git a/tests/integration_snapshots.rs b/tests/integration_snapshots.rs index 0b3ba987..6c00e646 100644 --- a/tests/integration_snapshots.rs +++ b/tests/integration_snapshots.rs @@ -119,6 +119,86 @@ fn z_help() { }) } +/// Pin down the behavior when running `cargo-semver-checks` on a package that +/// relies on `--cfg` based conditional compilation to enable or disable functionality. +/// +/// An example of such a crate in the wild: +/// https://docs.rs/aes/latest/aes/#configuration-flags +/// +/// Since `RUSTDOCFLAGS` is set to `--cfg custom`, the crate should be checked with that setting. +/// This should reveal two breaking changes -- one caused by `cfg` in the baseline +/// and one caused by `cfg` in the current. +#[test] +fn cfg_conditional_compilation() { + assert_integration_test("cfg_conditional_compilation", |cmd, settings| { + cmd.args([ + "--manifest-path", + "test_crates/cfg_conditional_compilation/new", + "--baseline-root", + "test_crates/cfg_conditional_compilation/old", + ]) + .env("RUSTDOCFLAGS", "--cfg custom"); + + set_snapshot_filters(settings); + }); +} + +/// Analogous test to the one above, but when the `--cfg` is not set. +/// In this case, no breakage should be reported. +#[test] +fn cfg_conditional_compilation_without_cfg_set() { + assert_integration_test( + "cfg_conditional_compilation_without_cfg_set", + |cmd, settings| { + cmd.args([ + "--manifest-path", + "test_crates/cfg_conditional_compilation/new", + "--baseline-root", + "test_crates/cfg_conditional_compilation/old", + ]); + + set_snapshot_filters(settings); + }, + ); +} + +fn set_snapshot_filters(settings: &mut insta::Settings) { + // Turn dynamic time strings like [ 0.123s] into [TIME] for reproducibility. + settings.add_filter(r"\[\s*[\d\.]+s\]", "[TIME]"); + // Turn total number of checks into [TOTAL] to not fail when new lints are added. + settings.add_filter(r"\d+ checks", "[TOTAL] checks"); + // Similarly, turn the number of passed checks to also not fail when new lints are added. + settings.add_filter(r"\d+ pass", "[PASS] pass"); + // Escape the root path (e.g., in lint spans) for deterministic results in different + // build environments. + let repo_root = get_root_path(); + settings.add_filter(®ex::escape(&repo_root.to_string_lossy()), "[ROOT]"); + // Remove cargo blocking lines (e.g. from `cargo doc` output) as the amount of blocks + // is not reproducible. + settings.add_filter(" Blocking waiting for file lock on package cache\n", ""); + // Filter out the current `cargo-semver-checks` version in links to lint references, + // as this will break across version changes. + settings.add_filter( + r"v\d+\.\d+\.\d+(-[\w\.-]+)?/src/lints", + "[VERSION]/src/lints", + ); +} + +/// Helper function to get the root of the source code repository, for +/// filtering the path in snapshots. +fn get_root_path() -> PathBuf { + let canonicalized = Path::new(file!()) + .canonicalize() + .expect("canonicalization failed"); + // this file is in `$ROOT/tests/integration_snapshots.rs`, so the repo root is two `parent`s up. + let repo_root = canonicalized + .parent() + .and_then(Path::parent) + .expect("getting repo root failed"); + + repo_root.to_owned() +} + /// Helper function to get a canonicalized version of the cargo executable bin. fn executable_path() -> PathBuf { Path::new(