Modify roll forward design document - behavior around "latest" (dotnet/core-setup...
authorVitek Karas <vitek.karas@microsoft.com>
Thu, 30 May 2019 10:09:28 +0000 (03:09 -0700)
committerGitHub <noreply@github.com>
Thu, 30 May 2019 10:09:28 +0000 (03:09 -0700)
The "latest" bit in `LatestMinor` and `LatestMajor` is now considered separately from the version range. It propagates on its own and also propagates into frameworks.

Also modifies how "roll to latest patch" is applied to pre-release versions. If the selected version if pre-release there's no additional automatic roll forward. If the selected version is release, normal "roll to latest patch" is applied.

Some tweaks to algorithm description and some more samples.

Co-Authored-By: Elinor Fung <47805090+elinor-fung@users.noreply.github.com>
Commit migrated from https://github.com/dotnet/core-setup/commit/6479d554f0513d2975017757d99d224dc77b73cd

docs/installer/design-docs/framework-version-resolution.md

index 59a2b5c..0fd0f78 100644 (file)
@@ -152,6 +152,13 @@ One special case which would not work:
 Later on component B is loaded which asks for `3.1.0-preview LatestMajor` (for example the one in active development). This load will fail since `3.0.0` is not enough to run this component.  
 Loading the components in reverse order (B first and then A) will work since the `3.1.0-preview` runtime will be selected.*
 
+Modification to automatic roll forward to latest patch:  
+Existing behavior is to find a matching framework based on the above rules and then apply roll forward to latest patch (except if `Disable` is specified). The new behavior should be:
+* If the above rules find a matching pre-release version of a framework, then automatic roll forward to latest patch is not applied.
+* If the above rules find a matching release version of a framework, automatic roll forward to latest patch is applied.
+
+This is done to adapt to .NET Core's usage of pre-release versions. Per semantic versioning the pre-release part of the version is the least significant - less significant than patch version. Without this modification, automatic roll forward to latest patch would mean that the latest available preview would always be selected. .NET Core usage of previews is more akin to major version - each preview release (Preview 1 to Preview 2 for example) can contain changes which are breaking with respect to the previous preview. Automatic roll forward to latest pre-release would also make it hard to test two preview releases side by side on a single machine.
+
 ### Proposed new "pre-release" mode
 The above behavior makes sense for most users, but it makes it hard for us to test new versions of frameworks. Let's assume .NET Core 3.0 already shipped and there are apps which target `3.0.0 rollForward = Minor` (the default). The shipped framework is version `3.0.0`. Now the next patch release is being prepared and `3.0.1-preview` is produced. With the proposed (and current) behavior, there's no good way to make the apps use the new preview for testing purposes.
 
@@ -203,7 +210,7 @@ To reconcile the various scopes the host will apply the following precedence:
 1. `.runtimeconfig.json` global setting - `rollForward` and `rollForwardOnNoCandidateFx` (it's invalid to specify both)
 1. `.runtimeconfig.json` per-framework setting - `rollForward` and `rollForwardOnNoCandidateFx` (it's invalid to specify both)
 1. environment variable `DOTNET_ROLL_FORWARD`
-1. command line arguments - `rollFoward` and `rollForwardOnNoCandidateFx` (it's invalid to specify both)
+1. command line arguments - `rollForward` and `rollForwardOnNoCandidateFx` (it's invalid to specify both)
 
 Items lower in the list override those higher in the list. At each precedence scope the host will determine an effective value of `rollForward` by converting any potential `rollForwardOnNoCandidateFx` setting to `rollForward`. *Note that there are never collisions between `rollForward` and `rollForwardOnNoCandidateFx` since both can't appear at the same level.*
 
@@ -232,38 +239,54 @@ In addition to the above any framework reference with a pre-release version will
 *This is to maintain backward compatibility. In 2.\* pre-release never rolled forward to a different `major.minor.patch` and completely ignored `applyPatches`. Starting to honor `applyPatches` would introduce potentially breaking behavior in some corner cases where the resolution might fail when previously it didn't.*
 
 ## Framework resolution
-The above described format and handling of settings on framework references will in the end produce a graph where the application is the first node and each dependent framework is a node. Each edge in the graph is a framework reference which has two attributes: the `version` and the `rollForward` setting.
-The goal of the framework resolution algorithm is to resolve any potentially conflicting framework references and to find actual available framework on disk which would match the framework references.
+The above described format and handling of settings on framework references will in the end produce a graph where the application is a node and each dependent framework is also a node. Each edge in the graph is a framework reference which has these attributes:
+* `version` - the minimum version required for the framework
+* `version_compatibility_range` - specifies the compatibility range for the framework, this can have values
+  * `exact` - only exact match is allowed
+  * `patch` - any higher version with the same `major.minor` is allowed
+  * `minor` - any higher version with the same `major` is allowed
+  * `major` - any higher version is allowed
+* `roll_to_highest_version` - specifies how the exact version within the allowed `version_compatibility_range` is selected
+  * `false` - select the closest higher available version
+  * `true` - select the highest available version
+
+Note that roll forward on all `version_compatibility_range` values except the `exact` will always pick the latest available `patch` version. So `roll_to_highest_version` is ignored for `patch` versions (it's effectively implied to be `true` in that case). One caveat: to maintain backward compatibility with `rollForwardOnNoCandidateFx` and `applyPatches`, the `patch` version range will not roll forward to latest patch if `applyPatches=false`.
+
+The goal of the framework resolution algorithm is to resolve any potentially conflicting framework references and to find the available framework on disk which would satisfy the framework references.
+
+There's a direct mapping from the `rollForward` setting to the internal representation of the framework references:
+
+| `rollForward`         | `version_compatibility_range` | `roll_to_highest_version`                  |
+| --------------------- | ----------------------------- | ------------------------------------------ |
+| `Disable`             | `exact`                       | `false`                                    |
+| `LatestPatch`         | `patch`                       | `false` (always picks latest patch anyway) | 
+| `Minor`               | `minor`                       | `false`                                    |
+| `LatestMinor`         | `minor`                       | `true`                                     |
+| `Major`               | `major`                       | `false`                                    |
+| `LatestMajor`         | `major`                       | `true`                                     |
 
 ### Framework reference conflict resolution
 If there are two references to the same framework name, then the host needs to resolve the potential conflict. The rules are:
 * Take the higher `version`
 * Validate that the reference with the lower `version` allows roll-forward to the higher version. If not, fail.
-* Take the more restrictive `rollForward` setting
-
-The check whether the roll-forward is allowed follows the rules described above in the list of available settings for `rollForward`.
+* Take the more restrictive `version_compatibility_range` from the two
+* If `roll_to_highest_version` is true on one of the framework references, apply the `true` value to the merged framework reference as well.
 
-To select the more restrictive `rollForward` setting the values are ordered like this:
-1. `Disable`
-1. `LatestPatch`
-1. `Minor`
-1. `LatestMinor`
-1. `Major`
-1. `LatestMajor`
-
-Setting which is higher in this list (lower order number) is more restrictive than the one which is lower. `Disable` is the most restrictive (no roll-forward allowed). `LatestMajor` is the least restrictive (any other setting is compatible with it). `Minor` and `LatestMinor` are more restrictive then `Major` because they don't allow different major version.
+The check for whether the roll-forward is allowed follows the rules described above in the list of available settings for `rollForward`.
 
 For example:
 In this example the two framework references are for the same framework name.
 
-| First framework reference | Second framework reference | Resolved framework reference |
-| ------------------------- | -------------------------- | ---------------------------- |
-| 2.1.0 Minor               | 2.2.0 Major                | 2.2.0 Minor                  |
-| 2.1.0 Minor               | 3.0.0 Minor                | failure                      |
-| 2.1.0 LatestMajor         | 3.0.0 Minor                | 3.0.0 Minor                  |
-| 2.1.0 LatestMajor         | 3.1.2 Disable              | 3.1.2 Disable                |
+| First framework reference                      | Second framework reference | Resolved framework reference               |
+| ---------------------------------------------- | -------------------------- | ------------------------------------------ |
+| `2.1.0 minor`                                  | `2.2.0 major`              | `2.2.0 minor`                              |
+| `2.1.0 minor`                                  | `3.0.0 minor`              | failure                                    |
+| `2.1.0 major roll_to_highest_version=true`     | `3.0.0 minor`              | `3.0.0 minor roll_to_highest_version=true` |
+| `2.1.0 major roll_to_highest_version=true`     | `3.1.2 exact`              | `3.1.2 exact roll_to_highest_version=true` |
+
+To maintain backward compatibility, each framework reference also carries `applyPatches` setting. In case of two references the more restrictive setting value is used. So if one of the two framework references has `applyPatches=false` then the resolved framework reference also has `applyPatches=false`.
 
-To maintain backward compatibility, each framework reference will also have to carry `applyPatches` setting. Similar policy will be used to resolve conflicts. The more restrictive setting value will be used. So if one of the two framework references has `applyPatches=false` then the resolved framework reference will also have `applyPatches=false`.
+The `roll_to_highest_version` flag is propagated into the referenced frameworks. So if the app has a reference like `Microsoft.AspNet.App 3.0.0 minor highest` then all references from the `Microsoft.AspNet.App` framework will have the `highest` flag applied to them as well (regardless of the settings in the framework).
 
 ### Algorithm
 Terminology
@@ -277,6 +300,8 @@ Steps
    * Parse the application's `.runtimeconfig.json` `runtimeOptions.frameworks` section.
    * Insert each `framework reference` into the `config fx references`.
 2. For each `framework reference` in `config fx references`:
+   * Apply the recursively passed value of `roll_to_highest_version` to the `framework reference`.
+   * Then apply the below steps:
 3. --> If the framework `name` is not currently in the `effective fx references` list Then add it.
    * By doing this for all `framework references` here, before the next loop, we minimize the number of re-try attempts.
 4. For each `framework reference` in `config fx references`:
@@ -290,7 +315,7 @@ Steps
    *Sometimes this is referred to as "hard-roll-forward".*
      * This follows the roll-forward framework selection rules as describe above.
    * If success add it to `resolved frameworks`
-     * Parse the `.runtimeconfig.json` of the resolved framework and create a new `config fx references`. Make a recursive call back to Step 2 with these new `config fx references`.
+     * Parse the `.runtimeconfig.json` of the resolved framework and create a new `config fx references`. Make a recursive call back to Step 2 with these new `config fx references`. Pass in the value of the `roll_to_highest_version` from the `framework reference` used to resolve the framework.
      * Continue with the next `framework reference` (Step 4).
 6. --> Else perform reconcile the `framework reference` with the one from `effective fx references`.
    * We may fail here if not compatible.
@@ -306,14 +331,11 @@ Notes on this algorithm:
 
 ### Best practices for a `.runtimeconfig.json`
 
-#### No Restrictive Roll-Forward Overrides
-Do not specify `rollForward` (and `applyPatches` and `rollForwardOnNoCandidateFx`) in the `.runtimeconfig.json` unless absolutely necessary. These should be mostly used by the app's `.runtimeconfig.json` and pretty much never in any framework's config.
- * The one exception to this is to use a *less restrictive* setting by using either `LatestMinor`, `Major` or `LatestMajor`.
-
 #### No Redundant References
 When a given framework "F1" ships it should not create a case of having more than one reference to the another framework "F2". The reason is that base frameworks already specify "F2" so there is no reason to re-specify it. However, there are potential valid reasons to re-specify the framework:
        * To force a newer version of a given framework which is referenced by lower-level frameworks. However assuming first-party frameworks are coordinated, this reason should not exist for first-party `.runtimeconfig.json` files.
        * To be redundant if there are several "smaller" or "optional" frameworks being used and no guarantee that a base framework will always reference the smaller frameworks over time.
+For first-party frameworks, this means that the app should only specify the reference to the highest-level framework. For example, the app should reference `Microsoft.AspNet.App` but should not then also specify a reference to `Microsoft.NETCore.App` as that is already implied by the higher level framework.
 
 #### No Circular References
 There should not be any circular dependencies between frameworks.