Managing version numbers in Cargo.toml

Hello folks,

I apologize if this is a bit of a meta question or if it's been answered before and I haven't found the answer (I have looked), but: how do people generally manage project version numbers in Cargo.toml in between project releases?

Context for the question: generally I like to use git tags to mark versions of software projects, and derive version numbers from those tags via git describe. This has the nice benefit that if I'm, say, 3 patches ahead of my last version tag, and I build the project, the resulting version number automatically reflects that (e.g. v1.3.2-3-{commit-hash}).

By contrast if the version number is in a config file, then either it will be outdated (if e.g. I update the version number to mark a release, and then continue with further development later), or it will be ahead of itself (if e.g. I update the version number as soon as I'm planning a new release, and then add all the changes I want to make in that release). Neither is really a very nice situation to be in.

This isn't rust-specific, obviously, but cargo does seem to require that the version parameter be set and there is not an obvious way to get it to derive the value from a git tag. So how do people go about managing this parameter when maintaining libraries?

I have come across cargo-release, and I may well use it, but I'm interested in what people think is the right way to manage the version parameter independent of any tool, and why. I will say that at first glance I'm not entirely sure I like the idea of -pre versions (as cargo-release seems to use) because they seem to me inherently less informative than git describe.

Basically I'm looking for strategies, thoughts, best practices, etc. from folks who are more experienced than me at maintaining rust apps and libs, and in particular to learn why people favour one method or another.

Thanks in advance for any advice, and best wishes,

  -- Joe
1 Like

In Tokio, the version number field is left unchanged until a new release is prepared. When we want to release a new version, somebody prepares a PR that updates the version number and change log. An example of this can be seen here. The process is described in the CONTRIBUTING.md file.

The release PR is squashed into a single commit, which is where the git tag for that version goes.

2 Likes

Well I guess I see how many other people are concerned about this from the number of replies :joy: Thanks @alice for being the kind exception (and sorry for the delay in responding).

That obviously makes sense as the most simple and straightforward way to handle things.

Interesting.

I've seen some crates in crates.io with 0.x.y version and they are very consistent. It always gives me the impression that the release version should be taken cautiously or at least that the crate is not okay for production yet.

On the other hand, npm gives 1.0.0 version as suggestion, when we do npm init. lol

I try to follow semver with my version numbers and will typically only change the version number when making a release (e.g. by opening Cargo.toml in vim). It feels like changing the version number every time you make a commit could be brittle unless everyone uses the same git hooks.

Usually I use the cargo-release tool to do my releases. When I run cargo release it'll automatically do the following:

  1. Go through each Cargo.toml file and bump the version number appropriately
  2. Make a new commit
  3. Tag that commit with something like v1.2.3
  4. Publish the updated version to crates.io (this can be skipped with --skip-publish if you don't want to publish your code)
  5. Update the version numbers (again) to something like v1.2.4-dev to indicate the code is in development
  6. Push all new commits and tags to your remote (i.e. git push && git push --tags)

I used to do all of this manually (except step 5), but it's nice to have it all done for you and preview the changes before they happen with --dry-run.

I see where you're coming from, but I personally haven't found much use for the number of commits between HEAD and the last tag, or the current commit hash. I don't think you gain much by adding the commit hash to the version number because unreleased code is almost always compiled from source, and if you are using a released version it'll typically come from crates.io (which already manages versions and such) or be tagged if using importing directly from a git repo.

Also, it's good to mention that cargo will try to cache the current project's compilation artefacts and if your version number is derived from git describe it might force a rebuild of the project (but not any dependencies) every time you make a commit.

As an aside, you can override Cargo.toml's parameters when calling cargo by setting environment variables. So maybe you could run cargo with CARGO_PACKAGE_VERSION=$(git describe) cargo build? I've never tried it personally, but that may work for you.

3 Likes

Honestly this is just a thing that you have to learn about the Rust ecosystem: There are lots of production ready crates still in 0.x.y. To give the example of Tokio again, in hindsight we should probably already be 1.0. That said, we do have a plan for when we want to switch to 1.0. We have to figure out what to do about a new api called io_uring, but besides that, there isn't much more to do.

2 Likes

Well assuming everyone is respecting SemVer, there is nothing inherently production-unready (in terms of code quality) about 0.x.y. It's just a matter of understanding that there might be breaking changes in minor releases. Whether that's a production-usage no-no is largely down to how readily one feels able to handle such breakage (and how much one trusts the developers not to do it gratuitously).

1 Like

Right, in general it's obviously not such a big deal. Where I have found it useful is e.g. in CI systems which produce package artifacts (which obviously should not be published in between release tags, but which might be useful to download and test in some circumstances), where it's useful to know exactly what commit they were generated from, and its relationship to release tags.

Similarly it can be useful in local testing with apps that log their version number on startup, to be able to swap about between different builds from different branches and keep track of which was which.

That's good to know, thank you! That might work for at least some of my use-cases.

Well, yes, but also no. SemVer the spec only talks about stability of API, of course. But it also says that a MAJOR version number of 0 means that there are no requirements of API stability at all, and that if you're working to maintain backwards compatibility between any two releases, you should have a MAJOR version 1 (or higher).

That's why Cargo and most other SemVer implementations use the "0.MAJOR.PATCH extension", which adds API stability to 0.MINOR versions.

1 Like

If that's the case, what about embedding the commit hash in your executable as a constant? With, say, the git_version crate.

It sounds like you aren't necessarily wanting a way to embed the commit hash in the crate's version number, just some mechanism that can be used to tell exactly what source code something came from.

Wow, that can be quite powerful... My day job is mostly centred around the single-monolithic-app-installed-on-a-client-machine workflow, but being able to reproduce things exactly by using commit hashes would be super useful when you're in a more rapidly evolving environment and have lots of moving parts.

Even then I've got a pre-build step that'll embed the commit hash in the executable which can be used for tracking when I give pre-release builds to people for testing.

1 Like

Well yes, in the strictest interpretation of SemVer even a patch version increment can introduce a breaking change for 0.x.y releases. But for practical purposes I think most responsible developers would follow the principle you cite of "no breaking changes in patch releases":

... which makes things a whole lot more robust and reliable in practice. And of course even allowing for the fact that 0.x releases can introduce breaking changes ... most don't. (A lot of people use 0.x to indicate that the API is not feature-complete, rather than than treating it as unstable.)

But generally speaking more important than any of that is that the developers are really clear about (and document well) the stability promises they offer, and the breaking changes that occur. If it's good and well-maintained code, it can be worth handling a few well-motivated breaking changes from time to time.

That's a really useful suggestion, thank you very much!

Pretty much, yes. But it can also be convenient to just avoid needing to edit the crate version number, and just apply git tags when needed. It's simply one less duplication of effort.

No big deal, though, because one can automate that.

Yup :slight_smile: That captures my working environments for the last years pretty well -- backend systems with frequent updates, multi-node deployments, lots of space for trying out experimental builds as one node among many. So it's pretty important to be able to track what was running when.

1 Like

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.