cargo icon indicating copy to clipboard operation
cargo copied to clipboard

Dependency updated when = is removed from dependency with multiple patches

Open jonhoo opened this issue 3 years ago • 1 comments

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]

jonhoo avatar May 06 '22 19:05 jonhoo

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.

weihanglo avatar May 01 '24 14:05 weihanglo