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

Extract rust-version as rust constraint from cargo.toml if present #26314

Open
rarkins opened this issue Dec 15, 2023 · 11 comments
Open

Extract rust-version as rust constraint from cargo.toml if present #26314

rarkins opened this issue Dec 15, 2023 · 11 comments
Labels
manager:cargo Cargo/Rust/crates.io priority-3-medium Default priority, "should be done" but isn't prioritised ahead of others type:feature Feature (new functionality)

Comments

@rarkins
Copy link
Collaborator

rarkins commented Dec 15, 2023

Describe the proposed change(s).

Ref: https://doc.rust-lang.org/cargo/reference/manifest.html#the-rust-version-field

This should be extracted both as a dependency, as well as a constraint. In both cases it refers to the minimum version supported, similar to go.mod's go directive.

The dependency should not be bumped by default.

@rarkins rarkins added type:feature Feature (new functionality) priority-3-medium Default priority, "should be done" but isn't prioritised ahead of others manager:cargo Cargo/Rust/crates.io labels Dec 15, 2023
@epage
Copy link

epage commented Dec 15, 2023

There is another source for a potential rust constraint. rustup is a rust version manager and supports a rust-toolchain file which lets maintainers change which version of rustc / cargo gets run by default.

rust-version is more likely to be set by libraries being published to crates.io while rust-toolchain.toml is more likely to be used by applications that need validation before upgrading their version of rustc.

rust-toolchain.toml can be used in more advanced cases (nightly, custom toolchains) which would make it hard or impossible for your to extract a constraint.

I expect that not all maintainers with a rust-toolchain.toml file will want the constraint applied (though assuming they do is likely a safe default).

I'm leaving this note here as I'm unsure if you'd prefer to keep this in one Issue or split it to a separate issue.

@rarkins
Copy link
Collaborator Author

rarkins commented Dec 16, 2023

My assumptions had been that:

  1. rust-version in a Cargo.toml is meant to indicate compatibility and for libraries it's ideally as low (wide) as possible.
  2. Any rustup definition is meant to indicate the exact version which developers/CI should use when installing, testing and running. When in use it's likely a high/recent/latest version.

When it comes to rust-version, we shouldn't "bump" it by default, certainly not to latest. This would immediately make the library less compatible for downstream users, and potentially should even be considered a breaking change. Note: in the JS ecosystem, libraries usually do a major semver release when they drop support for older versions of Node.

It might be useful though for Renovate to bump rust-version to the earliest supported version of Rust, if that's a thing? e.g. like in Node, v16 is now unsupported so v18 is the earliest supported version.

Next, we need to think what to do with these rust versions.

For rustup, I think that's easy - that's the version we should install/use when running cargo commands.

For rust-version, my proposal is that's the constraint we can use to determine if updates of dependencies are compatible. e.g. if a library has rust-version=1.61 and one of its dependencies has rust-version=1.60, but a newer version of the dependency has rust-version=1.62 then that's not compatible, because it does not satisfy 1.61.

@epage
Copy link

epage commented Dec 18, 2023

Any rustup definition is meant to indicate the exact version which developers/CI should use when installing, testing and running. When in use it's likely a high/recent/latest version.

Depending on the domain, application authors have little reason to set package.rust-version (only matters for cargo install) and they may set rust-toolchain.toml file to describe the only version of rust they support (not bothering with older; newer requires a full re-validation). For example, they may be developing with the certified Rust toolchain, Ferrocene.

Note: in the JS ecosystem, libraries usually do a major semver release when they drop support for older versions of Node.

We generally recommend against that in Rust (semver guidelines).

It might be useful though for Renovate to bump rust-version to the earliest supported version of Rust, if that's a thing? e.g. like in Node, v16 is now unsupported so v18 is the earliest supported version.

At this time, the Rust Project only supports the latest toolchain release. For anything else, they are expected to either upgrade or get support (ie backporting security fixes) from their vendor (like Ferrocene)

For rust-version, my proposal is that's the constraint we can use to determine if updates of dependencies are compatible. e.g. if a library has rust-version=1.61 and one of its dependencies has rust-version=1.60, but a newer version of the dependency has rust-version=1.62 then that's not compatible, because it does not satisfy 1.61.

I agree though I would fallback to using rust-toolchain.toml for the constraint if package.rust-version is not present in the for the reasons given above. This would match the current iteration of the RFC for how we'll handle this stuff internally.

The question remains as for what to do with dependencies without a package.rust-version for checking compatibility. Currently, the RFC sorts dependency versions as:

  1. Most compatible first with highest version among most compatible
  2. Highest version among no package.rust-version
  3. Highest version among incompatible

We do want to explore finding good fallbacks for package.rust-version, like tracking what version of the toolchain was used when publishing the package (this would end if in the dependency data source, maybe as a new field).

In both cases, the benefit of matching what we eventually stabilize (the end goal of the RFC) is that users will get a consistent experience which will make it more predictable.

@rarkins
Copy link
Collaborator Author

rarkins commented Dec 22, 2023

Any rustup definition is meant to indicate the exact version which developers/CI should use when installing, testing and running. When in use it's likely a high/recent/latest version.

Depending on the domain, application authors have little reason to set package.rust-version (only matters for cargo install) and they may set rust-toolchain.toml file to describe the only version of rust they support (not bothering with older; newer requires a full re-validation). For example, they may be developing with the certified Rust toolchain, Ferrocene.

This makes sense to me. As an application developer supporting multiple language versions adds work but no additional functionality.

Note: in the JS ecosystem, libraries usually do a major semver release when they drop support for older versions of Node.

We generally recommend against that in Rust (semver guidelines).

I don't think the Rust ecosystem can consider itself genuinely semver-compliant though if minor releases can knowingly break things. I don't mind, but I think the Node.js ecosystem has it right here.

It might be useful though for Renovate to bump rust-version to the earliest supported version of Rust, if that's a thing? e.g. like in Node, v16 is now unsupported so v18 is the earliest supported version.

At this time, the Rust Project only supports the latest toolchain release. For anything else, they are expected to either upgrade or get support (ie backporting security fixes) from their vendor (like Ferrocene)

There seems like there should be a difference between "we won't backport features or fixes" versus "the instant we release a new version, the old ones are EOL". In software it's useful to know when you're running EOL software, but if users get ominous warnings about a Rust version which was perfect 10 seconds earlier then it will numb them to start ignoring all such warnings.

Anyway, it does seem like Renovate should bump Rust version in package files or rust toolchains by default, if that's the way the Rust ecosystem works? (unlike Go, where it would annoy people)

I agree though I would fallback to using rust-toolchain.toml for the constraint if package.rust-version is not present in the for the reasons given above. This would match the current iteration of the RFC for how we'll handle this stuff internally.

Does the following make sense?

In case both rust-toolchain and rust-version are present, which should we use? I would think it might happen for libraries where:

  • rust-version indicates their compatibility to downstream users
  • rust-toolchain indicates which version of rust to install, build and test with for maintainers

The does rust-version mean "version X or later" while rust-toolchain means "exactly this version"?

Ultimately for Renovate, we just want to end up with a "constraint" which tells us "this project needs dependencies satisfying this constraint".

The question remains as for what to do with dependencies without a package.rust-version for checking compatibility. Currently, the RFC sorts dependency versions as:

  1. Most compatible first with highest version among most compatible

What does "most compatible" mean?

  1. Highest version among no package.rust-version
  2. Highest version among incompatible

@epage
Copy link

epage commented Dec 27, 2023

Anyway, it does seem like Renovate should bump Rust version in package files or rust toolchains by default, if that's the way the Rust ecosystem works? (unlike Go, where it would annoy people)

There are a lot of opinions on the updating of Rust versions in package files and rust toolchain files such that it likely shouldn't be updated by default.

In case both rust-toolchain and rust-version are present, which should we use? I would think it might happen for libraries where:

package.rust-version should have precedence over rust toolchain files.

What does "most compatible" mean?

Sorry, that should just read "Any compatible"

@FreezyLemon
Copy link

FreezyLemon commented Feb 21, 2024

Anyway, it does seem like Renovate should bump Rust version in package files or rust toolchains by default, if that's the way the Rust ecosystem works? (unlike Go, where it would annoy people)

Personally, I would prefer Renovate to just search for rust-version-compatible dependency upgrades by default, and not touch the Cargo.toml file. Both ways are arguably fine, but I think that library authors who specify a minimum supported rust-version (it's not required) generally don't want to increase this version without at least some consideration for the compatibility implications.

Either way, (and this is from the perspective of a user that doesn't know much about the internals of Renovate), it would be useful to have very explicit information about this. Something like "The minimum supported Rust version in Cargo.toml was increased from 1.60 to 1.70 to allow upgrading x, y, and z." and "x could not be upgraded from 1.2.3 to 1.2.4 because it requires a Rust version of 1.70, which is higher than this crate's minimum Rust version of 1.65."

If this information is provided somehow, the workflow could look like this (which would be quite nice IMO):

  • renovate opens PR with some updates, including info that dependencies A, B, C were held back due to MSRV mismatch
  • maintainer manually update MSRV to what they deem acceptable
  • PR gets rebased with all compatible updates (some deps might still be held back)
  • PR can be merged knowing MSRV guarantee is held

Unrelated: There's a significant number of projects (usually bigger ones) that have an explicit "MSRV policy" (e.g. "support at least the last 6 months of Rust releases"). Not sure if it's reasonably possible to account for something like this.

@epage
Copy link

epage commented Feb 21, 2024

There are two elements to an MSRV policy

  • An upgrade grace period
  • Supporting a fixed version, like Debian Stable, Ferrocene, the last compiler the company validatied, etc

Usually the community is focused on one or the other and sometimes people try to use a grace period to emulate support for a fixed version with mixed results.

So figuring out how to support an MSRV policy can be complicated.

Currently I use a grace period and rely on minimumReleaseAge to get that. I've been considering supporting fixed versions and creating repos that just proxy Rust releases but only when certain criteria are met (N%5=0, Debian stable was updated, etc) and so I could point RennovateBot at that.

@rarkins
Copy link
Collaborator Author

rarkins commented Mar 1, 2024

This issue has diverted a little, and I don't want it to get left behind.

If a Cargo.toml defines a rust version, we should do this:

  • Extract it as a dependency
  • Use cargo versioning
  • Use rangeStrategy=replace by default, meaning it won't be automatically upgraded via PRs unless a new major release occurs
  • Treat it as a constraint for the dependencies within the file

We also need to decide what is our source of truth for Rust versions. For containerbase today we use the "rust" library image in Docker. Is that also suitable here or should we have a different source of truth for what is a valid/released Rust version?

@FreezyLemon
Copy link

Skimming through the Docker tags for the rust image, it looks like the docker releases are usually a few days/weeks after the real release. I measured the real release date by looking at the announcement posts on the Rust blog ("Announcing Rust 1.XY.Z"). A source that is probably easier to read programmatically is the rust-lang/rust git tags.

@rarkins
Copy link
Collaborator Author

rarkins commented Mar 1, 2024

On the other hand if Renovate discovers new rust releases almost immediately then the proposed update might fail in the user's environment if the tooling they use to install rust doesn't source directly from the GitHub repository either and also has hours or days delays.

@epage
Copy link

epage commented Mar 1, 2024

I currently use github releases as my source of truth for rust releases, see https://github.com/clap-rs/clap/blob/690f5557d7f25904c31ec9f2a3c3657cbb68c98e/.github/renovate.json5#L26-L27

In general, updating of package.rust-version shouldn't be done by default. If the user wants to stay on "latest", the now approved RFC includes support for a package.rust-version = "current" (field value is not finalized), negating the need for immediate updates. Most likely, people will have minimumReleaseAge set, so the difference between whether the official release is out and their chosen form of infrastructure is updated is not likely to be a problem.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
manager:cargo Cargo/Rust/crates.io priority-3-medium Default priority, "should be done" but isn't prioritised ahead of others type:feature Feature (new functionality)
Projects
None yet
Development

No branches or pull requests

4 participants