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

"cargo install" apparently ignores "Cargo.lock" as opposed to "cargo build" #7169

Open
Tracked by #84
df5602 opened this issue Jul 23, 2019 · 83 comments
Open
Tracked by #84
Labels
Command-install S-needs-team-input Status: Needs input from team on whether/how to proceed.

Comments

@df5602
Copy link

df5602 commented Jul 23, 2019

I've encountered a strange issue today.

Background: The percent-encoding crate recently made a new major release. Knowing that stuff could break I held off on updating the crate because I haven't yet had time to check what changed.

Problem
When I compile my binary crate using cargo build (with and without --release) everything builds fine. However, if I compile my binary using cargo install --path . -f, compilation fails with an error related to percent-encoding (the breaking change warranting the major version bump I assume..). In the output I see that it compiled percent-encoding v2.0.0 instead of percent-encoding v1.0.1 as specified in Cargo.lock. (Note: The Cargo.toml doesn't specify a specific version.)

The crate is the following, if you want to try at home:
https://github.com/df5602/bingers

Steps

  1. $ cargo clean
  2. $ cargo build
...
Compiling percent-encoding v1.0.1
...
Finished dev [unoptimized + debuginfo] target(s) in 48.32s
  1. $ cargo build --release
...
Compiling percent-encoding v1.0.1
...
Finished release [optimized] target(s) in 1m 25s
  1. $ cargo install --path . -f
  Installing bingers v0.1.0 (/path/to/bingers)
    Updating crates.io index
   Compiling percent-encoding v2.0.0
   Compiling bingers v0.1.0 (/path/to/bingers)
error[E0432]: unresolved import `percent_encoding::QUERY_ENCODE_SET`
  --> src/tvmaze_api.rs:10:45
   |
10 | use percent_encoding::{utf8_percent_encode, QUERY_ENCODE_SET};
   |                                             ^^^^^^^^^^^^^^^^ no `QUERY_ENCODE_SET` in the root

error: aborting due to previous error

I can work around it by specifying an explicit version in Cargo.toml, but the observed behaviour was surprising to me...

Notes

Output of cargo version:

cargo 1.36.0 (c4fcfb725 2019-05-15)

OS: Mac

@df5602 df5602 added the C-bug Category: bug label Jul 23, 2019
@ehuss
Copy link
Contributor

ehuss commented Jul 23, 2019

This has been changed in 1.37 (beta). Starting in that version the --locked flag can be used to force cargo to use the Cargo.lock file.

The intent is that cargo install will behave the same whether it is from a registry or a local path.

We discussed this a bit (#6840 (review) is most relevant). Ideally Cargo would issue a message with a hint to try --locked, but I'm not so confident that would work well. I think since it can result in any error, it wouldn't be able to know if --locked would actually fix it, so I think it would be misleading too often.

@df5602
Copy link
Author

df5602 commented Jul 23, 2019

  1. I'm using cargo 1.36 (stable) which shouldn't show that behaviour yet, right? (Running cargo install with --locked worked, though...)

  2. Reading up on the linked issue: if I understand correctly, going forward, the behaviour will be different between cargo build and cargo install regarding dependency resolution (at least for binary crates)? If yes, I would consider that as very surprising! (So, what's the Cargo.lock for? Basically like this, you compile, run your tests and then cargo install possibly picks different dependencies?)

I know, I should probably fix my Cargo.toml and specify proper versions instead of using "*". But until now, it never really mattered for binary crates. Updating dependencies was always a manual process, so that you can deliberately audit the changes if you wish to do so (for me it was basically running cargo outdated, check the listed crates and look for the changelog, and eventually running cargo update -p xyz).
If that behaviour changes, I would expect at least a warning à la "Warning: Ignoring lock file!" and probably a PSA in the Rust release notes.

@ehuss
Copy link
Contributor

ehuss commented Jul 23, 2019

1. Running `cargo install` with `--locked` worked, though...

Ah, yea, in that sense there is no change in behavior. I mis-remembered how it used to work. The present stable behavior for install --path is that Cargo.lock is ignored by default, and --locked can be used to honor it. That hasn't changed. (What did change is installing from a registry, which didn't support lock files at all.)

It's difficult to balance the defaults here. I think it would be confusing if cargo used Cargo.lock differently based on the source. And for now we don't want to default to using Cargo.lock for installing from a registry. I think that's up for debate on changing it, but it was the conservative choice we made for now. If we ever decide to change it, we would probably want to add a flag to override it, and that adds more complexity.

@df5602
Copy link
Author

df5602 commented Jul 23, 2019

Ah ok, so I guess it was the first time I ran into this because I deliberately held off on updating a dependency (due to breaking changes) which happens quite rarely...

It's good to know that this is the default behaviour, but I think this should be a lot better documented, because it's highly unintuitive and surprising:
Until now, my mental model of cargo install --path was basically "run cargo build --release and copy the binary somewhere". But apparently that's wrong, it should be "compile the binary slightly differently than cargo build --release and copy the binary somewhere".

I think it's the wrong default for binary crates because it means that you're deploying different binaries than you're testing! (In my opinion it would be ok, if cargo build would ignore lockfiles by default and you could explicitly opt-in to using lockfiles. But currently the default is otherwise.)

@ehuss
Copy link
Contributor

ehuss commented Jul 23, 2019

I think this should be a lot better documented

There's a fairly lengthy paragraph on https://doc.rust-lang.org/nightly/cargo/commands/cargo-install.html that explains how cargo install handles Cargo.lock (this is new for 1.37, so it hasn't hit stable, yet). If there's any part that could be clarified let us know, we're always eager to improve the documentation.

@df5602
Copy link
Author

df5602 commented Jul 24, 2019

I assume this will also be part of the cargo install --help page? I think the behaviour is described very clearly in the updated help page.

Given that the described behaviour is apparently expected behaviour, I think we can close this issue?

@MaulingMonkey
Copy link

MaulingMonkey commented Jul 25, 2019

https://doc.rust-lang.org/nightly/cargo/commands/cargo-install.html has some misleading documentation under "Manifest Options" for --frozen / --locked:

The --frozen flag also prevents Cargo from attempting to access the network to determine if it is out-of-date.

This is untrue for cargo install. In fact, I don't think this can ever succeed for install unless the crate has no dependencies. Maybe not even then.

These may be used in environments where you want to assert that the Cargo.lock file is up-to-date (such as a CI build) or want to avoid network access.

This is inaccurate for cargo install. If anything, you'd use these flags to use an "out-of-date" lock file - in the sense that all the documentation suggesting cargo install xyz will be using different versions.

If the lock file is missing, or it needs to be updated ...

This is technically accurate, but the way it implies it might not need to be updated is kinda confusing, as which crates are used will always be updated without these flags - for the install build at least - regardless of if they "need to be" or not.

If this documentation is automatically shared with other commands, it might at least be worth breifly mentioning cargo install being a special case. Maybe something like:

Unlike cargo build, cargo install will ignore Cargo.lock files completely without --locked."

?

Ideally Cargo would issue a message with a hint to try --locked, but I'm not so confident that would work well.

Instead of trying to guess when an error can be fixed by --locked, it might be useful to instead warn if Cargo.lock is present, and specifies different versions than are being used, since that's just begging for version confusion and building everything twice needlessly. --locked is one possible fix, but cargo update is another - since presumably most docs suggest cargo install xyz rather than cargo install xyz --locked?

@MaulingMonkey
Copy link

(I also suspect I landed on the "Manifest Options" section by searching the cargo install docs page for "--frozen", which caused me to skip over the earlier/better explaination of locking behavior entirely.)

@ehuss
Copy link
Contributor

ehuss commented Jul 28, 2019

I assume this will also be part of the cargo install --help page?

Not for now, unfortunately we don't share the man pages with the help text. The long-term intent is to make it available in some fashion.

If this documentation is automatically shared

Yes, it is shared. We can probably make it special-cased for install, but that would take a bit of restructuring.

@df5602
Copy link
Author

df5602 commented Jul 29, 2019

In that case I take back what I said earlier: I don't think I'd have the idea to look-up a description of a command online, if said command also has an extensive man page, so I still consider it undocumented behaviour...

I also agree with @MaulingMonkey: If this is the desired behaviour, then it should at least generate warnings, something along those lines (modulo wordsmithing):

Warning: Ignoring `Cargo.lock` file!
Detecting version mismatch:
    | Specified in `Cargo.lock` | Used in build
------------------------------------------------
foo |                    v1.0.1 |        v1.0.3

... possibly followed by a few suggestions on how to deal with the situation (i.e. use --locked, run cargo update, etc.)

(Although I don't know the inner workings of cargo, so I can't judge how much work that would be...)

@Nemo157
Copy link
Member

Nemo157 commented Sep 11, 2019

(EDIT: Moved to #7495)

@ehuss ehuss removed the C-bug Category: bug label Sep 21, 2019
@matklad
Copy link
Member

matklad commented Oct 7, 2019

I've hit this in rust-analyzer a couple of times, and I would appreciate the warning. How does this interact with publish-lockfile? Will adding publish-lockfile = true to Cargo.toml make Cargo to use lockfile when installing? Or is the publish-lockfile feature effectively a no-op?

@ehuss
Copy link
Contributor

ehuss commented Oct 7, 2019

How does this interact with publish-lockfile?

publish-lockfile has been removed and is a no-op now. Cargo now automatically publishes the lock file if there are any binaries in the package. cargo install always ignores the Cargo.lock unless you specify --locked.

@nixpulvis
Copy link

nixpulvis commented Oct 7, 2019

Let me see if I'm understanding this correctly, because lockfiles have been a pet-peeve of mine in other ecosystems (looking at you NPM).

  • cargo build will respect the Cargo.lock file installing only dependencies that are known to work, since those are the versions everyone is building with
  • cargo install will ignore the Cargo.lock file, installing the latest matching versions with respect to the Cargo.toml file, presumably because there are security or other important patching an installing user may want

This seems wrong, since it seems like this whole setup assumes crates are following semantic versioning, strictly. As far as I'm concerned cargo build is to make as cargo install is to make install (as things are typically implemented). If we have a lockfile, both operations should respect it. How else will I know that people's builds (and installs) will work!?

As the author of Rust software, I want to use cargo update to try to update my dependencies, but I might hit problems, and they'll need to be resolved before I publish my updated lockfile. Of course, an ideal release of software is tested in some sense!

On the other hand I may find myself using only dependencies who follow "semantic versioning" effectively, and thus may decide to offer users automatic updates for dependencies. In this case I'd need to lock non-conforming dependencies explicitly in the Cargo.toml moving forward.

Now, since we do allow published crates to be installed remotely, and some of these crates will become stale, it makes sense to allow new users of software correctly following semantic versioning to automatically get the updates. But this should be an opt-in process, since not all software follows semantic versioning (or is generally capable of handling automatic updates to dependencies).

I might be missing a lot of details for why this next idea won't work, but in an automatically updating setup (like one following semver), I think I'd expect to not have a Cargo.lock file at all.

@ehuss
Copy link
Contributor

ehuss commented Oct 7, 2019

I personally would be fine with switching the default cargo install behavior, as I feel reliable builds are more important than possibly getting "fixed" dependencies. But there has been resistance to this in the past. I think I have mitigated some of the concern by issuing warnings on yanked dependencies. But presuming few use --locked, we don't really know what the impact of that will be. The warning isn't very helpful, since most users will have no idea what it means or control over updating those dependencies, so I imagine it has a risk to be confusing at the least.

There's also a bit of risk that this would change current behavior, which some projects may be relying upon in some way. I'm not sure how things like dh-cargo would be impacted, or what they would prefer.

@ojeda
Copy link

ojeda commented Oct 9, 2019

It is surprising, indeed, and a security concern too: consider projects/apps that may just provide source tarballs or a repo somewhere, specially small ones within the Linux ecosystem.

End-users will download a package and then run cargo install. They may or may not know about Rust/Cargo. Now:

  • If they are lucky, the binary works as intended.
  • If they are not so lucky, it doesn't.
  • And if they are really unlucky, arbitrary nasty code just got executed.

And all this happens even if they have verified the checksums/signatures of their downloads or their checked out signed Git tags!

I would say:

  • At least both build and install should behave the same in all respects (except actually installing, of course). I believe this is what users of traditional build systems expect.
  • Both commands should default to failure if Cargo.lock needs to be modified for any reason. For binaries, they should also fail if it has to be created. This makes the behavior more consistent by default (i.e. whether Cargo.lock is there does not change behavior of build), makes the act of updating the dependencies explicit, whether via update or a shorthand, and ensures source releases work as intended.

@anguslees
Copy link

(This is another example of "I don't want anything to change, but I also want new changes". It's surprising how often this obviously (when expressed in these simple terms) irreconcilable conflict arises :P )

@NickeZ
Copy link

NickeZ commented Jan 6, 2020

As a "simple user" I find this behavior completely unexpected and therefore breaks the most important rule of all rules when it comes to software development: https://en.wikipedia.org/wiki/Principle_of_least_astonishment

I have no idea if you have the power to change this, but I would change it even if it breaks backwards compatibility. AFAIU cargo install will strictly work more often than currently by changing the default behavior to enable --locked. Are there any cases where cargo install would not work if it used --locked?

Edit: Also I looked like an idiot reporting this issue in the "Alacritty" repository when this bug is in Cargo. And this is like the second most important rule in UX. Never make your users look stupid.

@nixpulvis
Copy link

nixpulvis commented Jan 6, 2020

@NickeZ I think the "case" that won't work is automatic (potentially security related) updates for downstream installers. However, as I've mentioned above, I believe that should be handled differently. Also, don't worry too much about looking like an idiot. This behavior surprised me too, and having some discussion on it in a now closed issue doesn't bother us.

@ghost
Copy link

ghost commented Feb 27, 2020

Our team got hit by this today and we found this behavior to be very surprising. Consistency is the key here, defaulting to use lock file for something and not for others are very inconsistent / surprising / confusing.

wiktor-k added a commit to wiktor-k/move that referenced this issue Mar 1, 2024
This makes cargo use the lockfile for dependencies. Without that, at the
time of writing this patch, the install fails with the following error:

    error: failed to compile `move-cli v0.1.0 (/home/wiktor/src/open-source/move/language/tools/move-cli)`, intermediate artifacts can be found at `/home/wiktor/tmp/cargo`

    Caused by:
      package `toml_datetime v0.6.5` cannot be built because it requires rustc 1.67 or newer, while the currently active rustc version is 1.65.0

See: rust-lang/cargo#7169 (comment)
wiktor-k added a commit to wiktor-k/move that referenced this issue Mar 1, 2024
This makes cargo use the lockfile for dependencies. Without that, at the
time of writing this patch, the install fails with the following error:

    error: failed to compile `move-cli v0.1.0 (/home/wiktor/src/open-source/move/language/tools/move-cli)`, intermediate artifacts can be found at `/home/wiktor/tmp/cargo`

    Caused by:
      package `toml_datetime v0.6.5` cannot be built because it requires rustc 1.67 or newer, while the currently active rustc version is 1.65.0

Alternative solution would be upgrading Rust version but adding `--locked`
to the tutorial is far less invasive.

See: rust-lang/cargo#7169 (comment)
@casey
Copy link
Contributor

casey commented Mar 9, 2024

I ran into this today. The issue wasn't the build breaking, but cargo install unexpectedly downloading and building newer versions of dependencies when it should have been using cached build artifacts. I was very confused by the default behavior of cargo install.

I think it would be better if cargo install defaulted to using the lockfile, and a new flag was added that enabled the current behavior, something like cargo install --update-dependencies.

I ran into this again today, when a user reported that cargo install just was failing due to an update to a dependency which introduced a build failure. I was able to fix it for the latest version by pinning the dependency to the last working version. However, this issue persists in 95 old versions of just, and backporting the fix to all of them is infeasible, so they will continue to be broken indefinitely.

There are definitely tradeoffs here, and they've been well enumerated in this thread. I don't mean to comment again without new information, but, given the amount of issues that have been caused by not respecting the lockfile by default, I feel like the tradeoffs are strongly in favor of respecting the lockfile by default.

If you search GitHub for "cargo install --locked", you'll find 6.2k results, and if you search for "cargo install --locked" path:*.md, restricting the search to markdown files, which are probably documentation, you'll find 2k results. This suggests that crate authors document cargo install --locked as the preferred way to install their crates, as well as extensively using cargo install --locked in installation scripts. This negates much of the benefits of not respecting the lockfile by default, since it seems to be very common practice to opt-out of the default behavior.

What needs to happen to drive this issue forward? I don't want to trivialize the issue, but I feel like it would be a net positive to simply change the default back to the pre cargo 1.37 behavior.

I think that the main benefit to not respecting lockfiles is for downstream packagers, e.g., linux distros and package managers like homebrew, and that those downstream packagers are in a good position to evaluate the tradeoffs appropriately, and opt-in to not respecting the lockfile. Especially since those downstream packagers often have continuous integration pipelines that check for build failures, and that they are likely to be able to report the issue to the correct project, i.e., the broken dependency, and not the parent crate, which is where issues are currently usually reported.

@plwalsh
Copy link

plwalsh commented Mar 9, 2024

@casey Searching the markdown usage was brilliant, and very telling. Thank you for your well-formed comment.

@RalfJung
Copy link
Member

If you search GitHub for "cargo install --locked", you'll find 6.2k results, and if you search for "cargo install --locked" path:*.md, restricting the search to markdown files, which are probably documentation, you'll find 2k results. This suggests that crate authors document cargo install --locked as the preferred way to install their crates, as well as extensively using cargo install --locked in installation scripts. This negates much of the benefits of not respecting the lockfile by default, since it seems to be very common practice to opt-out of the default behavior.

It suggests that some crate authors do that. But to actually put that number into perspective we'd need some sort of baseline. Is this 5% of crate authors, 50%, 95%? We have no way of knowing.

@bjorn-ove
Copy link

I just noticed this blog post from August 2023 where the guidance on how to deal with Cargo.lock changed.

May be relevant

https://blog.rust-lang.org/2023/08/29/committing-lockfiles.html

@taiki-e
Copy link
Member

taiki-e commented Mar 10, 2024

If you search GitHub for "cargo install --locked", you'll find 6.2k results, and if you search for "cargo install --locked" path:*.md, restricting the search to markdown files, which are probably documentation, you'll find 2k results. This suggests that crate authors document cargo install --locked as the preferred way to install their crates, as well as extensively using cargo install --locked in installation scripts. This negates much of the benefits of not respecting the lockfile by default, since it seems to be very common practice to opt-out of the default behavior.

It suggests that some crate authors do that. But to actually put that number into perspective we'd need some sort of baseline. Is this 5% of crate authors, 50%, 95%? We have no way of knowing.

FWIW, all "cargo install" in markdown files is 52.2k ("cargo install --locked" is 2k): https://github.com/search?q=path%3A*.md+%22cargo+install%22&type=code

Also note that these search results miss the cases where there are other flags (or spaces) between "cargo" and "install", and "install" and "--locked". (e.g., my CLI projects recommends "cargo +stable install ... --locked")

@casey
Copy link
Contributor

casey commented Mar 10, 2024

I opened a PR proposing to change cargo install to respect lockfiles by default. Copying my comment there:

Rendered

Currently, cargo install does not respect lockfiles by default, which causes breakages when dependencies release breaking but semver-compatible versions. This RFC proposes to change cargo install to respect lockfiles by default.

There's been a lot of discussion on #7169 without much progress, so I thought it would be productive to open an RFC proposing changing the default behavior of cargo install to respect lockfiles by default, to provide a concrete proposal that can be discussed.

Even if this RFC is ultimately rejected, it will provide some forward progress on the issue, since we can then focus on an alternative to changing the default behavior.

I think the last point is key. If the RFC is rejected, we can then all think hard about a solution to the issue aside from changing the default behavior, perhaps introducing a --unlocked flag, and making one of --locked and --unlocked mandatory.

@charles-r-earp
Copy link
Contributor

At the very least, Cargo and crates-io could provide more of a hint to users to use --locked when the package has a lockfile.

@ckcr4lyf
Copy link

ckcr4lyf commented Mar 11, 2024

FWIW, I do think using locked packages should be the default behavior.

As a maintainer of some small binary packages, I know the versions of dependencies used work exactly as expected, at least (in admittedly limited) the cases I've tried / intended for them to be used.

Regradless of the fact the "real semver" should "never break" - let's be real, it is very possible, and it is a pretty bad experience if now my package can't be installed because of a dependency. If I am not mistaken, in Node.JS, you can use a shrinkwrap (similar to lockfile) to force pinned dependencies, to ensure users effectively have "reproducible builds" - including bugs if any.

Furthermore, somewhat based on Hyrums Law , maybe my program unintentionally relies on a bug in a dependency (at least viewed as a bug by the dependency's author) - and if they decide to fix it in a patch version then it could break my application.

There's also the extreme case of "protestware", e.g. author intentionally introduces a breaking change in patch version. Now sure, this would eventually get caught by cargo mods (I am not entirely sure whom), but if I want to protect my users, I would want my lockfile to be respected.

@jonhoo
Copy link
Contributor

jonhoo commented Mar 11, 2024

One important aspect of this conversation is the knock-on effects of this kind of change. While it's true that this will lead to fewer failed builds due to accidental semver violations, it will also make it more likely for semver violations to not be caught, and thus to proliferate. In other words, what we're trading off is reduced user friction in the face of semver violations (build more likely to succeed out of the box) against more outdated dependencies and lower awareness of semver violations (both instances of them and the general concept).

I think any decision needs to start with an acknowledgement of this tradeoff, and then an explicit rationale for choosing one set of benefits + costs over those of the other options.

Personally, I think this is a case where the occasional user frustration is unfortunate, but better than the alternative. I'd be sad to see the semver violation learning opportunity disappear, and I'd be sad to see users end up (unknowingly) with older dependencies than are available at install time.

@chrisduerr
Copy link
Contributor

Detecting semver violations really shouldn't be up to your users.

@ckcr4lyf
Copy link

I'd be sad to see the semver violation learning opportunity disappear

I'd argue the opportunity is still there - but violations would be discovered by maintainer of a package when they do a dependency update, rather than the end user.

As an example, in a node project I had used rolling-rate-limiter , and locked the version. As a maintainer, I can use npm (Node's cargo , kind of) to view outdated dependencies, and if I want, update them and release a new patch version of my project. This would automatically update semver compatible dependencies due to my explicit action .

In doing so, I discovered a breaking change in their patch version (see: peterkhayes/rolling-rate-limiter#64) , raised the issue, and the maintainer acknowledged the error, and I think they did learn something.

@epage
Copy link
Contributor

epage commented Mar 11, 2024

At the very least, Cargo and crates-io could provide more of a hint to users to use --locked when the package has a lockfile.

Packages published with bins always have a lockfile.

We do suggest --locked when a dependency is found that isn't compatible with the toolchain you are using. I think it would be a good quality of life improvement to also suggest --locked if there is a compilation error. Even better if we can limit the suggestion to non-local dependencies. I also suspect we should print the project's homepage URL on error to help connect users with where to go for support.

@est31
Copy link
Member

est31 commented Mar 14, 2024

I still stand by my idea from four years ago of an online service that maintains a lockfile (as the default behaviour, obviously there should be overrides for both latest lockfile and --locked): #7169 (comment)

It doesn't need to immediately update the crates.io maintained Cargo.lock at every single update, but if it makes an attempt, it only updates the crates.io maintained Cargo.lock if some basic checks succeeded, like cargo check. This is not a full replacement of running the testsuite or manual verification, but it's way better than what we have now where builds are breaking. Don't let perfect be the enemy of the good :).

If after an attempt to build, the build fails, it maybe can show a warning to everyone running cargo install that the last Cargo.lock update on 2023-12-03 failed so it still uses the version from 2023-11-15, with a link to the build failure log and the request to maybe contact maintainers about it.

We already run crates.io wide compilations in multiple settings like rustdoc and crater. Admittedly, unlike those two earlier methods it would build at a higher cadence, but for costs we only care about the average cadence of updates. crates that see regular downloads and recent releases can get treated differently than those with few downloads, security fixes differently from normal ones.

If the default cadence was 6 weeks, and the top 10% by downloads get 2 weeks, and the top 5% get 1 day, we'd still have an average close to 6 weeks, so roughly the same as the cost we already spend on crater, if we built every crate, but we'd only run this on the minority of binary crates.

What would also be useful would be ways for crate authors to manually re-trigger the update process, say after some fix was released, and another way to revert to the previous Cargo.lock if there was some breakage.

We can also fizzle out updates google play style to exponentially increasing numbers of users, resembling a sigmoid curve, that way if there is some other breakage, it's not immediately broken for everyone.

@est31
Copy link
Member

est31 commented Mar 14, 2024

[a] way to revert to the previous Cargo.lock if there was some breakage.

one can make a version of this without crates.io maintained Cargo.lock: right now, binary authros are fully dependent on the dependency maintainers [sic] to release a fixed version of a crate. What if binary authors could issue (automatically expiring) version pins for a dependency of their binary crate? You can already now do something like that by depending on the dependency directly in your Cargo.toml in a way that it is pinned, but it's a bit unwieldy and requires an entire release.

@chrisduerr
Copy link
Contributor

@est31 Having a lockfile that automatically gets updated is just the worst of both worlds. The reason lockfiles are great because it ensures the output binary behaves exactly the same, not just that the output compiles. If you updated them automatically you'd just run into more esoteric issues that are essentially the same.

If I'm uploading a binary to crates.io, I want people to run exactly what I released. I do not want any dependencies updated, even if those are "security" updates. That should be up to me.

@ssokolow
Copy link

ssokolow commented Mar 14, 2024

If I'm uploading a binary to crates.io, I want people to run exactly what I released. I do not want any dependencies updated, even if those are "security" updates. That should be up to me.

You are aware, I hope, that there are already a lot of people who have a low opinion of distribution channels that aren't Linux distro packages because of how much responsibility they place on the upstream maintainer to respond promptly to security advisories.

(i.e. "Look at how badly Docker failed on ensuring a steady supply of security updates! Flatpak and statically linked languages like Go and Rust are evil and should not be allowed!")

...and Cargo.lock doesn't have the dodge Flatpak does, that the things which are most likely to need security updates are in the runtime, not the package.

I don't think there's a simple solution to reconcile the two conflicting needs here.

Not to mention that we don't know how places that focus on giving the downstream the tools to protect themselves (eg. lib.rs) would react to packages with stale lockfiles if something like this doesn't include a mechanism for allowing Crates.io to overrule Cargo.lock pins for packages when the maintainer is going through a bad bit and isn't in a state of mind to push an update that would have just gone through without a Cargo.lock.

We could easily wind up with packages getting tarred with a bad reputation because they don't meet some externally imposed standard for how quickly security fixes must be rolled out... heck, imposing unreasonable demands for prompt security response on hobby projects was one of the things that sparked a lot of fear in the earlier drafts of the E.U.'s new rules for safer software infrastructure.

@chrisduerr
Copy link
Contributor

You are aware, I hope, that there are already a lot of people who have a low opinion of distribution channels that aren't Linux distro packages because of how much responsibility they place on the upstream maintainer to respond promptly to security advisories.

Yes, that includes myself. But this just makes it worse, not better. If you're asking me, then this does nothing (or close to nothing) for security but has significant impact on reliability. You can't just cargo update some random abandoned project and pretend that suddenly makes it secure and up-to-date.

And it's not like cargo has a good way of updating binaries installed through it anyway. So the people most likely to be using outdated dependencies, even if upstream has already pushed a new lockfile, are the most vulnerable. Not the ones just installing it.

We could easily wind up with packages getting tarred with a bad reputation because they don't meet some externally imposed standard for how quickly security fixes must be rolled out... heck, imposing unreasonable demands for prompt security response on hobby projects was one of the things that sparked a lot of fear in the earlier drafts of the E.U.'s new rules for safer software infrastructure.

Updating the lockfile changes almost nothing about this. Security issues are introduced in binaries regularly, not just libraries. And it's also possible that a breaking change is required to fix them. If people don't update their software, it's not safe to use, that's just the nature of the beast.

@ssokolow
Copy link

ssokolow commented Mar 14, 2024

You can't just cargo update some random abandoned project and pretend that suddenly makes it secure and up-to-date.

As a maintainer of some projects I don't yet feel are ready to push to crates.io, I find that GitHub Dependabot is quite good at offering me dependency security fix PRs that pass all my GitHub Actions runs while only doing the kind of semver bumping that cargo install does implicitly.

(So long as the dependency in question actually offers a minor version update for Dependabot to generate a security-update PR from, of course.)

Updating the lockfile changes almost nothing about this. Security issues are introduced in binaries regularly, not just libraries. And it's also possible that a breaking change is required to fix them. If people don't update their software, it's not safe to use, that's just the nature of the beast.

That smells like a "Perfect solution or status quo. There's no such thing as an incremental improvement." argument to me.

...especially in projects like mine, where I'm generally cautious enough about how I design my things that any vulnerabilities are far more likely to be in my dependencies than in how I'm gluing them together.

In the real world, people install unmaintained software. It's just human psychology that "I need this solved now" is a more significant psychological input than "there might be a hidden flaw in this which could bite me later".

If anything, the current cargo install behaviour could be considered safer because, not only does it provide some level of protection against stale dependencies, it's more likely to turn "unmaintained" into "adding a speed-bump for users who might otherwise blindly install unmaintained software".

@charles-r-earp
Copy link
Contributor

At the very least, Cargo and crates-io could provide more of a hint to users to use --locked when the package has a lockfile.

Packages published with bins always have a lockfile.

We do suggest --locked when a dependency is found that isn't compatible with the toolchain you are using. I think it would be a good quality of life improvement to also suggest --locked if there is a compilation error. Even better if we can limit the suggestion to non-local dependencies. I also suspect we should print the project's homepage URL on error to help connect users with where to go for support.

It is nice that cargo does include a hint to try using --locked when install fails, but I was suggesting that this be a warning upfront. And in the case of crates-io, it suggests to do cargo add or add the package to the manifest, which is of course a current issue. So it would be nice if when that was fixed, in addition to suggesting to install the crate, it could hint that the package has a lock file that can be used via --locked.

@casey
Copy link
Contributor

casey commented Mar 17, 2024

Since concrete instances of issues stemming from not respecting lockfiles are well understood and documented, it would be useful to collect information on concrete instances of the benefits of respecting lockfiles by default.

It would be very helpful to know:

  • When has respecting lockfiles by default prevented binary crates from being compiled and installed with bugs, when those bugs would have been prevented by using the latest semver-compatible versions of dependencies?

  • When has respecting lockfiles by default prevented binary crates from being compiled and installed with security vulnerabilities, when those vulnerabilities would have been prevented by using the latest semver-compatible versions of dependencies? I mention security vulnerabilities separately from bugs, since avoiding security vulnerabilities is often called out specifically as one of the benefits of not respecting lockfiles by default.

For either of these to be evidence that not respecting lockfiles is beneficial, it isn't enough for a crate a dependency to have a bug or security vulnerability, and the semver-compatible version that is selected by cargo install to not have the bug or security vulnerability. Crates often use only a subset of the features of dependencies, a bug or security vulnerability might exist in a dependency that cannot be encountered when using the compiled binary. So, it must be possible to encounter the bug when using the dependent.

@casey
Copy link
Contributor

casey commented Mar 17, 2024

Other things which would be useful to know:

  • How many crates on crates.io build with cargo install --locked but not with cargo install? The opposite is possible as well, although less likely.
  • Which crates have tests where one of cargo test and cargo update && cargo test passes and the other fails? This is a pretty strong signal of respecting lockfiles being good or bad. If cargo test passes but cargo update && cargo test fails, then new dependencies are introduced which break the crate. If cargo install succeeds in that case, then that's doubly bad, because cargo install may have installed a binary with buggy behavior that was introduced by cargo update.
  • Over all crates on crates.io, which dependencies are selected by cargo install --locked, and how do they differ from the dependencies in the lockfile? Inspecting each diff by hand would be impractical, but it would be easy to compile a database with statistics of the different versions that have been selected.

Jake-Shadle added a commit to EmbarkStudios/cargo-deny that referenced this issue Mar 23, 2024
Fucked again by `cargo install` not using the lockfile unless explicitly
specified.

rust-lang/cargo#7169

Resolves: #641
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Command-install S-needs-team-input Status: Needs input from team on whether/how to proceed.
Projects
None yet
Development

No branches or pull requests