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 design for simplifying workload versioning #294

Open
wants to merge 13 commits into
base: main
Choose a base branch
from
77 changes: 77 additions & 0 deletions accepted/2023/simplify-workload-versioning-implementation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
## Implementation

Creating a workload set version should be a lightweight process, which involves creating a NuGet packages and corresponding installers to deliver the mapping from the workload set version to the workload manifest versions. The .NET SDK assets and installers, workload manifests, and workload packs should already be built and should not need to be created when building a workload set.
MiYanni marked this conversation as resolved.
Show resolved Hide resolved

### Current workload release schedule

Releases of .NET (both the SDK and runtime) typically align with Visual Studio releases and/or patch Tuesday. A .NET release needs to be built and validated ahead of the deadline to insert into Visual Studio or to be submitted to Microsoft Update. It takes time to build and validate a release of .NET, so we allow for roughly 2 weeks in the schedule for a release to build, validate, and if necessary fix any critical issues and re-build. This means that components that ship in the .NET SDK need to be inserted into it roughly 2 weeks before the Visual Studio or Microsoft Update deadline, which is itself some time before the actual release.

Workloads such as ios, android, and maui which are built outside of the .NET Runtime do not currently have to meet the .NET SDK insertion deadlines. This is because they are inserted into Visual Studio separately from the .NET SDK. So when Visual Studio is installed, it installs the .NET SDK as well as the workloads that were inserted into Visual Studio.

Non Visual Studio installs of the .NET SDK do not include workloads themselves in the .NET SDK package or installer. In this case the .NET SDK only hase workload manifests. Because the final versions of workloads are not inserted into the .NET SDK, these manifests (which we call "baseline manifests") are not the final versions and do not refer to the workloads that are actually released. When installing a workload, the default behavior of the .NET SDK is to first look for the latest workload manifests and install them before installing the workload. So as long as the workload packages are released publicly (on NuGet.org) on release day, installing a workload for a non-VS install of the .NET SDK will also install the right workload versions, even though the baseline manifest versions built into that SDK are not the final release versions.
MiYanni marked this conversation as resolved.
Show resolved Hide resolved

### Release schedule updates

Workload sets will be created in a separate repo (possibly dotnet/workloadversions). This repo will have the versions for all the workload manifests, either via Arcade dependency flow (for workloads that build as part of .NET) or via manual updates. The build will create a json file in the rollback file format from these versions, and create NuGet packages, MSIs, and (in the future) other installers to deliver this json file as a workload set.
MiYanni marked this conversation as resolved.
Show resolved Hide resolved
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
Workload sets will be created in a separate repo (possibly dotnet/workloadversions). This repo will have the versions for all the workload manifests, either via Arcade dependency flow (for workloads that build as part of .NET) or via manual updates. The build will create a json file in the rollback file format from these versions, and create NuGet packages, MSIs, and (in the future) other installers to deliver this json file as a workload set.
Workload sets will be created in a separate repo (possibly dotnet/workloadversions). This repo will have the versions for all the workload manifests, either via Arcade dependency flow (for workloads that build as part of .NET) or via manual updates. The build will create a json file in the rollback file format from these versions, and create NuGet packages, MSIs, and (in the future) other installers to deliver this json file as a workload set.


To insert a final workloads into Visual Studio, the versions in the workloadversions repo should be updated and a new build of that repo produced. Then the updated workload set artifacts should be inserted into Visual Studio together with the final workloads.

We intend that building the workloadversions repo should be quick (~1 hour) so that it should not have much schedule impact on when workloads can be inserted into Visual Studio.

### Workload set packages

The Workload set NuGet package for file-based installs will be named `Microsoft.NET.Workloads.<Feature band>`. Similar to the workload manifest NuGet packages the json file will be included in a `data` subfolder in the NuGet package. The version number of the package will be the version of the workload set.

### Workload set disk layout

Workload version information will be stored in `dotnet\sdk-manifests\<Feature Band>\workloadsets\<Workloads Version>`, for example `dotnet\sdk-manifests\8.0.200\workloadsets\8.0.201`. In this folder will be one or more `.json` files which will specify the versions of each manifest that should be used. These `.json` files will use the same format as rollback files currently use. For example, the following file would specify that the android manifest should be version 33.0.4 from the 8.0.200 feature band, while the mono toolchain manifest should be version 8.0.3 from the 8.0.100 band:

```json
{
"microsoft.net.sdk.android": "33.0.4/8.0.200",
"microsoft.net.workload.mono.toolchain": "8.0.3/8.0.100"
}
```

### Workload manifest layout

The layout of the workload manifests on disk will change. Currently, they go in a path which includes the feature band but not the version number of the manifest, such as `dotnet\sdk-manifests\8.0.200\microsoft.net.sdk.android`. This will change to also include the manifest version in the path, for example `dotnet\sdk-manifests\8.0.200\microsoft.net.sdk.android\33.0.4`. This will allow multiple workload sets to be installed side-by-side, and for a global.json file to select which one should be used.

Currently, the manifests for each feature band update in-place, and the corresponding MSIs are upgradeable. With the new design, each manifest version will not have an MSI upgrade relationship to other versions of that manifest. This should also help fix issues that occur when we try to roll back to manifests prior to those installed by Visual Studio. Now, the prior versions of the manifest can be installed side-by-side with the versions installed by Visual Studio, and the `workloadsets` files (possibly together with glebal.json) will determine which manifests are used.
MiYanni marked this conversation as resolved.
Show resolved Hide resolved

### Baseline workload sets

The final workload set for a release will be produced in the workloadversions repo. However, the installer repo will also produce a workload set for the baseline workload manifests that are included in the standalone SDK. The version assigned to this workload set will depend on whether the SDK being built is using a "stabilized" version number. If so, then the baseline workload set version will be `<SDK Version>-baseline.<Build Number>`, for example `8.0.201-baseline.23919.2`. Otherwise, the workload set version will use the same version number as the .NET SDK, for example `8.0.100-preview.4.23221.6`. The baseline workload set will be included in the layout for file-based builds of the .NET SDK, and packaged up as an MSI which is bundled in the standalone installer.

### Workload behavior when no workload patch information is installed

In some cases, internal builds of Visual Studio may not have workload set information. In this case, when the SDK needs to display a workload set version, one will be generated using the .NET SDK feature band, the pre-release specifier `manifests`, and a hash of the manifest IDs and versions. For example, `8.0.200-manifests.9e7f4b93`.

### Workload resolver updates

Because the `workloadsets` folder will be next to the other workload manifest folders, the resolver will be updated to ignore this folder when looking for workload manifests. Older versions of the resolver should not be impacted, because they will not be looking in the newer version band folder. This does mean that the resolver changes should go into Visual Studio as soon as possible so that full Framework MSBuild can work with the new manifests.

### Workload manifest garbage collection


### Rolling back and saving workload versions


### Managing workload set GC roots


### Workload updates

Currently, the .NET SDK tries to keep workloads updated. By default, it will update to newer workload manifests whenever you install a workload. It will also check once a day to see if updated workloads are available, and if they are it will print out a message suggesting you update workloads when you run basic commands such as `dotnet build`.

With the new system, instead of checking for updates to each manifest separately, the SDK will check for a new workload patch version. When updating to that workload patch, the patch will be downloaded and installed, and then the manifest versions from the patch will be downloaded and installed.

### Visual Studio installed SDKs

When the .NET SDK is installed by Visual Studio, it is usually best if the workloads are also installed via Visual Studio. That way they will get updated together with updates to Visual Studio and the .NET SDK. If the .NET SDK is installed by Visual Studio, and someone installs workloads from the command line (for example via `dotnet workload install`), then the workloads will stop working when Visual Studio updates the .NET SDK.

Because of this, we will change CLI workload commands such as `dotnet workload install`, `dotnet workload restore`, and `dotnet workload update` to check if the current .NET SDK has been installed (only) by Visual Studio. If so, those commands will generate an error recommending that the workloads be installed using the Visual Studio installer. The commands will also provide an option to override the error and allow installing workloads from the command line if needed.

### Telemetry

We would like to better understand whether people are using SDKs installed by Visual Studio, standalone installs, or both. We will add this information to the telemetry that we send. To support this we will update the .NET SDK MSI to conditionally include a `.vs` or `.standalone` file in the SDK folder to identify how it was installed.
164 changes: 164 additions & 0 deletions accepted/2023/simplify-workload-versioning.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
# Simplify Workload Versioning

.NET SDK Workloads are optional components of the .NET SDK. When designing workloads, one of the goals was to be able to update workloads separately from the .NET SDK. As a result, workloads version and update separately from the .NET SDK. This has made it hard to understand what versions of workloads are installed or available. Complicating this is the fact that workloads themselves don't directly have a version. Workload manifests, which can have a many-to-many relationship with workloads, have versions. These versions can be displayed with the `dotnet workload update --print-rollback` command, which displays output in the following format:

```
==workloadRollbackDefinitionJsonOutputStart==
{
"microsoft.net.sdk.android": "33.0.26/7.0.100",
"microsoft.net.sdk.ios": "16.2.1030/7.0.100",
"microsoft.net.sdk.maccatalyst": "16.2.1030/7.0.100",
"microsoft.net.sdk.macos": "13.1.1030/7.0.100",
"microsoft.net.sdk.maui": "7.0.59/7.0.100",
"microsoft.net.sdk.tvos": "16.1.1527/7.0.100",
"microsoft.net.workload.mono.toolchain.net6": "7.0.3/7.0.100",
"microsoft.net.workload.mono.toolchain.net7": "7.0.3/7.0.100",
"microsoft.net.workload.emscripten.net6": "7.0.3/7.0.100",
"microsoft.net.workload.emscripten.net7": "7.0.3/7.0.100"
}
==workloadRollbackDefinitionJsonOutputEnd==
```

This is very complicated and doesn't make it easy to understand workload versions.

## Goals

- Introduce a single version number that represents all workload versions for the the .NET SDK
- Workloads can continue to release outside the normal .NET SDK release schedule
- Deadlines for workload completion and insertion do not change from current processes
- Workload updates should require minimal (or no) additional validation of non-workload scenarios

## Workload sets

Currently, we use a 3 part version number for the .NET SDK, for example 8.0.100. Typically we release a patch each month, for example 8.0.101, 8.0.102, etc.

We will create the concept of a "workload set" which has a version number that encapsulates all the workload manifest versions that are released together. A workload set is essentially a mapping from the workload set version to the different versions for each workload manifest. For public releases, the workload set version will use the same version number as the .NET SDK. For example, when .NET SDK 8.0.101 releases, there will be a corresponding workload set version 8.0.101 that corresponds to the versions of the workloads that were released together with that .NET SDK.

## Baseline workload versions

.NET SDK workloads have similar shipping deadlines as the .NET SDK. That means that the .NET SDK itself can't include final versions of workloads which are built outside of the .NET build, as building and signing off on those workloads happens in parallel with .NET build and signoff. So the .NET SDK ships with *baseline* workload manifests, which are mainly used to enable the .NET SDK to list which workloads are available, and to know if a project requires a workload which isn't installed. By default, when a workload is installed via the .NET CLI, the .NET SDK will first look for and update to the latest workload manifests for the current feature band before installing workloads.

When we build the .NET SDK, we will assign a workload set version to the baseline workload manifest versions that are included in that SDK. For releases with stabilized version numbers, we need a different workload set version number for the baseline workload manifest versions than for the stabilized version number for the .NET SDK. In that case, we will use `baseline` as a the first semantic version pre-release identifier, followed by build number information. For example, the baseline workload set version for 8.0.201 could be `8.0.201-baseline.23919.2`.

There are various ways we could display this baseline workload set version number:

- `8.0.201-baseline.23919.2`
- `8.0.201-baseline`
- `8.0.201 (Baseline)`

For non-stabilized builds of the .NET SDK, we will use the same (prerelease) version number for the baseline workload set version as we do for the .NET SDK version.

QUESTION: Will it be OK for the `baseline` versions to be semantically less than `preview` and `rc` versions?

## Workload versions in Visual Studio

Visual Studio includes the .NET SDK, but rather than including baseline manifest versions for the .NET SDK workloads, it includes the final workload manifests for a given release. These manifests are inserted into Visual Studio together with the workloads.

For public releases of Visual Studio, the workload set version information should be created and inserted into Visual Studio together with the workloads. However, internal builds of Visual Studio may not have an assigned workload set version. In that case, the .NET SDK will use the same basic logic as it currently does to select manifests. This involves finding the latest installed manifest for the current feature band, and if an expected manifest isn't found for the current feature band, then falling back to previous feature bands. In this case, when a workload set version is needed, the .NET SDK will create a version using the .NET SDK feature band, the pre-release specifier `manifests`, and a hash of the manifest IDs and versions. For example, `8.0.200-manifests.9e7f4b93`.

## Experience

`dotnet --version` would continue to print the SDK version, as it does today:

```
> dotnet --version
8.0.201
```

We will add a new `dotnet workload --version` command to print the workload set version:

```
> dotnet workload --version
8.0.201
```

We will also update `dotnet --info` and `dotnet workload --info` to display the workload set version. For example:

```
> dotnet --info
.NET SDK:
Version: 8.0.201
Commit: <commit>
Workload set version: 8.0.201

<further information>

> dotnet workload --info
Workload set version: 8.0.201
[wasm-tools]
Installation Source: SDK 8.0.100-preview.4
Manifest Version: 8.0.0-preview.4.23181.9/8.0.100-preview.4
Manifest Path: C:\git\dotnet-sdk-main\.dotnet\sdk-manifests\8.0.100-preview.4\microsoft.net.workload.mono.toolchain.current\WorkloadManifest.json
Install Type: FileBased
```

When updating workloads, console output will include the old and new workload set versions:

```
> dotnet workload install wasm-tools
Checking for updated workload set...
Updating workload set version from 8.0.201-baseline.23919.2 to 8.0.201...
Installing workload manifest microsoft.net.sdk.android version 34.0.0-preview.4.230...
<Further workload manifest update messages>
Installing pack Microsoft.NET.Runtime.WebAssembly.Sdk version 8.0.0-preview.4.23181.9...
<Further workload pack installation manifests>
Garbage collecting for SDK feature band(s) 8.0.200...

Successfully updated workload set version from 8.0.201-baseline.23919.2 to 8.0.201.
Successfully installed workload(s) wasm-tools.
```

The proposed [workload history](https://github.com/dotnet/sdk/pull/30486) command will also display the workload set versions.

The .NET SDK will periodically (once a day, by default) check for updated workload versions in order to notify users if there is an update available. Currently, commands such as `dotnet build` print out the following message:

```
Workload updates are available. Run `dotnet workload list` for more information.
```

The `dotnet workload list` command, in turn, prints out the following message:

```
Updates are available for the following workload(s): wasm-tools. Run `dotnet workload update` to get the latest.
```

We will modify both commands to print the same message:

```
A workload update (version 8.0.202) is available. Run `dotnet workload update` to install the latest workloads.
```

We will add a new `--version` option to `dotnet workload update` to allow installing (or downgrading to) a specific workload set version:

```
> dotnet workload update --version 8.0.202
Updating workload set version from 8.0.201 to 8.0.202...
<Manifest and pack installation / garbage collection messages>
Successfully updated workload set version from 8.0.201 to 8.0.202.
```

## Specifying workload versions with global.json

We will add support for specifying the workload set version in global.json. For example:

```json
{
"sdk": {
"workloadsVersion": "8.0.201"
}
Copy link
Member

Choose a reason for hiding this comment

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

Right now, AutoImport.props is imported into every project for .NET 6, 7, 8, etc. If you have 8.0.201 and 8.0.202 workloads installed, is AutoImport.props only imported for the pinned version?

If so, we might want to add some more robust logging to understand why the workload resolver imported one file over another.

}
```

This would force the SDK to use workload set version `8.0.201`, and would error if that version was not installed.

We will support side-by-side workload version installations. If 8.0.202 is installed, we would support running `dotnet workload install --version 8.0.201` to install that version of the workloads. After installing the earlier version, the .NET SDK would still by default use the latest installed workload set version. An earlier workload set version would only be used if it was specified in a global.json file.

NOTE: We may not implement the global.json workload set version support in the same release as the rest of the changes described in this design.

## Development build behavior

Currently, when the .NET SDK looks for workload updates, it looks for the latest available version of each individual manifest. We will change this so that it will look for the latest "workload set version", which is a package that we create and publish.

However, for development builds of the .NET SDK, we may want to retain the old behavior of updating each manifest individually. This would allow the latest workloads to be used with a development build even if they hadn't flowed into the installer build and we hadn't created a workload set version package.

We will add a `--update-manifests-separately` parameter to the `dotnet workload update` and `install` commands, which will opt in to the old update behavior. We may also make this the default behavior for development builds, though there may not be a good way to tell whether a given build is a "development" build or a build that will be released as an official public preview. If development builds aren't signed the same way as full preview builds are, we may be able to key off of that.