Exploring the new rollForward and allowPrerelease settings in global.json
Tags: netcore, netcore30, devops
Published: 2020-02-23 19:45:13

---
title: Exploring the new rollForward and allowPrerelease settings in global.json
published: true
date: 2020-02-18 10:00:00 UTC
tags: .NET Core,.NET Core 3.0,DevOps
canonical_url: https://andrewlock.net/exploring-the-new-rollforward-and-allowprerelease-settings-in-global-json/
---

 ![Exploring the new rollForward and allowPrerelease settings in global.json](https://andrewlock.net/content/images/2020/sdk-selection-banner.png)

I was listening to the [Azure DevOps Podcast with Jeffrey Palermero recently](http://azuredevopspodcast.clear-measure.com/setting-up-your-machine-for-net-core-with-kathleen-dollard-episode-69) and heard [Kathleen Dollard](https://twitter.com/KathleenDollard) mention that there were some updates to the _global.json_ file and .NET Core SDK in 3.0. This post explores those additions and the effects they have on SDK selection for a machine with multiple SDKs installed.

The [official documentation](https://docs.microsoft.com/en-us/dotnet/core/tools/global-json?tabs=netcore3x) for this behaviour covers everything in this post, but I found it pretty hard to internalise the various rules it describes. This post primarily adds some extra background, a couple of pictures, and explores the rules using examples to make it easier to grok!

## The .NET Core Runtime vs the .NET Core SDK.

Before we start, it's important to understand the difference between the .NET Code runtime and the .NET Core SDK:

- The **.NET Core runtime** is what _runs_ a .NET application. It has very limited functionality - it literally just runs a compiled application. It's the important piece when you're _running your application in production_.
- The **.NET Core SDK** does everything else: it compiles your application, tests it, downloads NuGet packages, and a whole lot more. This is the important piece when you're _developing your application_.

Generally speaking, you need to choose the version of the .NET Core _runtime_ you use carefully. Different versions have different support windows ([depending if they're LTS or current](https://dotnet.microsoft.com/platform/support/policy/dotnet-core)) and have different features. It's the _runtime_ version that you specify in the `<TargetFramework>` element of your project file. For example, the project file below specifies that the .NET Core `3.1` runtime should be used:

```xml
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFramework>netcoreapp3.1</TargetFramework>
  </PropertyGroup>

</Project>
```

In contrast, you generally _don't_ specify a version of the .NET Core _SDK_ that's needed to build the application. Normally all that matters is that you have a version of the SDK that supports the given runtime version. So to target the `3.1` runtime, you'll need an SDK version that supports building for it, e.g. version SDK `3.1.101` of the SDK.

An important thing to note, is that the .NET Core SDK is supposed to be backwards compatible. So the `3.1.101` SDK can build .NET Core `2.1` applications and `2.2` applications etc as well. In other words, **you generally don't need to use a specific version of the .NET Core SDK**. Any SDK that is high enough will do.

## Specifying a specific SDK version with a _global.json_

Typically then you shouldn't have to worry about which versions of the SDK you have installed. Backwards compatibility means that the most recent SDK should be able to build everything.

Unfortunately that's not _always_ the case: bugs creep in, features change, and sometimes you need (or want) a specific version of the SDK installed. Some peripheral things change from version-t0-version too, such as the project templates that you use with `dotnet new`. The templates that come with the .NET Core `1.0` SDK are very different to those that come with the .NET Core `3.1` SDK for example.

For that reason, sometimes you might want to "pin" the version of the .NET Core SDK to a _specific_ version for a particular project or for a particular folder. For example you might want _one_ specific project to use the .NET Core `1.0` SDK, while letting all the other projects on your machine use the latest `3.1` version. To do that, you can use [a _global.json_ file](https://docs.microsoft.com/en-us/dotnet/core/tools/global-json).

Whenever you run a `dotnet` SDK command like `dotnet build`, `dotnet publish`, or `dotnet new`, the _dotnet.exe_ entrypoint looks for a _global.json_ file in the same directory as the command being run. If it doesn't find one, it looks in the parent directory instead. If it still doesn't find one it keeps working up through the parent directories until it finds a _global.json_ file, or until it reaches the root directory.

At this point _dotnet.exe_ will either have the "nearest" _global.json_ file, or no file at all. It then uses the values in the _global.json_ file (or the absence of the file), to decide which SDK version to use to handle the command (`dotnet build` etc).

The _rules_ governing which version to use depends on four things:

- Which versions of the .NET Core SDK do you have installed?
- Which version does the _global.json_ request (if any)
- What is the current "roll-forward" policy for SDK versions
- Are pre-release versions allowed to be used?

We'll look at how each of those affect the final version of the SDK selected in the remainder of the post.

## How to see which versions of the .NET Core SDK you have installed

The first variable for determining which version of the .NET Core SDK will be used to run a command, is which SDKs are available. Thankfully that's easy to check in .NET Core 3+, as you can use `dotnet --list-sdks`.

Running the command on my machine, I get:

```
> dotnet --list-sdks
1.1.14 [C:\Program Files\dotnet\sdk]
2.1.600 [C:\Program Files\dotnet\sdk]
2.1.602 [C:\Program Files\dotnet\sdk]
2.1.604 [C:\Program Files\dotnet\sdk]
2.1.700 [C:\Program Files\dotnet\sdk]
2.1.801 [C:\Program Files\dotnet\sdk]
2.2.203 [C:\Program Files\dotnet\sdk]
3.0.100 [C:\Program Files\dotnet\sdk]
3.1.101 [C:\Program Files\dotnet\sdk]
```

This shows that I have 9 SDKs currently installed (all in _C:\Program Files\dotnet\sdk_).

### Understanding the .NET Core SDK version numbers

A slightly tricky aspect of the .NET Core SDK, is that it doesn't really use the [semantic versioning](https://semver.org/) that you may be familiar with, [and which the runtime uses](https://docs.microsoft.com/en-us/dotnet/core/versions/). It _kind_ of does, but it's a bit more complicated than that, (plus it's changed throughout the last few versions of .NET Core).

Currently, the .NET Core SDK version number is broken down into 4 sections. For example, for the `2.1.801` SDK version:

<picture>
    <source srcset="/content/images/2020/version_numbers.svg" type="image/svg+xml">
    <img src="https://andrewlock.net/content/images/2020/version_numbers.png" alt="Breakdown of .NET Core SDK version number">
</source></picture>

Those different sections will become important in the next section, when we look at "roll-forward" policies. Broadly speaking, the `major` and `minor` version numbers align with the `major` and `minor` version of .NET Core, e.g. `2.2` or `3.1`. The `feature` version is the complicated one, where it gets incremented when new features are added to the _SDK_ (potentially without any changes in the _runtime_). The `patch` version is for patches to a given `feature` version.

## The _global.json_ schema in .NET Core 1/2 is very limited

The _global.json_ file has been available since .NET Core 1.0, and up until the recent changes in .NET Core 3.0, it had a very simple structure:

```json
{
  "sdk": {
    "version": "2.1.600"
  }
}

```

The version in the _global.json_ would define which version of the SDK you needed. If it was installed, that version of the SDK would be used, otherwise you would get an error message similar to the following:

```
A compatible installed .NET Core SDK for global.json version [2.1.600] from [C:\repos\globaljsontest\global.json] was not found
Install the [2.1.600] .NET Core SDK or update [C:\repos\globaljsontest\global.json] with an installed .NET Core SDK

```

> To be precise, the legacy behaviour was to use the `patch` roll-forward policy which [we'll discuss shortly](https://andrewlock.net/exploring-the-new-rollforward-and-allowprerelease-settings-in-global-json/#understanding-the-various-rollforward-policies).

Specifying a single version was often rather limiting. In many cases the intention of the version number was used to indicate either a _minimum_ SDK that was needed, or alternatively a _maximum major version_. Unfortunately the `version` field does not support wildcards, so that wasn't possible, and proved a poor substitute.

For example, if you still had projects stuck on .NET Core 1.0, you might add a _global.json_ to require a `1.x` SDK, like `1.1.14`. In reality, you likely wouldn't need a _specific_ version of the `1.x` SDK (`1.0.1` or `1.1.13` would work just fine), but the _global.json_ forces you to specify a single version.

That's a pain, as it forces anyone using your project to install a new SDK version, when they may well have one that works just fine already.

## Additions to _global.json_ in .NET Core 3.0

In NET Core 3.0, the _global.json_ file got a couple of important updates, the `rollForward` and `allowPrerelease` fields:

```json
{
  "sdk": {
    "version": "2.1.600",
    "allowPrerelease": true,
    "rollForward": "patch"
  }
}

```

The algorithm for determining which version of the SDK to use was relatively simple in .NET Core `1.x`/`2.x` - if a _global.json_ was found and the requested SDK version was installed, that version (or a patched version) was used.

In .NET Core 3.0 the algorithm gets rather more complex, giving you extra controls. The flow chart below shows how values for the `version`, `allowPrerelease`, and `rollForward` values are determined based on the presence of the _global.json_, whether each field is present in the _global.json_, and whether or not the command is being run explicitly from the command line, or it's being run _implicitly_ by Visual Studio (VS) (for example when Visual Studio runs a build):

<picture>
    <source srcset="/content/images/2020/sdk-selection.svg" type="image/svg+xml">
    <img src="https://andrewlock.net/content/images/2020/sdk-selection.png" alt="Flowchart for SDK version number selection">
</source></picture>

At the end of this flow chart, we have values for the following:

- `version`: Either a specific version requested by a _global.json_, or if none was set, the highest installed version.
- `allowPrerelease`: Determines whether prerelease/preview SDKs that are installed should be considered when calculating which SDK version to use (e.g. `3.1.100-preview1`)
- `rollForward`: The _roll-forward_ policy to apply.

This brings us to the most complex section, understanding the _roll-forward_ policy, and how each option controls which version of the SDK is selected.

## Understanding the various `rollForward` policies

The roll-forward policy is used to determine which of the various installed SDKs should be selected when a given `version` is requested. By changing the roll-forward policy, you can relax or tighten the selection criteria. That's a bit vague, but we'll looks at some examples soon.

In .NET Core 3.x, there are now _nine_ different values for the `rollForward` policy, which can broadly be separated into three different categories:

First we have the disable policy:

- `disable`: If the requested version doesn't exist, then fail outright. Don't ever use an SDK version other than the _specific_ version requested.

Next we have the conservative roll-forward policies, which get progressively more lenient in looking for suitable SDK versions:

- `patch`: _If the requested version doesn't exist_, use the highest installed SDK version with the same **major** , **minor** , and **feature** value e.g. `2.1.6xx`
- `feature`: Use the highest installed SDK version with the same **major** , **minor** , and **feature** value e.g. `2.1.6xx`. If no such version exists, uses the next installed SDK version with the same **major** and **minor** value e.g. `2.1.7xx`, otherwise fails.
- `minor`: Apply the `feature` policy. If no suitable SDK version is found, use the highest installed SDK version with the same **major** value e.g. `2.x.xxx`
- `major`: Apply the `minor` policy. If no suitable SDK version is found, use the highest installed SDK version, e.g. `x.x.xxx`

Finally we have the "latest" roll-forward policies, which always try and use the latest versions of suitable SDKs:

- `latestPatch` : Always use the highest installed SDK version with the same **major** , **minor** , and **feature** value e.g. `2.1.6xx`
- `latestFeature` : Uses highest installed SDK version with the same **major** and **minor** value e.g. `2.1.xxx`
- `latestMinor` : Uses highest installed SDK version with the same **major** value e.g. `2.x.xxx` 
- `latestMajor` : Uses highest installed SDK version

I'm aware that's a lot of information to digest! The conservative policies in particular are quite confusing, as the `patch` policy works subtly differently to the others. I think it's easiest to understand the differences by looking at examples, so the next few sections run through various scenarios, and describe the results in each case.

In each of these scenarios, I'm building a project on a system with the following SDKs installed (listed using `dotnet --list-sdks`):

```
1.1.14
2.1.600
2.1.602
2.1.604
2.1.700
2.1.801
2.2.203
3.0.100
3.1.101
```

You can view the SDK version that was selected based on a given _global.json_ by running `dotnet --version` in the same folder. When there is no _global.json_ in the folder (or in any parent folders) you should see the _latest_ SDK installed on your machine:

```
> dotnet --version
3.1.101

```

> Note that I'm ignoring the `allowPrerelease` flag in these tests. It doesn't have any impact if you don't have preview SDK versions installed. If you do have preview SDKs installed, the results will follow the same patterns shown below.

### When the requested SDK version is available

Lets start first by selecting an SDK version that _does_ exist on the system, `2.1.600`, and see which SDK version is selected for all the different `rollForward` values. I create a _global.json_ (by running `dotnet new global.json`) and change the `rollForward` property to test each policy:

```json
{
  "sdk": {
    "version": "2.1.600",
    "rollForward": "xxx"
  }
}

```

Running `dotnet --version` after applying each of the roll-forward policies in turn gives the following results:

| `rollForward` policy | Selected SDK Version | Notes |
| --- | --- | --- |
| `disable` | `2.1.600` | Uses requested SDK |
| `patch` | `2.1.600` | Uses requested SDK |
| `feature` | `2.1.604` | Rolls forward `patch` |
| `minor` | `2.1.604` | Rolls forward `patch` |
| `major` | `2.1.604` | Rolls forward `patch` |
| `latestPatch` | `2.1.604` | Rolls forward `patch` |
| `latestFeature` | `2.1.801` | Rolls forward `feature` |
| `latestMinor` | `2.2.203` | Rolls forward `minor` |
| `latestMajor` | `3.1.101` | Rolls forward to latest `major` |

Note that even though we have the exact requested version available, `2.1.600`, only the `disable` and `patch` policies use the actual SDK. Everything else uses _at least_ a patched version of the SDK. Also note that the "conservative" policies, _only_ use the patched version, even though we have additional minor and major versions available.

### When the requested SDK version is not available

Now lets try requesting an SDK version that _doesn't_ exist on our machine, `2.1.601`. Other than that, we have the same _global.json_ and the same SDKs installed.

```json
{
  "sdk": {
    "version": "2.1.601",
    "rollForward": "xxx"
  }
}

```

Running `dotnet --version` after applying each of the roll-forward policies in turn gives the following results:

| `rollForward` policy | Selected SDK Version | Notes |
| --- | --- | --- |
| `disable` | FAIL | You'll get an error when trying to run SDK commands |
| `patch` | `2.1.604` | Rolls forward to latest patch |
| `feature` | `2.1.604` | |
| `minor` | `2.1.604` | |
| `major` | `2.1.604` | |
| `latestPatch` | `2.1.604` | |
| `latestFeature` | `2.1.801` | |
| `latestMinor` | `2.2.203` | |
| `latestMajor` | `3.1.101` |

The results are _almost_ identical to the previous case, with the following exceptions:

- The `disable` policy causes all SDK commands to fail.
- The `patch` policy skips the next-highest patch version, `2.1.602`, and uses the latest patch `2.1.604` instead.

### When no higher patch version exists

Finally, let's imagine that we've requested the `2.1.605` SDK, which has a higher _patch_ version than any of the `2.1.6xx` SDKs installed on the machine. Let's see what happens in this case:

| `rollForward` policy | Selected SDK Version | Notes |
| --- | --- | --- |
| `disable` | FAIL | |
| `patch` | FAIL | No `2.1.6xx` SDKs equal or higher than `2.1.605` |
| `feature` | `2.1.700` | Only rolls forward to `2.1.700`, not `2.1.801` |
| `minor` | `2.1.700` | Same as `feature` |
| `major` | `2.1.700` | Same as `minor` |
| `latestPatch` | FAIL | No `2.1.6xx` SDKs equal or higher than `2.1.605` |
| `latestFeature` | `2.1.801` | |
| `latestMinor` | `2.2.203` | |
| `latestMajor` | `3.1.101` |

Now we have some interesting results:

- With no high enough SDK versions matching the `2.1.6xx` pattern the `disable`, `patch`, and `latestPatch` policies all fail. 
- The other `latest*` policies use the same versions they have in all other experiments.
- The conservative policies (`feature`, `minor`, and `major`) all roll-forward to the next available SDK, `2.1.700`, which is different to the previous experiments. Note that they _don't_ use the highest feature version available, `2.1.801`, they only roll forward to the _next_ feature version, `2.1.700`.

You could take these experiments further, but I think they demonstrate the patterns pretty well. That leaves just one final question…

## Which roll-forward policy should you use?

In general, I suggest you don't use a _global.json_ if you can help it. This effectively gives you the `latestMajor` policy by default, which uses the latest version of the .NET Core SDK, ensuring you get any associated bug fixes and performance improvements.

If you _have_ to use a _global.json_ and specify an SDK version for some reason, then I suggest you specify the _lowest_ SDK version that works, and apply the `latestMinor` or `latestFeature` policy as appropriate. That will ensure your project can be built by the widest number of people (while still allowing you to control the range of SDK versions that are compatible).

Note that the flow charts and matching rules I've described above are specific to the .NET Core 3.x SDK. However, [the matching rules for the highest SDK installed on your machine](https://docs.microsoft.com/en-us/dotnet/core/tools/global-json?tabs=netcore3x#matching-rules) are used, so as long as you have any .NET Core 3.x SDK installed, they will apply to you.

## Summary

In this post I looked in some depth at the new `allowPrerelease` and `rollForward` fields added to the _global.json_ file in .NET Core 3.0. I described the algorithm used to determine which `version`, `allowPrerelease`, and `rollForward` values would be used, based on the presence of a _global.json_ and whether or not you were running from Visual Studio. I then showed how each of the roll-forward policies affects the final selected SDK version.

Having the additional flexibility to define ranges of SDK versions is definitely useful, but should be used sparingly where possible. It can be easy to add accidental onerous requirements on people trying to build your project. Only add a _global.json_ where it is necessary, and try to use permissive roll-forward policies like `latestMajor` or `latestMinor` where possible.