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

[Proposal] .NET Tools should default to running on the latest available Runtime #30336

Open
Tracked by #29436
baronfel opened this issue Feb 3, 2023 · 8 comments
Open
Tracked by #29436
Milestone

Comments

@baronfel
Copy link
Member

baronfel commented Feb 3, 2023

Is your feature request related to a problem? Please describe.

Currently, .NET applications use a runtimeConfig to determine which runtime they run on. This is something that is determined at build time, but non-tool .NET applications can override this with various flags and environment variables given to the .NET runtime host. The default rollforward policy is LatestPatch, which allows an application to run on any runtime of the same major/minor version that is at least as high a version as the application was built against.

.NET (local) Tools are a specific kind of .NET application that are intended to be distributed and managed via SDK commands. They are generally development tools/utilities. We think the semantics of tools are such that tools should be able to run on any .NET runtime equal or greater than the runtime the application originally targeted. This will reduce friction for users adopting new SDKS/Runtimes by not completely blocking their workflows without some explicit proof on incompatibility.

Without this change, tool authors must either explicitly set a more flexible roll-forward policy, or multitarget. Multitargeting has the side effect of increasing the overall size of the tool, and still not solving the issue - a tool that multitargets net6.0 and net7.0 will still not run on net8.0 runtimes.

Describe the solution you'd like

There are two places we could address this mismatch:

  • build time, when a tool's RollForward defaults are baked into the resulting application
  • install time, when any apphosts/runtimeConfigs are authored by the SDK for subsequent executions

Build Time changes

We would change the RollForward of all tools from LatestPatch to LatestMajor. This would cause the desired behavior for all newly-built apps. We would also track a sentinel value that we set the RollForward, so that at install time we could tell if the user explicitly requested a change to the RollForward.

Install Time changes

We would ensure that tools that did not explicitly override to force a certain RollForward behavior would be written with LatestMajor behavior. This helps us plug the existing behavior gap for tools that haven't been authored with an SDK containing these changes.

Additional context

@baronfel baronfel added this to the 8.0.1xx milestone Feb 3, 2023
@dotnet-issue-labeler dotnet-issue-labeler bot added the untriaged Request triage from a team member label Feb 3, 2023
@baronfel
Copy link
Member Author

baronfel commented Feb 3, 2023

Pinging @richlander and @vitek-karas because this behavior change would technically limit the flexibility of the runtime to make breaking changes.

@baronfel baronfel removed the untriaged Request triage from a team member label Feb 4, 2023
@vitek-karas
Copy link
Member

I think it would be really unfortunate if this caused use to change the bar on breaking changes in .NET (it doesn't feel important enough to do that). So far we've been pretty specific about developers making an explicit decision to roll forward over major versions.

That said I agree that for the tools this makes a lot of sense. I think we should be very explicit about this - meaning that installing the tool will "force roll-forward" and what are the ways for tool's developers to avoid that. Basically still trying to keep our existing major version compatibility stance, and .NET tools would be an explicit exception to the rule - where developing a tool is in itself an opt-in to this behavioral change.

@richlander
Copy link
Member

richlander commented Feb 7, 2023

This other proposal is getting at much of the same thing, but in a different way. I'd rather see us focus on making it easier to move projects forward to new .NET versions. I think this other proposal has less of the issues that Vitek is concerned about.

I also agree that changing our roll-forward policy (for any binaries) is going to cause us pain. I think it is good we have an opt-in, for both producer and consumer of code.

@vitek-karas
Copy link
Member

My understanding is that this issue is trying to solve the problem that we have a slew of tools already on NuGet, but when we release 8.0 P1, only a few of them will work (those who added rollforward manually). I agree that it would be really nice to have a majority of the tools working from day 1 (especially since we rarely break people anymore).
There's also the fact that some tools are not maintained frequently so it may take a long time (if ever) to get them to run on 8.0)

#29949 will only make it easier to test the update (note that I can't publish the tool with that, since it would modify the min supported version). So the two are only related to the extent that latter makes it easier for people to test major versions.

@jander-msft
Copy link
Member

As a single data point, if this automatic roll-forward was applied to .NET tools, the dotnet-monitor tool (which is an ASP.NET application that ships as a .NET tool) would likely opt-out of it. There was a change (I believe it was this) between .NET 6 and .NET 7 regarding the NTAuthentication class on which the Microsoft.AspNetCore.Authentication.Negotiate assembly used reflection to access internal APIs. It was changed in .NET 7 in such a way that the Negotiate assembly could no longer lookup the API it was attempting to reflect over. We had to disable roll-forward due to this change.

We're okay with disabling roll-forward because we are actively working on dotnet-monitor and are releasing previews in lock-step with .NET 8 all up.

@richlander
Copy link
Member

You are right @vitek-karas. The thing I'm getting at doesn't solve the problem. However, one of the reasons why some people have pushed back on my proposal is compatibility. If we cannot enable an easier source upgrade option, then binary roll-forward is off the table. The two options are on a compatibility spectrum.

@richlander
Copy link
Member

richlander commented Jul 4, 2023

Our compat bar is based on the idea that no scenarios auto roll-forward. I think we should keep to that. Otherwise, we probably need to reframe our compat bar, which I believe hasn't happened.

An alternate premise to tools auto rollforward is that users lack sufficient UX to make on-demand rollforward tenable. Said differently, the tools auto roll-forward change is a band-aid change over poor UX. We should fix the UX and hold fixing the experience (by reverting the roll-forward change) until we can invest in changes like the ones proposed as follows.

Another philosophy point is that we've tried to make tools just a opinionated delivery of an app, but everything else is the same. If tools need some improved UX (beyond delivery), it is almost certain that general apps need that too. If we make those more general changes, everyone wins.

I propose we do the following (somewhat of a repeat of the OP).

  • No scenarios roll-forward by default.
  • Roll-forward to preview with one gesture
  • Tools can be configured to roll-forward on install (or even later).
  • dotnet run supports app roll-forward (including running the resultant binary at a later point).

Roll-forward with a single gesture

When I run a .NET 7 app in a .NET 8 Preview environment, I have to do the following:

export DOTNET_ROLL_FORWARD=LatestMajor
export DOTNET_ROLL_FORWARD_TO_PRERELEASE=1

Note: There are some cases where an app with just LatestMajor will roll-forward to a preview release, I believe, if there is only that one preview release installed. I'm not certain what the algorithm is for that.

I find this requirement to be over the top and requires a lot of extra knowledge of users. I also find that the harm it is saving me from to not be all that compelling.

We have two choices here:

  • Change the meaning of LatestMajor to include DOTNET_ROLL_FORWARD_TO_PRERELEASE=1, or
  • Invent a LatestMajorYolo value

We want installing pre-release versions to be pretty safe, so the first option is a little too scary. That leaves that last option.

The following proposal isn't perfect, but is basically the same with a short suffix. Perhaps someone can come up with a better name.

export DOTNET_ROLL_FORWARD=TestLatest

The idea is select a name that describes the intent and is clearly not prod oriented.

Roll-forward on install

This should be similar for dotnet tool install as dotnet run

dotnet run sort of already has a story for this:

% cat hello-world.csproj | grep Target
    <TargetFramework>net6.0</TargetFramework>
% dotnet run --roll-forward LatestMajor --
Hello, World!
.NET 7.0.8
% cat ./bin/Debug/net6.0/hello-world.runtimeconfig.json
{
  "runtimeOptions": {
    "tfm": "net6.0",
    "framework": {
      "name": "Microsoft.NETCore.App",
      "version": "6.0.0"
    }
  }
}

The problem with this experience is that it is just affects that one launch, since it is a host function, but doesn't affect runtimeconfig.

The following also doesn't work.

dotnet run /p:RollForward=LatestMajor

However, that same approach works fine with dotnet build.

% dotnet build /p:RollForward=LatestMajor
% cat bin/Debug/net6.0/hello-world.runtimeconfig.json
{
  "runtimeOptions": {
    "tfm": "net6.0",
    "rollForward": "LatestMajor",
    "framework": {
      "name": "Microsoft.NETCore.App",
      "version": "6.0.0"
    }
  }
}
% dotnet build /p:RuntimeFrameworkVersion=8.0.0-preview.5.23280.8   
% cat bin/Debug/net6.0/hello-world.runtimeconfig.json
{
  "runtimeOptions": {
    "tfm": "net6.0",
    "framework": {
      "name": "Microsoft.NETCore.App",
      "version": "8.0.0-preview.5.23280.8"
    }
  }
}

It should work with dotnet run as well.

Clearly, adding <RollForward>LatestMajor</RollForward> to the project file works, but doesn't work as an after-the-fact solution, particularly for tools.

Ideally, the following would work for tools:

dotnet tool install --roll-forward=LatestMajor
dotnet tool install --roll-forward

Note: LatestMajorPre would also be an option here.

The two commands would be equivalent. The intent is that runtimeconfig would be rewritten to include the matching rollForward value.

We could also (additionally) consider shipping a tool for rewriting runtimeconfig as a general helper.

dotnet tool install UX

One of the key issues with dotnet tool install is that the diagnostic UX is pretty bad. This is also what leads people to set roll-forward by default or to multi-target.

This is what happens with a single target non-forward package.

$ dotnet tool install -g Umbraco.Tools.Packages
You can invoke the tool using the following command: UmbPack
Tool 'umbraco.tools.packages' (version '1.0.1') was successfully installed.
$ UmbPack
You must install or update .NET to run this application.

App: /root/.dotnet/tools/UmbPack
Architecture: arm64
Framework: 'Microsoft.NETCore.App', version '3.0.0' (arm64)
.NET location: /usr/share/dotnet

The following frameworks were found:
  7.0.8 at [/usr/share/dotnet/shared/Microsoft.NETCore.App]

Learn about framework resolution:
https://aka.ms/dotnet/app-launch-failed

To install missing framework, download:
https://aka.ms/dotnet-core-applaunch?framework=Microsoft.NETCore.App&framework_version=3.0.0&arch=arm64&rid=debian.11-arm64

The key issue is that there is no feedback at install that the tool will fail to launch. Instead, we should fail installation by default when an app will fail to launch. We probably need a hosting API that takes a runtimeconfig file and produces similar diagnostic information. In particular, we'd want something like the following:

Installation failed.

This app wasn't installed because it won't run on your machine. This can be resolved by one of the following:

1. Install with the `--roll-forward` flag, using with the following command.

dotnet tool install -g --roll-forward footool (this should be copy/pastable)

2. Install .NET 7 (which the tool requires) with the following link

https://aka.ms/dotnet/download/sdk/7

4. Install with `--force` and manually configure the tool to run, using with the following command.

dotnet tool install -g --force  footool

Closing

These proposals would make it easier to maintain our no auto roll-forward policy, while making it easiest for users to roll-forward on demand.

Developers could do the following:

  • Configure apps and tools to run on any future runtime, including (optionally) previews, with a single project setting.
  • Configure apps and tools to run on a later runtime at run or install, including (optionally) previews, with a single CLI gesture.

@vitek-karas
Copy link
Member

Random comments :-) (this should be a PR so that I can comment on specific sections)

DOTNET_ROLL_FORWARD=LatestMajor
If there's only a pre-release version of runtime available, this will roll forward to it without setting the other variable. You only need to set the other variable if you want to use pre-release even though there's a matching release version.

I would be OK introducing DOTNET_ROLL_FORWARD=TestLatest as a shortcut effectively but I think it will have a limited value.

dotnet run /p:Property=Value
Unfortunately there's a bug. /p: doesn't work, but -p: does. See for example How gets RunArguments property evaluated? · Issue #32551 · dotnet/sdk (github.com) for a discussion on the topic.

Problem: rollforward on install fixes the old tools on a new machine problem. But it doesn't fix a problem with tools on a new (typically) preview install of SDK. Asking people to reinstall the tools in that case feels wrong. Maybe the env variable is the solution then? Other than the scenario you already described above I think there are two interesting cases:

  • I installed the latest preview globally (maybe VS did this for me) - in this case it should be relatively rare that this breaks tools regardless of roll forward, since the tools can still run on the older runtimes installed globally. The dotnet tool install --roll-forward fixes this scenario for the most part. Notably, I don't think setting DOTNET_ROLL_FORWARD=TestLatest should be even proposed in this case (as it would have to be done globally as well and has a high chance of breaking things left and right on the machine)
  • I installed the latest preview locally (private install) - in this case the dotnet tool install --roll-forward doesn't fix anything unfortunately. It will work for locally installed tools, but not the globally installed ones. If I have a globally installed tool which works just fine on the machine, when I switch to a private install of a new preview that tool may not work anymore. This is where the DOTNET_ROLL_FORWARD=LatestMajor fixes the problem (note that in this case you don't need the TestLatest option, there's only one runtime available in the private install, so LatestMajor will use it even if it is a preview release).

@marcpopMSFT marcpopMSFT modified the milestones: 8.0.1xx, 8.0.2xx Sep 15, 2023
MattKotsenas added a commit to MattKotsenas/PackedPrettier that referenced this issue Jan 25, 2024
If only .NET 8 is installed (common in CI scenarios) the tool won't run because it requires .NET 6 or 7. Instead of adding another target framework for .NET 8, add the `<RollForward>major</RollForward>` property to the package so that the tool can run on newer runtimes if required.

See https://learn.microsoft.com/en-us/dotnet/core/tools/global-json#rollforward for general information about `rollForward`.

See dotnet/sdk#10375 for additional details about rolling dotnet tools forward, and KirillOsenkov/MSBuildStructuredLog#721 for an example of other developer tools setting roll foward, and dotnet/sdk#30336 for more discussion on what the defaults should be.
Gitii pushed a commit to Gitii/PackedPrettier that referenced this issue Feb 10, 2024
If only .NET 8 is installed (common in CI scenarios) the tool won't run because it requires .NET 6 or 7. Instead of adding another target framework for .NET 8, add the `<RollForward>major</RollForward>` property to the package so that the tool can run on newer runtimes if required.

See https://learn.microsoft.com/en-us/dotnet/core/tools/global-json#rollforward for general information about `rollForward`.

See dotnet/sdk#10375 for additional details about rolling dotnet tools forward, and KirillOsenkov/MSBuildStructuredLog#721 for an example of other developer tools setting roll foward, and dotnet/sdk#30336 for more discussion on what the defaults should be.
@JL03-Yue JL03-Yue removed their assignment Apr 19, 2024
@marcpopMSFT marcpopMSFT modified the milestones: 8.0.2xx, 8.0.4xx Jun 7, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

6 participants