Dependency updated when = is removed from dependency with multiple patches
Problem
Okay, this one is a little obscure, but I promise I ran into it in a not-that-weird use-case.
The basic setup is that I have a package that depends on foo, and I have two patches, both for the same major but different minor versions of foo:
$ cargo new patch-fun
$ cd patch-fun
$ cargo new --lib foo
$ cp -r foo foo010
$ cp -r foo foo011
$ sed -i 's/0.1.0/0.1.1/' foo011/Cargo.toml
$ cat >>Cargo.toml <<EOF
[dependencies]
foo = "=0.1.0"
[patch.crates-io]
foo010 = { path = "foo010", package = "foo" }
foo011 = { path = "foo011", package = "foo" }
EOF
The = in the version specifier is to force the lockfile to initially contain foo 0.1.0 and point to foo010. This works:
cargo check
warning: Patch `foo v0.1.1 (/local/home/jongje/patch-fun/foo011)` was not used in the crate graph.
Perhaps you misspell the source URL being patched.
Possible URLs for `[patch.<URL>]`:
/local/home/jongje/patch-fun/foo010
Checking foo v0.1.0 (/local/home/jongje/patch-fun/foo010)
Checking patch-warning v0.1.0 (/local/home/jongje/patch-fun)
Finished dev [unoptimized + debuginfo] target(s) in 0.61s
Cargo.lock, as expected, holds
[[package]]
name = "foo"
version = "0.1.0"
[[package]]
name = "patch-warning"
version = "0.1.0"
dependencies = [
"foo",
]
[[patch.unused]]
name = "foo"
version = "0.1.1"
The bug (?) arises when I now remove the = constraint on foo, and then re-run cargo check. Note, crucially, that I do not run cargo update or anything of the sort. I would expect Cargo to continue using foo 0.1.0, but no:
$ cargo check
warning: Patch `foo v0.1.0 (/local/home/jongje/patch-fun/foo010)` was not used in the crate graph.
Perhaps you misspell the source URL being patched.
Possible URLs for `[patch.<URL>]`:
/local/home/jongje/patch-fun/foo011
Checking foo v0.1.1 (/local/home/jongje/patch-fun/foo011)
Checking patch-warning v0.1.0 (/local/home/jongje/patch-fun)
Finished dev [unoptimized + debuginfo] target(s) in 0.63s
Cargo.lock has now swapped to foo 0.1.1 as well:
[[package]]
name = "foo"
version = "0.1.1"
[[package]]
name = "patch-warning"
version = "0.1.0"
dependencies = [
"foo",
]
[[patch.unused]]
name = "foo"
version = "0.1.0"
If I add back the =, re-generate the foo 0.1.1 lockfile, remove the = again, and then run cargo check with --locked this time, I get:
$ cargo check --locked
warning: Patch `foo v0.1.0 (/local/home/jongje/patch-fun/foo010)` was not used in the crate graph.
Perhaps you misspell the source URL being patched.
Possible URLs for `[patch.<URL>]`:
/local/home/jongje/patch-fun/foo011
error: the lock file /local/home/jongje/patch-fun/Cargo.lock needs to be updated but --locked was passed to prevent this
If you want to try to generate the lock file without accessing the network, remove the --locked flag and use --offline instead.
which seems very wrong, since the lockfile certainly doesn't need to be updated.
Steps
No response
Possible Solution(s)
No response
Notes
No response
Version
cargo 1.60.0 (d1fd9fe 2022-03-01)
release: 1.60.0
commit-hash: d1fd9fe2c40a1a56af9132b5c92ab963ac7ae422
commit-date: 2022-03-01
host: x86_64-unknown-linux-gnu
libgit2: 1.3.0 (sys:0.13.23 vendored)
libcurl: 7.80.0-DEV (sys:0.4.51+curl-7.80.0 vendored ssl:OpenSSL/1.1.1m)
os: Amazon Linux AMI 2.0.0 [64-bit]
Have a #[cargo_test] flavor test case here:
Click to expand
#[cargo_test]
fn prefer_locked_patch_if_version_req_is_still_compatible() {
Package::new("bar", "0.1.0").publish();
let p = project()
.file(
"Cargo.toml",
r#"
[package]
name = "foo"
edition = "2015"
[dependencies]
bar = "=0.1.0"
[patch.crates-io]
bar010 = { path = "bar010", package = "bar" }
bar011 = { path = "bar011", package = "bar" }
"#,
)
.file("src/lib.rs", "")
.file("bar010/Cargo.toml", &basic_manifest("bar", "0.1.0"))
.file("bar010/src/lib.rs", "")
.file("bar011/Cargo.toml", &basic_manifest("bar", "0.1.1"))
.file("bar011/src/lib.rs", "")
.build();
p.cargo("check")
.with_stderr(
"\
[UPDATING] `dummy-registry` index
[WARNING] Patch `bar v0.1.1 ([CWD]/bar011)` was not used in the crate graph.
Perhaps you misspelled the source URL being patched.
Possible URLs for `[patch.<URL>]`:
[CWD]/bar010
[LOCKING] 2 packages to latest compatible versions
[CHECKING] bar v0.1.0 ([CWD]/bar010)
[CHECKING] foo v0.0.0 ([CWD])
[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [..]
",
)
.run();
p.change_file(
"Cargo.toml",
r#"
[package]
name = "foo"
edition = "2015"
[dependencies]
bar = "0.1.0"
[patch.crates-io]
bar010 = { path = "bar010", package = "bar" }
bar011 = { path = "bar011", package = "bar" }
"#,
);
let lock = p.read_lockfile();
assert!(lock.contains("[patch.unused]"));
p.cargo("check")
.with_stderr(
"\
[UPDATING] `dummy-registry` index
[WARNING] Patch `bar v0.1.1 ([CWD]/bar011)` was not used in the crate graph.
Perhaps you misspelled the source URL being patched.
Possible URLs for `[patch.<URL>]`:
[CWD]/bar010
[LOCKING] 2 packages to latest compatible versions
[CHECKING] bar v0.1.0 ([CWD]/bar010)
[CHECKING] foo v0.0.0 ([CWD])
[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [..]
",
)
.run();
let lock = p.read_lockfile();
assert!(lock.contains("[patch.unused]"));
}
I wonder it is Cargo registers patch entries first and then previous locks that causes the problem. It might also due to how Cargo registers patches. I didn't really look closer though.