setup-rust-toolchain icon indicating copy to clipboard operation
setup-rust-toolchain copied to clipboard

Reuse the pre-installed rust toolchain if possible

Open Veetaha opened this issue 7 months ago • 2 comments

Problem

I've been quite annoyed by the performance of Rust toolchain installation. For example, it takes ~40 seconds to install it (with additional rust-src and clippy) components on the Github-managed Windows runner. Performance on Ubuntu is much better though, but still not ideal: 14 seconds, and MacOS is in the middle with 22 seconds.

Proposed Solution

Github-managed runner images happen to be blessed with the pre-installed latest stable rust toolchain on them. These images are quite well-maintained, but they can lag for some time, so it's not guaranteed that they are always up-to-date. For example, at the time of this writing the runners do have the latest stable 1.87.0 version of rust toolchain in them. This includes all images, even the images with the oldest OS versions:

Anyhow, I think it would be cool to benefit from this to optimize the toolchain installation for the case of a pinned requirement (e.g. when one specifies 1.87.0 in the rust-toolchain.toml file precisely.

This action could check if there is already an existing stable toolchain installed and that its semver version is equal to the pinned rust toolchain version, and skip downloading it. Instead - copy the toolchain from the stable directory and call it done (almost).

This optimization will work only under the following conditions:

  • The repo uses an exact version requirement for the toolchain
  • The exact version requirement coincides with the version of the stable toolchain already installed on the runner

Note that the action can be specified with the additional components or targets to install. I'm not sure if, for example rustfmt or clippy are already available in Github managed runner images, but it's easy to check. In any case, installing additional components is fine - the main point is to reuse what's already available on the runner's FS if possible, and if something extra is needed - then download only that extra part instead of the full toolchain from scratch.


I understand that this may be an issue for rustup specifically. I believe this problem is unique to CI environments, so I began this discussion here, but if you feel like this should be moved to rustup, then that's fine. The fundamental problem here is that rustup separates stable and x.y.z toolchain even if the stable's toolchain is of the x.y.z version. It doesn't try to reuse it when installing the x.y.z version.

Alternatives

I also thought about caching the toolchain via the Github's builtin cache functionality, but I'm not sure how much of a win that would be. It probably will be a win since Github's cache is likely located very closely to their runners

Veetaha avatar Jun 09 '25 14:06 Veetaha

Thank you for the idea how to improve this action. This is an interesting idea and simply a pain point of using rustup. I could find this open issue about it https://github.com/rust-lang/rustup/issues/2617. I don't think that this is a CI related issue, as you get the same duplicate toolchain installations when using toolchain files on your development computer.

I am not going to spend the time investigating the feasibility of it. If the copying logic is small and safe I might consider adding it here. Have you seen anyone implement and use a copying logic? I am a bit wary of messing with the rustup files. There is "rustup toolchain link" which seems to do similar things, but that does not allow using the standard release channel names.


Regarding the times you report, this does include restoring the cached target/ directory. For a fairer comparison, you should exclude those times. I don't have any good overview over the timing breakdown, but I do have CI runs which ran the caching step, but did not restore any cache, and they only used up 15 seconds on Windows. Fully disabling the caching step with cache: false should speed it up even more, for the cost of a longer compilation phase later on.

jonasbb avatar Jun 09 '25 15:06 jonasbb

Have you seen anyone implement and use a copying logic

I haven't. On the surface, the toolchains look well-isolated - a single directory per toolchain. This does require some research. I might be able to look into this when I have more time. If it's as simple as just copying a dir without any other nuances we might do that as a temp hack until rustup implements a more general solution to this problem. The comment https://github.com/rust-lang/rustup/issues/2617#issuecomment-750288530 from the linked issue in rustup mentioned:

In theory doing [copying] for stable/numbered toolchains would be possible, however extending it to beta/nightly would be near impossible to do reliably since the channel names and the compiler versions don't line up and are not even guaranteed to be offset in the same way over time. As such, rustup would be unable to know what the current nightly is in terms of nightly-YYYY-MM-DD thus it'd be unable to predict what/when to copy.

However, in Github CI environments we only have the stable version, so optimizing beta/nightly by copying won't even be feasiable.

Also:

I'm loathe to introduce something for stable/numbered channels which cannot extend to beta/nightly in this way. I fear it'd just introduce even more confusion. At least for now it's consistent if not necessarily as helpful as people might hope.

As for me, the performance win should be worth the inconsistency. While I do have jobs that both use stable and nightly/beta, things like cargo fmt can run in a separate job that only requires the stable toolchain, and just getting quicker feedback from CI is also valuable.

So let's leave this open for further experimentation. My main goal was to just get an approval of integrating this idea (at least conservatively) into this action

Veetaha avatar Jun 09 '25 15:06 Veetaha

I made the same experience (30s of a 40s-in-total-flow), but I have a different proposal:

  • I saw that the plugin first installs the tools, and then enables the cache. How about doing it the other way round, first restoring the cache (and make sure the bin directory and others are included in the defaults) and then install what's not there yet. If necessary, use an extra file for status which will be cached too (similar to a .lock file). If there's a problem with caching the default bin directory (because of binaries from the image), use a custom bin directory and add that one to the cache defaults AND the PATH envvar

daald avatar Dec 05 '25 01:12 daald