Skip to content
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

Check Cargo.lock in version control for libraries #8728

Closed
tiziano88 opened this issue Sep 24, 2020 · 45 comments · Fixed by #12382
Closed

Check Cargo.lock in version control for libraries #8728

tiziano88 opened this issue Sep 24, 2020 · 45 comments · Fixed by #12382
Labels
A-documenting-cargo-itself Area: Cargo's documentation C-feature-request Category: proposal for a feature. Before PR, ping rust-lang/cargo if this is not `Feature accepted` disposition-merge FCP with intent to merge finished-final-comment-period FCP complete S-needs-team-input Status: Needs input from team on whether/how to proceed. T-cargo Team: Cargo to-announce

Comments

@tiziano88
Copy link

tiziano88 commented Sep 24, 2020

Describe the problem you are trying to solve

The official FAQ and various other places strongly suggest (almost mandate) that libraries do not check in Cargo.lock. This serves so that if the library is used as a dependency from another crate, the parent crate should get to decide what particular versions of dependencies to use for itself and all its transitive dependencies, respecting versioning restrictions indicated in the relevant Cargo.toml files.

Describe the solution you'd like

  • strongly suggest versioning Cargo.lock in version control even for libraries; without this, it is practically impossible to achieve reproducibility of builds, tests and CI pipelines: the same exact version (commit checksum) of a library may compile / pass at some point in time on someone's machine, but spuriously break / fail later on or on some other machine, just because some dependency published a new version on crates.io
  • add an option to cargo to skip packaging the local Cargo.lock when publishing a library to crates.io (by default it will exclude it for libraries and include it for binaries)
    • perhaps it is sufficient to manually exclude Cargo.lock from a package until this is implemented
  • suggest relying on https://dependabot.com/rust/ or manually bumping dependencies in order to detect incompatibilities with new version of dependent crates in an automated way that is tracked under version control and maintains the hermeticity and reproducibility of builds, even for libraries

Notes

@tiziano88 tiziano88 added the C-feature-request Category: proposal for a feature. Before PR, ping rust-lang/cargo if this is not `Feature accepted` label Sep 24, 2020
@Eh2406
Copy link
Contributor

Eh2406 commented Sep 25, 2020

Just to be clere a libraries Cargo.lock has no effect on resolution for a project that depends on that library. That is true if the library does not commit its Cargo.lock, and it is true if it commits it but does not include it when publishing, and it is true even if it ends up in the final published artifact. If you commit your Cargo.lock, so your CI is not broken by some new version on crates.io, then the first you will hear about the brokege is from your users.

Whether it is better to have flakey but real test in CI or to have reliable but mocked tests in CI, really depends on the priorities of the library. It is not so different from every other time a project considers using mock objects. Do you call that other microservice, then you will now fast if they broke their api but tests will be flakey, or do you mock it so you have fast reliable test of what that microservice used to respond.

@ehuss
Copy link
Contributor

ehuss commented Oct 20, 2020

I think it would be fine to add some more details to the documentation. There's a tradeoff, if Cargo.lock is included in source control, you'll likely be testing out-of-date dependencies. That's good for reproducibility as you say, but is bad because you won't quickly catch problems with new dependencies. If you have the CI budget, you can of course test both with Cargo.lock, and with the latest versions (by calling cargo update && cargo test).

@ehuss ehuss added the A-documenting-cargo-itself Area: Cargo's documentation label Oct 20, 2020
@tiziano88
Copy link
Author

Catching problems with new dependencies should not happen when someone is trying to test an unrelated change. Which often results in a new dependency breaking CI, which prevents even other, unrelated changes from going through.

dependabot (or something similar) seems the ideal solution to me, since it guarantees deterministic and hermetic CI, while at the same time allowing testing against new dependencies, in their own PR, so that if there is any issue after the fact it is also easy to narrow it down to the specific commit that introduced the new dependency version.

cargo update && cargo test has again reproducibility issues, since the result of cargo update depends on the exact point in time at which it is run.

danielocfb added a commit to danielocfb/libbpf-rs that referenced this issue Oct 6, 2022
It is uncommon for library crates (such as this one) to check in their
Cargo.lock file [0]. Binaries (or other libraries) consuming this one ignore
it anyway, so all the file ends up being is a bit of a maintenance
burden, because making it makes it harder to identify truly outdated
dependencies according to our Cargo.toml specification. In addition, it
mostly just pollutes version control history.
Arguably, with the recent change to test the crate against our minimum
supported Rust version in CI, it now actually serves a real purposes in
that it prevents breakage if a dependency now requires a more recent
version of Rust. However, there is an argument to be made that in that
case our minimum version should align with that of the dependency
(because of the lock file being ignored; see above), but also that it is
basically inevitable for us to follow said bump anyway down the line.

For the sake of completeness, there is some open discussion with respect
the FAQ guidance referenced above [1]. However, it has not received much
traction and the arguments brought up are a bit lopsided and generally
not the strongest.

Weighing the pros and cons, this change removes the file from the
repository.

[0] https://doc.rust-lang.org/cargo/faq.html#why-do-binaries-have-cargolock-in-version-control-but-not-libraries
[1] rust-lang/cargo#8728

Signed-off-by: Daniel Müller <deso@posteo.net>
@epage
Copy link
Contributor

epage commented Dec 26, 2022

Another reason to revisit this decision is interest in MSRV testing. Until we have a minimal versions resolver feature stablized, the lockfile is the easiest way to emulate it when verifying MSRV in libraries.

demurgos added a commit to demurgos/cargo that referenced this issue Jun 16, 2023
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.

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 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.

- Closes rust-lang#8728
demurgos added a commit to demurgos/cargo that referenced this issue Jun 16, 2023
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.

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 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.

Fixes rust-lang#8728
demurgos added a commit to demurgos/cargo that referenced this issue Jun 16, 2023
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.

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.

Fixes rust-lang#8728
demurgos added a commit to demurgos/cargo that referenced this issue Jun 16, 2023
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.

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.

Fixes rust-lang#8728
demurgos added a commit to demurgos/cargo that referenced this issue Jun 16, 2023
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.

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.

Fixes rust-lang#8728
@demurgos
Copy link
Contributor

I sent a PR to update the book and recommend always using a lock file: #12275

demurgos added a commit to demurgos/cargo that referenced this issue Jun 16, 2023
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.

Fixes rust-lang#8728
demurgos added a commit to demurgos/cargo that referenced this issue Jun 16, 2023
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.

Fixes rust-lang#8728
demurgos added a commit to demurgos/cargo that referenced this issue Jun 16, 2023
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
demurgos added a commit to demurgos/cargo that referenced this issue Jun 16, 2023
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
demurgos added a commit to demurgos/cargo that referenced this issue Jun 16, 2023
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
demurgos added a commit to demurgos/cargo that referenced this issue Jun 16, 2023
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
@epage epage added the S-needs-team-input Status: Needs input from team on whether/how to proceed. label Jun 17, 2023
demurgos added a commit to demurgos/cargo that referenced this issue Jun 17, 2023
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
@epage
Copy link
Contributor

epage commented Jun 27, 2023

Wanted to add on some more data points around MSRV.

We've seen some people misuse version requirements (rather than using a lock file) to make working with MSRV easier, including:

Apparently, the version requirement approach is common enough that its a top google search (haven't been able to find it myself)

Some quick Googling suggested that I could explicitly depend on half in my own Cargo.toml, and therefore choose a compatible version there;

See https://www.reddit.com/r/rust/comments/14khqdt/is_there_something_different_about_cargos/

And I've noticed that for winnow that 16 out of 26 dependents exist for MSRV version req hacks. That number would be higher except I've specifically reached out to some of the dependents and tried to steer them to doing things a different way.

@epage
Copy link
Contributor

epage commented Jun 27, 2023

Motivations

  • Until MSRV-dependent dependency version resolution #9930, there is not a viable way of having a fixed-length-lookback MSRV (X number of releases)
  • Green master (local and CI)
    • Master failing for local development or a PR's CI from an unrelated change is a bad contributor experience
      • The contributor assumes its their fault and looks into it, wasting their time. Hopefully they recognize its not their fault. If they don't, then they waste even more time figuring out how their change broke it
      • Maintainers need to drop everything and fix it to not make the experience worse for contributors
      • This is delayed by (detection_time + report_time + maintainer_availability + fix_time + review_time + ci_time_pr + ci_time_bors)
    • Different lockfiles between different contributors is easy to overlook when dealing with "it works on my machine"
    • Example: Before cargo had its own lockfile, snapshot tests would fail when human readable output from a dependency changed, causing CI to go red
    • Example: Before cargo had its own lockfile, CI would turn red if a new dependency was released that deprecated parts of it API (Reduce warning strictness #10742 was also considered for resolving it)
    • Example: if -Zdirect-minimal-versions (Dependencies resolution with --minimal-versions #5657) is merged and people verify it, then a dependency updating a shared dependency can cause a breakage
    • Example: Not being broken by dependencies that yank versions (see also internals)
  • git bisect
    • Example: I had to bisect a problem with cargo but found no "root cause" commit. Most likely, the cause was in some dependency but I couldn't control for that
  • Documents a "golden stack" for debugging problems
  • If Cargo.lock represents minimal versions of dependencies (see also Dependencies resolution with --minimal-versions #5657), it can help developers relying on too-new APIs that will break when they get to CI
    • Ideally there would be a rustc or clippy lint to report API items with a #[stable] version that is newer than the minimal version that matches the dependency
  • If you start out with a lib and later turn it into a workspace with a bin, you are likely to overlook adding a lockfile

Downsides with having a policy of checking-in lockfiles

  • [lib]s CI jobs act as a distributed ecosystem-focused "crater" run. This puts peer pressure on the ecosystem for fixing bugs and yanking unintended breakages, helping maintain quality and adherence to semver.
    • Note that this is limited to [lib]-only repos that are following the guideline (e.g. clap checks in its lockfile) and only for releases that are semver compatible
  • Testing the latest dependencies reflects what dependents will be doing
    • This is also true for workspaces with [[bin]] crates ("cargo install" apparently ignores "Cargo.lock" as opposed to "cargo build" #7169) but isn't covered in the policy
    • This is only sampling dependencies at specific times and does not reflect if a dependent is locked to a version that wasn't not covered in CI or newer than the last CI run. Even worse is bugs can be dependent on the specific versions of dependencies and dependents.
  • To verify latest dependencies requires extra resources
  • Libraries might be running malware in their CI
    • At least for Github, Dependabot seems to run for security vulnerabilities even if its not otherwise enabled
  • Confusion over differences in lockfiles when dependents file bugs or contribute changes

Related questions:

  • What should Cargo.lock represent as opposed to Cargo.lock.<topic> if used?
    • Possibilities
      • Optimal dev environment (latest for features, fixes)
      • New dependent (latest)
      • MSRV
    • Larger lockfile upgrades make bisecting dependency problems harder
    • The newer the lockfile versions are (closer to latest), the more dependency versions that can be captured when bisecting an end-users issue
    • Juggling Cargo.lock.<topic> files can be a pain especially if there are specific policy constraints on what they may contain (MSRV compatible, minimal version, latest versions)
      • Extra work is needed to verify these in CI
      • A contributor likely won't know something is wrong until CI, requiring an extra round of iteration which is frustrating
      • The contributor would then need to cycle between the different lock files and make sure they get verified in the way that specific lockfile needs or delegate that to CI for extra turnaround time

Potential routes

  • MSRV-dependent dependency version resolution #9930 and Cargo time machine (generate lock files based on old registry state) #5221
  • Recommend maintaining a Cargo.lock.msrv
    • A CI job copies it into place and does verification with --locked (to ensure it isn't stale)
    • High friction for adding or changing dependencies
  • Change the Cargo.lock policy to check it in, updating cargo new to not exclude it in the .gitignore file
    • Recommend either staying up-to-date with dependencies (e.g. Dependabot or RenovateBot) or verify the latest dependencies always work with a job that runs cargo update before doing its verification (whether just cargo check or cargo test)
      • Automated dependency update allows people to choose their level of frequency, whether they want to be bleeding edge or play it safe and isolates the updates (and potential breakages) to PRs doing the update
      • The verification job should likely be a scheduled job to avoid making CI go red for contributors but Github will auto-disable the full Workflow for all Triggers if it is scheduled and there has been a lull in activity (2 months?), hurting projects that are passively maintained
    • Also recommend Lint to detect semantic versioning failures #374 once its available to reduce breaking of dependents
  • (exclusively for MSRV) Abandon an MSRV policy
  • (exclusively for MSRV) Change MSRV policy to be max-of-all-dependencies-at-current-moment
  • (exclusively for MSRV) Limit dependencies to only those that have the same MSRV policy or wider

See also a zulip thread from earlier this year

@epage
Copy link
Contributor

epage commented Jun 28, 2023

#12275 highlights the approach taken from other ecosystems

  • Yarn for Node.js

    Which files should be gitignored?

    [...]

  • Poetry for Python

    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

    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.

@epage
Copy link
Contributor

epage commented Jul 28, 2023

FCP is finished but we will be waiting until the next beta branch is created before moving forward so we get a full 12 weeks of feedback.

@bors bors closed this as completed in 3bb02f2 Aug 21, 2023
dae added a commit to ankitects/fsrs-optimizer-burn that referenced this issue Aug 26, 2023
The presence of a lock file doesn't affect the versions that other crates
will use, and it ensures that tests in CI are reproducible. Rust currently
recommends not checking it in for libraries, but this appears to be about
to change:
rust-lang/cargo#8728
L-M-Sherlock pushed a commit to open-spaced-repetition/fsrs-rs that referenced this issue Aug 26, 2023
* Add Cargo.lock

The presence of a lock file doesn't affect the versions that other crates
will use, and it ensures that tests in CI are reproducible. Rust currently
recommends not checking it in for libraries, but this appears to be about
to change:
rust-lang/cargo#8728

* Update to the latest Burn; pin to a specific commit

* Silence a clippy warning

https://rust-lang.github.io/rust-clippy/master/index.html#/single_range_in_vec_init
was triggering for lines like

  let rating = ratings.clone().slice([i..i + 1]).transpose();

I think the warning probably doesn't apply for this API.

* Fix a clippy warning

* Pin the Rust version

Allowing it to float will lead to future clippy breakages, as clippy
routinely adds extra checks in new updates.
bjorn3 added a commit to bjorn3/portable-simd that referenced this issue Aug 26, 2023
As of rust-lang/cargo#8728 it is now recommended to always check in
Cargo.lock. This will help with reproducability and will avoid the need
for cg_clif's build system to keep it's own copy of Cargo.lock for
vendoring. It will also allow tidy to run on the portable-simd
workspace.
bjorn3 added a commit to bjorn3/backtrace-rs that referenced this issue Aug 27, 2023
As of rust-lang/cargo#8728 it is now recommended to always check in
Cargo.lock. This will help with reproducability. It will also allow
tidy to run on the backtrace workspace.
@safinaskar
Copy link

This fiasco is possibly related: rust-lang/rust#113152 (comment) . Nearly all my binary projects created nearly May 2023 (assuming they have Cargo.lock) don't build anymore. Removing Cargo.lock fixes them

@parasyte
Copy link

parasyte commented Aug 30, 2023

@safinaskar That breakage is unrelated to Cargo.lock; it is caused by a change to the nightly compiler. You can either build with the latest stable compiler (as mentioned in a response to your comment on that ticket) or, more appropriately perhaps, pin your nightly version with a toolchain file to avoid those kinds of breaking changes in the future.

@Nemo157
Copy link
Member

Nemo157 commented Aug 30, 2023

(or try to apply pressure on crate authors to not silently opt their users into unstable breakage simply because they use a nightly compiler)

@epage
Copy link
Contributor

epage commented Aug 30, 2023

I agree with @Nemo157 that that was a bug in proc-macro2 to use nightly features without a users consent (usually a unstable or nightly feature flag).

@ctz
Copy link

ctz commented Oct 4, 2023

suggest relying on https://dependabot.com/rust/

Note that dependabot uses the existence of Cargo.lock as a heuristic that a crate is an application. The behaviour seems not configurable, and by default means a library-with-a-lockfile will churn dependency versions forwards in the Cargo.toml.

ref: https://github.com/dependabot/dependabot-core/blob/81ca7cb1d8d016583834c4a632d8b78daec934a5/cargo/lib/dependabot/cargo/update_checker.rb#L111-L115

@Nemo157
Copy link
Member

Nemo157 commented Oct 4, 2023

This might be a good incentive to open an issue about having that configurable in dependabot, it's a big reason I don't use it on most of my repos.

@epage
Copy link
Contributor

epage commented Oct 4, 2023

The primary reason I switched to RenovateBot was to make dependency updating faster (dropped my monthly updates from taking a couple days to part of a day) but the greater control was a close second. For example, here is the config for clap

workingjubilee pushed a commit to rust-lang/backtrace-rs that referenced this issue Oct 6, 2023
As of rust-lang/cargo#8728 it is now recommended to always check in
Cargo.lock. This will help with reproducability. It will also allow
tidy to run on the backtrace workspace.
9999years added a commit to 9999years/safetensors that referenced this issue Apr 18, 2024
`Cargo.lock` files were added in huggingface#229 and then removed about a month
later in huggingface#253 (with the justification that "cargo.lock messes up
benchmarks").

Shortly after, the Rust project guidance was updated to encourage
committing lockfiles:

https://blog.rust-lang.org/2023/08/29/committing-lockfiles.html
https://doc.rust-lang.org/cargo/faq.html#why-have-cargolock-in-version-control
rust-lang/cargo#8728

Let's add the lockfiles back to make builds reproducible and
deterministic, especially for consumers of Python bindings.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-documenting-cargo-itself Area: Cargo's documentation C-feature-request Category: proposal for a feature. Before PR, ping rust-lang/cargo if this is not `Feature accepted` disposition-merge FCP with intent to merge finished-final-comment-period FCP complete S-needs-team-input Status: Needs input from team on whether/how to proceed. T-cargo Team: Cargo to-announce
Projects
None yet