Skip to content

When publishing, Cargo cannot unify a local and a registry dependency in the lockfile #15622

Open
@epage

Description

@epage

Problem

When you have a direct dependency on a workspace member and an indirect dependency on a copy from the registry, when publishing they aren't unified and the process fails.

A simplified version of when I ran into this

clap_complete_nushell (local) -> clap (local)
clap_complete_nushell (local) -> nu (dev)

nu -> clap

If the version requirement on clap is too high to use the registry version, then it fails to resolve because it is locked to the lower version.

The user has to manually publish one package, cargo update, and then publish the other package.

Originally reported at #1169 (comment)

Steps

As a test case:

#[cargo_test]
fn unify_local_and_regitry_dependency() {
    let registry = RegistryBuilder::new().http_api().http_index().build();
    Package::new("dep", "1.0.0+registry").publish();
    Package::new("transitive", "1.0.0")
        .dep("dep", "1.0.0+registry")
        .publish();

    let p = project()
        .file(
            "Cargo.toml",
            r#"
            [workspace]
            members = ["parent", "dep"]
            "#,
        )
        .file(
            "parent/Cargo.toml",
            r#"
            [package]
            name = "parent"
            version = "1.0.0"
            edition = "2015"
            license = "MIT"
            description = "foo"
            repository = "foo"

            [dependencies]
            transitive = "1.0.0"
            dep = { version = "1.100", path = "../dep" }
        "#,
        )
        .file("parent/src/lib.rs", "")
        .file(
            "dep/Cargo.toml",
            r#"
            [package]
            name = "dep"
            version = "1.100.0+local"
            edition = "2015"
            license = "MIT"
            description = "foo"
            repository = "foo"
        "#,
        )
        .file("dep/src/lib.rs", "")
        .build();

    p.cargo("generate-lockfile").run();
    p.cargo("package --workspace -Zpackage-workspace")
        .masquerade_as_nightly_cargo(&["package-workspace"])
        .replace_crates_io(registry.index_url())
        .with_status(101)
        .with_stderr_data(str![[r#"
[PACKAGING] dep v1.100.0+local ([ROOT]/foo/dep)
[PACKAGED] 4 files, [FILE_SIZE]B ([FILE_SIZE]B compressed)
[PACKAGING] parent v1.0.0 ([ROOT]/foo/parent)
[UPDATING] crates.io index
[ERROR] failed to prepare local package for uploading

Caused by:
  failed to select a version for `dep`.
      ... required by package `transitive v1.0.0`
      ... which satisfies dependency `transitive = "^1.0.0"` (locked to 1.0.0) of package `parent v1.0.0 ([ROOT]/foo/parent)`
  versions that meet the requirements `^1.0.0` (locked to 1.0.0+registry) are: 1.0.0+registry

  all possible versions conflict with previously selected packages.

    previously selected package `dep v1.100.0+local`
      ... which satisfies dependency `dep = "^1.100"` of package `parent v1.0.0 ([ROOT]/foo/parent)`

  failed to select a version for `dep` which could resolve this conflict

"#]])
        .run();

    p.cargo("publish --package dep")
        .replace_crates_io(registry.index_url())
        .with_stderr_data(str![[r#"
[UPDATING] crates.io index
[PACKAGING] dep v1.100.0+local ([ROOT]/foo/dep)
[PACKAGED] 4 files, [FILE_SIZE]B ([FILE_SIZE]B compressed)
[VERIFYING] dep v1.100.0+local ([ROOT]/foo/dep)
[COMPILING] dep v1.100.0+local ([ROOT]/foo/target/package/dep-1.100.0+local)
[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s
[UPLOADING] dep v1.100.0+local ([ROOT]/foo/dep)
[UPLOADED] dep v1.100.0+local to registry `crates-io`
[NOTE] waiting for `dep v1.100.0+local` to be available at registry `crates-io`.
You may press ctrl-c to skip waiting; the crate should be available shortly.
[PUBLISHED] dep v1.100.0+local at registry `crates-io`

"#]])
        .run();

    p.cargo("package --package parent")
        .replace_crates_io(registry.index_url())
        .with_status(101)
        .with_stderr_data(str![[r#"
[PACKAGING] parent v1.0.0 ([ROOT]/foo/parent)
[UPDATING] crates.io index
[ERROR] failed to prepare local package for uploading

Caused by:
  failed to select a version for `dep`.
      ... required by package `transitive v1.0.0`
      ... which satisfies dependency `transitive = "^1.0.0"` (locked to 1.0.0) of package `parent v1.0.0 ([ROOT]/foo/parent)`
  versions that meet the requirements `^1.0.0` (locked to 1.0.0+registry) are: 1.0.0+registry

  all possible versions conflict with previously selected packages.

    previously selected package `dep v1.100.0+local`
      ... which satisfies dependency `dep = "^1.100"` of package `parent v1.0.0 ([ROOT]/foo/parent)`

  failed to select a version for `dep` which could resolve this conflict

"#]])
        .run();

    p.cargo("update dep@1.0").run();
    p.cargo("package --package parent")
        .replace_crates_io(registry.index_url())
        .with_stderr_data(str![[r#"
[PACKAGING] parent v1.0.0 ([ROOT]/foo/parent)
[UPDATING] crates.io index
[PACKAGED] 4 files, [FILE_SIZE]B ([FILE_SIZE]B compressed)
[VERIFYING] parent v1.0.0 ([ROOT]/foo/parent)
[DOWNLOADING] crates ...
[DOWNLOADED] dep v1.100.0+local
[DOWNLOADED] transitive v1.0.0
[COMPILING] dep v1.100.0+local
[COMPILING] transitive v1.0.0
[COMPILING] parent v1.0.0 ([ROOT]/foo/target/package/parent-1.0.0)
[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s

"#]])
        .run();
}

Note: cargo package --exclude-lockfile allows the package to be published

Possible Solution(s)

Update the lockfile in the same way that a hand edit to a Cargo.toml would work: updates that one package and nothing else. Even better if we treated it as a --precise call

Notes

No response

Version

❯ cargo -V --verbose
cargo 1.87.0 (99624be96 2025-05-06)
release: 1.87.0
commit-hash: 99624be96e9d213b0e9b1e36451271f24e4a41d8
commit-date: 2025-05-06
host: x86_64-unknown-linux-gnu
libgit2: 1.9.0 (sys:0.20.0 vendored)
libcurl: 8.12.1-DEV (sys:0.4.80+curl-8.12.1 vendored ssl:OpenSSL/3.4.1)
ssl: OpenSSL 3.4.1 11 Feb 2025
os: Pop!_OS 22.4.0 (jammy) [64-bit]

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions