Skip to content

Commit 19c362c

Browse files
authored
Transparently ignore semver trick (#773)
The [semver trick](https://github.com/dtolnay/semver-trick) has been around for a while, but isn't widely used, however, recently `webpki-roots` did use it for 0.26.11 -> 1.0.0, which means that every crate with a dependency on it that banned duplicates needed to add a skip. With this change, cargo-deny will detect if the only dependents of a particular version are other versions of itself, skipping them automatically.
1 parent 12a1116 commit 19c362c

20 files changed

+428
-1166
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
88

99
<!-- next-header -->
1010
## [Unreleased] - ReleaseDate
11+
### Changed
12+
- [PR#773](https://github.com/EmbarkStudios/cargo-deny/pull/773) changed cargo-deny's duplicate detection to automatically ignore versions whose only dependent is another version of the same crate.
13+
1114
## [0.18.2] - 2025-03-10
1215
### Added
1316
- [PR#753](https://github.com/EmbarkStudios/cargo-deny/pull/753) resolved [#752](https://github.com/EmbarkStudios/cargo-deny/issues/752) by adding back the `advisories.unmaintained` config option. See the [docs](https://embarkstudios.github.io/cargo-deny/checks/advisories/cfg.html#the-unmaintained-field-optional) for how it can be used. The default matches the current behavior, which is to error on any `unmaintained` advisory, but adding `unmaintained = "workspace"` to the `[advisories]` table will mean unmaintained advisories will only error if the crate is a direct dependency of your workspace.

Cargo.lock

Lines changed: 8 additions & 8 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ fern = "0.7"
6565
# Glob matching
6666
globset = "0.4"
6767
# Native executable detection
68-
goblin = { version = "0.9", default-features = false, features = [
68+
goblin = { version = "0.10", default-features = false, features = [
6969
"elf32",
7070
"elf64",
7171
"mach32",
@@ -105,7 +105,7 @@ spdx = "0.10"
105105
# Lazy
106106
strum = { version = "0.27", features = ["derive"] }
107107
# Index retrieval and querying
108-
tame-index = { version = "0.21", default-features = false, features = [
108+
tame-index = { version = "0.22", default-features = false, features = [
109109
"git",
110110
"local",
111111
"sparse",
@@ -118,7 +118,7 @@ time = { version = "0.3", default-features = false, features = [
118118
# Deserialization of configuration files and crate manifests
119119
toml-span = { version = "0.5", features = ["reporting"] }
120120
# Small fast hash crate
121-
twox-hash = { version = "2.0", default-features = false, features = ["xxhash32"] }
121+
twox-hash = { version = "2.1", default-features = false, features = ["xxhash32"] }
122122
# Url parsing/manipulation
123123
url = "2.5"
124124
# Directory traversal
@@ -140,7 +140,7 @@ features = [
140140
fs_extra = "1.3"
141141
# Snapshot testing
142142
insta = { version = "1.43", features = ["json"] }
143-
tame-index = { version = "0.21", features = ["local-builder"] }
143+
tame-index = { version = "0.22", features = ["local-builder"] }
144144
time = { version = "0.3", features = ["serde"] }
145145
toml-span = { version = "0.5", features = ["serde"] }
146146
# We use this for creating fake crate directories for crawling license files on disk

deny.toml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,10 @@ skip = [
3030
{ crate = "[email protected]", reason = "gix uses this old version" },
3131
{ crate = "[email protected]", reason = "reqwest -> system-configuration uses this old version" },
3232
{ crate = "[email protected]", reason = "ring uses this old version" },
33-
{ crate = "[email protected]", reason = "semver trick" },
3433
]
3534
skip-tree = [
3635
{ crate = "[email protected]", reason = "a foundational crate for many that bumps far too frequently to ever have a shared version" },
37-
{ crate = "[email protected]", reason = "gix depends on both the 1.0 and 2.0 versions" },
36+
{ crate = "[email protected]", reason = "rustsec depends 1.0, patched, but not released https://github.com/rustsec/rustsec/commit/9b97c0fc155752c8298a5b5406eb175765ceac93" },
3837
]
3938

4039
[sources]

src/bans.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -381,6 +381,30 @@ pub fn check(
381381
);
382382

383383
let report_duplicates = |multi_detector: &mut MultiDetector<'_>, sink: &mut diag::ErrorSink| {
384+
if multi_detector.dupes.len() != 1 {
385+
// Filter out crates that depend on another version of themselves https://github.com/dtolnay/semver-trick
386+
multi_detector.dupes.retain(|(index, _)| {
387+
let krate = &ctx.krates[*index];
388+
389+
// We _could_ just see if this crate's dependencies is another
390+
// version of itself, but that means if there are other versions
391+
// of the crate then the version that is doing the trick is not
392+
// reported, so we do the more expensive check for the direct
393+
// dependents
394+
let direct = ctx
395+
.krates
396+
.direct_dependents(ctx.krates.nid_for_kid(&krate.id).unwrap());
397+
398+
let res = !direct.iter().all(|dir| dir.krate.name == krate.name);
399+
400+
if !res {
401+
log::debug!("ignoring duplicate crate '{krate}', its only dependents was another version of itself");
402+
}
403+
404+
res
405+
});
406+
}
407+
384408
let skipped = multi_detector
385409
.dupes
386410
.iter()

tests/bans.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,7 @@ fn ignores_dev() {
194194
r#"
195195
multiple-versions = 'deny'
196196
skip = [
197-
{ name = 'block-buffer', version = "=0.7.3" },
197+
"block-buffer@0.7.3"
198198
]
199199
"#,
200200
);

tests/snapshots/bans__deny_multiple_versions_for_specific_krates.snap

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -369,7 +369,7 @@ expression: diags
369369
"labels": [
370370
{
371371
"column": 1,
372-
"line": 50,
372+
"line": 51,
373373
"message": "lock entries",
374374
"span": "generic-array 0.12.4 registry+https://github.com/rust-lang/crates.io-index\ngeneric-array 0.14.5 registry+https://github.com/rust-lang/crates.io-index"
375375
}

tests/snapshots/bans__deterministic_duplicate_ordering.snap

Lines changed: 56 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -315,7 +315,7 @@ expression: diags
315315
"labels": [
316316
{
317317
"column": 1,
318-
"line": 31,
318+
"line": 32,
319319
"message": "lock entries",
320320
"span": "digest 0.8.1 registry+https://github.com/rust-lang/crates.io-index\ndigest 0.10.3 registry+https://github.com/rust-lang/crates.io-index"
321321
}
@@ -526,7 +526,7 @@ expression: diags
526526
"labels": [
527527
{
528528
"column": 1,
529-
"line": 50,
529+
"line": 51,
530530
"message": "lock entries",
531531
"span": "generic-array 0.12.4 registry+https://github.com/rust-lang/crates.io-index\ngeneric-array 0.14.5 registry+https://github.com/rust-lang/crates.io-index"
532532
}
@@ -535,5 +535,59 @@ expression: diags
535535
"severity": "error"
536536
},
537537
"type": "diagnostic"
538+
},
539+
{
540+
"fields": {
541+
"code": "duplicate",
542+
"graphs": [
543+
{
544+
"Krate": {
545+
"name": "webpki-roots",
546+
"version": "0.25.4"
547+
},
548+
"parents": [
549+
{
550+
"Krate": {
551+
"name": "minreq",
552+
"version": "2.13.4"
553+
},
554+
"parents": [
555+
{
556+
"Krate": {
557+
"name": "duplicates",
558+
"version": "0.1.0"
559+
}
560+
}
561+
]
562+
}
563+
]
564+
},
565+
{
566+
"Krate": {
567+
"name": "webpki-roots",
568+
"version": "0.26.11"
569+
},
570+
"parents": [
571+
{
572+
"Krate": {
573+
"name": "duplicates",
574+
"version": "0.1.0"
575+
}
576+
}
577+
]
578+
}
579+
],
580+
"labels": [
581+
{
582+
"column": 1,
583+
"line": 140,
584+
"message": "lock entries",
585+
"span": "webpki-roots 0.25.4 registry+https://github.com/rust-lang/crates.io-index\nwebpki-roots 0.26.11 registry+https://github.com/rust-lang/crates.io-index"
586+
}
587+
],
588+
"message": "found 2 duplicate entries for crate 'webpki-roots'",
589+
"severity": "error"
590+
},
591+
"type": "diagnostic"
538592
}
539593
]

tests/snapshots/bans__duplicate_graphs.snap

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,4 +141,19 @@ expression: dup_graphs.lock()
141141
}
142142
}
143143
,
144+
digraph {
145+
0 [label="0.25.4", shape=box, style=rounded, color=red]
146+
1 [label="0.26.11", shape=box, style=rounded, color=red]
147+
2 [label="duplicates 0.1.0", shape=box, style=rounded]
148+
3 [label="minreq 2.13.4", shape=box, style=rounded]
149+
2 -> 1 [color=red]
150+
3 -> 0 [color=blue]
151+
2 -> 3 [color=blue]
152+
subgraph cluster_0 {
153+
{rank=same 0 1 }
154+
style="rounded,filled";
155+
label="webpki-roots"
156+
}
157+
}
158+
,
144159
]

tests/snapshots/bans__ignores_dev.snap

Lines changed: 56 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,70 @@ source: tests/bans.rs
33
expression: diags
44
---
55
[
6+
{
7+
"fields": {
8+
"code": "duplicate",
9+
"graphs": [
10+
{
11+
"Krate": {
12+
"name": "webpki-roots",
13+
"version": "0.25.4"
14+
},
15+
"parents": [
16+
{
17+
"Krate": {
18+
"name": "minreq",
19+
"version": "2.13.4"
20+
},
21+
"parents": [
22+
{
23+
"Krate": {
24+
"name": "duplicates",
25+
"version": "0.1.0"
26+
}
27+
}
28+
]
29+
}
30+
]
31+
},
32+
{
33+
"Krate": {
34+
"name": "webpki-roots",
35+
"version": "0.26.11"
36+
},
37+
"parents": [
38+
{
39+
"Krate": {
40+
"name": "duplicates",
41+
"version": "0.1.0"
42+
}
43+
}
44+
]
45+
}
46+
],
47+
"labels": [
48+
{
49+
"column": 1,
50+
"line": 140,
51+
"message": "lock entries",
52+
"span": "webpki-roots 0.25.4 registry+https://github.com/rust-lang/crates.io-index\nwebpki-roots 0.26.11 registry+https://github.com/rust-lang/crates.io-index"
53+
}
54+
],
55+
"message": "found 2 duplicate entries for crate 'webpki-roots'",
56+
"severity": "error"
57+
},
58+
"type": "diagnostic"
59+
},
660
{
761
"fields": {
862
"code": "unmatched-skip",
963
"graphs": [],
1064
"labels": [
1165
{
12-
"column": 15,
66+
"column": 6,
1367
"line": 4,
1468
"message": "unmatched skip configuration",
15-
"span": "block-buffer"
69+
"span": "block-buffer@0.7.3"
1670
}
1771
],
1872
"message": "skipped crate 'block-buffer = =0.7.3' was not encountered",

0 commit comments

Comments
 (0)