cargo icon indicating copy to clipboard operation
cargo copied to clipboard

Support patching one with another package from the same source

Open BusyJay opened this issue 3 years ago • 9 comments

Describe the problem you are trying to solve

I want to patch an indirect dependency. And cloning the dependency can take a lot of time due to huge history, so I publish the patched version to crates.io with a different name and suppose to patch the dependency with the new name. I tried

[patch]
indirect-dep = { package = "new-name", version = "same-version" }

But it will report

patch for `new-name` in `https://github.com/rust-lang/crates.io-index` points to the same source, but patches must point to different sources

Seems only git and path are supported.

Describe the solution you'd like Supporting patch one dependency with another dependency from registry.

Notes

BusyJay avatar Mar 02 '21 14:03 BusyJay

@weihanglo Asked if I could provide some context for why I've needed this as well.

At $work, we enforce, among other things, that Rust crates always start with a particular prefix (say, $work-), and we mask out any packages that start with $work- from crates.io. This helps mitigate namesquatting attacks (again, among other things). A result of this is that if anyone needs to, say, test out an internal modification to a crates.io crate, or even build a longer-term variant or fork of some crates.io crate, they have to publish it internally with the $work- prefix as well to avoid colliding with the external crate name. Unfortunately, that means that they now cannot patch in the modified crate in place of the original one due to this issue. Their options are:

  1. Don't do an internal publish, and instead put the modified crate directly into their consuming package with its original crate name. But this doesn't allow for (easily) sharing a modification across packages.
  2. Do a publish, but have special build logic that runs before cargo that downloads and extracts the .crate for the modified version locally, sed out the name in the Cargo.toml, and inject a path-based [patch] in .cargo/config.toml. But this is very cumbersome.
  3. Don't publish, and instead declare a git dependency on the modified crate (retaining its original crate name). But this doesn't work for network-jailed builds.
  4. Find a way to modify everything in their dependency graph to declare a dependency on the modified crate's name instead. But this is untenable for anything beyond the packages under the direct control of the team wanting to do the experiment.

These are all suboptimal options. Whereas if it were possible to patch in one crate with another, the team could simply add something like

[patch.crates-io]
new_foo_instead = { as = "foo", package = "new-foo" }

and they'd get the behavior they want.

I think there are definitely some open questions here around things like what we do with features, whether there's a potential for resolving require infinite recursion, whether we should instead aim for a more general-purpose dependency graph grafting mechanism, etc., but at least in my mind there's no doubt that this ability is useful.

jonhoo avatar Nov 30 '22 19:11 jonhoo

We may need this feature as well for using drop-in replacement libraries where the original and the drop-in replacement are both published on crates.io.

Edit for context:

  • long term requirement, replacing ring with a FIPS-certified drop in replacement.
  • short term(?) requirement, replacing webpki with rustls-webpki (a fork to fix stale issues)

webern avatar Feb 03 '23 00:02 webern

+1 from me, this would be very useful to us too.

toml01 avatar Feb 22 '23 10:02 toml01

I just ran into this when I tried to do the following patch

[patch.crates-io]
buf_redux = { package = "buffer-redux", version = "1.0.0" }

to replace the no longer maintained buf_redux with the recent buffer-redux fork which fixes a future incompatibilities warning.

Skgland avatar Mar 20 '23 11:03 Skgland

We're in a situation where we have many transitive dependencies on sqlx 0.6.3, across many crates. sqlx, the main crate, has updated to an api-incompatible sqlx 0.7, and are not maintaining the 0.6 release any longer, which prompted a fork of sqlx 0.6 as sqlx-oldapi — now up to 0.6.11.

I would like to be able to tell cargo that I want all transitive copies of sqlx 0.6.3 to resolve to sqlx-oldapi 0.6.11 instead.

(my current solution to this is doing Crimes™ — i've forked sqlx-oldapi 0.6.11, renamed it back to sqlx, reversioned it back to 0.6.3, and put it on github, which lets me write this patch stanza:

[patch.crates-io]
sqlx = { git = "https://github.com/SohumB/sqlx-oldapi-for-patch", rev = "109f797" }
sqlx-core = { git = "https://github.com/SohumB/sqlx-oldapi-for-patch", rev = "109f797" }
sqlx-macros = { git = "https://github.com/SohumB/sqlx-oldapi-for-patch", rev = "109f797" }
sqlx-rt = { git = "https://github.com/SohumB/sqlx-oldapi-for-patch", rev = "109f797" }

this is obviously not ideal.)

SohumB avatar Aug 24 '23 02:08 SohumB

webpki and serde_derive have recently highlighted the need for this feature.

When it was noticed that serde_derive was shipping a precompiled binary with no way to opt out, there was a discussion of forking it, and although there were a few forks made, it was quickly pointed out that a fork published on Crates.io would not be usable as a drop-in replacement, specifically because there's no way to patch in a replacement crate from Crates.io.

Similarly, webpki has a new security advisory, but webpki itself is unmaintained; there is a fork called rustls-webpki that has the fix for this security advisory, but no way to recursively patch it into existing projects until all dependencies have been updated (which may not happen soon or at all in the case of infrequently updated crates such as hyper-proxy).

BatmanAoD avatar Aug 25 '23 23:08 BatmanAoD

We talked about this in today's cargo meeting.

A potential technical limitation is that patches are on the workspace, the local version of your package will be different than what is published

A potential technical impediment is that cargo uses name + (compatible) version for a lot of bookkeeping and this would likely be a big lift to support a different package name or semver incompatible version

We suspect an original social reason for the limitation was to discourage long-term patched forks. However, we do feel that there is value in intermediate-term patches that this would help with.

Likely the path forward would be for someone to dig in to the how cargo tracks packages and see how feasible it would be to support this before we worry too much about any of the social aspects to this (no point arguing over that if it just can't be done).

epage avatar Aug 29 '23 18:08 epage

Thank you for the update!

BatmanAoD avatar Aug 29 '23 20:08 BatmanAoD

Any news on this? I came across a situation that really feels like it should be easier:

I write an egui application using also egui_struct and catppuccin-egui. Since both additional packages are providing macros to generate egui code, they have egui as part of there public dependency interface. So i have to enforce that the versions are equal between egui, egui_struct.egui and catppuccin-egui.egui. The dependencies seem flexible enough to work properly with just version bumping internally their transitive egui dependency. At the moment i am just able to do this by forking egui_struct and catppuccin-egui, but this is really just too much for just patching one transitive dependency.

It seems the patch capabilites of cargo should do exactly that of handling on workspace level the version override even for transitive dependencies, but when i just go for:

[patch.crates-io]
egui = "0.26.2"

but with this i get:

cargo update
    Updating crates.io index
error: failed to resolve patches for `https://github.com/rust-lang/crates.io-index`

Caused by:
  patch for `egui` in `https://github.com/rust-lang/crates.io-index` points to the same source, but patches must point to different sources

I try at the moment also to come around this problem by changing the source to go for egui from git in same version by using:

[patch.crates-io]
egui = { git = 'https://github.com/emilk/egui', tag = '0.26.2' }

This leads to not abort anymore on cargo update, but it does not have the desired effect. My cargo.lock file does contain still three different versions of egui and egui_struct and catppuccin-egui do contain outdated egui versions in there dependency list.


[[package]]
name = "catppuccin-egui"
version = "4.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "64bd5a6c2c4bd822735e69d27609fe6fc41e8ec7dd0a7c338dedfdf69fd915b6"
dependencies = [
 "egui 0.25.0",
]

[[package]]
name = "egui_struct"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d6eca44277ee14d945cd398f91834d696c990c53b7f4ac2796368ce2b09b5c6c"
dependencies = [
 "egui 0.23.0",
 "egui_struct_macros",
]

[[package]]
name = "egui"
version = "0.23.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8bd69fed5fcf4fbb8225b24e80ea6193b61e17a625db105ef0c4d71dde6eb8b7"
dependencies = [
 "ahash",
 "epaint 0.23.0",
 "nohash-hasher",
]

[[package]]
name = "egui"
version = "0.25.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e0bf640ed7f3bf3d14ebf00d73bacc09c886443ee84ca6494bde37953012c9e3"
dependencies = [
 "ahash",
 "epaint 0.25.0",
 "nohash-hasher",
]

[[package]]
name = "egui"
version = "0.26.2""git+https://github.com/emilk/egui?tag=0.26.2#309586b42cc809af7e262a194eef5c35013d1bf1"
dependencies = [
source = 
 "accesskit",
 "ahash",
 "epaint 0.26.2 (git+https://github.com/emilk/egui?tag=0.26.2)",
 "log",
 "nohash-hasher",
 "puffin",
 "ron",
 "serde",
]

What is the desired way to just enforce some transitive dependencies to run the same version without the need of forking the dependencies?

cguentherTUChemnitz avatar Feb 28 '24 10:02 cguentherTUChemnitz