Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add path to global.json SDK version lock #8254

Open
rainersigwald opened this issue May 13, 2017 · 67 comments
Open

Add path to global.json SDK version lock #8254

rainersigwald opened this issue May 13, 2017 · 67 comments
Labels
Resolver untriaged Request triage from a team member
Milestone

Comments

@rainersigwald
Copy link
Member

Builders of large systems often want to be able to explicitly specify an SDK version and avoid installing the CLI to a machine-global location.

Currently, global.json allows the version lock, but the specified version must be installed in either a machine-global location or a nonstandard location must be specified by the environment variable DOTNET_MSBUILD_SDK_RESOLVER_SDKS_DIR.

That environment means that you must launch Visual Studio from a specific environment to get the downloaded/private SDKs.

There could be an extension to the SDK resolver to respect a path specified in the global.json. Something like

{
    "sdk": {
        "version": "1.0.0",
        "path": "tools/downloadedsdk"
  }
}

could be equivalent to setting DOTNET_MSBUILD_SDK_RESOLVER_SDKS_DIR=%GlobalJsonPath%\tools\downloadedsdk before invoking the resolver.

This would be visible by any invocation (dotnet, msbuild, VS, or MSBuild API) since it's file-based.

@rainersigwald
Copy link
Member Author

@nguerrera I think this might be a cleaner way to address @jaredpar's scenario from dotnet/msbuild#2095.

@jaredpar
Copy link
Member

I'm not sure this would work for Roslyn: we don't use the CLI to build. The CLI won't be able to build Roslyn anytime in the foreseeable future due to the number of desktop specific MSBuild extensions our build contains: WPF, VS SDK, SWIX, etc ...

@nguerrera
Copy link
Contributor

@rainersigwald This is exactly what I had in mind when I wrote:

(Now, that actually overlaps with another feature that's evolving and we will likely land in a place where you can edit global.json to get this behavior without setting any environment variables, but the mechanism here applies more generally to arbitrary resolvers with arbitrary input.)

There wasn't an issue for this yet (thanks for starting one), just mail threads so far, with a meeting scheduled soon to hash out the details. There's an existing mechanism called "multilevel lookup" in hostfxr that adds a well-known user profile location to the sdk search. This would be an evolution of that.

I did not expect dotnet/msbuild#2095 to be controversial, but I understand your concerns now. I wasn't intending for this and that to be mutually exclusive. I was imagining that being able to pass arbitrary data down to arbitrary resolvers would be generally useful.

@jaredpar

I'm not sure this would work for Roslyn: we don't use the CLI to build.

The resolver called by desktop msbuild will respect global.json too. That's part of it's main purpose: to pick the same msbuild targets for VS that would be used by the CLI msbuild invoked on the same project.

Do you build with CLI on Mac/Linux? If your process installs .NET Core SDK (official name of entire CLI not just dotnet/sdk) to a local location and sets this feature up via global.json, then you can have one mechanism for pinning down the precise set of things that come from the .NET Core SDK across platforms, and not require it to be globally installed anywhere. On Windows, you'd only use the tasks and targets, but if somebody wanted to use dotnet on the portable projects on Windows it would work. That could give you a simple way to reproduce issues that only happen on Mac or Linux official builds on a Windows dev box.

@nguerrera
Copy link
Contributor

nguerrera commented May 13, 2017

This could be equivalent to setting DOTNET_MSBUILD_SDK_RESOLVER_SDKS_DIR

global.json selects the full .NET Core SDK (CLI) used, not just the msbuild SDKs. We don't even have a resolver when we're invoked via dotnet as it will have taken us to a universe where we have matching msbuild SDKs for the given version.

So global.json should instead specify equivalent of DOTNET_MSBUILD_SDK_RESOLVER_CLI_DIR (matching layout of C:\Program Files\dotnet). More granular control over the resolver than that has to go somewhere other than global.json.

@jaredpar
Copy link
Member

jaredpar commented May 13, 2017

Can I get more information on global.json: in particular where it lives, how it's found, etc ...?

Do you build with CLI on Mac/Linux? I

Yes and depending on the answer above it seems like a problem. The path to the downloaded SDK is going to be very different between the two systems.

{
    "sdk": {
        "version": "1.0.0",
        "path": "tools/downloadedsdk"
  }
}

In order for this to solve the problem the SDK essentially has to be within the cone of the global.json. Guessing that mean that it has to be within the cone of the repo. No other tool has that requirement for our build and it's going to take some doing to make that work.

Still overall feels clunky. MSBuild properties can today define everything about my build. Makes it dynamic, very easy to manage. Now we have JSON (again) and I have to write generators to translate between my MSBuild files and this new JSON file.

@tmat
Copy link
Member

tmat commented Mar 16, 2018

In order for this to solve the problem the SDK essentially has to be within the cone of the global.json. Guessing that mean that it has to be within the cone of the repo. No other tool has that requirement for our build and it's going to take some doing to make that work.

I think this will be easy to make work in our repos. The only thing that needs to be restored to a directory specified in global.json is the immediate SDK used by the projects (which is not necessarily .NET SDK). In RepoToolset repos this is RepoToolset SDK (projects use <Project Sdk="RoslynTools.RepoToolset"/>).

Our current global.json for symreader repo looks like so:

{
  "sdk": {
    "version": "2.1.100-preview-007366"
  },
  "msbuild-sdks": {
    "RoslynTools.RepoToolset": "1.0.0-beta2-62705-02"
  }
}

Let's say we could add the path like so:

{
  "sdk": {
    "version": "2.1.100-preview-007366"
  },
  "msbuild-sdks": {
    "RoslynTools.RepoToolset": { "version": "1.0.0-beta2-62705-02", "path": ".sdk" }
  }
}

and this would restore the RepoToolset to {repo-root}\.sdk directory (next to global.json)

The RepoToolset restores .NET SDK by importing it in its sdk\Sdk.props file:

<Import Project="Sdk.props" Sdk="Microsoft.NET.Sdk" />

at which point we can use arbitrary msbuild properties to specify the path to restore to.

@jaredpar
Copy link
Member

@tmat that makes sense. I think that's workable.

@abelbraaksma
Copy link

abelbraaksma commented Sep 27, 2020

This would be a very welcome addition to the rather inflexible global.json config options. Currently, not being able to point to a local path, and the VS installer constantly uninstalling previous SDK versions, makes it pretty hard to do any decent SDK-dependent development (like the F# repo project files, see dotnet/fsharp#10193, which should be fixed on 3.1.302, but the VS installer removes this in favor of 3.1.402).

Since it is trivial to create an msbuild script that downloads a specific SDK version and installs it locally, it would be very, very nice if such behavior could be baked into global.json as well:

  • specify a local path, or just use token "local": true and standardize the path to be .dotnet
  • if SDK is absent in that location, VS installs the SDK automatically upon fist opening the solution

All in all, that would create a much better experience then the now rather hard-to-understand "cannot load project or solution" message, which baffles newcomers and experienced users alike...

@RussKie
Copy link
Member

RussKie commented Jun 11, 2021

This will have a significant positive impact on the developer experience for .NET repositories, such as https://github.com/dotnet/runtime/, https://github.com/dotnet/winforms, etc. Currently it is impossible to double click on a solution file to open the solutions in VS, because we build against nightly SDKs, which are located in custom locations and are forever changing.

@KirillOsenkov
Copy link
Member

As a workaround, I keep around a .bat file with this example content:

set PATH=C:\msbuild\.dotnet;%PATH%
set DOTNET_INSTALL_DIR=C:\msbuild\.dotnet
set DOTNET_MULTILEVEL_LOOKUP=0

If you go to the repo directory (C:\msbuild in my case) and run this .bat file, then run devenv.exe mysolution.sln, it will open with these environment variables set and the SDK resolution by MSBuild will use the .dotnet that's private to the repo.

@RussKie
Copy link
Member

RussKie commented Jun 11, 2021

That's what other dotnet repos are doing AFAIK in some shape or form. And this is not the best developer experience. It also makes it hard to run ad-hoc tests...

@KirillOsenkov
Copy link
Member

Oh, you don't have to convince me ;) I know it's terrible and I've spilled blood, sweat and tears to even arrive at this workaround by debugging deep into the SDK resolution process.

Lots of pain and friction caused by this over the years.

@RussKie
Copy link
Member

RussKie commented Jun 14, 2021

To include other points form offline chats and summarise:

There a number of use cases that really benefit from this (or comparable) functionality:

  1. Developer experience building and contributing to .NET repos, such as https://github.com/dotnet/runtime/, https://github.com/dotnet/winforms, etc. Currently it is impossible to double click on a solution file to open the solutions in VS, because we build against nightly SDKs, which are located in custom locations and are forever changing. This make it unnecessary hard and complicated for our (especially new) contributors. Right now a lot of our tooling is cli-centric (which may be expected for non Windows platforms and CI/CD scenarios), but our Windows/Visual Studio devex is very cumbersome. Repos have custom scripts or instructions that must be run in order to open solutions in Visual Studio.
  2. Local testing - it is very difficult to run customer repos against the nightlies. These builds must either be installed globally, or one has to jump through hoops and run custom scripts to bootstrap a sample.

If it is difficult to provide the download functionality, we could probably do this in staggered approach, i.e. provide the ability to resolve an SDK from a custom location.

  • If the path is not specified - continue doing what we do today.
  • If the path is invalid - fail.

Maybe add another property to specify an error message that can describe how to bootstrap or where to download from. E.g.:

{
  "sdk": {
    "version": "2.1.100-preview-007366"
  },
  "msbuild-sdks": {
    "RoslynTools.RepoToolset": { 
      "version": "1.0.0-beta2-62705-02",
      "path": ".sdk",
      "instructions": "Run .\restore.cmd to download the latest SDK.",
    }
  }
}

@marcpopMSFT
Copy link
Member

@vitek-karas We explored an option offline of just supporting this within the sdk resolver to find the SDK (essentially just DOTNET_MSBUILD_SDK_RESOLVER_CLI_DIR). Doing only that would unblock build but it would leave run (and potentially testing) still blocked as teams like WindowsDesktop have a test app that they would want to build and launch from VS against a version of the runtime listed in the global.json file.

Thoughts on finding the runtime from a path in global.json as well? I think the scenario for dotnet repos would have the runtime and SDK all in the same directory so potentially we only need one path still for both.

@RussKie
Copy link
Member

RussKie commented Jun 30, 2021

To add to @marcpopMSFT's post.

Starting VS without settings any paths or anything:
image

Starting VS with env:DOTNET_MSBUILD_SDK_RESOLVER_CLI_DIR settings:

PS C:\Development\winforms> $env:DOTNET_MSBUILD_SDK_RESOLVER_CLI_DIR='C:\Development\winforms\.dotnet'
PS C:\Development\winforms> start .\Winforms.sln

image
On a second F6 it all build successfully... ¯\_(ツ)_/¯

But I was unable to launch a project from the VS:
image

@vitek-karas
Copy link
Member

From runtime's perspective there's a risk of performance regressions. This would require the host to go looking for global.json (or some other similar file) performing file existence checks. The deeper the folder hierarchy, the more expensive the check. This would have to be done by EVERY application, EVERY time. Given that this feature is expected to be used almost exclusively by developers that doesn't feel like the right tradeoff.

Doing this for SDK only is in theory possible, but could open issues with compatibility between SDK and the necessary runtime/frameworks for it (since we could not rely on the dotnet.exe to be the right one anymore).

I absolutely understand the scenario and why it's important, but so far I was not able to figure out a solution which would not have a negative effect when the feature is not is use. (pay-for-play)

/cc @agocke

@marcpopMSFT
Copy link
Member

@RussKie Thoughts on only supporting this for the SDK as originally suggested given Vitek's feedback above? It wouldn't allow for launching test apps from the IDE but would allow for building. It's unclear if tests would work for not.

@danmoseley
Copy link
Member

@agocke thoughts? Seems there's agreement that it would be high value to solve this as it will make us and the community more productive in our repos.

@DustinCampbell
Copy link
Member

I (think) I like this. 😄 However, pinning the SDK in global.json is already a significant problem within Visual Studio, due to mismatches between the MSBuild engine in Visual Studio and the targets/tasks loaded from the .NET SDK. I have some worry that this might exasperate that problem more.

@KathleenDollard, do you have any thoughts?

@richlander
Copy link
Member

Is it possible to start with an ENV solution only? That would mitigate Vitek's concerns. I'd like to reduce the number of places we have to search on disk not invent more.

@agocke
Copy link
Member

agocke commented Jul 16, 2021

Having repo-local SDKs make sense and I think is tractable. The team talked about repo-local runtimes and that one seems much more difficult. Not only would it probably be a big perf problem, as more intermediate tools use .NET Core (I hope), it's less and less likely that you want everything to run on local runtime. For instance, would you want new VS components that run on .NET Core to suddenly pick your repo local runtime?

I think SDK is a good place to start, if we can make that work.

@richlander
Copy link
Member

If we had a repo root story, then putting a global.json at repo root would be workable and I suspect mitigate Vitek's concerns (depending on how repo-root worked; if it was an ENV, it would work awesome).

@RussKie
Copy link
Member

RussKie commented Jul 16, 2021

@RussKie Thoughts on only supporting this for the SDK as originally suggested given Vitek's feedback above? It wouldn't allow for launching test apps from the IDE but would allow for building. It's unclear if tests would work for not.

This wouldn't work for us. I can build from command line and don't need VS for that.
I'd like a seamless devex for VS: double click sln, F5, debug.

@jaredpar
Copy link
Member

If I have an SDK in hand why do I need to go through the trouble of setting an environment variable to use it? That means that it's a command line tool I can't reliably run unless I muck with some environment variables.

I still strongly think that I should be able to use dotnet build to use the SDK that came with the exe to build my application. Maybe I need to provide a command line switch like --this-one but the solution should be in the CLI itself. It shouldn't require any weird pathing.

Every other language framework in existing except .NET provides this solution.

@RussKie
Copy link
Member

RussKie commented Jan 27, 2022

I'm currently OOF and writing from a phone.

My main motivation for this change is outlined in #8254 (comment).

@davidwengier
Copy link

Just adding my vote to this, particularly #8254 (comment). If I'm understanding correctly, this would really help in the https://github.com/dotnet/razor-tooling/ repo, which currently requires setting a bunch of envars set. Having to ensure these are set before running VS is a pain, particularly if you're trying to use multiple versions of VS to verify things (switching between developer command prompts etc.), or if you're having to use multiple repos with similar set ups (as @RussKie mentioned too).

@richlander
Copy link
Member

@vitek-karas and I are working on a spec that possibly includes what you want. Will share (very) soon. In any case, it will be a good starting point.

@kdubau
Copy link
Member

kdubau commented Mar 1, 2022

The original idea (I think) was to effectively expose the analog of DOTNET_ROOT in global.json. I think that's an interesting direction IFF we have a new scenario for Visual Studio that it needs to respect that (which includes testing). Alternatively, we could create a convention where Visual Studio looks for a .dotnet directory at repo root to achieve the same end. I actually prefer that. Again, I think that's a Visual Studio scenario, not a .NET one.

@richlander IMO it should be in global.json. Irregardless, please make sure to loop in the Visual Studio for Mac team for any scenario.

@richlander
Copy link
Member

We backed a way a little from this. We decided NOT to put any new location content into global.json. It is too easy to trick developers into running malicious code. Instead, we're starting with a new CLI argument. VS* products can add support for this flag if they like. It won't happen automagically due to a .NET SDK feature.

@sandyarmstrong
Copy link
Member

@richlander can you provide some more detail on the malicious scenario here? I don't understand how this is riskier than restoring nuget packages.

Adding this info to global.json seems like the best way forward for painless IDE support of custom SDK locations on a per-solution basis.

@richlander
Copy link
Member

Today, you can clone a repo, build it, and assume that your globally installed SDK was used, provided by a vendor you trust. With this model, the global.json could point to a directory in the repo like ~/malicious-dotnet/ that could perform arbitrary operations on your machine as a consequence of running dotnet build. Certainly, NuGet packages can also do bad things in the build. We're aiming to reduce not increase security concerns.

I'm not suggesting that cloning and building an arbitrary repo is a safe thing to do today.

@akoeplinger
Copy link
Member

Not sure I follow your argument, how is that different from having a .csproj like this:

<Project Sdk="Microsoft.NET.Sdk">
   <Target Name="Build">
      <Exec Command="rm -rf /home/richlander" />
   </Target>
</Project>

The assumption today is that if you run dotnet build then you're trusting the code.

@vitek-karas
Copy link
Member

If I remember correctly the difference was that with the global.json path, it would be enough to run dotnet --info in the "wrong" directory.

@KirillOsenkov
Copy link
Member

After wasting another couple of hours on this today, I am more convinced than ever that we should allow specifying a path to the SDK in the global.json (as opposed to the environment variables, which are terrible UX for discoverability and automation). When you open the .sln in Visual Studio, for example, it doesn't have the environment variables, and so the design-time build fails completely (you get errors like "System.Object not found").

The tooling could even automate downloading the SDK into that folder instead of having arcane Arcade tools do it.

I have been shouting from the rooftops for years that for every repo in the .NET world anyone should be able to git clone && msbuild and it should just work, without custom build.cmd, build.sh etc. The environment variables get in the way of that. I absolutely do not buy the security argument because it doesn't make the status quo any worse (any risks from building with a local SDK are already present when restoring and/or building with vanilla SDK as Alexander explains). Both the project source or any of the NuGet packages that are restored can be malicious.

Not to mention that the current status quo is you run build.cmd. And of course, it still builds with that custom SDK, which still entails the same alleged security risks.

By ignoring this problem we exclude a whole class of tools and automation from working out of the box. If any repo is buildable out of the box, you could run security analysis, semantic indexing for SourceBrowser or other code search tools, etc. etc. These tools can't read your build.cmd as it's totally opaque imperative code. Had we had declarative configuration in global.json (download and use this exact SDK), it would open doors to automating analysis etc. Opening the .sln in VS would always "just work", whereas now it doesn't and causes errors in the design-time build that only a few of us know how to deal with.

I think the current situation is a major source of friction in the .NET ecosystem.

@xoofx
Copy link
Member

xoofx commented Nov 2, 2022

Hey, just found this thread.

We are actually looking for a solution at Unity to have the SDK configurable by the global.json. Our scenario is that we will ship/lock-in a specific version of the .NET SDK/Runtime as part of the Unity Editor, and we need a way to resolve our shipped .NET SDK for the sln/csproj files that we will compile with MSBuild or that we will open from Visual Studio.

Having a way to override the SDK root path in global.json seems to be a good fit for our use case.

@tmat
Copy link
Member

tmat commented Nov 2, 2022

Re Arcade -- the purpose of Arcade is to unify build across dotnet repos by implementing as few additions on top of the shipping .NET SDK as possible. This goal has been achieved. The Arcade SDK has been pretty stable for a while now. The next step should be to review the additions/workarounds that were introduced by Arcade SDK. Those that are generally useful, i.e. not specific to Microsoft build process should be productized in the shipping .NET SDK/msbuild/VS and removed from Arcade SDK. SDK downloading should be one of those features. That said, you'd still need to have a build script in the repo that downloads the SDK for non-VS scenarios.

@tmat
Copy link
Member

tmat commented Nov 2, 2022

every repo in the .NET world anyone should be able to git clone && msbuild and it should just work, without custom build.cmd, build.sh et

Do you mean that VS msbuild or dotnet build would check the version of the SDK specified in global.json and download one if it is not the one available on the machine and then run dotnet build from that matching SDK?

@RussKie
Copy link
Member

RussKie commented Nov 2, 2022

@tmat - yes. Or as an easier alternative to emit an error message directing developers how to get the required SDK.

@hknielsen
Copy link

To add to @xoofx's comment, for the work we are doing with MsBuild in Unity this is one of our big workflow issues.
The only way i've found to handle SDK resolving to something specific is to add local nuget repo and pin the SDK version in the global.json.

I would really like Sdk Resolving to be much more flexible. Creating a SdkResolver instead of a path in global.json would maybe be better, but theres no way to have it automatically picked up across vs/dotnet/msbuild in a nice way. Or at least I havent found a way!

I would be super happy to handle the Sdk resolving myself, path or SdkResolver

@xoofx
Copy link
Member

xoofx commented Nov 3, 2022

Do you mean that VS msbuild or dotnet build would check the version of the SDK specified in global.json and download one if it is not the one available on the machine and then run dotnet build from that matching SDK?

If possible, I would actually prefer to split the requirement/concern of the automatic downloading of a SDK version in a separate issue and keep this issue for allowing to specify a specific location for a SDK. It seems that allowing to override the path would require a small change to the existing MSBuild Sdk Resolver (I don't know if it would require lots of change to VS), while downloading a version that is not installed and put it on the drive look more cumbersome to align (and where, and how to cleanup the cache and...etc.).

@simonferquel
Copy link

As VS already support downloading custom SDKs (and using them) and use the global.json file to know which version to download, I feel the change is not more complex than it is for dotnet build (which seem quite trivial from an external point of view, but I could be wrong). Maybe it uses the same code as dotnet CLI for implementing that ?

If the issue is accepted, I think someone in Unity Scripting team can implement it for dotnet CLI, and see if there is any additional work to do for VS.

@hknielsen
Copy link

hknielsen commented Feb 14, 2023

@rainersigwald I can do the work, as we need to have a solution for moving to MSBuild.

Two possible solutions;

  1. Path to an additional location for picking up SDKResolver`s, that will be loaded by the SDKResolverLoader.
  2. Path that can Resolve additional MSBuild SDK's from

Im ok with both, but I could see more flexibility with option 1.
Ie. if someone wanted a SDKResolver that downloaded the msbuild SDK from another location, they could create their SDK resolver that automatically would do that, and it would work in VS/MSBuild/Dotnet cli

@rainersigwald
Copy link
Member Author

@hknielsen there's no consensus on what should be done/would be approved here so I don't think there's work to be done yet.

@sharwell
Copy link
Member

We decided NOT to put any new location content into global.json. It is too easy to trick developers into running malicious code.
...
Today, you can clone a repo, build it, and assume that your globally installed SDK was used, provided by a vendor you trust.

@richlander This is not even remotely correct. The default use of NuGet allows for arbitrary code to be executed by dotnet restore, including the ability to bypass anything we might put on nuget.org for detection mechanisms by redirecting to a privately hosted feed. Please reconsider the basis for the argument, as teams are being unnecessarily penalized by this decision.

@KirillOsenkov
Copy link
Member

I wish we had this support. Environment variables are causing us so much pain during local dev with a pre-release SDK. The need to set several environment variables and ensure your IDE also inherits them is a constant source of friction.

@jaredpar
Copy link
Member

jaredpar commented Nov 8, 2023

I've written up a design proposal that seeks to address the problems shared on this thread as well as others I've seen. Appreciate any feedback you all have on this.

dotnet/designs#303

@KirillOsenkov
Copy link
Member

long sad story

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Resolver untriaged Request triage from a team member
Projects
None yet
Development

No branches or pull requests