Code change:
This is a recent regression, starting with when is set, in my case to where I have prepared a locally built sysroot (that's ), then rustc now assumes it can find the linker in the sysroot, which does not work. See for some more details. It is new to me that the sysroot is supposed to contain the linker as well, is that expected behavior? Also the error message in that case is really unhelpful.^^ Maybe rustc should check that the path it sets for actually exists before starting ?
Yes. The blog post has a summary of that PR
That blog post doesn't mention the term "sysroot" so it doesn't help with my issue.
FWIW what I would have expected is that rustc searches for the linker binary somewhere around . Isn't that how we find some other things as well that are usually installed with rustc by rustup?
How do you handle windows-gnu? Imo this also uses a self-contained linker by default.
No idea. CI passes on Windows, whatever github gives us when we say .
Yeah the rust-lld linker has been in the sysroot since , is used by default in e.g. some of the wasm and aarch64 targets, and rustc had flags to opt-into using it since that PR. What is new is that it is now used by nightlies we distribute. bootstrap puts it in the sysroot when is true (and it's now the default on x64 linux under certain conditions), so you can also set that when -ing if you want it. It works with or without download-ci-llvm.
We resolve the sysroot from rustc driver, not current executable.
Yes, the surprise is just that the linker is searched in the sysroot and thus affected by --sysroot, rather than being searched directly. But I guess this is working as intended, I just need to add some more stuff to these custom sysroots then.
This broke cargo's CI since the build-std tests try to verify that nothing in the sysroot is required. Would it be possible to change it so that it will only require rust-lld if it exists in the sysroot (similar to windows-gnu and other targets)?
I don't know what we do on windows-gnu but we can do something similar, yes. I'll open a PR.
FWIW I managed to add tests for windows-gnu and they work like a charm. So no copying of files seems to be required there. Though I do recall xargo copying some things for windows-gnu so maybe I am not testing enough... but my tests do involve building a small program against the sysroot and running it, so maybe those files are just not needed any more.
We actually hit a binutils linker bug last week on windows-gnu (not windows-gnullvm), and the linker flavor there is gnu-cc, so do these targets really use rust-lld by default?
How does this work with cross-compilation -- I assume we are always taking the host sysroot even when building for another target? To me that seems like a sign that this shouldn't be part of the sysroot, it should be part of the rustc distribution. The sysroot is for target-related things.
In particular, I would expect that if I set to a folder that only contains a sysroot for some foreign target, then that should work if I also set appropriately (and use the right linker and everything). But currently that will then likely fail as it tries to search the host target in the folder, and with it will instead silently fall back to the slow linker.
They do use a self-contained linker from sysroot by default, but not rust-lld. We ship from binutils for those targets.
Thanks for the clarification. That explains why there's no existence check (and your 2nd hack for older GCCs in that other issue :). Locally, it was just using mingw's ld probably because of $PATH differences.
Linkers are generally target-dependent, so it's pretty reasonable to ship them in target sysroot, I guess. rust-lld is just applicable to a wide range of targets by being a cross-linker. Also there's no target vs host separation for sysroot in rustc right now, there's just one sysroot. Maybe there's some space for improvements here, it's just not many people use to notice any issues.
I still hope to migrate both rust-lld and mingw linker to the common directory layout scheme based on .
rust-lld is just applicable to a wide range of targets by being a cross-linker. If I'm on macOS and want to build for a Linux target, then the binary shipped in the sysroot is going to be completely useless to me. So in that sense shipping them in the target sysroot seems pointless. (Of course our cross-compile story is not great so such cross-building will hardly ever work, but it demonstrates the fundamental issue I was pointing out.) Even when I am on and building for I probably want to use a 64bit program for the linking, and not a binary shipped in the i686 sysroot -- this is e.g. how Firefox is (or was) built as 4GB of RAM (the maximum accessible to 32bit programs) are just not enough for linking. Given that these are binaries that need to be run on the host (i.e. the machine where the build happens), IMO the only sensible location is together with the other host stuff, i.e., where lives.
Ah I think I understand. That's right, and on macOS we wouldn't look for in the "x8664-unknown-linux-gnu sysroot" . You can try this locally on a helloworld (look for the path to ), from with a target:
Oh, so we always assume that contains a sysroot for the host and the current target. That's news to me as well (and Miri certainly doesn't guarantee it). But the fallback in your PR should solve that as well, I think.

Related discussion:
text: ) } <del> /// Returns a list of directories where target-specific tool binaries are located. </del> <ins> /// Returns a list of directories where target-specific tool binaries are located. Some fallback /// directories are also returned, for example if `--sysroot` is used but tools are missing /// (#125246): we also add the bin directories to the sysroot where rustc is located. </ins> pub fn get_tools_search_paths(&self, self_contained: bool) -> Vec<PathBuf> { <del> let rustlib_path = rustc_target::target_rustlib_path(&self.sysroot, config::host_triple()); let p = PathBuf::from_iter([ Path::new(&self.sysroot), Path::new(&rustlib_path), Path::new("bin"), ]); if self_contained { vec![p.clone(), p.join("self-contained")] } else { vec![p] } </del> <ins> let bin_path = filesearch::make_target_bin_path(&self.sysroot, config::host_triple()); let fallback_sysroot_paths = filesearch::sysroot_candidates() .into_iter() .map(|sysroot| filesearch::make_target_bin_path(&sysroot, config::host_triple())); let search_paths = std::iter::once(bin_path).chain(fallback_sysroot_paths); if self_contained { // The self-contained tools are expected to be e.g. in `bin/self-contained` in the // sysroot's `rustlib` path, so we add such a subfolder to the bin path, and the // fallback paths. search_paths.flat_map(|path| [path.clone(), path.join("self-contained")]).collect() } else { search_paths.collect() } </ins> } pub fn init_incr_comp_session(&self, session_dir: PathBuf, lock_file: flock::Lock) {