Skip to content

[MSYS2] Allow default host triple to be overridden by an environment variable #5024

Open

Description

@pgalbraith

Problem you are trying to solve

MSYS2 on Windows provides multiple development environments which can share the same $HOME and therefore the same rustup configuration, but which require different Rust host ABIs. For example:

native Windows → x86_64-pc-windows-msvc
UCRT64         → x86_64-pc-windows-gnu
CLANG64        → x86_64-pc-windows-gnullvm

The appropriate host is therefore a property of the current development environment rather than of the user, machine, or project.

At the same time, a project may want to pin its Rust version without pinning its host ABI:

[toolchain]
channel = "1.97"

Ideally, the same project would then resolve that partial toolchain specification according to the active environment:

native Windows → 1.97-x86_64-pc-windows-msvc
UCRT64         → 1.97-x86_64-pc-windows-gnu
CLANG64        → 1.97-x86_64-pc-windows-gnullvm

Rustup already supports this distinction conceptually: an unqualified toolchain such as 1.97 is completed using rustup's default host. However, the default host is persisted in rustup's configuration, so environments sharing the same rustup home cannot have different defaults.

RUSTUP_TOOLCHAIN does not solve the problem because it selects the complete toolchain and overrides rust-toolchain.toml. Using it to select the ABI would therefore also override the project's choice of Rust version.

Separate RUSTUP_HOMEs can provide separate persisted default hosts, but that makes the host choice a property of separate rustup installations. For environments such as MSYS2, the host choice is naturally a property of the currently active shell environment, while the environments otherwise share the same home directory, projects, and installed tooling.

Solution you'd like

Add an environment variable, perhaps RUSTUP_DEFAULT_HOST, which overrides rustup's configured default host when resolving a toolchain specification that does not contain an explicit host.

For example:

export RUSTUP_DEFAULT_HOST=x86_64-pc-windows-gnullvm

combined with:

[toolchain]
channel = "1.97"

would resolve to:

1.97-x86_64-pc-windows-gnullvm

This would allow environments such as MSYS2 UCRT64 and CLANG64 to set their appropriate default host during environment initialization while continuing to share the same RUSTUP_HOME.

The environment variable would affect only resolution of the omitted host component. It would not select or override the channel/version as RUSTUP_TOOLCHAIN does.

Conceptually, host resolution could follow:

explicit host in toolchain specification
    ↓
RUSTUP_DEFAULT_HOST
    ↓
configured default_host_triple
    ↓
normal rustup host detection/defaulting

An explicitly host-qualified toolchain would therefore remain authoritative, while an unqualified toolchain could inherit its host ABI from the current execution environment.

Notes

No response

Activity

  1. rami3l commented on Aug 20, 2026

    @rami3l
    Member

    @pgalbraith Hello, and thanks for filing this issue!

    I think the problem reported here is quite reasonable and the suggested solution might even play quite well with #4947, but I remember that there had been some concerns WRT the extra complexity introduced by adding new environment variable overrides to this project.

    @rust-lang/rustup do you think this would be a nice feature to have? If not, what potential alternatives could/should we suggest? 🙏

  2. rami3l commented on Aug 20, 2026

    @rami3l
    Member

    @pgalbraith Also, I have an additional question since I'm not super familiar with MSYS2 to see if this is the best way forward: in other environments, we'd expect the host machine to have a single main host tuple and multiple target tuples, overridable with CARGO_BUILD_TARGET. What is preventing you from using that in your case?

  3. pgalbraith commented on Aug 20, 2026

    @pgalbraith
    Author

    @rami3l Thanks for picking up on the topic, I hope others can weigh in because I feel I'm too new to Rust, especially, to confidently drive this home. My suggestion and PR here are my best read on the situation after only a few days with Rust :-).

    If you're using MSYS2 environments on windows, you can easily land in one of three relatively independent toolchain environments depending on your env config: either standard windows powershell with msvc toolchain, msys2 bash shell with gnu toolhain, or msys2 bash shell with llvm toolchain. So the 'host' is really describing your build toolchain, it's the build toolchain that varies from env to env, not the build target. My understanding varying CARGO_BUILD_TARGET is you're not varying your build toolchain, rather just the target ABI, so this doesn't solve the problem. I hope it makes sense :-).

  4. rami3l commented on Aug 20, 2026

    @rami3l
    Member

    @pgalbraith Thanks for your reply, but I think I may need to know your original issue to see the full picture.

    What I was trying to say is that if you just want to compile a single project to different target platforms, then installing 3 different toolchains of different host tuples on the same machine makes little sense, as you should probably just install the 3 targets on a single toolchain of your choice (say MSVC) and compile to those targets, and that final part is absolutely overridable with CARGO_BUILD_TARGET.

    Otherwise, the only possibility I can think of is that you're relying on certain parts of the host toolchain for your particular goal, not just the compiled artifacts from your project? What would that be?

  5. ChrisDenton commented on Aug 20, 2026

    @ChrisDenton
    Member

    cc @mati865, in case you have any thoughts on switching between different msys2/cygwin environments with rust.

  6. pgalbraith commented on Aug 20, 2026

    @pgalbraith
    Author

    The smoking gun, for me, is the user guide for windows, which steers you to changing the default host, to work with MSYS2, rather than simply setting CARGO_BUILD_TARGET, which would be a much cleaner solution, if it worked.

  7. rami3l commented on Aug 20, 2026

    @rami3l
    Member

    The smoking gun, for me, is the user guide for windows

    @pgalbraith ... and the end of that page states exactly that you can use a different CARGO_BUILD_TARGET to support a different target tuple:

    You don’t need to switch toolchains to support all windows targets though; a single toolchain supports all four x86 windows targets

    ... as long as you have rustup target added that tuple beforehand :)

  8. pgalbraith commented on Aug 21, 2026

    @pgalbraith
    Author

    @rami3l Looking at this again, I agree, it's not standing up to scrutiny. My original problem has evaporated with correct target, and the best example of any remaining potential problem that I've been able to find, is in rust-libxml. For me, building this fails under msys (even with the correct target set), but I'm also not convinced this build script just isn't plain looking at the wrong env value.

    I'll withdraw the PR and leave this issue to see if anyone with more rust/msys experience wants to chime in.

  9. changed the title [-]Allow default host triple to be overridden by an environment variable[/-] [+][MSYS2] Allow default host triple to be overridden by an environment variable[/+] on Aug 21, 2026
  10. mati865 commented on Aug 24, 2026

    @mati865
    Member

    cc @mati865, in case you have any thoughts on switching between different msys2/cygwin environments with rust.

    I definitely can see the use case for that.

    To make the situation clear how MSYS2 behaves, there are multiple sysroots, called environments (listing just the non-deprecated ones):

    • ucrt64 (path starting at <path to MSYS2 install>/ucrt64)
    • clang64 (path starting at <path to MSYS2 install>/clang64)
    • clangarm64 (path starting at <path to MSYS2 install>/clangarm64)
    • msys (path starting at <path to MSYS2 install>/usr)

    Imagine if you had on Linux sysroots organised like that:

    • /glibc
    • /musl
    • /ucrt
    • ...

    Each sysroot is a fully-fledged independent host toolchain, so you don't have to cross compile from glibc to musl. Instead, you just use /musl/bin/cc which is MUSL native C compiler.

    Returning to the issue. You typically get x86_64-pc-windows-gnu as the default Rustup host (which translates to now deprecated MSYS2 /msys64 environment, but /ucrt64 also works). So, targeting x86_64-pc-windows-gnullvm (MSYS2 /clang64 environment) means you are cross-compiling from x86_64-pc-windows-gnu to x86_64-pc-windows-gnullvm.
    That wouldn't be an issue on Linux with RPATH, but on Windows, with its DLL Hell things are not as simple. Entering CLANG64 environment in MSYS2 puts /clang64/bin at the beginning of PATH, which depending on what you are building may break the cross-compilation from x86_64-pc-windows-gnu thanks to mentioned DLL Hell. So, there are two options:

    • call rustup set default-host ... every time you change the environment, so you run the native toolchain
    • put /mingw64/bin or /ucrt64/bin before /clang64/bin in PATH, but only when invoking Cargo

    Personally, I very rarely boot Windows nowadays. Although, when I do, I only use gnullvm toolchains. So, for me, setting rustup set default-host x86_64-pc-windows-gnullvm permanentny checks out.

  11. ChrisDenton commented on Aug 24, 2026

    @ChrisDenton
    Member

    Maybe rustup should re-evaluate its gnu default.

  12. rami3l commented on Aug 24, 2026

    @rami3l
    Member

    Maybe rustup should re-evaluate its gnu default.

    @ChrisDenton Wait... would you mind clarifying?

    I thought x86_64-pc-windows-gnu was never the default... As far as I can remember we default to msvc and the user can override that. Am I missing something here?

  13. ChrisDenton commented on Aug 24, 2026

    @ChrisDenton
    Member

    It's overridden when using the rustup-init.sh install script.

  14. mati865 commented on Aug 24, 2026

    @mati865
    Member

    I think rustup-init.exe also defaults to windows-gnu if you don't have MSVC installed. Or at least used to do that years ago.

  15. rami3l commented on Aug 25, 2026

    @rami3l
    Member

    It's overridden when using the rustup-init.sh install script.

    @ChrisDenton Oh, thanks for pointing that out! In the script I think we already have pc-windows-gnullvm by default for Windows ARM64. The next natural question is: can/should we do the same for Windows x64?

  16. mati865 commented on Aug 25, 2026

    @mati865
    Member

    In the script I think we already have pc-windows-gnullvm by default for Windows ARM64.

    Because there is no AArch64 windows-gnu triple, windows-gnullvm is the only free/open source tooling for that arch.

    The next natural question is: can/should we do the same for Windows x64?

    That wouldn't really help as you'd turn the problem in the other direction: you'd be cross-compiling from x86_64-pc-windows-gnullvm to x86_64-pc-windows-gnu rather than using x86_64-pc-windows-gnu natively when using MINGW64 or UCRT64 environments in MSYS2.

    And keep in mind that x86_64-pc-windows-gnu remains tier 1 platform while x86_64-pc-windows-gnullvm is tier 2.

  17. pgalbraith commented on Aug 30, 2026

    @pgalbraith
    Author

    cc @mati865, in case you have any thoughts on switching between different msys2/cygwin environments with rust.

    I definitely can see the use case for that.

    To make the situation clear how MSYS2 behaves, there are multiple sysroots, called environments (listing just the non-deprecated ones):

    • ucrt64 (path starting at <path to MSYS2 install>/ucrt64)
    • clang64 (path starting at <path to MSYS2 install>/clang64)
    • clangarm64 (path starting at <path to MSYS2 install>/clangarm64)
    • msys (path starting at <path to MSYS2 install>/usr)

    Imagine if you had on Linux sysroots organised like that:

    • /glibc
    • /musl
    • /ucrt
    • ...

    Each sysroot is a fully-fledged independent host toolchain, so you don't have to cross compile from glibc to musl. Instead, you just use /musl/bin/cc which is MUSL native C compiler.

    Yes, exactly. E.g. how do I specify in a build script that "I want MSYS Python to execute X while building for target Y", vs. "I want Windows Python to execute X while building for target Y". The choice of Python varies by host, in that case.

    Certainly, it's worth waiting for a case that proves it, but I think the argument is ideologically sound, and I expect the proof will come.

  18. rami3l commented on Aug 30, 2026

    @rami3l
    Member

    @pgalbraith Note that this feature has been available in today's rustup, but it's not documented in any way (on purpose):

    rustup/src/dist/mod.rs

    Lines 644 to 645 in 8d732ef

    if let Ok(tuple) = process.var("RUSTUP_OVERRIDE_HOST_TUPLE") {
    Some(Self(tuple))

    Looking back at your original issue which has triggered the whole discussion, I have the impression that some libraries, notably cross-language ones (those involve C/Python/Perl or whatnot in their build processes), are essentially hostile to cross compilation so we cannot rely on cross builds?

    If so, then I am thinking about the user journey: what should we recommend regular users (i.e. not Windows experts) to use in order to get better overall compilation experience? Having to override RUSTUP_OVERRIDE_HOST_TUPLE each time still feels somewhat subpar.

    I highly suspect that this should be a problem with our host tuple detection (so far it only detects the host architecture), rather than a missing opportunity to add a new level of override (or complete its user experience):

    rustup/src/dist/mod.rs

    Lines 570 to 573 in 8d732ef

    // Default to msvc
    let arch = arch_primary().or_else(arch_fallback)?;
    let msvc_tuple = format!("{arch}-pc-windows-msvc");
    Some(TargetTuple(msvc_tuple))

    @ChrisDenton @mati865 How difficult would it be to add host environment detection WRT the ABI?

    PS: Apart from that there is a small issue that set_default_host_tuple() doesn't accept none as a legal input, but that's a minor issue which can be resolved pretty easily.

  19. pinned and unpinned a comment on Aug 30, 2026
  20. mati865 commented on Aug 30, 2026

    @mati865
    Member

    Yes, exactly. E.g. how do I specify in a build script that "I want MSYS Python to execute X while building for target Y", vs. "I want Windows Python to execute X while building for target Y". The choice of Python varies by host, in that case.

    Why would you need to control which Python to run?
    When cross-compiling, build scripts are always ran with the host toolchain. So, the first python executable found in PATH is going to be used.
    Of course, to avoid path separator clashes (Cygwin vs Win32) you need a way to override rustup toolchain to match your env.

    @pgalbraith Note that this feature has been available in today's rustup, but it's not documented in any way (on purpose):

    rustup/src/dist/mod.rs

    Lines 644 to 645 in 8d732ef

    if let Ok(tuple) = process.var("RUSTUP_OVERRIDE_HOST_TUPLE") {
    Some(Self(tuple))

    Am I reading it wrong, or does that only override the toolchain for install/update. The problem here is overriding it at the runtime, so something like RUSTUP_TOOLCHAIN but for the host triple.

    If so, then I am thinking about the user journey: what should we recommend regular users (i.e. not Windows experts) to use in order to get better overall compilation experience? Having to override RUSTUP_OVERRIDE_HOST_TUPLE each time still feels somewhat subpar.

    Recommend using Linux for coding...

    More seriously, setting RUSTUP_OVERRIDE_HOST_TUPLE via bash.rc/config.fish/.env is the only thing I can think of 🙃
    For example, MSYS2 sets MSYSTEM that describes which environment is currently used (of course only if you use dedicated source shell <msys/ucrt64/clang64/...>). RUSTUP_OVERRIDE_HOST_TUPLE could be set the same way.

    How difficult would it be to add host environment detection WRT the ABI?

    I'm not sure if that is worth the trouble, but if you want, the most generic way would be to check what cc --version prints. So, it'd look sorta like this:

    1. If MSVC is found (rustup already checks that) use it.
    2. Call cc --version if cc is present in PATH.
    3. If it shows clang and something like Target: x86_64-w64-windows-gnu or Target: x86_64-w64-mingw32 then it's likely windows-gnullvm.
    4. Else, if it shows gcc and something like Target: x86_64-w64-mingw32 then it's windows-gnu.
    5. Finally, if it shows gcc and something like Target: x86_64-pc-cygwin then it's windows-gnu.

    Full outputs of cc --verbose below. MSYS2 provides said MSYSTEM environment variable, but it wouldn't apply anywhere else.

    You might be wondering why GCC, Clang and Rustc all use different strings for their target triple. There is no standardised structure for it, everybody just starts up with what seems reasonable to them, and then changes accumulate over time.
    For example, GCC previously used i686-pc-mingw32 for the original MinGW (a bit dated Win32 API, 32-bit only), then when its fork - mingw-w64 was made (modern Win32 API, 32 and 64 bit), they added triples with w64 in place of previous pc.

    Details
    # /usr/bin/cc --verbose
    Using built-in specs.
    COLLECT_GCC=/usr/bin/cc
    COLLECT_LTO_WRAPPER=/usr/lib/gcc/x86_64-pc-cygwin/15.3.0/lto-wrapper.exe
    Target: x86_64-pc-cygwin
    Configured with: /d/S/B/src/gcc-15.3.0/configure --build=x86_64-pc-cygwin --prefix=/usr --libexecdir=/usr/lib --enable-bootstrap --enable-static --enable-shared --enable-shared-libgcc --enable-version-specific-runtime-
    libs --with-arch=nocona --with-tune=generic --disable-multilib --enable-__cxa_atexit --with-dwarf2 --enable-languages=c,c++,lto --enable-graphite --enable-threads=posix --enable-libatomic --enable-libgomp --disable-lib
    itm --enable-libquadmath --enable-libquadmath-support --disable-libssp --disable-win32-registry --disable-symvers --with-gnu-ld --with-gnu-as --disable-isl-version-check --enable-checking=release --without-libiconv-pre
    fix --without-libintl-prefix --with-system-zlib --enable-linker-build-id --enable-libstdcxx-filesystem-ts
    Thread model: posix
    Supported LTO compression algorithms: zlib
    gcc version 15.3.0 (GCC)
    
    # /ucrt64/bin/cc --verbose
    Using built-in specs.
    COLLECT_GCC=C:\msys64\ucrt64\bin\cc.exe
    COLLECT_LTO_WRAPPER=C:/msys64/ucrt64/bin/../lib/gcc/x86_64-w64-mingw32/16.2.0/lto-wrapper.exe
    Target: x86_64-w64-mingw32
    Configured with: ../gcc-16.2.0/configure --prefix=/ucrt64 --with-sysroot=/ucrt64 --with-build-sysroot=D:/M/msys64/ucrt64 --with-local-prefix=/local --with-native-system-header-dir=/include --libexecdir=/ucrt64/lib --en
    able-bootstrap --enable-checking=release --with-arch=nocona --with-tune=generic --enable-mingw-wildcard --enable-languages=c,c++,ada,fortran,lto,objc,obj-c++,jit --enable-shared --enable-static --enable-libatomic --ena
    ble-threads=posix --enable-tls --enable-graphite --enable-fully-dynamic-string --enable-libstdcxx-backtrace=yes --enable-libstdcxx-filesystem-ts --enable-libstdcxx-time --disable-libstdcxx-pch --enable-libgomp --disabl
    e-libssp --disable-multilib --disable-rpath --disable-win32-registry --disable-nls --disable-werror --disable-symvers --with-libiconv --with-system-zlib --with-gmp=/ucrt64 --with-mpfr=/ucrt64 --with-mpc=/ucrt64 --with-
    isl=/ucrt64 --with-pkgversion='Rev3, Built by MSYS2 project' --with-bugurl=https://github.com/msys2/MINGW-packages/issues --with-gnu-as --with-gnu-ld --with-libstdcxx-zoneinfo=yes --disable-libstdcxx-debug --enable-lto
     --enable-plugin --with-boot-ldflags=-static-libstdc++ --with-stage1-ldflags=-static-libstdc++
    Thread model: posix
    Supported LTO compression algorithms: zlib zstd
    gcc version 16.2.0 (Rev3, Built by MSYS2 project)
    
    # /clang64/bin/cc --verbose
    clang version 22.1.8 (https://github.com/msys2/MINGW-packages 6e4e79c2f86eeb534e324e583f2057dc9fd5ecab)
    Target: x86_64-w64-windows-gnu
    Thread model: posix
    InstalledDir: C:/msys64/clang64/bin
    
  21. rami3l commented on Aug 30, 2026

    @rami3l
    Member

    @mati865 Thanks for all the explanation! Especially regarding the target detection, that's actually discussed elsewhere previously IIRC, so definitely worth saving the details you've provided here 🙏

    One point I'd love to clarify:

    Am I reading it wrong, or does that only override the toolchain for install/update. The problem here is overriding it at the runtime, so something like RUSTUP_TOOLCHAIN but for the host triple.

    Since #4947 we have this (the demo is on my Mac):

    > rustup default stable # This forces the default toolchain to be unqualified.
    [..]
    
    > rustup show active-toolchain
    stable-aarch64-apple-darwin (default)
    
    > RUSTUP_AUTO_INSTALL=0 RUSTUP_OVERRIDE_HOST_TUPLE=x86_64-pc-windows-gnu cargo --version
    error: toolchain 'stable-x86_64-pc-windows-gnu' is not installed
    help: run `rustup toolchain install` to install it

    So what @pgalbraith has asked is basically already implemented because rustup will try to call the stable with the host tuple you've specified, not the one it auto-detected.

    More seriously, setting RUSTUP_OVERRIDE_HOST_TUPLE via bash.rc/config.fish/.env is the only thing I can think of

    I see, so it'd be the same as what has been originally suggested here.

    @pgalbraith What do you think has been going wrong with the building process of the libxml binding, does it really have to be resolved by switching the host tuple?

  22. pgalbraith commented on Aug 31, 2026

    @pgalbraith
    Author

    @pgalbraith What do you think has been going wrong with the building process of the libxml binding, does it really have to be resolved by switching the host tuple?

    I can try to reason about this ideologically, but please understand I'm no longer speaking from personal experience here, I'm just reasoning from the outside of things. My own problem, which was the genesis of this issue, melted away rather quickly when I followed your advise. I am merely suspicious that there is still a thread of an underlying problem, and I'm really hoping someone more knowledgeable than I can confirm this. (i.e. please don't rely solely on my arguments at this point :-D).

    For libxml, I think even that was not a good example, I suspect maybe that build script was just conflating build/target environment when it shouldn't have, and it couldn't find msys libxml because it was looking at host env and deciding "I need to find native windows libxml", when in fact it should have looked at the target env instead and located the msys lib instead (i.e. fault of the build script alone).

    My current 10,000-foot view of this, goes roughly like this: there is a difference between *-gnu and *-msvc host toolchains, and even though I may have the elements of both fully installed and available, I may legitimately want the behaviour of one specific host toolchain and not the other. By my still very-limited understanding of the Rust ecosystem, I think those differences probably surface during executing of complex custom build scripts which may be making tool selections based on the host. If you agree that, in principle, yes there probably are some obscure cases of this, I'm just extending this argument a little to say, it probably makes sense to use a *-gnu (or gnullvm) host when building in an msys environment, and a *-msvc host when building outside of one, to more consistently support those edges (and the best way to do that is via the env var).

  23. rami3l commented on Sep 1, 2026

    @rami3l
    Member

    @pgalbraith Thanks for your clarification!

    Given all the background provided here, I'd prefer keeping RUSTUP_OVERRIDE_HOST_TUPLE as an undocumented workaround here, and keeping this issue open just in case we need to complete its UX and/or come up with more creative solutions.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions