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

Runtime binding proposal #51

Merged
merged 12 commits into from
Feb 7, 2019
115 changes: 79 additions & 36 deletions proposed/runtime-binding.md
Original file line number Diff line number Diff line change
Expand Up @@ -205,20 +205,37 @@ We will offer a configuration knobs for hosts that enable artificial roll-forwar

The host exposes the following configuration knobs that you can use to control binding behavior:

### Version

The `version` knob specifies the minimum (or floor) version required by an application as a 3-part version.

`version` can be set in the following ways:

* `RuntimeFrameworkVersion` in an MSBuild project
* `version` in `.runtimeconfig.json`
* `--fx-version` via the CLI


If `RuntimeFrameworkVersion` is not set, then `version` defaults to the `0` patch for the specified target framework. For example, `version` would be set to `3.0.0` by default for the `netcoreapp3.0` target framework.

MSBuild project settings are only consulted at build-time and are then written as properties to `.runtimeconfig.json`, which can be consulted at runtime.

### RollForward

We will introduce a new knob, called `RollForward`, with the following values:
`RollForward` specifies the roll-forward policy for an application, either as a fallback in case a specific version
Copy link
Member

Choose a reason for hiding this comment

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

Probably a copy paste error - the sentence seem to end too early.


`RollForward` can have the following values:

* `None` -- Do not roll forward. Only bind to specified version.
* `Patch` -- Roll forward to the highest patch verison. This disables minor version roll forward.
* `Minor` -- Roll forward to the lowest higher minor verison, if requested minor version is missing. If the requested minor version is present, then the `Patch` policy is used.
* `Major` -- Roll forward to lowest higher major version, and lowest minor version, if requested major version is missing. If the requested major version is present, then the `Minor` and `Patch` policies are used, as appropriate.
* `AlwaysLatestMinor` -- Roll forward to latest minor version, even if requested minor version is present. Intended for COM hosting scenario.
* `AlwaysLatestMajor` -- Roll forward to latest major and highest minor version, even if requested major is present. Intended for COM hosting scenario.
* `LatestPatch` -- Roll forward to the highest patch verison. This disables minor version roll forward.
* `Minor` -- Roll forward to the lowest higher minor verison, if requested minor version is missing. If the requested minor version is present, then the `LatestPatch` policy is used.
* `Major` -- Roll forward to lowest higher major version, and lowest minor version, if requested major version is missing. If the requested major version is present, then the `Minor` policy is used.
* `LatestMinor` -- Roll forward to latest minor version, even if requested minor version is present. Intended for COM hosting scenario.
Copy link
Member

Choose a reason for hiding this comment

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

Not sure we should explicitly state that this is for COM... it is useful in other cases as well. But if you think it makes sense, I'm fine with it.

* `LatestMajor` -- Roll forward to latest major and highest minor version, even if requested major is present. Intended for COM hosting scenario.
* `Disable` -- Do not roll forward. Only bind to specified version. This policy is not recommended for general use since it disable the ability to roll-forward to the latest patches. It is only recommended for testing.

`Minor` is the default setting. See **Configuration Precedence** for more information.
Copy link
Contributor

Choose a reason for hiding this comment

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

Last time we spoke in a big group about this topic, there was a lot of openness to Major becoming the default. Is that now off the table? cc @DamianEdwards

I think that this proposal makes configuration easier and the whole process easier to understand, I was really hoping that global tools would start to just workin mismatched major case. cc @wli3

Copy link
Member Author

Choose a reason for hiding this comment

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

There was more openness to it at the time. I'm not proposing that, however, as the way to move forward. When we flip that lever, the journey to re-inventing the .NET Framework will be complete. Yeahh!

Another way of putting is asking which UX experience we want (since both will exist to some degree):

  • Why didn't think app start? Oh, I have to install something from Microsoft. Ain't got time for that. Screw this app!
  • Why did this app crash? I don't know. Screw this app!

I'm pushing choice 1 as the good default. The second one is too hard for everyone involved.

Copy link
Member

Choose a reason for hiding this comment

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

There's also the argument about hosting environments (think ASP.NET hosting). That is running my app on a container/VM which is managed by somebody else. If in such case I try to run a 2.* app on a host which only has 3.0 and it silently works... everybody thinks (including the hosting provider), that it will work just fine. In reality there might be some slight behavioral difference which can't be tested for upfront before deployment. It makes it harder for both sides to have a solid experience.

Copy link
Contributor

Choose a reason for hiding this comment

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

OK. Next question: do you consider it OK if we recommend to global tool authors to put RollForward=Major in their csproj. We have a lot of feedback that folks don't like that their global tools do not work with only 3.0 installed.

Copy link
Member

Choose a reason for hiding this comment

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

OK if we recommend to global tool authors to put RollForward=Major

It is ok as long as the global tool authors understand the consequences. This setting increases probability that the tool won't work.

I think it is manageable for small tools that are unlikely to break. I would not recommend it for large complex tools.


Can be specified via the following ways:
`RollForward` can be set in the following ways:

* Project file property: `RollForward`
* Runtime configuration file property: `rollForward`
Expand All @@ -238,13 +255,51 @@ The following policy will be used in 3.0:

Note: The ENV syntax is used above, but the same rules apply for any way that the two settings are set.

### fx-version
### Configuration Precedence

The host will consult the various ways of setting `RollForward`, in order (later scopes take precendence over earlier ones):
Copy link

Choose a reason for hiding this comment

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

typo precendence -> precedence

richlander marked this conversation as resolved.
Show resolved Hide resolved

1. `.runtimeconfig.json` properties (AKA "json")
2. Environment variables (AKA "ENV")
3. CLI arguments

The `version` and `roll-forward` settings compose in the following way:

* A `version` setting at higher precedent scopes overwrites both the `version` and `roll-forward` values at lower scopes. For example, `version` specified at the CLI scope (with `--fx-version`) overwrites both a `version` and `roll-forward` setting that might exist at the json scope.
* A `version` setting can flow to higher scopes if it is not replaced by another `version` value. This enables a `roll-forward` setting at a higher scope to compose with a `version` setting at a lower scope.
* The absense of a `version` value is an error when the `roll-forward` value of `LatestPatch`, `Minor`, `Major` or `LatestMinor` is set since the initial version state is not available. It is not an error when the `roll-forward` value of `LatestMajor` is set since an initial version state is not meaningful.

More generally:

You can specify the exact desired runtime version using the command line argument '--fx-version'.
* The `version` value establishes a floor for roll-forward behavior, except `LatestMajor`.
* The default `roll-forward` setting is `Minor` except when `--fx-version` is specified when it is `Disabled`.

### Using `version` and `roll-forward` in practice

The following examples demonstrate various (non-exhaustive) ways that `version` and `roll-forward` can be used.

Installed versions:

The following example shows the existing behavior of this knob:
* `2.1.0`
* `2.1.1`
* `2.1.7`
* `2.2.1`
* `2.2.3`
* `3.1.0`
* `4.0.0`
* `4.2.1`

```console
C:\testapps\twooneapp>type twooneapp.csproj
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.1</TargetFramework>
</PropertyGroup>

</Project>

C:\testapps\twooneapp>type Program.cs
using System;

Expand All @@ -259,46 +314,34 @@ namespace twooneapp
}
}
}
C:\testapps\twooneapp>

C:\testapps\twooneapp>dotnet bin\Debug\netcoreapp2.1\twooneapp.dll
Hello World!
C:\Program Files\dotnet\shared\Microsoft.NETCore.App\2.1.7\System.Private.CoreLib.dll

C:\testapps\twooneapp>dotnet --fx-version 2.1.0 bin\Debug\netcoreapp2.1\twooneapp.dll
Hello World!
C:\Program Files\dotnet\shared\Microsoft.NETCore.App\2.1.0\System.Private.CoreLib.dll
```

The `--fx-version` argument can be composed with the new `RollForward` settings, either via CLI or ENV. By default, `--fx-version` does not roll-forward.

The following example enables a .NET Core 2.1 application to run on .NET Core 2.2 without having to select the correct 2.2 patch version. In this case `--fx-version` specifies the floor version. In this scenario, 2.2.0 is not on the machine, but a later patch version is.

Copy link
Contributor

Choose a reason for hiding this comment

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

Earlier a project (.csproj/.vbproj) setting was proposed. It's not mentioned here in precedence. Is that because it rolls into the runtimeconfig? Cool, but we should mention it I think.

Copy link
Member Author

@richlander richlander Feb 4, 2019

Choose a reason for hiding this comment

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

Is that because it rolls into the runtimeconfig?

Yes. Project values have no meaning at runtime since (in most cases) it no longer exists as an accessible artifact. I will add a note for that.

```console
C:\testapps\twooneapp>dotnet --fx-version 2.2.0 bin\Debug\netcoreapp2.1\twooneapp.dll
It was not possible to find any compatible framework version
The specified framework 'Microsoft.NETCore.App', version '2.2.0' was not found.

C:\testapps\twooneapp>dotnet --fx-version 2.2.0 --roll-forward Patch bin\Debug\netcoreapp2.1\twooneapp.dll
Hello World!
C:\Program Files\dotnet\shared\Microsoft.NETCore.App\2.2.3\System.Private.CoreLib.dll
Copy link
Member

Choose a reason for hiding this comment

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

I like the overall outcome of this. The only confusion might arise when all the different ways to specify these settings is combined. If specified on command line together like this, it makes perfect sense. If the RollFoward is specified through an environment variable which is effectively invisible, it gets a bit more complicated (as compared to existing behavior of --fx-version).
Basically if I specify --fx-version today I get some relatively predictable behavior. After this change that behavior becomes less predictable since the ambient env. variable may modify it. Now I have to know about the env. variable (and the runtimeconfig setting to be able to decide the exact behavior.

I'm not sure if it's OK to leave it like that, or if there's something we should do... and I don't know what... just raising a concern.

Copy link
Member Author

Choose a reason for hiding this comment

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

It is a concern. The order of precedence for command line and ENV is really tough.

Here are two scenarios to consider (where both CLI and ENV config is used):

  • --fx-version 3.0.0 & ENV DOTNET_ROLL_FORWARD=Major
  • --fx-version 3.0.0 --roll-forward=Patch & ENV DOTNET_ROLL_FORWARD=Major

I now think I have it wrong. I think most people expect that the CLI would beat config in the second case. That said, I do think that the ENV needs to be honored in the first example to enable hosters to have control over a large number of apps.

```

### Configuration Precedence

The host will consult the various ways of setting `RollForward`, in order (later scopes take precendence over earlier ones):

1. `.runtimeconfig.json` properties (AKA "json")
2. CLI arguments
3. Environment variables (AKA "ENV")
C:\testapps\twooneapp>SET DOTNET_ROLL_FORWARD=LatestMajor

Note: The project file property is not listed because the project file is a build-time not runtime artifact. Runtime-oriented values in project files need to be written to `.runtimeconfig.json` as part of the build in order to influence runtime behavior.

The `version` and `roll-forward` settings compose in the following way:

* A `version` setting at higher precedent scopes overwrites both the `version` and `roll-forward` values at lower scopes. For example, `version` specified at the CLI scope (with `--fx-version`) overwrites both a `version` and `roll-forward` setting that might exist at the json scope.
* A `version` setting can flow to higher scopes if it is not replaced by another `version` value. This enables a `roll-forward` setting at a higher scope to compose with a `version` setting at a lower scope.
* The absense of a `version` value is an error when the `roll-forward` value of `Patch`, `Minor`, `Major` or `AlwaysLatestMinor` is set since the initial version state is not available. It is not an error when the `roll-forward` value of `AlwaysLatestMajor` is set since an initial version state is not meaningful.
C:\testapps\twooneapp>dotnet bin\Debug\netcoreapp2.1\twooneapp.dll
Hello World!
C:\Program Files\dotnet\shared\Microsoft.NETCore.App\4.2.1\System.Private.CoreLib.dll

More generally:
C:\testapps\twooneapp>dotnet --fx-version 2.2.0 bin\Debug\netcoreapp2.1\twooneapp.dll
It was not possible to find any compatible framework version
The specified framework 'Microsoft.NETCore.App', version '2.2.0' was not found.

* The `version` value establishes a floor for roll-forward behavior, except `AlwaysLatestMajor`.
* The default `roll-forward` setting is `Minor` except when `--fx-version` is specified when it is `None`.
C:\testapps\twooneapp>dotnet --fx-version 2.2.0 --roll-forward Patch bin\Debug\netcoreapp2.1\twooneapp.dll
Hello World!
C:\Program Files\dotnet\shared\Microsoft.NETCore.App\2.2.3\System.Private.CoreLib.dll
```