How do I pin indirect dependencies of a crate?
Asked Answered
R

3

25

My project A depends on library B that depends on library C.

Library B sets the dependency version to "*" (any) so Cargo will download the latest version of C.

How can I instruct Cargo to build library B using a specific version of library C?


I'm trying to build iron.

The current build is failing, but I can see the last successful build, including Rust and Cargo package versions.

I downloaded the specific Rust nightly used in the build and I've set the the direct dependencies of iron to the ones used in that build by editing Cargo.toml:

[dependencies]
hyper = "0.0.18"
typemap = "0.0.5"
url = "0.2.9"

rust-serialized, which is a dependency of the url and time packages, is downloaded as the latest version which doesn't compile with my specific Rust version.

If I used the version used in the Travis build above I'm sure it will compile successfully.

Rigdon answered 4/1, 2015 at 20:30 Comment(4)
As an aside, you shouldn't manually edit Cargo.lock. Humans edit Cargo.toml, Cargo edits Cargo.lock.Granulite
Yeah, but if I had the exact Cargo.lock which generated that build, I should be able to generate the exact same build. So ideally there should be an way for me to tell cargo to build a Cargo.lock exactly like the one that generated that build, including dependency of dependency's version.Rigdon
@John do you have a specific set of crates that exhibit the problem today? It's very hard to test with the 6-year-old examples in the post.Granulite
Yes, here is my example: I have a crate that depends on zstd. zstd depends on zstd-safe. zstd-safe depends on zstd-sys. My crate gets linked into a C++ binary that also links zstd as a C library. The versions of the zstd C library and the zstd vendored by zstd-sys must match exactly, or I get link errors. So I want to pin an exact version of zstd-sys.Entice
G
15

Manual editing

You can check out Iron, modify Cargo.toml to specify versions (as you have already done). Then you repeat the process, checking out url, modifying its Cargo.toml, then make sure you are using your version of url in Iron's Cargo.toml. Rinse and repeat.

Patch overrides

From the Cargo docs:

The [patch] section of Cargo.toml can be used to override dependencies with other copies. The syntax is similar to the [dependencies] section:

[patch.crates-io]
foo = { git = 'https://github.com/example/foo' }
bar = { path = 'my/local/bar' }

Sources can be patched with versions of crates that do not exist, and they can also be patched with versions of crates that already exist. If a source is patched with a crate version that already exists in the source, then the source's original crate is replaced.

Path overrides

From the Cargo docs:

Sometimes you're only temporarily working on a crate and you don't want to have to modify Cargo.toml like with the [patch] section above. For this use case Cargo offers a much more limited version of overrides called path overrides.

Path overrides are specified through .cargo/config.toml instead of Cargo.toml. Inside of .cargo/config.toml you'll specify a key called paths:

paths = ["/path/to/uuid"]

Specific versions

You might be able to simply specify versions (or SHA hashes) for each dependency that you know works with your Rust version. Cargo should be able to resolve the transitive dependencies and lock you to a previous version if there is one that fits all the requirements.

Alternatively, you can use cargo update -p somecrate --precise major.minor.patch to specify the exact version of a crate and record it in your Cargo.lock.

This may not work in all cases; Rust can have multiple versions of the same library compiled into one binary. That would mean that there's no one place you can specify a version that applies all over.

Addressing the bounty

John adds:

I have a crate that depends on zstd. zstd depends on zstd-safe. zstd-safe depends on zstd-sys. My crate gets linked into a C++ binary that also links zstd as a C library. The versions of the zstd C library and the zstd vendored by zstd-sys must match exactly, or I get link errors. So I want to pin an exact version of zstd-sys

This case can follow the "specific versions" example above, but because zstd-sys uses a links key, there can only ever be exactly one of that crate in the entire crate graph. That means you can add zstd-sys to your top-level dependencies and feel comfortable that Cargo will complain if a conflicting version is introduced:

[dependencies]
zstd = "0.9.0"
zstd-sys = "=1.6.1"

If I edit this to specify version 1.6.0, I get an error:

error: failed to select a version for `zstd-sys`.
    ... required by package `zstd-safe v4.1.1+zstd.1.5.0`
    ... which is depended on by `zstd v0.9.0+zstd.1.5.0`
    ... which is depended on by `so v0.1.0 (/private/tmp/so)`
versions that meet the requirements `=1.6.1` are: 1.6.1+zstd.1.5.0

the package `zstd-sys` links to the native library `zstd`, but it conflicts with a previous package which links to `zstd` as well:
package `zstd-sys v1.6.0+zstd.1.5.0`
    ... which is depended on by `so v0.1.0 (/private/tmp/so)`
Granulite answered 4/1, 2015 at 20:43 Comment(9)
FWIW, this would require manually downloading corresponding package versions and extracting them somewhere.Goldshell
@VladimirMatveev yeah, good point. I reordered my two suggestions as "specify all the versions" is probably a better solution (if it works in this case)Granulite
I know the repository for rustc-serialize, but I don't know which commit generated the package version 0.1.5. How do I download the package source for a specific version? How do I know which commit generated a specific package version?Rigdon
Normally I'd say to just grab the git tag for 0.1.5, but it seems they aren't pushing those. That's no good... That being said, you can check the history for that file, and you can spelunk a bit and get to the commit that bumped the version numberGranulite
Yeah, sure I can guess the commit which generated the package. But by cargo's philosophy I should be able to generate the exact same build. If I guessed the commit there's no guarantee that it would be the exact build. So it would solve my problem, but it's not ideal.Rigdon
Well, you can only generate the same build if you have the same Cargo.lock. In this case, Iron is a library, so it doesn't maintain a lockfile and it's up to the end application(s) to create a lockfile. The CI build isn't the ideal place to be starting this, but neither is it fun trying to keep pace with a language going to 1.0!Granulite
I'm sorry but it "feels wrong" to me. If I could go back in time to the state crate.io was in the date of the compilation I could get a build running. But because there were updates in packages in crate.io I no longer can, even though crate.io contains the packages in the specific version I want. It feels to me like a "regression" that is not really cargo's user's fault. The user is then obligated to download and compile each of those packages, and set the path in .cargo/config. This would be useful even after rust is stabilized.Rigdon
FWIW, the "first idea" works perfectly, and is the recommended way if you have multiple libraries that depend on the same crate that need to interoperate.Natasha
I'd award the bounty to this answer with the following improvements: * Add an introduction to contextualize the details. Something like: "Cargo does not have a feature specifically addressed to the use case of pinning indirect dependencies. However, the following techniques may help in certain circumstances." * Lead with the "Specific versions" / "Addressing the bounty" sections, as (when applicable), it seems preferable to forking dependencies. * Explain "Patch overrides" / "Path overrides" in greater detail. Are these what to do after manual editing, to use your manual edits?Entice
A
2

The answer by Shepmaster is good, but doesn't show how to target a specific commit, which is important in my opinion since it ensures stability of builds. (ie. you always get the same crate contents until you manually update the Cargo.toml file to target a newer commit, and no chance of rust-analyzer missing a commit you just pushed to the branch and wanted integrated)

Here's how to target a specific commit:

[patch.crates-io]
hyper = { git = "https://github.com/YOUR_USERNAME/hyper.git", rev = "SHA1_OF_TARGET_COMMIT", features = ["client"] }

The above should be added to your root Cargo.toml file. (eg. not in ./pkg/component1/Cargo.toml, if using a mono-repo)

Autotomize answered 9/3 at 15:11 Comment(1)
It should be noted that the Cargo.lock keeps the commit hash regardless whether you specify it or not, and you should always have Cargo.lock anyway to ensure build stability. On top of that, branch or tag may be more appropriate than rev depending on your use-case.Decontaminate
L
0

Since the anwser provided, Cargo has added the [patch] section to the manifest which allows you to do this use case.

overriding-dependencies

Lelialelith answered 12/12, 2019 at 2:24 Comment(3)
This answer would be more helpful if it included an example. The use case as mentioned in the question is not covered in the official documentation.Annunciata
I could not figure out how to use [patch] to accomplish this.Entice
yeah, it is non-obvious how [patch] solves this problem, at least from the documentationConsidered

© 2022 - 2024 — McMap. All rights reserved.