NUnit build infrastructure
The build is opinionated. Several patterns that look like ordinary csproj/nuspec editing in other repos are wrong here, and a few that look strange are load-bearing. When editing any build file, follow these conventions.
Adding a package dependency
Two files always change, in lockstep:
- Add the version to
src/NUnitFramework/Directory.Packages.props:
<PackageVersion Include="SomePackage" Version="1.2.3" />
- Add a version-less reference in the consuming
.csproj:
<PackageReference Include="SomePackage" />
Never put Version="..." on a <PackageReference>. If you see one in a diff (often left by an IDE after a package update), delete the attribute.
Adding a build-only or analyzer-only package
Mark with PrivateAssets="all" so it doesn't propagate to consumers, and place under the analyzers/build-tooling ItemGroup in Directory.Build.props rather than per-project:
<PackageReference Include="StyleCop.Analyzers" PrivateAssets="all" />
In the corresponding nuspec entry, mark such packages as developmentDependency="true" so consumers of the NUnit nuget don't pull them transitively.
Versioning — don't touch it
Versioning is handled at build time by MinVer, which derives the version from git tags. Specifically:
- Never set
<AssemblyVersion>,<FileVersion>,<Version>, or<InformationalVersion>in a.csproj. - Never hand-write
[assembly: AssemblyVersion(...)]or related attributes —<GenerateAssemblyInfo>true</GenerateAssemblyInfo>produces them. - The deliberate convention is that
AssemblyVersionstays atMajor.0.0.0(so binding redirects don't churn on every release) whileAssemblyFileVersiondrifts. Don't "fix" this.
If you need to influence the version (e.g. for a release branch), do it via MinVer's environment variables in CI, not by editing project files.
Directory.Build.props vs Directory.Build.targets
The split is enforced by convention, not by MSBuild:
Directory.Build.props: properties (<PropertyGroup>), package references (<ItemGroup>withPackageReference), feature constants, build settings. Anything evaluated at project-load time.Directory.Build.targets:<Target>definitions — anything that runs as part of the build pipeline (custom build steps, post-compile work).
Don't put a <Target> in .props or a property in .targets. Reviewers will ask you to move it.
nuspec and .csproj must agree
Each shipped NuGet has a .nuspec that lists its dependencies. Those must match the <PackageReference>s of the corresponding .csproj for each target framework.
This is asserted at test time by NuspecDependenciesTests.cs — drift fails the test suite during dotnet test. When you add a runtime dependency to a project that ships, update the nuspec in the same change. If the test reports drift, the nuspec is the file to update (not the test).
Build-only or analyzer-only packages should not appear in the nuspec, or should appear with developmentDependency="true" if they must.
Directory.Build.targets and target authoring
When adding a target:
- Give it a clear, capitalised name that says what it does (
SetAssemblyVersion,CopyContractFiles). - Specify when it runs explicitly (
BeforeTargets="Build"/AfterTargets="..."); don't rely on placement order. - Targets that emit output for diagnostic purposes should respect MSBuild's terminal logger —
<Message Importance="High">is necessary to be seen with the new logger.
CI workflows (.github/workflows)
- Don't add a new TFM build matrix entry as part of an unrelated PR — adding a TFM has consequences for package payload and is reviewed separately.
- When adding a new dotnet step, install via
actions/setup-dotnet@v4with an explicitdotnet-version. Don't depend on the runner's preinstalled SDK. - Caches keyed on
Directory.Packages.propsand lock files — if you add a new file that influences restore, include it in the cache key.
Common cleanup tasks while editing csproj
- IDE package-update tooling sometimes adds
Version="..."to a<PackageReference>it just bumped. Delete the attribute and updateDirectory.Packages.propsinstead. - IDE tooling sometimes inserts
<TargetFramework>for a project that defines<TargetFrameworks>(plural) centrally. Remove the singular form unless the project genuinely targets one TFM only. - Stale
<None>/<Compile>items left behind after a file rename or move — clean these up; SDK-style projects use globs and don't need explicit entries for normal.csfiles.