Skip to content
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
4 changes: 2 additions & 2 deletions .config/dotnet-tools.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"isRoot": true,
"tools": {
"csharpier": {
"version": "1.3.0",
"version": "1.4.0",
"commands": [
"csharpier"
],
Expand All @@ -17,7 +17,7 @@
"rollForward": false
},
"purview.build": {
"version": "0.8.2",
"version": "0.8.1",
"commands": [
"purview-build"
],
Expand Down
2 changes: 2 additions & 0 deletions .config/purview-build.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
"Sdk/Sdk.props",
"Sdk/Sdk.targets",
"Sdk/Targets/Blazor.targets",
"Sdk/Targets/Modern.targets",
"Sdk/Targets/ServerShared.targets",
"Sdk/Targets/VersionDetection.targets"
],
"purview.skills.testing": [
Expand Down
2 changes: 1 addition & 1 deletion .editorconfig
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Purview.BuildSdk 1.3.2 - managed file, do not edit
# Purview.BuildSdk 2.0.0 - 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
Expand Down
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ project.lock.json
project.fragment.lock.json
artifacts/

*.rsp
*_i.c
*_p.c
*_i.h
Expand All @@ -60,6 +61,9 @@ artifacts/
*.pgc
*.pgd
*.rsp
# The SDK bootstraps a managed response file at the repository root; it is tracked so the first build
# in a fresh clone already runs MSBuild's multithreaded mode.
!/Directory.Build.rsp
*.sbr
*.tlb
*.tli
Expand Down
29 changes: 29 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,35 @@ release summarise what the commits recorded rather than what was written at the

## [Unreleased]

### Added

- **`ServerShared` projects.** A `ServerShared*` project is referenced automatically by every
non-client-side project, and never by a WebAssembly client, `PurviewBrowserSafe` library, Razor
class library or `*.Client`/`*.Browser`/`*.Wasm`/`*.WebAssembly`/`*.Components` project.
`IsClientSideProject` names the classification and can be set explicitly; `PRSHR0001` (client-side
reference) and `PRSHR0002` (Shared reference) catch a reference that would still reach a browser.
Opt the automatic reference out with `PurviewAutoServerSharedReference=false`.
- **.NET 11 compiler opt-ins.** A `net11.0`+ target compiles with Runtime Async
(`<Features>runtime-async=on</Features>`) and the C# 15 updated memory-safety rules
(`MemorySafetyRules=2`), gated per-TFM so the `net10.0` leg is unchanged. `AllowUnsafeBlocks` is
left at the compiler default (`false`). Opt out with `PurviewRuntimeAsync=false`,
`UseRuntimeAsync=false`, or `MemorySafetyRules=0`; an unrecognised tier fails with `PRCFG0001`.
- **Multithreaded builds.** A managed `Directory.Build.rsp` at the repository root enables MSBuild's
`-mt` on .NET 11, bootstrapped like `.editorconfig` and `global.json` and refreshed only when its
marker line is present. `PurviewMultiThreadedBuild=false` rewrites it to `-mt:false`.

### Changed

- **The default target frameworks are now `net10.0;net11.0`.** `Current` — the set that applies when a
project selects none — resolves to the current LTS plus the latest release, so a project that declares
neither `TargetFramework` nor `TargetFrameworks` now multi-targets. This is a breaking change: pin
`PurviewTargetFrameworksCurrent`, select another set, or declare the TFM explicitly to keep the previous
single-target output. `Supported` still names the same pair so a repository can pin the meaning rather
than depend on where `Current` moves next.
- **`LangVersion` defaults to `latest` (C# 15)** instead of `preview`. C# 15 is the latest shipped
language version and includes union declarations; `preview` remains available per project when a
repository deliberately wants unshipped features. Roslyn components keep `LangVersion=latest`.

## [1.3.2] - 2026-11-09

### Changed
Expand Down
4 changes: 4 additions & 0 deletions Directory.Build.rsp
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Purview.BuildSdk - managed file
# Managed by Purview.BuildSdk. Delete the marker line to take ownership of this file.
-mt
-nr:false
2 changes: 1 addition & 1 deletion Directory.Packages.props
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<Project>
<PropertyGroup>
<TUnitVersion>1.76.0</TUnitVersion>
<TUnitVersion>1.83.0</TUnitVersion>
<MicrosoftRoslynVersion>5.9.0</MicrosoftRoslynVersion>
</PropertyGroup>
<ItemGroup>
Expand Down
104 changes: 95 additions & 9 deletions README.md

Large diffs are not rendered by default.

79 changes: 67 additions & 12 deletions docs/wiki/Configuration-Reference.md

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion docs/wiki/Getting-Started.md
Original file line number Diff line number Diff line change
Expand Up @@ -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": "1.3.2"
"Purview.BuildSdk": "2.0.0"
}
}
```
Expand Down
6 changes: 3 additions & 3 deletions docs/wiki/Home.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@ repo root inherits everything automatically.
`.csproj` filename, the `Sdk` attribute, and on-disk markers.
- **Engineering conventions** — repository layout, project naming, namespace identity, test readability,
and the default boundaries between source, test, and shared test-support projects.
- **C# defaults** — `net10.0` TFM (overridable, `netstandard2.0` for Roslyn components),
`LangVersion=preview` (`latest` for Roslyn components), `Nullable=enable`, `ImplicitUsings=enable`,
- **C# defaults** — `net10.0;net11.0` TFMs (overridable, `netstandard2.0` for Roslyn components),
`LangVersion=latest` (C# 15, including unions), `Nullable=enable`, `ImplicitUsings=enable`,
deterministic builds, `ManagePackageVersionsCentrally=true`.
- **Code style** — an `.editorconfig` baked into the package, applied via `EditorConfigFilePath`, and
auto-bootstrapped to the repo root if missing; `EnforceCodeStyleInBuild=true`,
Expand Down Expand Up @@ -65,7 +65,7 @@ repo root inherits everything automatically.

## Requirements

- .NET SDK 10.0 or later (the default TFM for source projects is `net10.0`).
- .NET SDK 11.0 or later (the default TFMs for source projects are `net10.0` and `net11.0`).
- A `NamespacePrefix` set before the SDK import.
- A `Directory.Packages.props` at the repo root when Central Package Management is used (the SDK sets
`ManagePackageVersionsCentrally=true`).
Expand Down
12 changes: 12 additions & 0 deletions docs/wiki/Project-Type-Detection.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ wiki.
| `IsTestProject` | Project name matches `^.*\.([\w|_]\w*)Tests?$` — a target name, a dot, then any word before `Test`/`Tests`. The captured word becomes `TestingType`. |
| `IsSharedTestingProject` | Project name is one of `SharedTestingFramework`, `SharedTestingInfrastructure`, `SharedTestingInfra`, `SharedTestingUtilities`, `SharedTestingUtils`, `SharedTestingLibrary`, `SharedTestingLib`, `SharedTestingHelpers`. |
| `IsSharedProject` | Project name is one of `Shared`, `SharedFramework`, `SharedInfrastructure`, `SharedInfra`, `SharedUtilities`, `SharedUtils`, `SharedLibrary`, `SharedLib`, `SharedHelpers`. |
| `IsServerSharedProject` | Project name is one of `ServerShared`, `ServerSharedFramework`, `ServerSharedInfrastructure`, `ServerSharedInfra`, `ServerSharedUtilities`, `ServerSharedUtils`, `ServerSharedLibrary`, `ServerSharedLib`, `ServerSharedHelpers`. |
| `IsClientSideProject` | A WebAssembly client, a `PurviewBrowserSafe` library, a Razor class library, or a project named `*.Client`, `*.ClientShared`, `*.Browser`, `*.Wasm`, `*.WebAssembly`, `*.Components`, `Components`, or `ClientShared`. An explicit value overrides the detection. |
| `IsContainerProject` | A `Dockerfile`, `dockerfile`, or `Dockerfile.dev` exists in the project directory. |
| `IsSdkProject` | An `Sdk` value is parsed from the `<Project>`/`<Import>` element. |
| `IsWebSdkProject` | `SdkProjectName` is `Microsoft.NET.Sdk.Web`. |
Expand Down Expand Up @@ -87,6 +89,16 @@ The SDK ships the following full `TestType` set, in addition to the standard `Un
(`../Shared*/Shared*.csproj`); test projects automatically reference
`../SharedTesting*/SharedTesting*.csproj`.

### ServerShared projects (`IsServerSharedProject=true`)

- Referenced automatically by every non-client-side project via wildcard
(`../ServerShared*/ServerShared*.csproj`).
- Never referenced automatically by a WebAssembly client, a `PurviewBrowserSafe` library, a Razor class
library, a project named with a client-side segment, or another `Shared`/`SharedTesting` project.
`IsClientSideProject` overrides the classification either way.
- `PRSHR0001` reports a client-side project that references one; `PRSHR0002` reports a `Shared`
project that does. `PurviewBrowserSafe="true"` on the reference records an accepted decision.

### Container projects

- `InvariantGlobalization=true`, `PublishAot=true`, `DockerDefaultTargetOS=Linux`,
Expand Down
40 changes: 36 additions & 4 deletions docs/wiki/Repository-Bootstrap.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,9 +66,41 @@ file, and honours `PurviewRepoBootstrapMode`:
| `DisableAutoCopySdkFiles` | `false` | Master switch that disables repo-level SDK file bootstrapping. |
| `PurviewRepoBootstrapMode` | `IfMissing` | `IfMissing`, `Always`, `WarnOnDrift` or `Never`. |

## `Directory.Build.rsp` bootstrapping

`-mt` is an MSBuild **host** switch, so no project property can enable it: the host reads its switches
before evaluation. A `Directory.Build.rsp` in any ancestor of a project is the one file the CLI reads
that can, so the SDK writes one at the repository root:

```text
# Purview.BuildSdk - managed file
# Managed by Purview.BuildSdk. Delete the marker line to take ownership of this file.
-mt
-nr:false
```

`-nr:false` is deliberate. A node left by a non-`-mt` build can hold a cached `RoslynCodeTaskFactory`
task with no assembly path, and the first `-mt` build that reuses it fails with `MSB0001`
([dotnet/msbuild#15213](https://github.com/dotnet/msbuild/issues/15213)) — which is exactly the
transition a repository makes when this file first appears. A short-lived server still provides the
Server GC `-mt` expects. `-mt` requires .NET 11; the bootstrap is skipped on an older SDK.

The file is SDK-managed: a destination carrying the marker line is refreshed, one without it belongs
to the developer and is never touched. `PurviewMultiThreadedBuild=false` rewrites it to `-mt:false`
rather than deleting it, so opting out cannot leave a stale copy enabling the mode.

Like the other bootstraps it writes atomically, retries quietly, and honours `PurviewRepoBootstrapMode`:

| Property | Default | Description |
| -- | -- | -- |
| `BootstrapMultiThreadedBuildRspToRepoRoot` | `true` | Creates the `Directory.Build.rsp` when missing. |
| `RepositoryDirectoryBuildRspFilePath` | *(auto-detected)* | Override the destination path. |
| `PurviewMultiThreadedBuild` | `true` | `false` writes `-mt:false` instead of `-mt`. |
| `DisableAutoCopySdkFiles` | `false` | Master switch that disables repo-level SDK file bootstrapping. |

## Retry and failure behaviour

Both bootstraps share the same retry contract:
All bootstraps share the same retry contract:

| Property | Default | Description |
| -- | -- | -- |
Expand All @@ -82,11 +114,11 @@ error by default, including the destination path and the underlying OS error.

## Repository root discovery

Both bootstrap targets locate the repository root by:
All bootstrap targets locate the repository root by:

1. Running `git rev-parse --show-toplevel` from the `Directory.Build.props` directory.
2. Falling back to probing upward for a `.git` marker.
3. Finally falling back to the `Directory.Build.props` directory itself.

Set the `RepositoryEditorConfigFilePath` / `RepositoryGlobalJsonFilePath` properties explicitly to
override discovery.
Set the `RepositoryEditorConfigFilePath`, `RepositoryGlobalJsonFilePath` or
`RepositoryDirectoryBuildRspFilePath` property explicitly to override discovery.
10 changes: 6 additions & 4 deletions global.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
{
"test": {
"runner": "Microsoft.Testing.Platform"
}
}
"sdk": { "version": "11.0.100" },

"test": {
"runner": "Microsoft.Testing.Platform"
}
}
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "build-sdk",
"version": "1.3.2",
"version": "2.0.0",
"homepage": "https://purview.dev/projects/build-sdk/",
"bugs": {
"url": "https://github.com/purview-dev/build-sdk/issues"
Expand All @@ -9,4 +9,4 @@
"type": "git",
"url": "git+https://github.com/purview-dev/build-sdk.git"
}
}
}
13 changes: 11 additions & 2 deletions skills/purview-buildsdk-configuration/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ in `Directory.Build.props` unless the setting is deliberately project-specific.
| `TestingFramework` | `TUnit` | `TUnit`, `Xunit` or `None` |
| `SubstituteFramework` | `TUnitMocks` | `TUnitMocks`, `NSubstitute` or `None` |
| `TestDataFramework` | `Bogus` | `Bogus` or `None` |
| `TargetFramework` | `net10.0` | `netstandard2.0` when `IsRoslynComponent=true` |
| `TargetFramework` | `net10.0;net11.0` | `netstandard2.0` when `IsRoslynComponent=true` |
| `IsPackable` | `false` | Opts a project into the packable defaults |
| `UsePackageJsonVersion` | `true` | `true`, `false` or `Strict` — version from `package.json` |
| `ExcludePurviewTelemetry` | `false` | Drops the telemetry source generator |
Expand All @@ -50,6 +50,11 @@ in `Directory.Build.props` unless the setting is deliberately project-specific.
| `PurviewBrowserSafe` | *(unset)* | On a class library a WebAssembly client downloads: trim, AOT and browser-unsupported APIs fail the build |
| `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`+ |
| `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` |

The full set, grouped by area and with the behaviour each one changes, is in
[references/properties.md](references/properties.md). Read it rather than guessing a property name —
Expand Down Expand Up @@ -89,11 +94,15 @@ is the SDK's answer, and it stays correct when the rules change. The exported se

## Repository bootstrap

The SDK bootstraps two repository-wide files from the package: `.editorconfig` and `global.json`.
The SDK bootstraps three repository-wide files from the package: `.editorconfig`, `global.json` and
`Directory.Build.rsp`.

- `PurviewRepoBootstrapMode` — `IfMissing` (default), `Always`, `WarnOnDrift`, `Never`
- `DisableAutoCopySdkFiles` — the master off switch
- `PurviewRepoBootstrapCopyRetries` / `...RetryDelayMilliseconds` / `...FailureAsError`
- `BootstrapMultiThreadedBuildRspToRepoRoot` / `PurviewMultiThreadedBuild` — the response file that
enables MSBuild `-mt` (and `-nr:false`, which avoids a `RoslynCodeTaskFactory` cache left by a
non-`-mt` build)

Every project in a solution writes those same destinations, so the write is race-tolerant by
design: an existing file is never overwritten unless asked, each write is staged into a temporary
Expand Down
24 changes: 22 additions & 2 deletions skills/purview-buildsdk-configuration/references/properties.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ consuming project supplies nothing.

| Property | Default | Behaviour |
| -- | -- | -- |
| `TargetFramework` | `net10.0` | Applied when neither `TargetFramework` nor `TargetFrameworks` is set. A project that explicitly declares `IsRoslynComponent=true` defaults to `netstandard2.0` |
| `TargetFramework` | `net10.0;net11.0` | Applied when neither `TargetFramework` nor `TargetFrameworks` is set. A project that explicitly declares `IsRoslynComponent=true` defaults to `netstandard2.0` |
| `IsRoslynComponent` | *(unset)* | When explicitly `true`: a single `netstandard2.0` target, `LangVersion=latest`, `Nullable=enable`, `TreatWarningsAsErrors=true`, `Deterministic=true`, extended analyzer rules, SourceLink with `EmbedUntrackedSources=true`, no dependency file, telemetry exclusion, and `PrivateAssets=all` on `Microsoft.CodeAnalysis.*` references |
| `IsRoslynComponentOnly` | `true` for Roslyn components | Analyzer-only package: `IncludeBuildOutput=false`, `IncludeSymbols=false`, portable PDB packed beside the analyzer under `analyzers/dotnet/cs/`. Set `false` for a dual-role component using normal library symbol packaging |
| `DisableRoslynCompilerDefaultsValidation` | `false` | Opts out of `ValidateRoslynComponentCompilerSettings`, which fails the pack of a packable Roslyn component missing `LangVersion`, `Nullable`, `TreatWarningsAsErrors` or `EnforceExtendedAnalyzerRules` |
Expand All @@ -36,6 +36,19 @@ consuming project supplies nothing.
| `DisablePurviewTestContextRuleSet` | `false` | Makes test and shared-testing projects enforce the production API-surface rules too |
| `PurviewPolicyExemptNoWarn` | *(derived)* | The SDK-injected `NoWarn` entries `ValidatePurviewStylePolicy` accepts |

## .NET 11 features and ServerShared

| 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 |
| `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 |
| `PurviewAutoServerSharedReference` | `true` | Adds `../ServerShared*/ServerShared*.csproj` to a non-client-side project |
| `PurviewServerSharedReferenceCheckAsError` | `false` | Makes `PRSHR0001`/`PRSHR0002` errors |
| `DisablePurviewServerSharedReferenceCheck` | `false` | Turns `PRSHR0001`/`PRSHR0002` off |

## Testing

| Property | Default | Behaviour |
Expand Down Expand Up @@ -119,6 +132,9 @@ reference was judged browser-safe when it cannot say so itself.
| `RepositoryEditorConfigFilePath` | *(derived)* | Overrides the destination `.editorconfig` |
| `BootstrapGlobalJsonToRepoRoot` | `true` | |
| `RepositoryGlobalJsonFilePath` | *(derived)* | Overrides the destination `global.json` |
| `BootstrapMultiThreadedBuildRspToRepoRoot` | `true` | Creates a managed `Directory.Build.rsp` enabling `-mt` |
| `RepositoryDirectoryBuildRspFilePath` | *(derived)* | Overrides the destination `Directory.Build.rsp` |
| `PurviewMultiThreadedBuild` | `true` | `false` rewrites a managed rsp to `-mt:false` |
| `PurviewBuildSdkVersionForGlobalJson` | restored package version, else the version it was packed as | |
| `PurviewRepoBootstrapCopyRetries` | `3` | |
| `PurviewRepoBootstrapCopyRetryDelayMilliseconds` | `500` | |
Expand Down Expand Up @@ -148,7 +164,11 @@ Exported to analyzers and generators as `build_property.<PropertyName>`:
- `ExcludePurviewTelemetry`, `ExcludeMSTelemetryExtension`
- `EnableAssemblyNameGeneration`, `DisableAutoInternalsVisibleTo`, `DisableGenerateAssemblyInfoClass`,
`DisableGenerateAssemblyIdentifierClass`, `AssemblyIdentifierClassName`
- `IsCSharpProject`, `IsTestProject`, `IsSharedTestingProject`, `IsSharedProject`
- `IsCSharpProject`, `IsTestProject`, `IsSharedTestingProject`, `IsSharedProject`,
`IsServerSharedProject`, `IsClientSideProject`
- `PurviewAutoServerSharedReference`, `DisablePurviewServerSharedReferenceCheck`,
`PurviewServerSharedReferenceCheckAsError`
- `MemorySafetyRules`, `PurviewRuntimeAsync`, `PurviewMultiThreadedBuild`
- `TestingType`, `TargetProjectName`
- `IsContainerProject`, `IsSdkProject`, `SdkProjectName`, `IsWebProject`, `IsWebSdkProject`,
`IsWorkerSdkProject`, `IsAspireHostProject`, `IsCLIProject`
Expand Down
Loading
Loading