diff --git a/.editorconfig b/.editorconfig index a9b39cb..229c475 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,4 +1,4 @@ -# Purview.BuildSdk 2.0.0 - managed file, do not edit +# Purview.BuildSdk 2.0.1 - managed file, do not edit # # This file is written by Purview.BuildSdk and is replaced whenever the SDK version changes. The version # on the first line is how the SDK recognises a file it owns: a .editorconfig without that marker is diff --git a/CHANGELOG.md b/CHANGELOG.md index 8a49fbf..96e5fd7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,18 @@ release summarise what the commits recorded rather than what was written at the ## [Unreleased] +## [2.0.1] - 2026-11-12 + +### Changed + +- **Runtime Async is off by default.** A `net11.0`+ target no longer compiles with + `runtime-async=on` unless asked: it changes how every `await` compiles, so it + is an opt-in rather than a baseline. Set `PurviewRuntimeAsync=true` — or `UseRuntimeAsync=true`, + the name the .NET team uses — to enable it; an explicit `false` under either name keeps it off. + This reverts the 2.0.0 default, so a repository that relied on it should opt in. + +## [2.0.0] - 2026-11-11 + ### Added - **`ServerShared` projects.** A `ServerShared*` project is referenced automatically by every diff --git a/README.md b/README.md index 3adc17d..2717e48 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ A reusable MSBuild SDK NuGet package that delivers standardised .NET project def | Feature | Detail | | -- | -- | | **Project type detection** | `IsCSharpProject`, `IsTestProject`, `IsSharedTestingProject`, `IsServerSharedProject`, `IsClientSideProject`, `IsContainerProject`, `IsWebSdkProject`, `IsAspireHostProject`, `IsBlazorWebAssemblyProject`, `IsBffProject`, … | -| **.NET 11 opt-ins** | A `net11.0` target compiles with Runtime Async (`runtime-async=on`) and the C# 15 updated memory-safety rules (`MemorySafetyRules=2`), while `AllowUnsafeBlocks` stays at its compiler default (`false`); builds use MSBuild's multithreaded mode through a bootstrapped `Directory.Build.rsp` (see [.NET 11 features](#net-11-features)) | +| **.NET 11 opt-ins** | A `net11.0` target compiles with the C# 15 updated memory-safety rules (`MemorySafetyRules=2`) and, when opted in with `PurviewRuntimeAsync=true`, Runtime Async (`runtime-async=on`), while `AllowUnsafeBlocks` stays at its compiler default (`false`); builds use MSBuild's multithreaded mode through a bootstrapped `Directory.Build.rsp` (see [.NET 11 features](#net-11-features)) | | **ServerShared** | A `ServerShared*` project is referenced automatically by every non-client-side project, and never by a WebAssembly client, browser-safe library, Razor class library or `*.Client`-style project; `PRSHR0001`/`PRSHR0002` report a reference that would still put it in a browser download (see [ServerShared](#servershared)) | | **Blazor and BFF** | Blazor WebAssembly clients, Blazor Web App hosts and BFF hosts are detected; `PurviewBrowserSafe` makes a library the client downloads fail the build on trim, AOT and browser-unsupported APIs, and the client checks every reference sets it (see [Blazor WebAssembly and BFF](#blazor-webassembly-and-bff)) | | **C# defaults** | `net10.0;net11.0` TFMs (overridable), `LangVersion=latest` (C# 15), `Nullable=enable`, `ImplicitUsings=enable`, deterministic builds | @@ -44,7 +44,7 @@ A reusable MSBuild SDK NuGet package that delivers standardised .NET project def "runner": "Microsoft.Testing.Platform" }, "msbuild-sdks": { - "Purview.BuildSdk": "2.0.0" + "Purview.BuildSdk": "2.0.1" } } ``` @@ -203,7 +203,7 @@ to a low-importance message) and only a copy that still fails after every retry The bootstrapped `.editorconfig` opens with a version marker: ```editorconfig -# Purview.BuildSdk 2.0.0 - managed file, do not edit +# Purview.BuildSdk 2.0.1 - managed file, do not edit ``` That line is how the SDK recognises a file **it** wrote. When the SDK version changes, a file carrying @@ -378,7 +378,7 @@ Version detection logging is disabled by default. Set `VersionDetectionLogEnable | `AssemblyIdentifierClassName` | `Assembly` | Overrides the name of the generated assembly identifier marker class. | | `AutoIncludeUsings` | `true` | Controls SDK-added global usings for `NamespacePrefix` and `RootNamespace`. | | `MemorySafetyRules` | `2` | C# 15 memory-safety tier for `net11.0`+ targets: `0` off, `1` preview pointer syntax, `2` updated rules (`updated-memory-safety-rules`, the default). `AllowUnsafeBlocks` is never changed. | -| `PurviewRuntimeAsync` | `true` | Compiles `net11.0`+ targets with `runtime-async=on`. `UseRuntimeAsync=false` also opts out. | +| `PurviewRuntimeAsync` | `false` | Set to `true` to compile `net11.0`+ targets with `runtime-async=on`. `UseRuntimeAsync=true` also opts in; an explicit `false` under either name keeps it off. | | `IsClientSideProject` | *(detected)* | `true` for a project whose assemblies are downloaded to a browser — a WebAssembly client, `PurviewBrowserSafe` library, Razor class library, or `*.Client`/`*.Browser`/`*.Wasm`/`*.WebAssembly`/`*.Components` name. Set explicitly to override; a `ServerShared` project is never added to a client-side project. | | `PurviewAutoServerSharedReference` | `true` | Adds `../ServerShared*/ServerShared*.csproj` to a non-client-side project. `false` removes the automatic reference. | | `DisablePurviewServerSharedReferenceCheck` | `false` | Suppresses `PRSHR0001`/`PRSHR0002`. | @@ -583,7 +583,7 @@ later) target, so the `net10.0` leg of the default `net10.0;net11.0` set is unaf | Feature | Applied | Why | | -- | -- | -- | -| Runtime Async V2 | `runtime-async=on` | .NET 11 no longer requires `EnablePreviewFeatures`, and the runtime libraries ship compiled with it. Cleaner live stack traces, lower await overhead, and continuations that skip `ExecutionContext` when nothing is ambient. Opt out with `PurviewRuntimeAsync=false` or `UseRuntimeAsync=false`. | +| Runtime Async V2 (opt-in) | `runtime-async=on` when enabled | Enables Runtime Async for a target where the team has decided it is wanted: cleaner live stack traces, lower await overhead, and continuations that skip `ExecutionContext` when nothing is ambient. It is **off by default** because it changes how every `await` compiles; opt in with `PurviewRuntimeAsync=true` or `UseRuntimeAsync=true`. An explicit `false` under either name keeps it off. | | C# 15 updated memory-safety rules | `updated-memory-safety-rules` (`MemorySafetyRules=2`) | `unsafe` marks requires-unsafe APIs and the compiler records the choice in `MemorySafetyRulesAttribute`. Independent of `LangVersion`, so the default `latest` is enough. | | Unsafe code | **not** enabled | `AllowUnsafeBlocks` stays at the compiler default (`false`). The updated model is strictest with unsafe code disallowed; a project that needs pointers opts in itself. | | Multithreaded MSBuild | `Directory.Build.rsp` with `-mt` | Runs a build's projects concurrently in one process, which is 20–26% faster on large solutions per the MSBuild measurements. `-mt` is a host switch, so it cannot be set from a project; the response file is the only mechanism. `-nr:false` accompanies it because a node left by a non-`-mt` build can cache an `RoslynCodeTaskFactory` task with no assembly path and fail the next `-mt` build (`dotnet/msbuild#15213`); the short-lived server still gives Server GC. | diff --git a/docs/wiki/Configuration-Reference.md b/docs/wiki/Configuration-Reference.md index 0e56da2..2bac8d1 100644 --- a/docs/wiki/Configuration-Reference.md +++ b/docs/wiki/Configuration-Reference.md @@ -37,7 +37,7 @@ See [Version Detection](Version-Detection.md) for the full resolution rules. | `AssemblyIdentifierClassName` | `Assembly` | Overrides the name of the generated assembly identifier marker class. | | `AutoIncludeUsings` | `true` | Controls SDK-added global usings for `NamespacePrefix` and `RootNamespace`. | | `MemorySafetyRules` | `2` | C# 15 memory-safety tier on `net11.0`+ targets: `0` off, `1` preview pointer syntax (sets `LangVersion=preview` when the project has not chosen one), `2` updated rules (`updated-memory-safety-rules`). An unrecognised value fails with `PRCFG0001`. `AllowUnsafeBlocks` is never changed. | -| `PurviewRuntimeAsync` | `true` | Adds `runtime-async=on` on `net11.0`+ targets. `PurviewRuntimeAsync=false` or `UseRuntimeAsync=false` opts out. | +| `PurviewRuntimeAsync` | `false` | Set to `true` to add `runtime-async=on` on `net11.0`+ targets. `UseRuntimeAsync=true` also opts in; an explicit `false` under either name keeps it off. | | `IsClientSideProject` | *(detected)* | `true` when the project is downloaded to a browser: a WebAssembly client, a `PurviewBrowserSafe` library, a Razor class library, or a `*.Client`/`*.Browser`/`*.Wasm`/`*.WebAssembly`/`*.Components` name. Set explicitly to override the detection either way. | | `PurviewAutoServerSharedReference` | `true` | Adds `../ServerShared*/ServerShared*.csproj` to every non-client-side project. `false` disables it. | | `DisablePurviewServerSharedReferenceCheck` | `false` | Turns off `PRSHR0001` and `PRSHR0002`. | @@ -186,7 +186,7 @@ unchanged. | Property | Applied | Notes | | -- | -- | -- | -| `PurviewRuntimeAsync` | `true` | `runtime-async=on`. `UseRuntimeAsync=false` also opts out. | +| `PurviewRuntimeAsync` | `false` | When set to `true`, adds `runtime-async=on`. `UseRuntimeAsync=true` also opts in; an explicit `false` under either name keeps it off. | | `MemorySafetyRules` | `2` | `updated-memory-safety-rules`; `1` sets `LangVersion=preview` for the pointer-syntax tier; `0` disables. `PRCFG0001` on an unknown value. | | `AllowUnsafeBlocks` | *(compiler default: false)* | Never set by the SDK. | @@ -301,7 +301,7 @@ read them through `build_property.`: | `DisablePurviewServerSharedReferenceCheck` | When `true`, suppresses `PRSHR0001`/`PRSHR0002`. | | `PurviewServerSharedReferenceCheckAsError` | When `true`, reports `PRSHR0001`/`PRSHR0002` as errors. | | `MemorySafetyRules` | C# 15 memory-safety tier applied to `net11.0`+ targets. | -| `PurviewRuntimeAsync` | When `true` (default), `net11.0`+ targets compile with `runtime-async=on`. | +| `PurviewRuntimeAsync` | When `true`, `net11.0`+ targets compile with `runtime-async=on`. Defaults to `false`; `UseRuntimeAsync=true` also opts in. | | `PurviewMultiThreadedBuild` | When `true` (default), a managed `Directory.Build.rsp` enables MSBuild `-mt`. | | `EditorConfigFilePath` | Path to the SDK-provided `.editorconfig` injected into `@(EditorConfigFiles)`. | | `RepositoryEditorConfigFilePath` | Destination path for bootstrapping a physical repo-level `.editorconfig`. | diff --git a/docs/wiki/Getting-Started.md b/docs/wiki/Getting-Started.md index 1b3c797..734d6b1 100644 --- a/docs/wiki/Getting-Started.md +++ b/docs/wiki/Getting-Started.md @@ -14,7 +14,7 @@ Add `Purview.BuildSdk` to the `msbuild-sdks` section so MSBuild can resolve the "runner": "Microsoft.Testing.Platform" }, "msbuild-sdks": { - "Purview.BuildSdk": "2.0.0" + "Purview.BuildSdk": "2.0.1" } } ``` diff --git a/package.json b/package.json index e361a6d..0d62d59 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "build-sdk", - "version": "2.0.0", + "version": "2.0.1", "homepage": "https://purview.dev/projects/build-sdk/", "bugs": { "url": "https://github.com/purview-dev/build-sdk/issues" diff --git a/skills/purview-buildsdk-configuration/SKILL.md b/skills/purview-buildsdk-configuration/SKILL.md index b540704..fca74cc 100644 --- a/skills/purview-buildsdk-configuration/SKILL.md +++ b/skills/purview-buildsdk-configuration/SKILL.md @@ -51,7 +51,7 @@ in `Directory.Build.props` unless the setting is deliberately project-specific. | `IsBffProject` | name ends `Bff` | Classifies a BFF host; `false` opts out | | `PurviewBrowserSafeReferenceCheckAsError` | `false` | Makes `PRBLZ0001` (a client reference that is not browser-safe) an error | | `MemorySafetyRules` | `2` | C# 15 memory-safety tier on `net11.0`+: `0` off, `1` preview syntax, `2` updated rules | -| `PurviewRuntimeAsync` | `true` | `runtime-async=on` on `net11.0`+ | +| `PurviewRuntimeAsync` | `false` | Opts a `net11.0`+ target into `runtime-async=on`; `UseRuntimeAsync=true` also opts in | | `IsClientSideProject` | *(computed)* | Whether the project's assemblies can reach a browser; overrides the `ServerShared` classification | | `PurviewAutoServerSharedReference` | `true` | Adds a sibling `ServerShared` reference to a non-client-side project | | `PurviewMultiThreadedBuild` | `true` | Bootstraps a `Directory.Build.rsp` enabling MSBuild `-mt` | diff --git a/skills/purview-buildsdk-configuration/references/properties.md b/skills/purview-buildsdk-configuration/references/properties.md index 82f84d7..8a8f139 100644 --- a/skills/purview-buildsdk-configuration/references/properties.md +++ b/skills/purview-buildsdk-configuration/references/properties.md @@ -41,7 +41,7 @@ consuming project supplies nothing. | Property | Default | Behaviour | | -- | -- | -- | | `MemorySafetyRules` | `2` | C# 15 memory-safety tier on `net11.0`+ targets: `0` off, `1` preview pointer syntax (sets `LangVersion=preview` when none was chosen), `2` `updated-memory-safety-rules`. Unknown values fail with `PRCFG0001`. Never touches `AllowUnsafeBlocks` | -| `PurviewRuntimeAsync` | `true` | `runtime-async=on` on `net11.0`+ targets. `UseRuntimeAsync=false` also opts out | +| `PurviewRuntimeAsync` | `false` | Set to `true` for `runtime-async=on` on `net11.0`+ targets. `UseRuntimeAsync=true` also opts in; an explicit `false` under either name keeps it off | | `PurviewMultiThreadedBuild` | `true` | Bootstraps a managed `Directory.Build.rsp` with `-mt`/`-nr:false`; `false` writes `-mt:false` | | `BootstrapMultiThreadedBuildRspToRepoRoot` | `true` | Whether that bootstrapped response file is written at all | | `IsClientSideProject` | *(computed)* | `true` for a WebAssembly client, `PurviewBrowserSafe` library, Razor class library, or `*.Client`/`*.Browser`/`*.Wasm`/`*.WebAssembly`/`*.Components` name. Explicit value wins | diff --git a/src/src/BuildSdk/Sdk/Props/Defaults.props b/src/src/BuildSdk/Sdk/Props/Defaults.props index 6733b4e..931fe8b 100644 --- a/src/src/BuildSdk/Sdk/Props/Defaults.props +++ b/src/src/BuildSdk/Sdk/Props/Defaults.props @@ -196,7 +196,9 @@ and PRSHR0002). Warning by default; set this to true to fail the build instead. --> false - true + true diff --git a/src/src/BuildSdk/Sdk/Sdk.props b/src/src/BuildSdk/Sdk/Sdk.props index e8a2b73..9f1e380 100644 --- a/src/src/BuildSdk/Sdk/Sdk.props +++ b/src/src/BuildSdk/Sdk/Sdk.props @@ -564,7 +564,7 @@ The C# 15 memory-safety tier: 0 off, 1 preview syntax, 2 updated rules (default). AllowUnsafeBlocks is left unset. - When true (default), a net11.0+ project compiles with the runtime-async compiler feature. UseRuntimeAsync=false opts out. + When true, a net11.0+ project compiles with the runtime-async compiler feature. Defaults to false; UseRuntimeAsync=true also opts in. When true (default), boots a Directory.Build.rsp that enables MSBuild's multithreaded build (-mt). diff --git a/src/src/BuildSdk/Sdk/Targets/Modern.targets b/src/src/BuildSdk/Sdk/Targets/Modern.targets index 3a155db..c84ef10 100644 --- a/src/src/BuildSdk/Sdk/Targets/Modern.targets +++ b/src/src/BuildSdk/Sdk/Targets/Modern.targets @@ -7,15 +7,15 @@ property. That matters: TargetFrameworks defaults to net10.0;net11.0, so a switch that improves net11.0 must not be applied to the net10.0 leg. - Two compiler features are enabled for a net11.0+ target: + One compiler feature is enabled by default for a net11.0+ target, and + a second is available as an opt-in: - runtime-async=on Runtime Async V2. .NET 11 no longer needs - EnablePreviewFeatures for it, and the - runtime libraries themselves ship built with - it, so an application built without it runs - against libraries that did opt in. + runtime-async=on Runtime Async V2. Off by default: it + changes how every await compiles, so it is + opted into with PurviewRuntimeAsync=true + (or UseRuntimeAsync=true), never assumed. - updated-memory-safety-rules + updated-memory-safety-rules (default) The C# 15 memory-safety model: 'unsafe' marks requires-unsafe APIs and the caller must acknowledge the access. Independent of @@ -43,7 +43,11 @@ - true + + $(UseRuntimeAsync) + false 2 <_PurviewMemorySafetyRulesNormalized>$([System.String]::Copy('$(MemorySafetyRules)').Trim().ToLowerInvariant()) diff --git a/src/tests/BuildSdk.IntegrationTests/Dotnet11FeaturesTests.cs b/src/tests/BuildSdk.IntegrationTests/Dotnet11FeaturesTests.cs index 1811df3..cdf294b 100644 --- a/src/tests/BuildSdk.IntegrationTests/Dotnet11FeaturesTests.cs +++ b/src/tests/BuildSdk.IntegrationTests/Dotnet11FeaturesTests.cs @@ -15,7 +15,7 @@ namespace Purview.BuildSdk; sealed class Dotnet11FeaturesTests { [Test] - public async Task Net11Target_EnablesRuntimeAsyncAndUpdatedMemorySafetyRules(CancellationToken cancellationToken) + public async Task Net11Target_EnablesUpdatedMemorySafetyRulesButNotRuntimeAsync(CancellationToken cancellationToken) { using var h = await ProjectHarness.CreateAsync( "MyLibrary", @@ -30,8 +30,8 @@ public async Task Net11Target_EnablesRuntimeAsyncAndUpdatedMemorySafetyRules(Can await Assert .That(eval.Properties["Features"]) - .Contains("runtime-async=on") - .Because("Runtime Async V2 is the .NET 11 default and no longer needs EnablePreviewFeatures"); + .DoesNotContain("runtime-async=on") + .Because("Runtime Async is opt-in: it changes how every await compiles"); await Assert .That(eval.Properties["Features"]) .Contains("updated-memory-safety-rules") @@ -75,37 +75,53 @@ public async Task Net11Target_KeepsFeaturesTheProjectAlreadySet(CancellationToke var features = await h.GetPropertyAsync("Features", cancellationToken); await Assert.That(features).Contains("project-feature=on"); - await Assert.That(features).Contains("runtime-async=on"); await Assert.That(features).Contains("updated-memory-safety-rules"); } [Test] - public async Task RuntimeAsync_CanBeDisabledPerProject(CancellationToken cancellationToken) + public async Task RuntimeAsync_IsOptedInByPurviewRuntimeAsync(CancellationToken cancellationToken) { using var h = await ProjectHarness.CreateAsync( "MyLibrary", targetFramework: "net11.0", - extraProps: "false", + extraProps: "true", cancellationToken: cancellationToken ); var features = await h.GetPropertyAsync("Features", cancellationToken); - await Assert.That(features).DoesNotContain("runtime-async=on"); + await Assert.That(features).Contains("runtime-async=on"); await Assert.That(features).Contains("updated-memory-safety-rules"); } /// - /// The documented opt-out is UseRuntimeAsync=false; honouring it even though the SDK's own - /// switch is separate is what keeps a project compatible with a future .NET SDK that owns the name. + /// The .NET team's property name opts in as well, so a repository can use the name it already + /// knows rather than learn the SDK's own switch. /// [Test] - public async Task RuntimeAsync_HonoursTheDocumentedUseRuntimeAsyncOptOut(CancellationToken cancellationToken) + public async Task RuntimeAsync_IsOptedInByUseRuntimeAsync(CancellationToken cancellationToken) { using var h = await ProjectHarness.CreateAsync( "MyLibrary", targetFramework: "net11.0", - extraProps: "false", + extraProps: "true", + cancellationToken: cancellationToken + ); + + await Assert.That(await h.GetPropertyAsync("Features", cancellationToken)).Contains("runtime-async=on"); + } + + /// + /// An explicit opt-out under either name wins over the other name's opt-in, so a repository can + /// disable Runtime Async without finding every place that enabled it. + /// + [Test] + public async Task RuntimeAsync_ExplicitOptOutWinsOverAnOptIn(CancellationToken cancellationToken) + { + using var h = await ProjectHarness.CreateAsync( + "MyLibrary", + targetFramework: "net11.0", + extraProps: "truefalse", cancellationToken: cancellationToken ); @@ -125,7 +141,7 @@ public async Task MemorySafetyTierZero_DisablesTheUpdatedRules(CancellationToken var features = await h.GetPropertyAsync("Features", cancellationToken); await Assert.That(features).DoesNotContain("updated-memory-safety-rules"); - await Assert.That(features).Contains("runtime-async=on"); + await Assert.That(features).DoesNotContain("runtime-async=on"); } /// diff --git a/templates/global.json b/templates/global.json index 739d4fb..6846896 100644 --- a/templates/global.json +++ b/templates/global.json @@ -3,6 +3,6 @@ "runner": "Microsoft.Testing.Platform" }, "msbuild-sdks": { - "Purview.BuildSdk": "2.0.0" + "Purview.BuildSdk": "2.0.1" } }