Skip to content

Adding guidance on what is considered a breaking change in the .NET SDK #45288

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

Merged
merged 4 commits into from
Mar 20, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions documentation/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ Documents Index
- [Roadmap and OS support](https://github.com/dotnet/core/blob/main/roadmap.md)
- [Comprehensive CLI documentation](https://docs.microsoft.com/en-us/dotnet/articles/core/preview3/tools/)
- [ASP.NET Core Command Line Tools](general/aspnetcore-tools.md)
- [Guidelines for how to introduce a new diagnostic or breaking change](project-docs/breaking-change-guidelines.md)

## Working with the CLI repo

Expand Down
88 changes: 88 additions & 0 deletions documentation/project-docs/breaking-change-guidelines.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
# SDK Breaking Change and Diagnostic Guidelines

This document provides guidelines for introducing new diagnostics or breaking changes to the .NET SDK, which configuration knobs are available, what criteria should guide the decision around severity, timeline for introduction, and deprecation of diagnostics, and what steps are required by the .NET release process.

## General guidance

In general, we want to make updating the .NET SDK as smooth as possible for developers. This means:

* Introducing new changes in a staged/gradual way.
* Tying new analyzers/diagnostics to a mechanism that requires explicit opt-in.
* Providing a way to opt out of a change entirely.

## Kinds of .NET SDK breaking changes

There are many kinds of breaking changes that can ship in the .NET SDK, such as:

* New MSBuild warnings and errors (props/targets).
* New NuGet warnings and errors.
* For example, NuGet Audit.
* Roslyn Analyzers and CodeFixes.
* This includes trimming/ILLink analyzers and codefixes.
* Behavioral/implementation changes.
* MSBuild engine changes like MSBuild Server.
* Implementation changes for MSBuild Tasks.
* NuGet Restore algorithm enhancements.
* Changes to DotNet CLI behavior or output.
* Changes to DotNet CLI grammar.
* Changes to defaults in CLI flags that impact behavior.
* For example, `--configuration` flag defaulting to `Release` instead of `Debug`.

## Configuration Knobs

The following knobs are available to enable/disable these changes (some may not apply to all kinds of changes):
Copy link
Member

Choose a reason for hiding this comment

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

Change waves and LangVer also apply to the tooling ecosystem.

Copy link
Contributor

Choose a reason for hiding this comment

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

LangVer - I read https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/configure-language-version, and it looks like it's the same as TFM for .NET Core. So, I'm wondering in which cases langver would be used instead of TFM.

Change waves - I found a description here: https://learn.microsoft.com/en-us/visualstudio/msbuild/change-waves?view=vs-2022. Moving forward, do we recommend people using change waves or using the new SdkAnalysisLevel property?

@baronfel

Copy link
Member Author

Choose a reason for hiding this comment

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

LangVersion is often the same as the numeric part of TFM, but that's purely a convenience. Users often set it explicitly when

  • multitargeting and wanting to ensure a consistent level of analysis across TFMs
  • when they want to use preview language features

Copy link
Member Author

Choose a reason for hiding this comment

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

MSBuild Change Waves are necessary still for changes to MSBuild engine behavior, especially changes that occur before project evaluation takes place (e.g. globbing). MSBuild engine changes remain a place where we do not have great user-facing controls. MSBuild logic (props/targets files) are great for SdkAnalysisLevel, WarningLevel, etc knobs that we already have.

Copy link
Contributor

Choose a reason for hiding this comment

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

Done.


* [TargetFramework](https://learn.microsoft.com/en-us/dotnet/standard/frameworks)
* [SdkAnalysisLevel](https://learn.microsoft.com/en-us/dotnet/core/project-sdk/msbuild-props#sdkanalysislevel)
* EditorConfig (for Analyzers)
* AnalysisLevel (for Analyzers/CodeFixes)
* WarningLevel
* [Change waves](https://learn.microsoft.com/en-us/visualstudio/msbuild/change-waves) (for MSBuild engine behavior)
* [LangVer](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/configure-language-version)

## Deciding how to Surface Changes

### Support new and old commands in the first release

For changes in the CLI grammar, fully support both commands in one release, add a message to the old command in the subsequent release, and remove the old command in the release after that. This allows users to migrate to the new command at their own pace.

Specific example: When the `dotnet new --list` command was changed to `dotnet new list` to adhere to CLI design guidelines,
the old command was still the default supported and a warning was written to the console when the old form was used pointing users to the new form. This allowed users and scripts to continue using the old form and gradually migrate to the new.

### Implement changes in an informational/non-blocking way initially

What this means will vary change-to-change. For example, for a change expressed as an Analyzer or MSBuild diagnostic, consider
Informational level severities initially. For a behavioral change on a CLI, consider an informational message written to the
Copy link
Member

Choose a reason for hiding this comment

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

theoretically, this is also breaking but we have to draw a line somewhere and I think if some script is reading and depending on the stderr output, that seems like a reasonable line to draw.

Copy link
Contributor

Choose a reason for hiding this comment

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

@rainersigwald - Based on your experience, should changes int the output be considered breaking?

Copy link
Member

Choose a reason for hiding this comment

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

Changes in output can certainly be breaking. MSBuild has had to demote some high-importance info messages (e.g. dotnet/msbuild#9228) because including location and/or a code made them look "too much" like warnings for enough users.

Copy link
Contributor

Choose a reason for hiding this comment

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

Addressed.

stderr channel on the console instead of making the stdout output unparseable by tools.

### Gradually increase severity over each release

If a change in introduced in an informational/non-blocking way, determine the time frame where it is safe to increase the severity. For Analyzers, this may mean tying it to the next value of AnalysisLevel (which is downstream of TFM). For small MSBuild and NuGet diagnostics, this may mean tying it to the next Warning Level or SdkAnalysisLevel. For CLI changes, this may mean tying it to the next LTS major version of the SDK. Ideally the way you would structure this increase would be automated and documented so that users know what's coming down the pipe.

### Cut-over to new behavior after a long introduction period
Copy link
Member

Choose a reason for hiding this comment

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

FYI, for CLI grammar, our model has been one release with both the new and old, one release with a message when using the old, and then remove in the third release. I don't know if we've done a good job of always having a release where they are both available first and I'm also not always in a rush to remove things later. An example would be dotnet package add versus dotnet add package.

I don't know if you want to specifically call out this as gradually increase over major release fwiw.

Copy link
Contributor

Choose a reason for hiding this comment

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

I called this out separately now.


After the change has been introduced in a gradual way, cut over to the new behavior. This may mean removing the old behavior entirely, or it may mean making the new behavior the default and providing a way to opt out of it. It is important that you
provide enough time for users to adapt - for example the `dotnet new --list` example above took an entire major release to make the new forms the default.
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggest a newline for each start of sentence to make future updates cleaner.

Copy link
Contributor

Choose a reason for hiding this comment

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

I'm following the convention of other markdown in the repo.

Copy link
Contributor

Choose a reason for hiding this comment

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

Git diffs on markdown work better with my suggestion. Up to you whether that's important.


### Always provide a way to opt out of the change

Have some kind of knob that allows users to opt out of the change entirely. Preferably, this would be a project property, but, for depending on the change, other mechanisms would be more appropriate. For example, an environment variable for MsBuild engine changes or a CLI argument to opt out of a new output format. This allows users to continue using the old behavior if they need to in exceptional situations.

It is important to document the opt out mechanism in the SDK documentation, as well as document the timeline for when this opt out mechanism will be removed entirely, forcing users to adopt the new behavior. For systems like Analyzers, that time may be _never_, because the cost of detection is so low. This is a product-level decision that is hard to give universal guidance for.

### Hook into the unified SdkAnalysisLevel knob to allow users to easily opt out of all changes

This knob exists so that users can safely and consistently say "for whatever reason, I just need you to act like SDK version X". This is the one-stop shop - users no longer need to know about all the individual knobs that are available to them.

### Tie potentially impactful changes to the target TFM

Changes that are expected to cause significant disruption should only be introduced behind the Target Framework knob. This ensures business continuity and allows developers to address changes needed as part of scheduled work to migrate a codebase to a new TFM.

Specific example: NuGet warnings for vulnerable transitive dependencies were introduced in the .NET 10 SDK only for applications targeting .NET 10 and higher.

## Required process for all .NET SDK breaking changes

* Create an issue in the appropriate GitHub repository to track the change, if one does not already exist.
* Add the breaking-change label to the issue. This label should be available in all .NET repositories that ship as part of the .NET SDK. If the label is not available, please file an issue in [dotnet/sdk](https://github.com/dotnet/sdk).
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
* Add the breaking-change label to the issue. This label should be available in all .NET repositories that ship as part of the .NET SDK. If the label is not available, please file an issue in [dotnet/sdk](https://github.com/dotnet/sdk).
* Add the breaking-change label to the issue. This label should be available in all .NET repositories that ship as part of the .NET SDK. If the label is not available, please file an issue in [dotnet/sdk](https://github.com/dotnet/sdk/issues).

* The issue will trigger a message with instructions to add documentation. In addition, you are invited to work with the SDK team to publish a blog post for the change.
* Consider creating and pinning an issue in the appropriate GitHub repository where the community can provide feedback.