-
Notifications
You must be signed in to change notification settings - Fork 2.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
docs(faq): Recommend always committing Cargo.lock
#12275
Conversation
Thanks for the pull request, and welcome! The Rust team is excited to review your changes, and you should hear from @epage (or someone else) soon. Please see the contribution instructions for more information. Namely, in order to ensure the minimum review times lag, PR authors and assigned reviewers should ensure that the review label (
|
a7e07b6
to
5f7669a
Compare
I object to this change. We should instead recommend that Cargo.lock never be committed. Committing lock files optimizes for a tiny handful of use cases (that are primarily encountered by core developers, hence core developers tend to think this is a good idea) at the expense of all other use cases. In particular, committed lockfiles are a hindrance for anyone who wants to take some existing software and reuse it in a context other than exactly how its core developers were using it. (I'm honestly tempted to write and submit a PR that completely removes the lockfile feature, that's how strongly I feel about this.) |
IMO this is an unequivocally good change to commit. There is no valid reason for dealing with non-deterministic build / tests nowadays. Especially with dependabot which keeps track of dependencies and bumps them by preserving reproducibility. If you go back to an old commit at any point, everything should keep working in the same exact way; all the information about the dependencies must be contained within the repo at that commit. |
The mere existence of dependabot should clue you in to the fact that dependency locking is a mistake. It makes so much extra work for everyone that we had to create a bot to automate part of the process! And it's still extra work for everyone, vs. the alternative of not locking dependencies in the first place. Reproducibility is exactly what I was talking about when I said "a tiny handful of use cases primarily encountered by core developers". Yes, it's valuable to be able to know what versions were used to test an old revision of the code --- for core developers. For everyone who isn't developing a piece of software, who just wants to use the damn thing, that information is only of interest if something goes wrong when they try to use the program with set of dependencies they want. Apart from that, dependency locks are nothing but an obstacle to their ability to control that set. |
Maybe this will help you understand where I'm coming from: Suppose cargo was changed so that it would still create |
I'm not sure why it would be better, as a "user" (not core developer) of a crate (e.g. a binary), to try your luck and get the latest version of all the deps, which has a nonzero chance of not working, vs just use the version of the deps that was checked in (and hopefully tested at least once) by the developer. AFAICT you are assuming not only that things will keep working, but that presumably they will automatically improve the experience for users by virtue of picking a later version of all the dependencies? |
|
If anything, personally I would also like to see this reversed at some point: use the checked-in Cargo.lock by default, unless you invoke |
I'm not assuming that, my experience over the past 25 years has invariably been that the user experience is improved by picking the latest version of all the dependencies, consistent with the actual constraints as stated in |
I have yet to see a single instance in which blindly picking a later version makes anything better -- OTOH I have certainly observed many cases in which nothing at all was working, or things broke in subtle ways. But I guess at least now we know what we agree to disagree about 👍 |
Those semantics are still an obstacle for non-core developers, particularly of library crates. Consider the following scenario:
It would be better to honor |
I think you've had bad experiences with software where the authors were sloppy about stating their actual version constraints. Ironically, dependency locking encourages that kind of sloppiness... |
Without commenting on whether we should make this change or not (I'll wade into that one at some point but not right this minute): if we make this change, we should also change |
ccc6ac6
to
a002f09
Compare
a002f09
to
67fcc3d
Compare
@joshtriplett Thank you for your comment, you're right that |
67fcc3d
to
7272d86
Compare
@zackw
From my point of view, the fact that the library has a lock file committed is already a very good starting point. Since we're talking about a lib with loose requirements (e.g. I feel that we can at least agree that it's useful information to have available when debugging such problems? Your concern seems mostly directed at the fact that cargo uses lock files by default. My opinion is that it's a safer default that makes software more approachable actually. I like how the Ruby community captured it (see quote in original post). Having the lockfile enabled by default means that anyone can clone a project and confidently run |
7272d86
to
1a34081
Compare
Echo what joshtriplett said, there are places more than cargo/src/cargo/core/package.rs Lines 206 to 209 in d93b018
(not commenting on whether should make this change either) |
1a34081
to
878c2d4
Compare
Whatever my own thoughts are on this, can we move this conversation to #8728? That is where the discussion should have originally happened before opening a PR and this is unlikely to be decided within the confines of this PR, so I think it would help if we did not bifurcate the discussion between here and the issue. |
This commit updates the FAQ to recommend to always commit `Cargo.lock` in all projects, both binaries and libraries. The FAQ previously discouraged `Cargo.lock` in libraries. The question of changing the recommendation to always commit the lock file was raised in rust-lang#8728, as well as in a few Rust Internals threads in the previous months ([Feedback of cargo-upgrade](https://internals.rust-lang.org/t/feedback-on-cargo-upgrade-to-prepare-it-for-merging/17101/124?u=demurgos), [Cargo yank is a misfeature](https://internals.rust-lang.org/t/suggestion-cargo-yank-is-a-misfeature-and-should-be-deprecated-and-eventually-removed/18486/15)) and was a relatively popular proposition. `Cargo.lock` enables reproducible builds, I argue that this is a desirable property for _all_ projects. Reproducible builds are a safe and powerful default both for binaries and libraries. Wanting a non-reproducible build is less common, and still easy to achieve even if a lock file is committed. Applications have binaries compiled through `cargo build` and executed by `cargo run`. Libraries also have binaries, they're just executed through `cargo test` instead. In both cases, it is desirable to be able to work with the version control system when debugging some regression. A `Cargo.lock` enables workflows such as `git bisect` or reproducing a failed test across all environments (e.g. CI and dev machines). 1. A lockfile enables reproducible builds across commits. This means that you can browse the git history and reproduce your tests as they were at this time. This unlocks `git bisect` workflow and is extremely useful when working on transitive dependency issues. 2. A lockfile ensures that you can reproduce an older build even if it contains yanked dependencies or weakly constrained versions (semver requirement `*`, git repository without a revision, ...). 3. Even if a lock file is present, it is easy to refresh it / force dependency resolution again. On the other hand, if the lockfile is missing then it is very hard to retrieve the resolution at this time (or even impossible). Having a lock file is a safer default (no information loss). 4. When multiple developers contribute to the same project (even a library), a lock file ensures that all contributors test with the same dependencies. Without a lockfile, developers may see different behavior when testing. This also enables reproducing CI errors locally for example. 5. The usual objection to committing a lockfile is that it may take longer to detect regressions in transitive dependencies. In practice, this does not change much: refreshing the dependencies is always an explicit operation in Cargo anyway. The only time when it's done implicitly is when cloning a project for the first if it does not have a lock file yet. Checking transitive dependencies is better handled explicitly in CI or through tools such as Dependabot. Catching problems with new dependencies should not happen when someone is trying to test an unrelated change. There is also a magnitude more builds in consumer projects than CI builds for a lib, so you should be ready to get messaged by your users anyway if you break something :) This change to always commit lock files also aligns with prior art from other ecosystems. - [Yarn for Node.js](https://yarnpkg.com/getting-started/qa#which-files-should-be-gitignored) > Which files should be gitignored? > > [...] > > - yarn.lock should always be stored within your repository ([even if you develop a library](https://yarnpkg.com/getting-started/qa#should-lockfiles-be-committed-to-the-repository)). - [Poetry for Python](https://python-poetry.org/docs/basic-usage/#committing-your-poetrylock-file-to-version-control) > As a library developer > > A simple way to avoid such a scenario [testing with latest transitive dependencies] is to omit the `poetry.lock` file. However, by doing so, you sacrifice reproducibility and performance to a certain extent. Without a lockfile, it can be difficult to find the reason for failing tests, because in addition to obvious code changes an unnoticed library update might be the culprit. [...] > > If you do not want to give up the reproducibility and performance benefits, consider a regular refresh of `poetry.lock` to stay up-to-date and reduce the risk of sudden breakage for users. - [Bundler for Ruby](https://bundler.io/guides/faq.html#using-gemfiles-inside-gems) > Q: Should I commit my `Gemfile.lock` when writing a gem? > > A: Yes, you should commit it. The presence of a `Gemfile.lock` in a gem’s repository ensures that a fresh checkout of the repository uses the exact same set of dependencies every time. We believe this makes repositories more friendly towards new and existing contributors. Ideally, anyone should be able to clone the repo, run `bundle install`, and have passing tests. If you don’t check in your `Gemfile.lock`, new contributors can get different versions of your dependencies, and run into failing tests that they don’t know how to fix. This commit also updates `cargo new` to no longer ignore `Cargo.lock`. Fixes rust-lang#8728
878c2d4
to
dda69ae
Compare
This was initially a documentation only PR so discussion felt adequate here, but as I see that it needs more and more code changes I agree that the PR should remain for technical issue and the general discussion should move to the issue. |
This isn't just a documentation change but a change in policy which needs to be hashed out in the issue and a FCP with the cargo team before we can consider merging any docs/code changes. |
☔ The latest upstream changes (presumably #12397) made this pull request unmergeable. Please resolve the merge conflicts. |
This commit updates the FAQ to recommend to always commit
Cargo.lock
in all projects, both binaries and libraries. The FAQ previously discouragedCargo.lock
in libraries.The question of changing the recommendation to always commit the lock file was raised in #8728, as well as in a few Rust Internals threads in the previous months (Feedback of cargo-upgrade, Cargo yank is a misfeature) and was a relatively popular proposition.
Cargo.lock
enables reproducible builds, I argue that this is a desirable property for all projects. Reproducible builds are a safe and powerful default both for binaries and libraries. Wanting a non-reproducible build is less common, and still easy to achieve even if a lock file is committed.Applications have binaries compiled through
cargo build
and executed bycargo run
. Libraries also have binaries, they're just executed throughcargo test
instead. In both cases, it is desirable to be able to work with the version control system when debugging some regression. ACargo.lock
enables workflows such asgit bisect
or reproducing a failed test across all environments (e.g. CI and dev machines).A lockfile enables reproducible builds across commits. This means that you can browse the git history and reproduce your tests as they were at this time. This unlocks
git bisect
workflow and is extremely useful when working on transitive dependency issues.A lockfile ensures that you can reproduce an older build even if it contains yanked dependencies or weakly constrained versions (semver requirement
*
, git repository without a revision, ...).Even if a lock file is present, it is easy to refresh it / force dependency resolution again. On the other hand, if the lockfile is missing then it is very hard to retrieve the resolution at this time (or even impossible). Having a lock file is a safer default (no information loss).
When multiple developers contribute to the same project (even a library), a lock file ensures that all contributors test with the same dependencies. Without a lockfile, developers may see different behavior when testing. This also enables reproducing CI errors locally for example.
The usual objection to committing a lockfile is that it may take longer to detect regressions in transitive dependencies. In practice, this does not change much: refreshing the dependencies is always an explicit operation in Cargo anyway. The only time when it's done implicitly is when cloning a project for the first if it does not have a lock file yet. Checking transitive dependencies is better handled explicitly in CI or through tools such as Dependabot. Catching problems with new dependencies should not happen when someone is trying to test an unrelated change. There is also a magnitude more builds in consumer projects than CI builds for a lib, so you should be ready to get messaged by your users anyway if you break something :)
This change to always commit lock files also aligns with prior art from other ecosystems.
This commit also updates
cargo new
to no longer ignoreCargo.lock
.Fixes #8728