Skip to content

Conversation

@marcpopMSFT
Copy link
Member

Proposal for improving the SDK selection for the host and have it work better with admin installs (especially visual Studio).

Include a proposal for fast SDK version switching. Not-included is api work to share the SDK location to callers of the host/dotnetup.

… the SDK selection for the host and have it work better with admin installs (especially visual Studio).

Include a proposal for fast SDK version switching.
Not-included is api work to share the SDK location to callers of the host/dotnetup.
@jkotas
Copy link
Member

jkotas commented Nov 5, 2025

If I understand this correctly, this environment variable is meant to allow de-coupling location of dotnet.exe/dotnet and the actual SDK bits.

Current state: The SDK has to be in the known directory structure under that dotnet used to launch it. For example, if dotnet is located at /foo/dotnet, the SDK has to be located at /foo (/foo/sdk/10.0.123/,..) too.

Proposal change: Optionally override the SDK location using env variable. For example, if dotnet is located at /foo/dotnet, the SDK can be located at /bar (e.g. /bar/sdk/10.0.123/,..).

Is this correct understanding?

@baronfel
Copy link
Member

baronfel commented Nov 5, 2025

That's a huge part of it, yeah. The thought being that if we can't always control PATH on Windows due to the choices we've made in our MSIs in the past, we can at least make the dotnet binaries that are invoked use the SDKs and Runtimes that the user intended.

Then as a value-add on top of that, if the muxer also allowed for specific SDK version selection to be driven externally then you can put together a really good UX around experimenting with different SDK versions in the same workspace without having to make any permanent changes. This is something that folks struggle with today, as you have to create/update global.jsons to get the same experience. This isn't high friction, but it's also not no friction, and there's substantial prior art in other ecosystems for making it easy to switch tool chains on a temporary basis.

* **User Experience:**
* Detect if the global path points to `Program Files/dotnet` and the customer doesn't have the .NET 11 host which supports the SDK root feature.
* Notify the user:
> This configuration will only work in this window. Use `dotnetup use` each time you open a new command prompt.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The debugger etc still rely on the PATH so a lot of features won't work in this scenario. We explored the alternative of updating the muxer at the system hive but decided against that for concerns, such as an MSRC that could happen in a pre-release muxer (very unlikely.)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So dotnet use will work for your window but your debugger (and potentially other scenarios not going through the CLI) will be in a bad place until net11?

Copy link
Member

@nagilson nagilson Nov 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right - I'd have to open my IDE/workspace in a way that gets the effects of running dotnetup (aka launch from the terminal). But users might not notice that nuance.

@jkotas
Copy link
Member

jkotas commented Nov 6, 2025

we can at least make the dotnet binaries that are invoked use the SDKs and Runtimes that the user intended.

Won't you also need this to control the runtime selection for dotnet myapp.dll to make it work well? The proposal talks about SDK selection only.


### **Behavior**

* If `global.json` includes an SDK path setting, **prefer that path over the environment variable**.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like usually env vars have precedence over config files (since env vars can be set easily on the command line for one off scenarios). Is there a reason to have the precedence inverted here?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We're thinking of the most common use case being dotnetup and the global.json path is a fallback hence having the precedence flipped. @baronfel thoughts here? I think jjonescz is thinking about this from a more engineering focused position where envs usually are the overrides.

## **Proposal: DOTNET\_SDK\_ROOT**

Introduce a new environment variable:
`DOTNET_SDK_ROOT`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not obvious to me what's the difference between DOTNET_SDK_ROOT and DOTNET_ROOT; could the doc perhaps explain that difference?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One controls the SDK location and one controls the runtime location. DOTNET_ROOT is generally for when launching apps (with an app host presumably) and doesn't today control the SDK location. We could have it affect the SDK as well but that would be breaking hence proposing a new value. I'd have to think through whether the SDK_ROOT should affect the runtime ROOT and if there are customer scenarios who would want those different. Would a customer who's installed a user local hive want to run an app using the admin hive?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could have it affect the SDK as well but that would be breaking

Do we know how breaking it would be? Having a single env. variable that sets the location for both runtime and SDK would be much easier to explain.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a good question. We'd need to understand what scenarios customers have where they are setting DOTNET_ROOT today. In particular, cases where they are setting it to a different location than their SDK and on a box where they are using their SDK.

Do we have any idea how common that is? I assume we don't have a lot of info for this so we'd be a bit blind (which is what makes me nervous about doing that). I'd be ok with one value though fwiw.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@richlander @baronfel for thoughts on making DOTNET_ROOT apply to the SDK as well and if that's too risky.

@marcpopMSFT
Copy link
Member Author

we can at least make the dotnet binaries that are invoked use the SDKs and Runtimes that the user intended.

Won't you also need this to control the runtime selection for dotnet myapp.dll to make it work well? The proposal talks about SDK selection only.

For the dotnetup scenarios, we are going to be setting DOTNET_ROOT in addition to DOTNET_SDK_ROOT. For the muxer, we are proposing that the environment variable function like the global.json paths setting (with the addition of env expansion). I previously suggested having the muxer override DOTNET_ROOT for the global.json path scenario but there was pushback hence keeping two separate values.

What's the behavior today if someone uses global.json paths and calls dotnet myapp.dll?

@jkotas
Copy link
Member

jkotas commented Nov 6, 2025

What's the behavior today if someone uses global.json paths and calls dotnet myapp.dll?

I believe that dotnet myapp.dll does not consult global.json nor DOTNET_ROOT. dotnet myapp.dll is going to run on the runtime that is in the subdirectory of where dotnet is located.

@marcpopMSFT
Copy link
Member Author

What's the behavior today if someone uses global.json paths and calls dotnet myapp.dll?

I believe that dotnet myapp.dll does not consult global.json nor DOTNET_ROOT. dotnet myapp.dll is going to run on the runtime that is in the subdirectory of where dotnet is located.

So in the case of someone using dotnetup where we've set both DOTNET_SDK_ROOT and DOTNET_ROOT, they'll get the runtime they expect. In the case where a customer sets only the SDK one themselves without going through dotnetup, they might be confused by the behavior but I think the result is ok (for the same reason that we're ok with DOTNET_ROOT not affecting the chosen sdk today).

@jkotas
Copy link
Member

jkotas commented Nov 7, 2025

So in the case of someone using dotnetup where we've set both DOTNET_SDK_ROOT and DOTNET_ROOT, they'll get the runtime they expect.

They will get the global runtime, they won't get the runtime that DOTNET_SDK_ROOT and DOTNET_ROOT point to. It is not the behavior that I would expect.

@marcpopMSFT
Copy link
Member Author

Let me try to clarify as there are a few scenarios. Kind of sounds like you're saying the current behavior for scenarios 2 and 3 as they exist today are not expected.

  1. No variables set -- uses the runtime next to the muxer on the PATH
  2. Only DOTNET_ROOT -- uses the runtime from dotnet muxer on the path ignoring DOTNET_ROOT. Unexpected?
  3. Only DOTNET_SDK_ROOT -- Ignores global.json and SDK_ROOT. Uses muxer on the path. Unexpected?
  4. Both are set to the same value -- ???
  5. Both are set to different values -- ???

@jkotas
Copy link
Member

jkotas commented Nov 10, 2025

No variables set -- uses the runtime next to the muxer on the PATH

myapp.exe uses runtime from the default location. dotnet myapp.dll uses the runtime next to the muxer on the PATH that does not have to be the one in the default location.

Only DOTNET_ROOT -- uses the runtime from dotnet muxer on the path ignoring DOTNET_ROOT. Unexpected?

The effect of DOTNET_ROOT is very limited and makes sense in the shipping bits today.

It stops making sense to me in the motivating scenario for this proposal. (Paraphrasing) The motivating scenario for this proposal is to allow one to completely relocated the .NET runtime and SDK, without touching PATH (assuming that the muxer on the PATH is recent enough). If DOTNET_ROOT is ignored for dotnet myapp.dll, .NET runtime is not fully relocated.

@agocke agocke requested a review from elinor-fung November 11, 2025 00:34
@agocke
Copy link
Member

agocke commented Nov 13, 2025

What would happen to a loose zip file in this system if you set the environment variable at the system or user level?

* [.NET Environment Variables](https://learn.microsoft.com/en-us/dotnet/core/tools/dotnet-environment-variables) - Official documentation for .NET environment variables
* [global.json Overview](https://learn.microsoft.com/en-us/dotnet/core/tools/global-json) - Documentation for global.json configuration
* [.NET Installation Guide](https://learn.microsoft.com/en-us/dotnet/core/install/) - Official installation documentation
* [SDK Resolution in .NET](https://github.com/dotnet/designs/blob/main/accepted/2019/sdk-version-selection.md) - Design document for SDK version selection
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This link 404s.


## Background

The .NET ecosystem has long needed better SDK selection capabilities similar to tools like `nvm` or `rustup`. Previous efforts explored this through the [dotnet bootstrapper path proposal](https://github.com/dotnet/designs/blob/a87cfdf032351fd7e7b449a11edeba1b92ba53b1/proposed/dotnet-bootstrapper/dotnet-bootstrapper-path.md), which investigated how to integrate a 'dotnet' bootstrapper with Visual Studio while managing PATH environment variable conflicts.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be very useful to see an in-depth appendix that demonstrates how these tools work, on both Windows and some unix (to provide sufficient comparse/contrast). Otherwise, it seems like we're designing in a bit of a vaccum.

@marcpopMSFT
Copy link
Member Author

What would happen to a loose zip file in this system if you set the environment variable at the system or user level?

Per offline discussion, this is the big sticking point with this proposal as the moment as we want to ensure that a customer downloading a zip independently of dotnetup can navigate to it (or add it to their path) and use it without getting super confused. The current proposal will lead to unexpected behavior in that scenario. We're exploring whether it's possible to only redirect for the centrally installed muxer (or just the program files\dotnet one on windows as windows is the most challenging to resolve).

…n on previous alternatives that were considered.

If this is still too much risk for the runtime team, we'll have to proceed with the guidance to customers modifying their profile/path.
### Behavior

* These environment variables only affect SDK loading behavior when running `dotnet` commands that require SDK resolution.
* Direct execution of applications via `dotnet foo.dll` is **not affected** to avoid impacting runtime behavior.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't that a confusing behavior?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was a compromise with Andy as he didn't want to change the runtime load behavior. We're still talking with Elinor with how this might work but per my comment below, I think we're going to focus on finding a way to get feedback that is fewer long-term implications. If we get strong signal during previews, we'll revisit.


* These environment variables only affect SDK loading behavior when running `dotnet` commands that require SDK resolution.
* Direct execution of applications via `dotnet foo.dll` is **not affected** to avoid impacting runtime behavior.
* If the host is launched from the source path specified in `DOTNET_ROOT_REDIRECT_SOURCES`, SDK resolution will use the path specified in `DOTNET_ROOT_REDIRECT_TARGET`.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I dislike CLI tools that change the behavior based on the path they are launched from. It means e.g. copying/linking the tool to some other place or launching it via an alternative path produces different results...

* Microsoft could still modify the VS Developer Command Prompt and DevKit terminal creation to use appropriate SDK paths
* Users would be responsible for setting up their own terminal environments

**Issues with this approach:**
Copy link
Member

@jkotas jkotas Nov 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is simplest solution, it has the least amount of magic, and it does not require any changes in the admin installed .NET. I have strong preference for this solution, at least as a starting point.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ultimately, the team is trying to find a point in time solution that allows us to get feedback on dotnetup on windows machines with admin installs. I think given the concerns raised from runtime folks about making permanent changes to unblock feedback on an experiment, we'll probably start with options that are more temporary in nature (like dotnetup elevating and modifying your system path or having a "dotnetup dotnet" command or one of the targeted changes for specific terminals/profiles).

@richlander
Copy link
Member

The .NET ecosystem has long needed better SDK selection capabilities similar to tools like nvm or rustup.

I did a search on the doc and seem that nvm and rustup show up just once each in the doc, in that first sentence. I don't think we should merge this design until an in-depth analysis is addded that compare/contrasts the proposal with the "long needed" capabilities that are "similar to". In fact, the entire premise of the proposal is that we should do something similar, yet, there is no commentary provided on how well the design achieves that. If similarity isn't a goal, then maybe the comparison with other ecosystems should be removed.

The RID simplification spec I wrote is most similar to this one and could offer a guide on how to provide industry context. Searches on "rust", "python" and "manylinux" within that doc will demonstrate the research the team did to generate the design and how the final design differs from the existing SOTA in only nuanced and practical ways. The final doc documented the design in the context of that foundational and inspirational research and helped readers understand the need for the change and why we were adopting (subtly) different approaches. As the primary writer of that doc, the compare/contrast with existing solutions helped me build up confidence on the strength of our plan.

At least some of the comments in the doc likely wouldn't be present in their current form if industry analysis was present.

For fairness, most of our self-litigated analysis is not present in our spec, just the final derived insight. We also looked at golang and it isn't mentioned at all. If an analysis can be shared compactly like I did, that likely works well.

@marcpopMSFT
Copy link
Member Author

This proposal was strictly focused on trying to enable us to get feedback from previews on machines that had Windows Admin installs and the muxer is unique enough that it may require a unique solution. I went ahead and added some compact ecosystem context to the primary document that outlines the cli-acquisition proposal here.

I'm going to leave this open as we continue to have conversations but per my comment above, we have a few options that are potentially more temporary in nature to unblock feedback and we'll focus on those. We'll gather feedback on usage of dotnetup previews and use that to drive how we solve the admin problem long-term.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

9 participants