From 3e8699c68204f8f6e4cadfe7bb652ebe6e8f0367 Mon Sep 17 00:00:00 2001 From: VasiliyF <5789590+vfofanov@users.noreply.github.com> Date: Tue, 6 Oct 2026 06:17:25 +0200 Subject: [PATCH 1/8] Add a docs check that compares the docs with the kit tools/docs-check.sh lists every property, item and MSKIT_ code the kit defines or reads, requires a row for each in docs/reference/, rejects names and codes the kit does not have, and resolves relative links and their anchors. --- tools/docs-check.sh | 147 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 147 insertions(+) create mode 100644 tools/docs-check.sh diff --git a/tools/docs-check.sh b/tools/docs-check.sh new file mode 100644 index 0000000..fac2bb1 --- /dev/null +++ b/tools/docs-check.sh @@ -0,0 +1,147 @@ +#!/bin/sh +# Keeps the documentation honest against the kit: every property, item and MSKIT_ code the kit +# defines has a row in docs/reference/, every MSKit_ name and MSKIT_ code the docs mention exists in +# the kit, and every relative link resolves (anchors included). +# Usage: sh tools/docs-check.sh [--root DIR] [--list properties|items|codes] +set -eu + +root=$(cd "$(dirname "$0")/.." && pwd) +list="" +while [ $# -gt 0 ]; do + case "$1" in + --root) [ $# -ge 2 ] || { echo "docs-check: --root needs a value" >&2; exit 2; }; root=$(cd "$2" && pwd); shift 2 ;; + --list) [ $# -ge 2 ] || { echo "docs-check: --list needs a value" >&2; exit 2; }; list="$2"; shift 2 ;; + -h|--help) sed -n '2,5p' "$0" | sed 's/^# \{0,1\}//'; exit 0 ;; + *) echo "docs-check: unknown argument '$1'" >&2; exit 2 ;; + esac +done +case "$list" in ""|properties|items|codes) ;; *) echo "docs-check: --list takes properties, items or codes" >&2; exit 2 ;; esac + +[ -d "$root/kit/.toolkit/msbuild" ] || { echo "docs-check: no kit at $root/kit/.toolkit/msbuild" >&2; exit 2; } +work=$(mktemp -d) +trap 'rm -rf "$work"' EXIT INT TERM +cd "$root" + +# Kit inventory with XML comments removed, one process for every file: +# "P " property set, "I" item, "R" property read, "C" diagnostic code. +find kit/.toolkit/msbuild -type f \( -name '*.props' -o -name '*.targets' -o -name '*.cs.txt' \) -exec awk ' + FNR == 1 { incomment = 0; pg = 0; ig = 0 } + { sub(/\r$/, "") } + FILENAME ~ /\.cs\.txt$/ { + rest = $0 + while (match(rest, /"MSKIT_[A-Z]+[0-9][0-9][0-9]"/)) { print "C", substr(rest, RSTART + 1, RLENGTH - 2), FILENAME ":" FNR; rest = substr(rest, RSTART + RLENGTH) } + next + } + { + line = $0; out = "" + while (line != "") { + if (incomment) { e = index(line, "-->"); if (e == 0) break; line = substr(line, e + 3); incomment = 0 } + else { s = index(line, " - - - -``` - -Then declare the next version in `Directory.Version.props` next to them, and commit `.toolkit/` with the rest: - -```xml - - - 1.4.0 - - -``` - -`samples/MinimalLibrary` is a complete example: one library, one test project, every check passing. - -## Update - -```sh -sh .toolkit/update.sh # the version pinned in .toolkit/kit.json, or the latest release -sh .toolkit/update.sh --version 0.2.0 # move to another version -sh .toolkit/update.sh --add EF --dry-run # preview adding an optional part -``` - -`pwsh .toolkit/update.ps1` takes the same options as `-Version`, `-Add`, `-Remove`, `-DryRun`, `-Source`, `-Sha256`, `-Repo`, `-Root`. The script downloads `msbuildkit-.zip` from the release, compares its SHA-256 with the published `.sha256` (and with `kit.json` when the version did not change), and rewrites `.toolkit/msbuild/` only. `.toolkit/.local/` and your own files are left alone. `--source ` installs a local build of the kit. - -## Parts - -Default parts are always installed; add optional ones with `--add`. - -| Part | Default | What it does | -| --- | --- | --- | -| `Core` | yes | Developer-vs-CI switch, solution and git roots, branch, `TargetFramework(s)` switching, Roslyn project-type detection | -| `Trunk` (`DragoAnt.MSBuildKit`) | yes | Language defaults, product and copyright, the version engine, global usings, reference audits | -| `Vcs.GitHub` | yes | Maps `GITHUB_*` variables: CI detection, run number, release tag, pull-request number, repository URL | -| `TfmConstants` | yes | `IsNET8` … `IsNET14`, `IsNET8_OR_GREATER` …, `IsNETSTANDARD` for conditions; final in item and target conditions and `Directory.Build.targets`, in the props phase only once the framework is known (an inner build of a multi-targeted project) | -| `Packaging` | yes | nuget.org metadata defaults and the `MSKIT_PKG` checks | -| `Testing`, `Testing.XUnit.v3` | yes | Test-project detection, Microsoft.Testing.Platform, coverage, TRX, xUnit v3 | -| `Locals.Secrets`, `Locals.DirectorySecrets`, `Locals.Compile` | yes | Local-only secrets and source files kept outside the repository | -| `PrivateAssets` | yes | `MSKit_ProjectReferenceAsPrivateAssets` / `MSKit_PackageReferenceAsPrivateAssets` | -| `PackageAsProj` | `--add` | Swap a `PackageReference` for a `ProjectReference` to debug a dependency from source | -| `Project.RoslynComponent`, `.CodeAnalyzer`, `.CodeFixer`, `.SourceGenerator` | `--add` | Packaging for analyzers, code fixes and source generators (`*.Analyzers`, `*.CodeFixes`, `*.SourceGenerator`) | -| `ProjMetadata` | `--add` | Writes each project's packages and assemblies to YAML (`-p:ProjMetadataOutDir=`) | -| `EF` | `--add` | Entity Framework migration scripts (`add-migration.ps1`, `apply-migrations.ps1`, …) | - -## Versions - -The default strategy is `ReleaseTag`. `VersionPrefix` is the next version the repository will release. - -| Build | Version | Template property | -| --- | --- | --- | -| Local (no `GITHUB_RUN_ID`) | `9999.0.0` | always | -| Release tag `v2.0.0`, `2.0.0`, `v2.1.0-beta.1` | `2.0.0`, `2.0.0`, `2.1.0-beta.1` | `MSKit_ReleaseVersionTemplate` = `{releaseTag}` | -| Pull request 15, run 7 | `1.4.0-pr.15.7` | `MSKit_PullRequestVersionTemplate` = `{prefix}-pr.{prNumber}.{buildNumber}` | -| Any branch, run 7 | `1.4.0-ci.7` | `MSKit_VersionTemplate` = `{prefix}-ci.{buildNumber}` | - -A `Version` passed by the caller (`-p:Version=2.0.0`, or set in `Directory.Build.props` above the import) always wins. Other strategies: `SemVer`, `SemVer4`, `DateBased`, `VersionTag`, `Manual` (`MSKit_VersionStrategy`). Placeholders: `{prefix}`, `{buildNumber}`, `{pipelineId}`, `{releaseTag}`, `{prNumber}`, `{branchSlug}`, `{branchTicket}`, `{branchAspectSuffix}`, `{buildDateUtcDash}`, `{buildDateUtcDot}`, `{versionTag}`, `{commitShaShort}`. - -## Packaging rules - -Defaults apply to projects with `IsPackable=True` and yield to any value you set. - -| Property | Default | Why | -| --- | --- | --- | -| `PackageLicenseExpression` | `MIT` (owner layer) | [licensing](https://learn.microsoft.com/nuget/create-packages/package-authoring-best-practices#licensing) | -| `Authors`, `Copyright` | owner name; `Copyright (c) ` | [copyright](https://learn.microsoft.com/nuget/create-packages/package-authoring-best-practices#copyright) | -| `PackageIcon` | `.toolkit/res/package.icon.png` (128×128), packed as `icon.png` | [icon](https://learn.microsoft.com/nuget/create-packages/package-authoring-best-practices#icon) | -| `PackageReadmeFile` | generated from `MSKit_PackageReadmeFrom` ([package readme](./docs/package-readme.md)), else `package.readme.md` (else `README.md`) next to the csproj, packed as `readme.md` | [README](https://learn.microsoft.com/nuget/reference/msbuild-targets#packagereadmefile) | -| `RepositoryUrl`, `PackageProjectUrl` | from `GITHUB_REPOSITORY`, else the git remote via Source Link | [repository](https://learn.microsoft.com/nuget/create-packages/package-authoring-best-practices#repository-type-and-url) | -| `PackageReleaseNotes` | the GitHub release page of the tag, else the releases page; on other hosts, the releases page the generated readme links (`MSKit_PackageReadmeFrom`) | [release notes](https://learn.microsoft.com/nuget/create-packages/package-authoring-best-practices#release-notes) | -| `PublishRepositoryUrl`, `EmbedUntrackedSources`, `Deterministic`, `ContinuousIntegrationBuild` (CI) | `true` | [Source Link](https://learn.microsoft.com/dotnet/standard/library-guidance/sourcelink) | -| `IncludeSymbols`, `SymbolPackageFormat` | `true`, `snupkg` | [symbols](https://learn.microsoft.com/nuget/create-packages/symbol-packages-snupkg) | -| `EnablePackageValidation` | `true`; baseline from `MSKit_PackageValidationBaselineVersion` | [package validation](https://learn.microsoft.com/dotnet/fundamentals/apicompat/package-validation/overview) | -| `NuGetAudit`, `NuGetAuditMode` | `true`, `all` | [auditing](https://learn.microsoft.com/nuget/concepts/auditing-packages) | -| `GenerateDocumentationFile` | `True` | XML docs ship with the package | - -### Checks - -`dotnet pack` reports these as warnings locally and as errors on CI (`MSKit_PackageChecksAsErrors`). Skip one with `MSKit_SkipPackageChecks=MSKIT_PKG004` (several: separate with `;`, all: `All`) or by adding the code to `NoWarn`. - -| Code | Fires when | Source | -| --- | --- | --- | -| `MSKIT_PKG001` | `Description` is missing, the SDK default or the package id | [description](https://learn.microsoft.com/nuget/create-packages/package-authoring-best-practices#description) | -| `MSKIT_PKG002` | `Description` is shorter than `MSKit_PackageDescriptionMinLength` (30) | [description](https://learn.microsoft.com/nuget/create-packages/package-authoring-best-practices#description) | -| `MSKIT_PKG003` | no README is packed | [README](https://learn.microsoft.com/nuget/create-packages/package-authoring-best-practices#readme) | -| `MSKIT_PKG004` | no `PackageTags` | [tags](https://learn.microsoft.com/nuget/create-packages/package-authoring-best-practices#tags) | -| `MSKIT_PKG005` | no icon | [icon](https://learn.microsoft.com/nuget/create-packages/package-authoring-best-practices#icon) | -| `MSKIT_PKG006` | no licence expression or file | [licensing](https://learn.microsoft.com/nuget/create-packages/package-authoring-best-practices#licensing) | -| `MSKIT_PKG007` | deprecated `PackageLicenseUrl` is set | [licensing](https://learn.microsoft.com/nuget/create-packages/package-authoring-best-practices#licensing) | -| `MSKIT_PKG008` | deprecated `PackageIconUrl` is set | [icon](https://learn.microsoft.com/nuget/create-packages/package-authoring-best-practices#icon) | -| `MSKIT_PKG009` | the package README has relative images | [allowed images](https://learn.microsoft.com/nuget/nuget-org/package-readme-on-nuget-org#allowed-domains-for-images-and-badges) | -| `MSKIT_PKG010` | the package README contains HTML | [supported Markdown](https://learn.microsoft.com/nuget/nuget-org/package-readme-on-nuget-org#supported-markdown-features) | -| `MSKIT_PKG011` | the package README uses GitHub alerts (`> [!NOTE]`) | [supported Markdown](https://learn.microsoft.com/nuget/nuget-org/package-readme-on-nuget-org#supported-markdown-features) | -| `MSKIT_PKG012` | the package README loads images from hosts nuget.org blocks | [allowed images](https://learn.microsoft.com/nuget/nuget-org/package-readme-on-nuget-org#allowed-domains-for-images-and-badges) | -| `MSKIT_PKG013` | the package version is not SemVer 2.0 | [package version](https://learn.microsoft.com/nuget/create-packages/package-authoring-best-practices#package-version) | -| `MSKIT_PKG014` | no repository or project URL | [repository](https://learn.microsoft.com/nuget/create-packages/package-authoring-best-practices#repository-type-and-url) | -| `MSKIT_PKG015` | the icon is not a 128×128 PNG (`MSKit_PackageIconSize`) | [icon](https://learn.microsoft.com/nuget/create-packages/package-authoring-best-practices#icon) | -| `MSKIT_PKG016` | no `PackageReleaseNotes` | [release notes](https://learn.microsoft.com/nuget/create-packages/package-authoring-best-practices#release-notes) | -| `MSKIT_PKG017` | the package README has relative links | [package README](https://learn.microsoft.com/nuget/nuget-org/package-readme-on-nuget-org) | -| `MSKIT_PKG018` | an open-source licence with an "All rights reserved" copyright | [copyright](https://learn.microsoft.com/nuget/create-packages/package-authoring-best-practices#copyright) | -| `MSKIT_PKG019` | the package README contains a Mermaid diagram | [supported Markdown](https://learn.microsoft.com/nuget/nuget-org/package-readme-on-nuget-org#supported-markdown-features) | - -With `MSKit_PackageReadmeFrom` the checks read the generated readme, and three more warnings come from the generator; they stay warnings on CI ([package readme](./docs/package-readme.md#warnings)): - -| Code | Fires when | Source | -| --- | --- | --- | -| `MSKIT_PKG020` | the README is missing, a `nuget:` marker is unbalanced, or links cannot be rewritten (no repository URL, unknown host, no commit) | [package readme](./docs/package-readme.md) | -| `MSKIT_PKG021` | an image comes from a host nuget.org does not render images from; names the image and its README line | [allowed images](https://learn.microsoft.com/nuget/nuget-org/package-readme-on-nuget-org#allowed-domains-for-images-and-badges) | -| `MSKIT_PKG022` | the repository is private or internal (`MSKit_RepositoryVisibility`), so the readme links will not open | [package readme](./docs/package-readme.md) | - -## Tests and coverage - -Projects whose name matches `MSKit_TestsProjectNameRegex` (default: ending in `.Tests`, `.UnitTests`, `.IntegrationTests`) are test projects; `*.TestsSuite`, `*.TestsFixtures` and `*.Fixtures` are test helper libraries (or set `IsTestsLibProject=True`). Add `"test": { "runner": "Microsoft.Testing.Platform" }` to `global.json`, then: - -```sh -dotnet test --solution MyRepo.slnx -c Release --report-xunit-trx --report-xunit-junit --coverage --coverage-output-format cobertura --results-directory TestResults -``` - -| Property | Default | Meaning | -| --- | --- | --- | -| `MSKit_TestingFramework` | `xunit.v3` | Framework wiring | -| `MSKit_TestsAssertions` | `AwesomeAssertions` | `AwesomeAssertions`, `FluentAssertions` (`[7.2.2]`), `Shouldly` or `None` | -| `MSKit_TestsMocking` | `NSubstitute` | `NSubstitute` or `None` | -| `EnableMicrosoftTestingPlatform` | `True` | `False` falls back to VSTest | -| `InternalsVisibleToAllTestsProjects` | `True` | Code projects expose internals to the test projects under `MSKit_TestsDir` | -| `MSKit_PackageVersion_*` | see `.toolkit/msbuild/*/implicit.package.targets` | Central package versions the kit provides; `MSKit_ImplicitPackageVersions=False` turns them off | - -## Other properties - -| Property | Default | Meaning | -| --- | --- | --- | -| `IsPackable` | `True` (owner layer) | Test projects are never packable | -| `TreatWarningsAsErrors` | `True` (owner layer) | NuGet vulnerability warnings `NU1901`-`NU1904` stay warnings | -| `MSKit_IsStableBranchRegex` | `^(main\|release/.+)$` | Branches that use `MSKit_StableVersionTemplate` | -| `MSKit_PrereleasePackagePrefix` | `DragoAnt.` | `MSKIT_PRE001` warns about prerelease versions of these ids on a stable branch | -| `MSKit_RestrictPackageReference` items | `Moq` (error) | Banned or discouraged packages (`MSKIT_RES001`/`002`) | -| `MSKit_BeforeInitProps`, `MSKit_AfterInitProps`, `MSKit_BeforeInitTargets`, `MSKit_AfterInitTargets` | empty | Your own files imported around the kit | - -To use the kit for another owner, fork it and edit `kit/.toolkit/msbuild/init.company.props` and `kit/.toolkit/res/package.icon.png`; nothing else names an owner. - -## Migrating from MSBuild.Routine - -1. Remove the submodule: `git rm .msbuild` and delete `.gitmodules` (and `submodules:` from your workflows). -2. Install the kit (`--add PackageAsProj` if you use `Directory.PackageAsProj.targets`) and replace the `.msbuild\shared\init.props` / `init.targets` imports with the `.toolkit/msbuild/` ones. -3. Drop what is now a default: `Copyright`, `PackageLicenseExpression`, `RepositoryUrl`, `PackageReleaseNotes`, `TargetFrameworkStrategy`, and the `.msbuild\tfm.constants.props` import. -4. Remove from `Directory.Packages.props` the versions the kit provides (`xunit.v3*`, `xunit.runner.visualstudio`, `Microsoft.NET.Test.Sdk`, `Microsoft.Testing.Extensions.CodeCoverage`, `NSubstitute*`, your assertion library, `coverlet.collector`), and the explicit `Microsoft.Testing.Extensions.CodeCoverage` references from test projects; `MSKIT_DUP001` lists any you missed. Keep FluentAssertions 7 with `MSKit_TestsAssertions=FluentAssertions`. -5. Add `Directory.Version.props` with the next `VersionPrefix`, and publish releases with tags such as `v2.0.1`. +## Documentation -| MSBuild.Routine | MSBuildKit | -| --- | --- | -| `IncrementVersionType` | `MSKit_VersionStrategy` | -| `IsDevEnv`, `Branch`, `BuildNumber`, `CommitSha` | `MSKit_IsDevEnv`, `MSKit_Branch`, `MSKit_BuildNumber`, `MSKit_CommitSha` | -| `SlnSecretsId`, `SecretsTemplatesDir` | `MSKit_SlnSecretsId`, `MSKit_Templates` | -| `TestsDir` | `MSKit_TestsDir` | -| `SkipCheck_*` | `MSKit_SkipAudit_*` | -| `IsCodeAnalizerLib` | the `Project.CodeAnalyzer` part (`*.Analyzers` projects) | +[docs/README.md](./docs/README.md) lists the pages in reading order. Every property is in the [property reference](./docs/reference/properties.md), every warning and error in the [code reference](./docs/reference/codes.md). Coming from MSBuild.Routine: [migration guide](./docs/migrating-from-msbuild-routine.md). ## Contributing diff --git a/tools/docs-check.sh b/tools/docs-check.sh index fac2bb1..55a7b35 100644 --- a/tools/docs-check.sh +++ b/tools/docs-check.sh @@ -1,6 +1,7 @@ #!/bin/sh -# Keeps the documentation honest against the kit: every property, item and MSKIT_ code the kit -# defines has a row in docs/reference/, every MSKit_ name and MSKIT_ code the docs mention exists in +# Keeps the documentation honest against the kit: every property and item the kit defines has a row +# in docs/reference/properties.md, every code it reports a section headed by the code id without the +# underscore in docs/reference/codes.md, every MSKit_ name and MSKIT code the docs mention exists in # the kit, and every relative link resolves (anchors included). # Usage: sh tools/docs-check.sh [--root DIR] [--list properties|items|codes] set -eu @@ -11,7 +12,7 @@ while [ $# -gt 0 ]; do case "$1" in --root) [ $# -ge 2 ] || { echo "docs-check: --root needs a value" >&2; exit 2; }; root=$(cd "$2" && pwd); shift 2 ;; --list) [ $# -ge 2 ] || { echo "docs-check: --list needs a value" >&2; exit 2; }; list="$2"; shift 2 ;; - -h|--help) sed -n '2,5p' "$0" | sed 's/^# \{0,1\}//'; exit 0 ;; + -h|--help) sed -n '2,6p' "$0" | sed 's/^# \{0,1\}//'; exit 0 ;; *) echo "docs-check: unknown argument '$1'" >&2; exit 2 ;; esac done @@ -24,12 +25,13 @@ cd "$root" # Kit inventory with XML comments removed, one process for every file: # "P " property set, "I" item, "R" property read, "C" diagnostic code. +# A code is keyed by its id without the underscore (MSKIT_VER006 and MSKITVER006 are one code). find kit/.toolkit/msbuild -type f \( -name '*.props' -o -name '*.targets' -o -name '*.cs.txt' \) -exec awk ' FNR == 1 { incomment = 0; pg = 0; ig = 0 } { sub(/\r$/, "") } FILENAME ~ /\.cs\.txt$/ { rest = $0 - while (match(rest, /"MSKIT_[A-Z]+[0-9][0-9][0-9]"/)) { print "C", substr(rest, RSTART + 1, RLENGTH - 2), FILENAME ":" FNR; rest = substr(rest, RSTART + RLENGTH) } + while (match(rest, /"MSKIT_?[A-Z]+[0-9][0-9][0-9]"/)) { print "C", id(substr(rest, RSTART + 1, RLENGTH - 2)), FILENAME ":" FNR; rest = substr(rest, RSTART + RLENGTH) } next } { @@ -51,8 +53,9 @@ find kit/.toolkit/msbuild -type f \( -name '*.props' -o -name '*.targets' -o -na rest = out while (match(rest, /\$\(MSKit_[A-Za-z0-9_]+/)) { print "R", substr(rest, RSTART + 2, RLENGTH - 2), FILENAME ":" FNR; rest = substr(rest, RSTART + RLENGTH) } rest = out - while (match(rest, /MSKIT_[A-Z]+[0-9][0-9][0-9]/)) { print "C", substr(rest, RSTART, RLENGTH), FILENAME ":" FNR; rest = substr(rest, RSTART + RLENGTH) } - }' {} + > "$work/raw" + while (match(rest, /MSKIT_?[A-Z]+[0-9][0-9][0-9]/)) { print "C", id(substr(rest, RSTART, RLENGTH)), FILENAME ":" FNR; rest = substr(rest, RSTART + RLENGTH) } + } + function id(c) { sub(/^MSKIT_/, "MSKIT", c); return c }' {} + > "$work/raw" # Public names: MSKit_* (set or read) and Is* (set); never the kit's own _-prefixed state. # Each is listed with the first place that sets it, else the first place that reads it. @@ -91,10 +94,14 @@ awk -v dir="$work" ' { mention() } fence { next } /^#+[[:space:]]/ { h = $0; sub(/^#+[[:space:]]+/, "", h); sub(/[[:space:]]+#+[[:space:]]*$/, "", h); h = tolower(h); gsub(/[^a-z0-9 _-]/, "", h); gsub(/ /, "-", h); print "S\t" f "\t" h } - (f == "docs/reference/properties.md" || f == "docs/reference/codes.md") && /^\|/ { + f == "docs/reference/properties.md" && /^\|/ { split($0, cell, "|"); c = cell[2] while (match(c, /`[^`]+`/)) { print "D\t" f "\t" substr(c, RSTART + 1, RLENGTH - 2); c = substr(c, RSTART + RLENGTH) } } + f == "docs/reference/codes.md" && /^#+[[:space:]]+`?MSKIT_?[A-Z]+[0-9][0-9][0-9]/ { + match($0, /MSKIT_?[A-Z]+[0-9][0-9][0-9]/); c = substr($0, RSTART, RLENGTH) + print (c ~ /^MSKIT_/ ? "U\t" f ":" FNR "\t" c : "D\t" f "\t" c) + } { line = $0; gsub(/`[^`]*`/, "", line) while (match(line, /\]\([^) ]+\)/)) { print "L\t" f "\t" FNR "\t" d "\t" substr(line, RSTART + 2, RLENGTH - 3); line = substr(line, RSTART + RLENGTH) } } function mention( rest, t) { @@ -102,7 +109,7 @@ awk -v dir="$work" ' rest = $0 while (match(rest, /MSKit_[A-Za-z0-9_]+[*<]?/)) { t = substr(rest, RSTART, RLENGTH); rest = substr(rest, RSTART + RLENGTH); if (t !~ /[*<]$/) print "N\t" f ":" FNR "\t" t } rest = $0 - while (match(rest, /MSKIT_[A-Z]+[0-9][0-9][0-9]/)) { print "M\t" f ":" FNR "\t" substr(rest, RSTART, RLENGTH); rest = substr(rest, RSTART + RLENGTH) } + while (match(rest, /MSKIT_?[A-Z]+[0-9][0-9][0-9]/)) { t = substr(rest, RSTART, RLENGTH); rest = substr(rest, RSTART + RLENGTH); sub(/^MSKIT_/, "MSKIT", t); print "M\t" f ":" FNR "\t" t } }' $(cat "$work/docs.lst") > "$work/docs.tsv" awk -v dir="$work" -F'\t' ' @@ -123,10 +130,12 @@ awk -v dir="$work" -F'\t' ' $1 == "D" { if ($2 == "docs/reference/properties.md") documented[$3] = 1; else documentedCode[$3] = 1; next } $1 == "N" { if (!($3 in known)) problem($2 " mentions " $3 ", which the kit does not define or read"); next } $1 == "M" { if (!($3 in code)) problem($2 " mentions " $3 ", which the kit never reports"); next } + $1 == "U" { problem($2 ": write the heading as " gensub_id($3) " so its anchor is the code id without the underscore"); next } + function gensub_id(c) { sub(/^MSKIT_/, "MSKIT", c); return c } $1 == "L" { links[++nl] = $0; next } END { for (i = 1; i <= n; i++) if (!(order[i] in documented)) problem(what[order[i]] " " order[i] " (" known[order[i]] ") has no row in docs/reference/properties.md") - for (i = 1; i <= nc; i++) if (!(corder[i] in documentedCode)) problem("code " corder[i] " (" code[corder[i]] ") has no row in docs/reference/codes.md") + for (i = 1; i <= nc; i++) if (!(corder[i] in documentedCode)) problem("code " corder[i] " (" code[corder[i]] ") has no section in docs/reference/codes.md") relative = 0 for (i = 1; i <= nl; i++) { split(links[i], l, "\t"); src = l[2]; ln = l[3]; base = l[4]; target = l[5] From 2c111a65652467a63f52e77103eb34530d3c4640 Mon Sep 17 00:00:00 2001 From: VasiliyF <5789590+vfofanov@users.noreply.github.com> Date: Tue, 6 Oct 2026 06:28:08 +0200 Subject: [PATCH 3/8] Add getting-started, install, parts, build and versioning pages Content moved out of the README and checked against the kit: the update rewrites more than .toolkit/msbuild/, any tag build is a release build, the TFM constants start at net7.0 and include net48 and netstandard, and the owner layer's NoWarn and unconditional values are named. --- docs/README.md | 24 ++++++++ docs/build.md | 114 +++++++++++++++++++++++++++++++++++++ docs/getting-started.md | 111 ++++++++++++++++++++++++++++++++++++ docs/install-and-update.md | 61 ++++++++++++++++++++ docs/parts.md | 33 +++++++++++ docs/versioning.md | 74 ++++++++++++++++++++++++ 6 files changed, 417 insertions(+) create mode 100644 docs/README.md create mode 100644 docs/build.md create mode 100644 docs/getting-started.md create mode 100644 docs/install-and-update.md create mode 100644 docs/parts.md create mode 100644 docs/versioning.md diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..c3d4cd5 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,24 @@ +# MSBuildKit documentation + +Read in this order; each page stands on its own, so jump to the one you need. + +| # | Page | Read it to | +| --- | --- | --- | +| 1 | [Getting started](./getting-started.md) | install the kit, build, test and pack a first library | +| 2 | [Install and update](./install-and-update.md) | move to another version, add or remove parts, install a local build | +| 3 | [Parts](./parts.md) | see what each part does, which are installed by default and the order they load in | +| 4 | [Build defaults and checks](./build.md) | know the language defaults, the owner layer, CI detection, target-framework rules and reference audits | +| 5 | [Versioning](./versioning.md) | control how package versions are computed | +| 6 | [Local files and secrets](./local-files.md) | keep secrets and machine-only source files out of the repository | +| 7 | [Optional parts](./optional-parts.md) | debug a dependency from source, list a project's packages, run Entity Framework migrations | +| 8 | [Packaging](./packaging.md) | understand the nuget.org defaults and the `MSKIT_PKG` checks | +| 9 | [Package readme](./package-readme.md) | generate every package's readme from the repository README | +| 10 | [Testing](./testing.md) | set up test projects, assertions, mocking and coverage | +| 11 | [Roslyn components](./roslyn.md) | build analyzers, code fixes and source generators | +| 12 | [Customizing](./customizing.md) | change the owner, import your own files, override a default | +| 13 | [Troubleshooting](./troubleshooting.md) | fix a failing build or update | +| 14 | [Code reference](./reference/codes.md) | look up any `MSKIT` warning or error | +| 15 | [Property reference](./reference/properties.md) | look up any property the kit sets or reads | +| 16 | [Migrating from MSBuild.Routine](./migrating-from-msbuild-routine.md) | move a repository off the older submodule | + +Contributing to the kit itself: [CONTRIBUTING.md](../CONTRIBUTING.md). diff --git a/docs/build.md b/docs/build.md new file mode 100644 index 0000000..65cfd5a --- /dev/null +++ b/docs/build.md @@ -0,0 +1,114 @@ +# Build defaults and checks + +What the Core, Trunk, TfmConstants and PrivateAssets parts set for every project, and the checks that keep a repository consistent. Every default yields to a value you set ([load order](./parts.md#load-order)); every property is in the [property reference](./reference/properties.md). + +## Language and product defaults + +| Property | Default | +| --- | --- | +| [`Nullable`](https://learn.microsoft.com/dotnet/csharp/language-reference/compiler-options/language#nullable), [`ImplicitUsings`](https://learn.microsoft.com/dotnet/core/project-sdk/overview#implicit-using-directives) | `enable` | +| [`LangVersion`](https://learn.microsoft.com/dotnet/csharp/language-reference/configure-language-version) | `latest` | +| [`EnforceCodeStyleInBuild`](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/overview#code-style-analysis) | `True` | +| `GeneratePackageOnBuild` | `False` | +| `AssemblyTitle` | the project name | +| `Authors`, `LegalTrademarks` | `$(ManufacturerName)` | +| `Company` | `$(FullManufacturerName)` | +| `Copyright` | `Copyright (c) $(FullManufacturerName)` — no "All rights reserved", which contradicts an open-source licence | + +The owner layer adds `IsPackable=True`, `TreatWarningsAsErrors=True`, `NoWarn` `CS1591;xUnit1051` and keeps NuGet vulnerability warnings `NU1901`-`NU1904` as warnings ([Customizing](./customizing.md#the-owner-layer)). + +## Developer machine or CI + +`MSKit_IsDevEnv` is `True` unless the Vcs.GitHub part sees `GITHUB_RUN_ID`, which GitHub Actions sets for every job. It decides: + +| | Developer machine | CI | +| --- | --- | --- | +| Version | `9999.0.0` | from the tag, pull request or run ([Versioning](./versioning.md)) | +| `MSKIT_PKG` checks | warnings | errors ([Packaging](./packaging.md)) | +| [`ContinuousIntegrationBuild`](https://learn.microsoft.com/dotnet/core/project-sdk/msbuild-props#continuousintegrationbuild) | unset (PDBs keep real paths) | `true` | +| Local secrets | created from templates | deleted ([Local files](./local-files.md)) | +| `TreatWarningsAsErrors` drift check | on | off: CI passes its own `-p:TreatWarningsAsErrors` | + +Set `MSKit_IsDevEnv` yourself to build "as CI" locally (`-p:MSKit_IsDevEnv=False`). + +## Roots, branch and commit + +| Property | Value | +| --- | --- | +| `MSKit_SlnFileDirectory` | the folder of the `.slnx` being built (`SlnxFilePath`), else the folder that holds `.toolkit/`; ends with `/` | +| `MSKit_SlnFileName` | the solution name without extension, when building a `.slnx` | +| `MSKit_GitRoot` | the nearest folder above the project with a `.git` (worktrees included) | +| `MSKit_ToolkitDir`, `MSKit_ToolkitMSBuildDir` | `.toolkit/` and `.toolkit/msbuild/`, absolute, ending with `/` | +| `MSKit_ProjectObjDir` | the project's `obj` folder, absolute | +| `MSKit_Branch` | on GitHub Actions `GITHUB_HEAD_REF` (pull requests) or `GITHUB_REF_NAME`; else read from `.git/HEAD`; else `unknown-branch` | +| `MSKit_IsStableBranch` | `true` when `MSKit_Branch` matches `MSKit_IsStableBranchRegex` (case-insensitive): the owner layer's `^(main\|release/.+)$`, the kit's own default `^(main\|master\|release/.+)$` | +| `MSKit_CommitSha` | `GITHUB_SHA` on CI, empty on a developer machine | + +The kit imports these files from `MSKit_SlnFileDirectory` when they exist: `Directory.Version.props`, `Directory.GlobalUsings.props` / `.targets`, `Directory.Packages.Metadata.targets`, `Directory.PackageAsProj.targets` ([Customizing](./customizing.md#extension-files)). + +## Target frameworks declared once + +Declare `TargetFramework` or `TargetFrameworks` once, in `Directory.Build.props` above the kit import, and leave it out of the projects. A project that needs the other shape declares only that one; the kit clears the shared value of the shape the project does not use, so a library can multi-target while one tool project targets a single framework. The props phase reads the two files as text for this, because the SDK fixes cross-targeting before the targets phase. + +| Code | When | +| --- | --- | +| [`MSKITSHARED006`](./reference/codes.md#mskitshared006), [`MSKITSHARED007`](./reference/codes.md#mskitshared007) | error: the csproj repeats the shared `TargetFramework` / `TargetFrameworks` value | +| [`MSKITSHARED008`](./reference/codes.md#mskitshared008), [`MSKITSHARED009`](./reference/codes.md#mskitshared009) | warning: the csproj sets a different value of the same shape; `MSKit_SkipAudit_TargetFrameworkOverride=True` accepts it | +| [`MSKITSHARED010`](./reference/codes.md#mskitshared010) | error: the csproj declares both shapes | + +`MSKit_GuardXmlPeekRoutine=False` and `MSKit_GuardXmlPeekAudit=False` turn off a cheap text pre-check and always parse the csproj; leave them alone unless a declaration is missed. + +## Target framework constants + +The TfmConstants part sets these to `True` for conditions in items, targets and `Directory.Build.targets`: + +| Property | `True` when `TargetFramework` is | +| --- | --- | +| `IsNET7` … `IsNET14` | `net7.0` … `net14.0` | +| `IsNET7_OR_GREATER` … `IsNET14_OR_GREATER` | that version or later, up to `net14.0` | +| `IsNETSTANDARD20`, `IsNETSTANDARD21`, `IsNETSTANDARD` | `netstandard2.0`, `netstandard2.1`, either | +| `IsNETFRAMEWORK` | `net48` (only that one) | +| `IsNETFRAMEWORK_OR_STANDARD` | `IsNETFRAMEWORK` or `IsNETSTANDARD` | + +`TargetFrameworkVersionMajor` holds `7` … `14`. They are evaluated twice: in the props phase, where only a multi-targeted inner build knows its framework, and again in the targets phase, after the csproj body, where a single `TargetFramework` is known too. A `PropertyGroup` in the csproj itself runs between the two and cannot rely on them for a single-framework project. + +```xml + + + +``` + +## Global usings + +Every project except analyzers and source generators gets `global using` for `System.Diagnostics.CodeAnalysis` and `System.Runtime.CompilerServices`, and `global using static` for `System.Runtime.CompilerServices.MethodImplOptions` and (not on `netstandard2.0`) `System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes`. `MSKit_IncludeCodeAnalysisGlobalUsings=False` turns them off. Add your own in `Directory.GlobalUsings.props` / `.targets` next to the solution. + +A project with `ExcludeFromCodeCoverage=true` gets the [`[ExcludeFromCodeCoverage]`](https://learn.microsoft.com/dotnet/api/system.diagnostics.codeanalysis.excludefromcodecoverageattribute) assembly attribute; test projects set it by default. + +## Central package versions + +The parts that add package references also provide their versions: as `PackageVersion` items with [central package management](https://learn.microsoft.com/nuget/consume-packages/central-package-management), else on the `PackageReference` itself when it has no version. Each version is a property you can override, `MSKit_PackageVersion_` (lists: [Testing](./testing.md#package-versions), [Roslyn](./roslyn.md#package-versions)); `MSKit_ImplicitPackageVersions=False` turns them all off. A `PackageVersion` of yours for the same id fails restore with [`MSKITDUP001`](./reference/codes.md#mskitdup001) (bypass: `MSKit_SkipAudit_ImplicitPackageDuplicates=True`). + +`PrivateAssets=all` is set on references to `Fody`, `ConfigureAwait.Fody`, `IgnoresAccessChecksToGenerator`, `Grpc.Tools`, `Microsoft.EntityFrameworkCore.Design` and `Microsoft.EntityFrameworkCore.Tools`, so these build-time tools never become package dependencies. Add your own `Update` items in `Directory.Packages.Metadata.targets`. + +## Reference checks + +**Banned and discouraged packages.** Each `MSKit_RestrictPackageReference` item names a package: + +```xml + + + + +``` + +A reference to a `Type="Error"` package fails with [`MSKITRES001`](./reference/codes.md#mskitres001), a `Type="Warning"` one warns with [`MSKITRES002`](./reference/codes.md#mskitres002). `SkipGlobalRestriction="True"` on one `PackageReference` allows it (an error becomes a warning). The owner layer bans `Moq`. + +**Allow-list mode.** With `MSKit_RestrictProjectReferences=True` (or `MSKit_RestrictPackageReferences`, or `MSKit_RestrictReferences` for both) every reference must carry `Allowed="True"`, else [`MSKITRES003`](./reference/codes.md#mskitres003) / [`MSKITRES004`](./reference/codes.md#mskitres004). Off by default. + +**Prerelease dependencies on a stable branch.** On a stable branch a reference to a prerelease version of a package whose id starts with `MSKit_PrereleasePackagePrefix` (owner layer: `DragoAnt.`) warns with [`MSKITPRE001`](./reference/codes.md#mskitpre001); `MSKit_PrereleasePackageCheckAsWarning=false` makes it an error. + +**`TreatWarningsAsErrors` drift.** On a developer machine a csproj that changes the shared `TreatWarningsAsErrors` fails with [`MSKITSHARED020`](./reference/codes.md#mskitshared020); `MSKit_SkipAudit_TreatWarningsAsErrors=True` allows it. + +## Private references + +The PrivateAssets part sets `PrivateAssets="All"` on every `ProjectReference` of a project with `MSKit_ProjectReferenceAsPrivateAssets=True`, and on every `PackageReference` with `MSKit_PackageReferenceAsPrivateAssets=True`, so they do not flow to projects and packages that depend on it. diff --git a/docs/getting-started.md b/docs/getting-started.md new file mode 100644 index 0000000..b3feba8 --- /dev/null +++ b/docs/getting-started.md @@ -0,0 +1,111 @@ +# Getting started + +From an empty repository to a packed library with passing tests. You need the [.NET SDK](https://dotnet.microsoft.com/download) 8 or later (the kit's own CI uses 10), and `sh` (Git Bash on Windows) or [PowerShell 7](https://learn.microsoft.com/powershell/scripting/install/installing-powershell). + +## 1 Install + +From the repository root: + +```sh +curl -fsSLO https://github.com/DragoAnt/MSBuildKit/releases/latest/download/update.sh +sh update.sh && rm update.sh +``` + +```powershell +Invoke-WebRequest https://github.com/DragoAnt/MSBuildKit/releases/latest/download/update.ps1 -OutFile update.ps1 +pwsh ./update.ps1; Remove-Item update.ps1 +``` + +The script downloads the latest release, checks its SHA-256, and writes: + +| Path | What | +| --- | --- | +| `.toolkit/msbuild/` | the kit: one folder per [part](./parts.md) and the `init.props` / `init.targets` entry points | +| `.toolkit/res/package.icon.png` | the package icon | +| `.toolkit/kit.json` | the pinned version, its SHA-256 and your optional parts | +| `.toolkit/kit.parts`, `.toolkit/update.sh`, `.toolkit/update.ps1` | the part list and the update scripts | +| `Directory.Build.props`, `Directory.Build.targets` | only when missing: one import each | + +If the two `Directory.Build.*` files already exist, the script prints the line each needs instead of editing them: + +```xml + + + + +``` + +Commit `.toolkit/` with the rest of the repository: the kit is reviewed like any other change. [Install and update](./install-and-update.md) has every option. + +## 2 Declare the next version + +Next to `Directory.Build.props`, create `Directory.Version.props`: + +```xml + + + 1.4.0 + + +``` + +`VersionPrefix` is the version the repository will release next. Release builds take their version from the tag, branch and pull-request builds from this prefix ([Versioning](./versioning.md)). + +## 3 Set the test runner + +Tests run on [Microsoft.Testing.Platform](https://learn.microsoft.com/dotnet/core/testing/microsoft-testing-platform-intro). Tell `dotnet test` in `global.json`: + +```json +{ + "sdk": { "version": "10.0.100", "rollForward": "latestFeature" }, + "test": { "runner": "Microsoft.Testing.Platform" } +} +``` + +## 4 Projects + +A library needs a description and tags, nothing else; licence, icon, README, Source Link and symbols come from the kit: + +```xml + + + net8.0;net10.0 + What the package does and what sets it apart, in one or two sentences. + json;masking;logging + + +``` + +Add a `package.readme.md` next to it, or generate every readme from the repository README ([Package readme](./package-readme.md)). + +A project whose name ends in `.Tests` is a test project: [xUnit v3](https://xunit.net/), an assertion library, [NSubstitute](https://nsubstitute.github.io/) and coverage are wired in, so it only references the code it tests: + +```xml + + + + + +``` + +With [central package management](https://learn.microsoft.com/nuget/consume-packages/central-package-management), leave the test packages out of `Directory.Packages.props`: the kit provides their versions ([`MSKITDUP001`](./reference/codes.md#mskitdup001) reports a duplicate). + +## 5 Build, test, pack + +```sh +dotnet build MyRepo.slnx -c Release +dotnet test --solution MyRepo.slnx -c Release --coverage --coverage-output-format cobertura --report-trx --report-xunit-junit --results-directory TestResults +dotnet pack MyRepo.slnx -c Release -o artifacts +``` + +On your machine the version is `9999.0.0` and the package checks are warnings; on GitHub Actions the version comes from the run ([Versioning](./versioning.md)) and the checks are errors ([Packaging](./packaging.md)). + +## A complete example + +[`samples/MinimalLibrary`](../samples/MinimalLibrary) is a repository with one library and one test project that passes every check; the kit's self-test installs the kit into it and builds, tests and packs it on Linux and Windows. + +## Next + +- [Parts](./parts.md): what is installed and how to add an optional part. +- [Customizing](./customizing.md): another owner name, your own files around the kit, a default you want changed. +- [Troubleshooting](./troubleshooting.md) when a build or an update fails. diff --git a/docs/install-and-update.md b/docs/install-and-update.md new file mode 100644 index 0000000..024f354 --- /dev/null +++ b/docs/install-and-update.md @@ -0,0 +1,61 @@ +# Install and update + +The kit is installed and updated by one script, `update.sh` (POSIX `sh`) or its twin `update.ps1` ([PowerShell 7](https://learn.microsoft.com/powershell/scripting/install/installing-powershell)); both produce the same `.toolkit/`. A copy of each lives in `.toolkit/` after the first install. + +```sh +sh .toolkit/update.sh # reinstall the version pinned in .toolkit/kit.json +sh .toolkit/update.sh --version 0.2.1 # move to another version +sh .toolkit/update.sh --add EF --dry-run # preview adding an optional part +sh .toolkit/update.sh --remove EF # uninstall it again +``` + +## Options + +| `update.sh` | `update.ps1` | Meaning | +| --- | --- | --- | +| `--version X.Y.Z` | `-Version` | The kit version to install; a leading `v` is ignored. Default: the version in `.toolkit/kit.json`, else the latest release | +| `--add PART` | `-Add` | Install an optional part; repeat for several. Parts it requires come with it | +| `--remove PART` | `-Remove` | Uninstall an optional part; a default part cannot be removed | +| `--dry-run` | `-DryRun` | List the files under `.toolkit/msbuild/` that would be added, removed or changed, and change nothing | +| `--source DIR\|ZIP` | `-Source` | Install a local build of the kit: a folder holding `.toolkit/msbuild/init.props`, or a release zip | +| `--sha256 HEX` | `-Sha256` | The SHA-256 the release zip (or a `--source` zip) must have | +| `--repo OWNER/NAME` | `-Repo` | The GitHub repository to download from. Default: the one in `kit.json`, else `DragoAnt/MSBuildKit` | +| `--root DIR` | `-Root` | The repository root. Default: the current folder | + +**Moving to the newest release:** once `kit.json` exists, a run without `--version` reinstalls the pinned version. Pass the version you want; the [releases page](https://github.com/DragoAnt/MSBuildKit/releases) lists them, with what changed. + +## What a run checks and writes + +1. It downloads `msbuildkit-.zip` and its `.sha256` from the release, and stops unless the zip matches the published hash. When the version is the one already pinned, the zip must also match the `sha256` in `kit.json`, so a release replaced after you installed it is refused. +2. It selects the parts: every default part, the optional parts recorded in `kit.json`, plus `--add`, minus `--remove`, plus everything those require ([Parts](./parts.md)). +3. It replaces `.toolkit/msbuild/` with the selected parts, copies `.toolkit/res/`, `.toolkit/kit.parts` and the two update scripts, and rewrites `kit.json`. +4. It creates `Directory.Build.props` and `Directory.Build.targets` when they are missing; when they exist without the kit import, it prints the line to add. + +`.toolkit/.local/` and every other file of yours are left alone. `--dry-run` reports step 3 for `.toolkit/msbuild/` only and writes nothing. + +## `kit.json` + +```json +{ + "repository": "DragoAnt/MSBuildKit", + "version": "0.2.1", + "sha256": "", + "parts": ["PackageAsProj", "EF"] +} +``` + +Commit it with `.toolkit/`. `parts` lists only the optional parts you chose; default parts are always installed. A `--source` install records the local build's version (or `0.0.0-local`) and the zip's hash, empty for a folder. + +## Installing a local build + +To try a change to the kit in a consumer repository before it is released: + +```sh +sh /kit/.toolkit/update.sh --source /kit --root +``` + +`tools/pack-kit.sh ` in the kit repository builds the same zip a release publishes ([CONTRIBUTING.md](../CONTRIBUTING.md)). + +## Updating from 0.2.0 or earlier + +An update started from 0.2.0 or earlier still runs the old script, which can stop with a `syntax error` after copying the files and before writing `kit.json` when the new `update.sh` differs in length (a CRLF checkout does). Run the same command once more; updates started from 0.2.1 on finish in one run. diff --git a/docs/parts.md b/docs/parts.md new file mode 100644 index 0000000..b905ffd --- /dev/null +++ b/docs/parts.md @@ -0,0 +1,33 @@ +# Parts + +The kit is split into parts, one folder each under `.toolkit/msbuild/` (`DragoAnt.MSBuildKit.`; the trunk is `DragoAnt.MSBuildKit`). Default parts are always installed; optional ones are added with `update.sh --add ` and recorded in `kit.json` ([Install and update](./install-and-update.md)). The list lives in `.toolkit/kit.parts`. + +| Part | Installed | Requires | What it does | Page | +| --- | --- | --- | --- | --- | +| `Core` | default | | Developer machine or CI, solution and git roots, branch, `TargetFramework` / `TargetFrameworks` switching, Roslyn project-type detection | [Build](./build.md) | +| `Trunk` | default | | Language defaults, product and copyright, the version engine, global usings, reference and consistency checks | [Build](./build.md), [Versioning](./versioning.md) | +| `Vcs.GitHub` | default | | Reads the GitHub Actions variables: CI detection, run number, tag, pull request, repository URL | [Versioning](./versioning.md#ci-variables) | +| `TfmConstants` | default | | `IsNET8`, `IsNET8_OR_GREATER`, `IsNETSTANDARD` and the rest, for conditions | [Build](./build.md#target-framework-constants) | +| `Packaging` | default | | nuget.org metadata defaults, the readme generator and the `MSKIT_PKG` checks | [Packaging](./packaging.md) | +| `Testing` | default | | Test-project detection, Microsoft.Testing.Platform, assertions, mocking, `InternalsVisibleTo` | [Testing](./testing.md) | +| `Testing.XUnit.v3` | default | `Testing` | The xUnit v3 wiring | [Testing](./testing.md) | +| `Locals.Secrets`, `Locals.DirectorySecrets`, `Locals.Compile` | default | | Secrets and source files that stay on the developer's machine | [Local files](./local-files.md) | +| `PrivateAssets` | default | | Keeps a project's references from flowing to its dependents | [Build](./build.md#private-references) | +| `Project.RoslynComponent` | optional | | The shared base of the three below | [Roslyn](./roslyn.md) | +| `Project.CodeAnalyzer` | optional | `Project.RoslynComponent` | Analyzer projects | [Roslyn](./roslyn.md) | +| `Project.CodeFixer` | optional | `Project.RoslynComponent`, `Project.CodeAnalyzer` | Code-fix projects, packed into the analyzer's package | [Roslyn](./roslyn.md) | +| `Project.SourceGenerator` | optional | `Project.RoslynComponent` | Source-generator projects | [Roslyn](./roslyn.md) | +| `PackageAsProj` | optional | | Swap a `PackageReference` for a `ProjectReference` to debug a dependency from source | [Optional parts](./optional-parts.md#packageasproj) | +| `ProjMetadata` | optional | | Writes each project's packages and assemblies to YAML | [Optional parts](./optional-parts.md#projmetadata) | +| `EF` | optional | | Entity Framework migration scripts | [Optional parts](./optional-parts.md#ef) | + +## Load order + +`Directory.Build.props` imports `.toolkit/msbuild/init.props`, which imports each installed part's `init.props` in a fixed order; `Directory.Build.targets` imports `init.targets` the same way. A part that is not installed is skipped. + +| Phase | Order | +| --- | --- | +| props | `MSKit_BeforeInitProps` → owner layer (`init.company.props`) → Core → Vcs.GitHub → Locals.Compile → Locals.DirectorySecrets → Locals.Secrets → Project.RoslynComponent → Project.CodeFixer → Project.CodeAnalyzer → Project.SourceGenerator → TfmConstants → Trunk → Packaging → Testing → Testing.XUnit.v3 → EF → Trunk `init.last.props` (version engine) → `MSKit_AfterInitProps` | +| targets | `MSKit_BeforeInitTargets` → TfmConstants → owner layer (`init.company.targets`) → Core → Locals.* → Project.CodeAnalyzer → Project.SourceGenerator → Trunk → Packaging → Testing → Testing.XUnit.v3 → the `init.last.targets` of Trunk, Project.RoslynComponent, Testing.XUnit.v3, Testing, Project.CodeAnalyzer, PrivateAssets, PackageAsProj, ProjMetadata → every part's `audit/*.targets` → `MSKit_AfterInitTargets` | + +In the props phase a default is written as ``, so the **first** writer wins: a value you set in `Directory.Build.props` above the kit import beats the owner layer, which beats the parts. The exceptions are the owner layer's `ManufacturerName`, `FullManufacturerName` and `NoWarn`, which it sets unconditionally ([Customizing](./customizing.md#the-owner-layer)). The csproj body runs after all props, so a value set there wins too, except for the few properties the kit reads in the props phase (the test-project switches, `TargetFramework` detection); those pages say so. How to hook in your own files: [Customizing](./customizing.md). diff --git a/docs/versioning.md b/docs/versioning.md new file mode 100644 index 0000000..330c9be --- /dev/null +++ b/docs/versioning.md @@ -0,0 +1,74 @@ +# Versioning + +The Trunk part computes `Version` and `PackageVersion` from a template; the Vcs.GitHub part feeds it the CI facts. `VersionPrefix`, declared in `Directory.Version.props`, is the next version the repository will release. + +## The default: release tags + +With the owner layer's `MSKit_VersionStrategy=ReleaseTag`: + +| Build | Version | Template | +| --- | --- | --- | +| Developer machine (no `GITHUB_RUN_ID`) | `9999.0.0` | always, whatever the strategy (except `Manual`) | +| Tag `v2.0.0`, `2.0.0`, `v2.1.0-beta.1` | `2.0.0`, `2.0.0`, `2.1.0-beta.1` | `MSKit_ReleaseVersionTemplate` = `{releaseTag}` | +| Pull request 15, run 7 | `1.4.0-pr.15.7` | `MSKit_PullRequestVersionTemplate` = `{prefix}-pr.{prNumber}.{buildNumber}` | +| A stable branch (`main`, `release/*`), run 7 | `1.4.0-ci.7` | `MSKit_StableVersionTemplate` = `{prefix}-ci.{buildNumber}` | +| Any other branch, run 7 | `1.4.0-ci.7` | `MSKit_VersionTemplate` = `{prefix}-ci.{buildNumber}` | + +A **tag build** is any GitHub Actions run with `GITHUB_REF_TYPE=tag`: publishing a GitHub release creates one, and so does pushing a tag without a release. A leading `v` or `V` is removed; the rest must be [SemVer 2.0](https://semver.org/), or restore fails with [`MSKITVER006`](./reference/codes.md#mskitver006). When the tag's `MAJOR.MINOR.PATCH` differs from `VersionPrefix`, [`MSKITVER007`](./reference/codes.md#mskitver007) reminds you to bump `VersionPrefix` after the release, so branch builds sort above it. + +The template is picked in this order: a tag build with `MSKit_ReleaseVersionTemplate` set; a pull-request build with `MSKit_PullRequestVersionTemplate` set; a stable branch (`MSKit_IsStableBranch`, [Build](./build.md#roots-branch-and-commit)) → `MSKit_StableVersionTemplate`; anything else → `MSKit_VersionTemplate`. The version is rendered twice — in the props phase, so `dotnet msbuild -getProperty:Version` answers, and again before compile and pack, so a `VersionPrefix` or template set in a csproj is honoured. + +## Other strategies + +Set `MSKit_VersionStrategy` in `Directory.Build.props`. Any template can be overridden in `Directory.Build.props` or a csproj. + +| Strategy | `VersionPrefix` default | `MSKit_VersionTemplate` | `MSKit_StableVersionTemplate` | +| --- | --- | --- | --- | +| `ReleaseTag` (owner layer) | `0.0.0` | `{prefix}-ci.{buildNumber}` | `{prefix}-ci.{buildNumber}` | +| `SemVer` (the kit's own default) | `1.0.0` | `{prefix}-{buildDateUtcDash}-{branchTicket}{branchAspectSuffix}` | `{prefix}` | +| `SemVer4` | `1.0.0` | `{prefix}.{buildNumber}-{buildDateUtcDash}-{branchTicket}{branchAspectSuffix}` | `{prefix}.{buildNumber}` | +| `DateBased` | the build date `yyyy.M.d` | `{buildDateUtcDot}.{buildNumber}-{branchTicket}{branchAspectSuffix}` | `{buildDateUtcDot}.{buildNumber}` | +| `VersionTag` | | `{versionTag}` | `{versionTag}` | +| `Manual` | | the engine is off: set `Version` yourself | | + +Only `ReleaseTag` defines release-tag and pull-request templates; with the others a tag or pull-request build uses the branch templates unless you set them. `DateBased` uses the run id modulo 65535 as `{buildNumber}` (never 0). `VersionTag` needs `-p:VersionTag=1.2.3`, else [`MSKITVER004`](./reference/codes.md#mskitver004). + +## Placeholders + +| Placeholder | Value | +| --- | --- | +| `{prefix}` | `VersionPrefix` | +| `{buildNumber}` | `MSKit_BuildNumber`: the CI run number (`GITHUB_RUN_NUMBER`), `0` locally | +| `{pipelineId}` | `MSKit_CIPipelineId`: the CI run id (`GITHUB_RUN_ID`) | +| `{releaseTag}` | the tag without its leading `v` (tag builds only) | +| `{prNumber}` | the pull request number (pull-request builds only) | +| `{branchSlug}` | the branch with every character outside `[0-9A-Za-z-]` replaced by `-` | +| `{branchTicket}` | the first `[A-Za-z]+-[0-9]+` in `{branchSlug}` (`feat/ABC-12-x` → `ABC-12`), else `{branchSlug}` | +| `{branchAspectSuffix}` | the part of `{branchSlug}` from its first `--` on, else empty | +| `{buildDateUtcDash}` | the UTC build time as `yyyyMMdd-HHmmss` | +| `{buildDateUtcDot}` | the UTC build date as `yyyy.M.d` | +| `{versionTag}` | `VersionTag` | +| `{commitShaShort}` | the first 8 characters of `MSKit_CommitSha` | + +An unknown placeholder fails the build with [`MSKITVER002`](./reference/codes.md#mskitver002). Each project reads the clock on its own; set `MSKit_BuildDateTimeUtc` (property or environment variable) once in CI so every project of one build gets the same date. + +## Setting the version yourself + +- **`-p:Version=2.0.0`**, or `Version` set in `Directory.Build.props` above the kit import, always wins: the engine renders nothing (`MSKit_ExplicitVersion`). +- **`` in a csproj** fails with [`MSKITVER001`](./reference/codes.md#mskitver001) while a template strategy is active, because it would be ignored: declare `VersionPrefix` in `Directory.Version.props`, or set `MSKit_VersionStrategy=Manual`. + +## CI variables + +The Vcs.GitHub part maps the [GitHub Actions variables](https://docs.github.com/actions/reference/variables-reference#default-environment-variables) onto the kit's properties. Each one except `MSKit_IsDevEnv`, `MSKit_IsGitHubCI` and `MSKit_VcsProvider` yields to a value you set, which is how another CI system can drive the engine: set `MSKit_IsDevEnv=False` and the properties below from its own variables. + +| Property | From | +| --- | --- | +| `MSKit_IsDevEnv` | `False` when `GITHUB_RUN_ID` is set | +| `MSKit_IsGitHubCI` | `True` when `GITHUB_ACTIONS=true` or `GITHUB_RUN_ID` is set | +| `MSKit_CIPipelineId` | `GITHUB_RUN_ID` | +| `MSKit_BuildNumber` | `GITHUB_RUN_NUMBER` (the run id would overflow a version part) | +| `MSKit_CommitSha` | `GITHUB_SHA` | +| `MSKit_IsReleaseTag`, `MSKit_ReleaseTagRaw`, `MSKit_ReleaseTag` | `GITHUB_REF_TYPE=tag`, `GITHUB_REF_NAME`, and the latter without a leading `v` | +| `MSKit_PullRequestNumber` | from `GITHUB_REF` (`refs/pull//…`) on `pull_request` / `pull_request_target` events, or from a `GITHUB_REF_NAME` of `/merge` | +| `RepositoryUrl` | `GITHUB_SERVER_URL/GITHUB_REPOSITORY` | +| `MSKit_VcsProvider` | `GitHub` | From ef8e227d2fa7a5ef4add35f5941e821e4073c9b6 Mon Sep 17 00:00:00 2001 From: VasiliyF <5789590+vfofanov@users.noreply.github.com> Date: Tue, 6 Oct 2026 06:32:03 +0200 Subject: [PATCH 4/8] Add local files, optional parts and packaging pages Packaging separates the settings every project gets from the metadata packable projects get, and names the symbol, release-notes and readme fallbacks the README left out. Code links point at the code reference anchors. --- docs/build.md | 16 ++++----- docs/getting-started.md | 2 +- docs/local-files.md | 56 ++++++++++++++++++++++++++++++ docs/optional-parts.md | 76 +++++++++++++++++++++++++++++++++++++++++ docs/package-readme.md | 6 ++-- docs/packaging.md | 65 +++++++++++++++++++++++++++++++++++ docs/versioning.md | 8 ++--- 7 files changed, 213 insertions(+), 16 deletions(-) create mode 100644 docs/local-files.md create mode 100644 docs/optional-parts.md create mode 100644 docs/packaging.md diff --git a/docs/build.md b/docs/build.md index 65cfd5a..9956b9a 100644 --- a/docs/build.md +++ b/docs/build.md @@ -52,9 +52,9 @@ Declare `TargetFramework` or `TargetFrameworks` once, in `Directory.Build.props` | Code | When | | --- | --- | -| [`MSKITSHARED006`](./reference/codes.md#mskitshared006), [`MSKITSHARED007`](./reference/codes.md#mskitshared007) | error: the csproj repeats the shared `TargetFramework` / `TargetFrameworks` value | -| [`MSKITSHARED008`](./reference/codes.md#mskitshared008), [`MSKITSHARED009`](./reference/codes.md#mskitshared009) | warning: the csproj sets a different value of the same shape; `MSKit_SkipAudit_TargetFrameworkOverride=True` accepts it | -| [`MSKITSHARED010`](./reference/codes.md#mskitshared010) | error: the csproj declares both shapes | +| [`MSKIT_SHARED006`](./reference/codes.md#mskitshared006), [`MSKIT_SHARED007`](./reference/codes.md#mskitshared007) | error: the csproj repeats the shared `TargetFramework` / `TargetFrameworks` value | +| [`MSKIT_SHARED008`](./reference/codes.md#mskitshared008), [`MSKIT_SHARED009`](./reference/codes.md#mskitshared009) | warning: the csproj sets a different value of the same shape; `MSKit_SkipAudit_TargetFrameworkOverride=True` accepts it | +| [`MSKIT_SHARED010`](./reference/codes.md#mskitshared010) | error: the csproj declares both shapes | `MSKit_GuardXmlPeekRoutine=False` and `MSKit_GuardXmlPeekAudit=False` turn off a cheap text pre-check and always parse the csproj; leave them alone unless a declaration is missed. @@ -86,7 +86,7 @@ A project with `ExcludeFromCodeCoverage=true` gets the [`[ExcludeFromCodeCoverag ## Central package versions -The parts that add package references also provide their versions: as `PackageVersion` items with [central package management](https://learn.microsoft.com/nuget/consume-packages/central-package-management), else on the `PackageReference` itself when it has no version. Each version is a property you can override, `MSKit_PackageVersion_` (lists: [Testing](./testing.md#package-versions), [Roslyn](./roslyn.md#package-versions)); `MSKit_ImplicitPackageVersions=False` turns them all off. A `PackageVersion` of yours for the same id fails restore with [`MSKITDUP001`](./reference/codes.md#mskitdup001) (bypass: `MSKit_SkipAudit_ImplicitPackageDuplicates=True`). +The parts that add package references also provide their versions: as `PackageVersion` items with [central package management](https://learn.microsoft.com/nuget/consume-packages/central-package-management), else on the `PackageReference` itself when it has no version. Each version is a property you can override, `MSKit_PackageVersion_` (lists: [Testing](./testing.md#package-versions), [Roslyn](./roslyn.md#package-versions)); `MSKit_ImplicitPackageVersions=False` turns them all off. A `PackageVersion` of yours for the same id fails restore with [`MSKIT_DUP001`](./reference/codes.md#mskitdup001) (bypass: `MSKit_SkipAudit_ImplicitPackageDuplicates=True`). `PrivateAssets=all` is set on references to `Fody`, `ConfigureAwait.Fody`, `IgnoresAccessChecksToGenerator`, `Grpc.Tools`, `Microsoft.EntityFrameworkCore.Design` and `Microsoft.EntityFrameworkCore.Tools`, so these build-time tools never become package dependencies. Add your own `Update` items in `Directory.Packages.Metadata.targets`. @@ -101,13 +101,13 @@ The parts that add package references also provide their versions: as `PackageVe ``` -A reference to a `Type="Error"` package fails with [`MSKITRES001`](./reference/codes.md#mskitres001), a `Type="Warning"` one warns with [`MSKITRES002`](./reference/codes.md#mskitres002). `SkipGlobalRestriction="True"` on one `PackageReference` allows it (an error becomes a warning). The owner layer bans `Moq`. +A reference to a `Type="Error"` package fails with [`MSKIT_RES001`](./reference/codes.md#mskitres001), a `Type="Warning"` one warns with [`MSKIT_RES002`](./reference/codes.md#mskitres002). `SkipGlobalRestriction="True"` on one `PackageReference` allows it (an error becomes a warning). The owner layer bans `Moq`. -**Allow-list mode.** With `MSKit_RestrictProjectReferences=True` (or `MSKit_RestrictPackageReferences`, or `MSKit_RestrictReferences` for both) every reference must carry `Allowed="True"`, else [`MSKITRES003`](./reference/codes.md#mskitres003) / [`MSKITRES004`](./reference/codes.md#mskitres004). Off by default. +**Allow-list mode.** With `MSKit_RestrictProjectReferences=True` (or `MSKit_RestrictPackageReferences`, or `MSKit_RestrictReferences` for both) every reference must carry `Allowed="True"`, else [`MSKIT_RES003`](./reference/codes.md#mskitres003) / [`MSKIT_RES004`](./reference/codes.md#mskitres004). Off by default. -**Prerelease dependencies on a stable branch.** On a stable branch a reference to a prerelease version of a package whose id starts with `MSKit_PrereleasePackagePrefix` (owner layer: `DragoAnt.`) warns with [`MSKITPRE001`](./reference/codes.md#mskitpre001); `MSKit_PrereleasePackageCheckAsWarning=false` makes it an error. +**Prerelease dependencies on a stable branch.** On a stable branch a reference to a prerelease version of a package whose id starts with `MSKit_PrereleasePackagePrefix` (owner layer: `DragoAnt.`) warns with [`MSKIT_PRE001`](./reference/codes.md#mskitpre001); `MSKit_PrereleasePackageCheckAsWarning=false` makes it an error. -**`TreatWarningsAsErrors` drift.** On a developer machine a csproj that changes the shared `TreatWarningsAsErrors` fails with [`MSKITSHARED020`](./reference/codes.md#mskitshared020); `MSKit_SkipAudit_TreatWarningsAsErrors=True` allows it. +**`TreatWarningsAsErrors` drift.** On a developer machine a csproj that changes the shared `TreatWarningsAsErrors` fails with [`MSKIT_SHARED020`](./reference/codes.md#mskitshared020); `MSKit_SkipAudit_TreatWarningsAsErrors=True` allows it. ## Private references diff --git a/docs/getting-started.md b/docs/getting-started.md index b3feba8..87c4a61 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -88,7 +88,7 @@ A project whose name ends in `.Tests` is a test project: [xUnit v3](https://xuni ``` -With [central package management](https://learn.microsoft.com/nuget/consume-packages/central-package-management), leave the test packages out of `Directory.Packages.props`: the kit provides their versions ([`MSKITDUP001`](./reference/codes.md#mskitdup001) reports a duplicate). +With [central package management](https://learn.microsoft.com/nuget/consume-packages/central-package-management), leave the test packages out of `Directory.Packages.props`: the kit provides their versions ([`MSKIT_DUP001`](./reference/codes.md#mskitdup001) reports a duplicate). ## 5 Build, test, pack diff --git a/docs/local-files.md b/docs/local-files.md new file mode 100644 index 0000000..22cc82b --- /dev/null +++ b/docs/local-files.md @@ -0,0 +1,56 @@ +# Local files and secrets + +Three default parts keep values that must not be committed — connection strings, API keys, a developer's own settings — in the same per-user folder [`dotnet user-secrets`](https://learn.microsoft.com/aspnet/core/security/app-secrets) uses, and create them from templates that **are** committed. All three only act when a project (or the solution) opts in. + +| Folder | Property | +| --- | --- | +| `%APPDATA%\Microsoft\UserSecrets\` (Windows), `~/.microsoft/usersecrets/` (Linux, macOS) | `MSKit_LocalSecretsBaseDirectory` | +| `//` for a project | `LocalSecretsDir` | +| the templates: `.toolkit/.local/` | `MSKit_Templates` | + +`update.sh` never touches `.toolkit/.local/`. Commit the templates with placeholders only — they reach every clone; the real values live only in the per-user folder. + +## Locals.Secrets — a project's `secrets.json` + +A project with a [`UserSecretsId`](https://learn.microsoft.com/aspnet/core/security/app-secrets#enable-secret-storage) gets, on a developer machine: + +1. the template `.toolkit/.local/.secrets.json`, created as `{}` when missing; +2. `secrets.json` in its `LocalSecretsDir`, copied from the template when missing (your edits there are kept); +3. that file linked into the project as `appsettings.UserSecrets.json` (`UserSecretsLinkNamePrefix` changes the `appsettings` part), so it is one click away in the IDE. + +| Property | Default | Effect | +| --- | --- | --- | +| `MSKit_CopyUserSecretsIdToOutput` | empty | `true` copies the file to the build output | +| `MSKit_CopyUserSecretsIdToPublish` | empty | `true` copies it to the publish output | +| `MSKit_CopySecretsToProject`, `CopySecretsToProjectFileName` | `false`, empty | copy the file into the project folder under that name — and **replace the project's `.gitignore`** with that one name | +| `MSKit_SecretsCleanUp` | `True` on CI | delete `secrets.json` before restore and build | + +## Locals.DirectorySecrets — solution-wide MSBuild secrets + +For values MSBuild itself needs (a private feed's key, a signing password), turn on a solution-level props or targets file in `Directory.Build.props`: + +```xml + + True + +``` + +On a developer machine every project then imports `//Directory.Secrets.props` (`MSKit_UseSlnSecretsTargets` does the same for `Directory.Secrets.targets`, in the targets phase), created from `.toolkit/.local/Directory.Secrets.props` when missing; the template is created as an empty `` when it is missing too. A file created during a build is imported from the next one on. + +| Property | Default | Effect | +| --- | --- | --- | +| `MSKit_SlnSecretsId` | `MSKit_SlnFileName` | The folder name under the user-secrets folder. It is empty when a project is built on its own rather than through its `.slnx`: set it in `Directory.Build.props` for such builds | +| `MSKit_UseSlnSecretsProps`, `MSKit_UseSlnSecretsTargets` | `False` | Turn the two files on | +| `MSKit_SlnSecretsCleanUp` | `True` on CI | Delete the two files before restore and build | + +## Locals.Compile — source files that stay local + +A `LocalCompile` item compiles a file that lives in the project's `LocalSecretsDir`, for code that differs per developer (a fake credential provider, a debug switch): + +```xml + + + +``` + +The project needs a `UserSecretsId`. The file is copied from the template `.toolkit/.local/LocalCompile/LocalSettings.cs` when it is missing; when the template is missing too, the kit writes a stub (`namespace ;`) to it. This runs on every build, CI included, so the template must compile on its own. `LocalCompileTemplateDir` moves the template folder. diff --git a/docs/optional-parts.md b/docs/optional-parts.md new file mode 100644 index 0000000..d2a26d0 --- /dev/null +++ b/docs/optional-parts.md @@ -0,0 +1,76 @@ +# Optional parts + +Three parts that are not installed by default. Add one with `sh .toolkit/update.sh --add ` ([Install and update](./install-and-update.md)); the Roslyn parts have [their own page](./roslyn.md). + +## PackageAsProj + +Debug a dependency from its source: swap its `PackageReference` for a `ProjectReference` without editing every project. Point the package at a project file and switch it on: + +```xml + + + + + + +``` + +The kit imports `Directory.PackageAsProj.targets` from the solution folder when it exists, so the switch reaches every project; the same `Update` item works in a single csproj. Run `dotnet restore --force` after switching either way. `AsProj="false"` keeps the package and checks it is restored again. + +| Code | When | +| --- | --- | +| [`MSKIT_PAP001`](./reference/codes.md#mskitpap001) | a package switched to a project is still resolved from the package: restore with `--force` | +| [`MSKIT_PAP002`](./reference/codes.md#mskitpap002) | a package switched back is not restored yet: restore with `--force`, or set `PackageAsProj_SkipChecks=True` | + +Keep `Directory.PackageAsProj.targets` out of git if the paths point at your own checkouts. + +## ProjMetadata + +Lists each project's packages and resolved assemblies, per target framework, as YAML — for dependency reports and audits: + +```sh +dotnet build MyRepo.slnx -p:ProjMetadataOutDir=artifacts/metadata +``` + +Each build of a project writes `/..metadata.yml` once its references are resolved: + +```yaml +project: Acme.Masking +description: Masks sensitive values in JSON. +tfm: net8.0 +useCPM: true +isPackage: True +packages: + Acme.Shared: 1.2.0 +assemblies: + Acme.Shared: + PackageId: Acme.Shared + AssemblyVersion: 1.2.0.0 + PackageVersion: 1.2.0 +``` + +With central package management, `packages` lists every `PackageVersion` the build knows, not only the ones the project references. Without `ProjMetadataOutDir` nothing is written. + +## EF + +PowerShell scripts around [`dotnet ef`](https://learn.microsoft.com/ef/core/cli/dotnet) migrations. They read the context and its project from `ef-scripts-init.ps1` in the repository root: + +```powershell +# ef-scripts-init.ps1 +$dbContext = 'OrdersDbContext' +$dbContextProj = "$PSScriptRoot/src/Acme.Orders.Data/Acme.Orders.Data.csproj" +``` + +```sh +pwsh .toolkit/msbuild/DragoAnt.MSBuildKit.EF/scripts/add-migration.ps1 -migrationName AddShippingAddress +pwsh .toolkit/msbuild/DragoAnt.MSBuildKit.EF/scripts/apply-migrations.ps1 +pwsh .toolkit/msbuild/DragoAnt.MSBuildKit.EF/scripts/remove-migration.ps1 +``` + +| Script | Runs | +| --- | --- | +| `add-migration.ps1 -migrationName ` | `dotnet ef migrations add`; a migration with an empty `Up` is deleted again | +| `apply-migrations.ps1` | `dotnet ef database update` | +| `remove-migration.ps1` | `dotnet ef migrations remove` | + +Each script first builds the context project (`--no-restore`) and runs `dotnet tool update --global dotnet-ef`, which installs or updates the **global** `dotnet-ef` tool. `-initFile ` uses another init file (absolute, or relative to the scripts folder). `MSKit_EFScriptsDir` holds the scripts folder for your own MSBuild targets. diff --git a/docs/package-readme.md b/docs/package-readme.md index c965196..4992077 100644 --- a/docs/package-readme.md +++ b/docs/package-readme.md @@ -73,9 +73,9 @@ These stay warnings on CI; skip one with `MSKit_SkipPackageChecks` or `NoWarn` l | Code | Fires when | | --- | --- | -| `MSKIT_PKG020` | the README is missing, a marker is unbalanced, a path leaves the repository, or links cannot be rewritten (no repository URL, an unknown host, no commit) | -| `MSKIT_PKG021` | an image is served from a host nuget.org does not render images from; the warning names the image and its README line | -| `MSKIT_PKG022` | the repository is private or internal, so the links will not open for package readers | +| [`MSKIT_PKG020`](./reference/codes.md#mskitpkg020) | the README is missing, a marker is unbalanced, a path leaves the repository, or links cannot be rewritten (no repository URL, an unknown host, no commit) | +| [`MSKIT_PKG021`](./reference/codes.md#mskitpkg021) | an image is served from a host nuget.org does not render images from; the warning names the image and its README line | +| [`MSKIT_PKG022`](./reference/codes.md#mskitpkg022) | the repository is private or internal, so the links will not open for package readers | ## Allowed image hosts diff --git a/docs/packaging.md b/docs/packaging.md new file mode 100644 index 0000000..06c86c7 --- /dev/null +++ b/docs/packaging.md @@ -0,0 +1,65 @@ +# Packaging + +The Packaging part sets what nuget.org expects from a package and checks it on `dotnet pack`. Every default yields to a value you set. The rules come from Microsoft's [package authoring best practices](https://learn.microsoft.com/nuget/create-packages/package-authoring-best-practices) and [package readme on nuget.org](https://learn.microsoft.com/nuget/nuget-org/package-readme-on-nuget-org). + +## Build settings for every project + +These apply to **all** projects, packable or not, so a library and the projects it references are built the same way: + +| Property | Default | Why | +| --- | --- | --- | +| `PublishRepositoryUrl`, `EmbedUntrackedSources`, `Deterministic` | `true` | [Source Link](https://learn.microsoft.com/dotnet/standard/library-guidance/sourcelink) | +| `ContinuousIntegrationBuild` | `true` on CI only, so local PDBs keep real paths | [Source Link](https://learn.microsoft.com/dotnet/standard/library-guidance/sourcelink) | +| `GenerateDocumentationFile` | `True` | XML docs ship with the package | +| `NuGetAudit`, `NuGetAuditMode`, `NuGetAuditLevel` | `true`, `all`, `low` | [auditing packages](https://learn.microsoft.com/nuget/concepts/auditing-packages) | + +## Package metadata + +For projects with `IsPackable=True` (the owner layer makes that the default; test projects and code fixers are never packable): + +| Property | Default | Rule | +| --- | --- | --- | +| `PackageId` | the project name | | +| `PackageLicenseExpression` | `MIT` (owner layer), unless `PackageLicenseFile` is set | [licensing](https://learn.microsoft.com/nuget/create-packages/package-authoring-best-practices#licensing) | +| `PackageRequireLicenseAcceptance` | `false` | | +| `Authors`, `Copyright` | the owner; `Copyright (c) ` ([Build](./build.md#language-and-product-defaults)) | [copyright](https://learn.microsoft.com/nuget/create-packages/package-authoring-best-practices#copyright) | +| `PackageIcon` | `PackageIconPath` (owner layer: `.toolkit/res/package.icon.png`, 128×128) packed as `icon.png` | [icon](https://learn.microsoft.com/nuget/create-packages/package-authoring-best-practices#icon) | +| `PackageReadmeFile` | generated from `MSKit_PackageReadmeFrom` ([Package readme](./package-readme.md)), else `package.readme.md` next to the csproj, else `README.md` next to it; packed as `readme.md`. `MSKit_PackageReadmeSourcePath` names another file | [README](https://learn.microsoft.com/nuget/create-packages/package-authoring-best-practices#readme) | +| `RepositoryType`, `RepositoryUrl`, `PackageProjectUrl` | `git`; `GITHUB_SERVER_URL/GITHUB_REPOSITORY` on GitHub Actions, else the git remote [Source Link](https://learn.microsoft.com/dotnet/standard/library-guidance/sourcelink) reads, without `.git` | [repository](https://learn.microsoft.com/nuget/create-packages/package-authoring-best-practices#repository-type-and-url) | +| `PackageReleaseNotes` | on a `https://github.com/` repository the tag's release page on a tag build, else its releases page; on other hosts the releases page the generated readme links (needs `MSKit_PackageReadmeFrom`). `MSKit_DefaultReleaseNotes=False` turns the default off | [release notes](https://learn.microsoft.com/nuget/create-packages/package-authoring-best-practices#release-notes) | +| `IncludeSymbols`, `SymbolPackageFormat` | `true`, `snupkg` — `false` when `IncludeBuildOutput=false` or `DebugType` is `embedded` / `none`, since there is no PDB to ship | [symbol packages](https://learn.microsoft.com/nuget/create-packages/symbol-packages-snupkg) | +| `EnablePackageValidation` | `true`; `MSKit_PackageValidationBaselineVersion` (or `PackageValidationBaselineVersion`) also compares against that release | [package validation](https://learn.microsoft.com/dotnet/fundamentals/apicompat/package-validation/overview) | + +Folders named `build/`, `buildMultiTargeting/` and `buildTransitive/` next to the csproj are packed under the same names, so a package can ship [MSBuild props and targets](https://learn.microsoft.com/nuget/concepts/msbuild-props-and-targets) by dropping them there. + +Set `MSKit_PackageValidationBaselineVersion` to the last published version once there is one; package validation then reports API breaks against it. + +## Checks + +`dotnet pack` runs nineteen checks on each packable project. They are **warnings on a developer machine and errors on CI** (`MSKit_PackageChecksAsErrors`, default `True` when `MSKit_IsDevEnv` is not); every message names the rule and how to skip it. + +| Code | Fires when | +| --- | --- | +| [`MSKIT_PKG001`](./reference/codes.md#mskitpkg001) | `Description` is missing, the SDK default, or the package id | +| [`MSKIT_PKG002`](./reference/codes.md#mskitpkg002) | `Description` is shorter than `MSKit_PackageDescriptionMinLength` (30) | +| [`MSKIT_PKG003`](./reference/codes.md#mskitpkg003) | no README is packed | +| [`MSKIT_PKG004`](./reference/codes.md#mskitpkg004) | no `PackageTags` | +| [`MSKIT_PKG005`](./reference/codes.md#mskitpkg005) | no icon | +| [`MSKIT_PKG006`](./reference/codes.md#mskitpkg006) | no licence expression or file | +| [`MSKIT_PKG007`](./reference/codes.md#mskitpkg007) | the deprecated `PackageLicenseUrl` is set | +| [`MSKIT_PKG008`](./reference/codes.md#mskitpkg008) | the deprecated `PackageIconUrl` is set | +| [`MSKIT_PKG009`](./reference/codes.md#mskitpkg009) | the README has relative images | +| [`MSKIT_PKG010`](./reference/codes.md#mskitpkg010) | the README contains HTML | +| [`MSKIT_PKG011`](./reference/codes.md#mskitpkg011) | the README uses GitHub alerts (`> [!NOTE]`) | +| [`MSKIT_PKG012`](./reference/codes.md#mskitpkg012) | the README loads images from a host nuget.org blocks | +| [`MSKIT_PKG013`](./reference/codes.md#mskitpkg013) | the version is not SemVer 2.0 (`MSKit_SemVerRegex`) | +| [`MSKIT_PKG014`](./reference/codes.md#mskitpkg014) | no repository or project URL | +| [`MSKIT_PKG015`](./reference/codes.md#mskitpkg015) | the icon is not a 128×128 PNG (`MSKit_PackageIconSize`) | +| [`MSKIT_PKG016`](./reference/codes.md#mskitpkg016) | no `PackageReleaseNotes` | +| [`MSKIT_PKG017`](./reference/codes.md#mskitpkg017) | the README has relative links | +| [`MSKIT_PKG018`](./reference/codes.md#mskitpkg018) | an open-source licence with an "All rights reserved" copyright | +| [`MSKIT_PKG019`](./reference/codes.md#mskitpkg019) | the README contains a Mermaid diagram | + +Images, links, HTML and alerts inside a fenced block or inline code are ignored; a GitHub Actions workflow badge from `github.com` counts as an allowed image. The readme generator adds three warnings of its own, `MSKIT_PKG020`-`022`, which stay warnings on CI ([Package readme](./package-readme.md#warnings)). + +**Skipping a check:** list its code in `MSKit_SkipPackageChecks` (`MSKIT_PKG004;MSKIT_PKG016`) or in `NoWarn`; `MSKit_SkipPackageChecks=All` skips them all. Set it in a csproj to skip for one package. diff --git a/docs/versioning.md b/docs/versioning.md index 330c9be..a9fc710 100644 --- a/docs/versioning.md +++ b/docs/versioning.md @@ -14,7 +14,7 @@ With the owner layer's `MSKit_VersionStrategy=ReleaseTag`: | A stable branch (`main`, `release/*`), run 7 | `1.4.0-ci.7` | `MSKit_StableVersionTemplate` = `{prefix}-ci.{buildNumber}` | | Any other branch, run 7 | `1.4.0-ci.7` | `MSKit_VersionTemplate` = `{prefix}-ci.{buildNumber}` | -A **tag build** is any GitHub Actions run with `GITHUB_REF_TYPE=tag`: publishing a GitHub release creates one, and so does pushing a tag without a release. A leading `v` or `V` is removed; the rest must be [SemVer 2.0](https://semver.org/), or restore fails with [`MSKITVER006`](./reference/codes.md#mskitver006). When the tag's `MAJOR.MINOR.PATCH` differs from `VersionPrefix`, [`MSKITVER007`](./reference/codes.md#mskitver007) reminds you to bump `VersionPrefix` after the release, so branch builds sort above it. +A **tag build** is any GitHub Actions run with `GITHUB_REF_TYPE=tag`: publishing a GitHub release creates one, and so does pushing a tag without a release. A leading `v` or `V` is removed; the rest must be [SemVer 2.0](https://semver.org/), or restore fails with [`MSKIT_VER006`](./reference/codes.md#mskitver006). When the tag's `MAJOR.MINOR.PATCH` differs from `VersionPrefix`, [`MSKIT_VER007`](./reference/codes.md#mskitver007) reminds you to bump `VersionPrefix` after the release, so branch builds sort above it. The template is picked in this order: a tag build with `MSKit_ReleaseVersionTemplate` set; a pull-request build with `MSKit_PullRequestVersionTemplate` set; a stable branch (`MSKit_IsStableBranch`, [Build](./build.md#roots-branch-and-commit)) → `MSKit_StableVersionTemplate`; anything else → `MSKit_VersionTemplate`. The version is rendered twice — in the props phase, so `dotnet msbuild -getProperty:Version` answers, and again before compile and pack, so a `VersionPrefix` or template set in a csproj is honoured. @@ -31,7 +31,7 @@ Set `MSKit_VersionStrategy` in `Directory.Build.props`. Any template can be over | `VersionTag` | | `{versionTag}` | `{versionTag}` | | `Manual` | | the engine is off: set `Version` yourself | | -Only `ReleaseTag` defines release-tag and pull-request templates; with the others a tag or pull-request build uses the branch templates unless you set them. `DateBased` uses the run id modulo 65535 as `{buildNumber}` (never 0). `VersionTag` needs `-p:VersionTag=1.2.3`, else [`MSKITVER004`](./reference/codes.md#mskitver004). +Only `ReleaseTag` defines release-tag and pull-request templates; with the others a tag or pull-request build uses the branch templates unless you set them. `DateBased` uses the run id modulo 65535 as `{buildNumber}` (never 0). `VersionTag` needs `-p:VersionTag=1.2.3`, else [`MSKIT_VER004`](./reference/codes.md#mskitver004). ## Placeholders @@ -50,12 +50,12 @@ Only `ReleaseTag` defines release-tag and pull-request templates; with the other | `{versionTag}` | `VersionTag` | | `{commitShaShort}` | the first 8 characters of `MSKit_CommitSha` | -An unknown placeholder fails the build with [`MSKITVER002`](./reference/codes.md#mskitver002). Each project reads the clock on its own; set `MSKit_BuildDateTimeUtc` (property or environment variable) once in CI so every project of one build gets the same date. +An unknown placeholder fails the build with [`MSKIT_VER002`](./reference/codes.md#mskitver002). Each project reads the clock on its own; set `MSKit_BuildDateTimeUtc` (property or environment variable) once in CI so every project of one build gets the same date. ## Setting the version yourself - **`-p:Version=2.0.0`**, or `Version` set in `Directory.Build.props` above the kit import, always wins: the engine renders nothing (`MSKit_ExplicitVersion`). -- **`` in a csproj** fails with [`MSKITVER001`](./reference/codes.md#mskitver001) while a template strategy is active, because it would be ignored: declare `VersionPrefix` in `Directory.Version.props`, or set `MSKit_VersionStrategy=Manual`. +- **`` in a csproj** fails with [`MSKIT_VER001`](./reference/codes.md#mskitver001) while a template strategy is active, because it would be ignored: declare `VersionPrefix` in `Directory.Version.props`, or set `MSKit_VersionStrategy=Manual`. ## CI variables From 303b33582b9fd6d57f9dd1c340892a5d4c7aa711 Mon Sep 17 00:00:00 2001 From: VasiliyF <5789590+vfofanov@users.noreply.github.com> Date: Tue, 6 Oct 2026 06:36:38 +0200 Subject: [PATCH 5/8] Add testing, Roslyn, customizing and troubleshooting pages Roslyn states that a component imports its role's props itself; the name only sets the role. Testing lists every detected name and the versions the kit provides; customizing names the owner layer's unconditional values and the extension files. --- docs/customizing.md | 66 ++++++++++++++++++++++++++++++++++ docs/roslyn.md | 79 ++++++++++++++++++++++++++++++++++++++++ docs/testing.md | 80 +++++++++++++++++++++++++++++++++++++++++ docs/troubleshooting.md | 42 ++++++++++++++++++++++ 4 files changed, 267 insertions(+) create mode 100644 docs/customizing.md create mode 100644 docs/roslyn.md create mode 100644 docs/testing.md create mode 100644 docs/troubleshooting.md diff --git a/docs/customizing.md b/docs/customizing.md new file mode 100644 index 0000000..92d1eeb --- /dev/null +++ b/docs/customizing.md @@ -0,0 +1,66 @@ +# Customizing + +Three ways to change what the kit does without editing `.toolkit/msbuild/` (an update overwrites it): set a property, add one of the extension files the kit looks for, or hook your own file in around the kit. To use the kit for another owner, change the owner layer. + +## Overriding a default + +Almost every default is written as ``, so the first value set wins ([load order](./parts.md#load-order)): + +| Where you set it | Effect | +| --- | --- | +| `Directory.Build.props`, above the kit import | every project, before the kit's defaults — the place for repository-wide choices such as `MSKit_VersionStrategy` or `MSKit_TestsAssertions` | +| `Directory.Build.props`, below the kit import | every project, after the kit's props-phase defaults — for values the kit sets unconditionally | +| the csproj | one project; it runs after all props, so it wins except for what the kit decides in the props phase (test-project detection, Roslyn role detection, `TargetFramework` shape) | +| `-p:Name=Value` | one build; a global property beats everything | + +## The owner layer + +`.toolkit/msbuild/init.company.props` is loaded before every part and holds the owner's defaults; `init.company.targets` is its targets-phase twin (empty for DragoAnt). + +| Setting | DragoAnt value | Note | +| --- | --- | --- | +| `ManufacturerName`, `FullManufacturerName` | `DragoAnt` | **unconditional**: set them below the kit import to change them; they feed `Authors`, `Company`, `Copyright` | +| `PackageLicenseExpression` | `MIT`, unless `PackageLicenseFile` is set | | +| `PackageIconPath` | `.toolkit/res/package.icon.png` | | +| `MSKit_PrereleasePackagePrefix` | `DragoAnt.` | [prerelease check](./build.md#reference-checks) | +| `MSKit_IsStableBranchRegex` | `^(main\|release/.+)$` | | +| `MSKit_VersionStrategy` | `ReleaseTag` | [Versioning](./versioning.md) | +| `IsPackable` | `True` | test projects and code fixes opt out | +| `TreatWarningsAsErrors` | `True` | | +| `NoWarn` | adds `CS1591;xUnit1051` | missing XML docs on public members; `CancellationToken` overloads in tests. Remove `CS1591` below the kit import to gate on XML docs | +| `WarningsNotAsErrors` | adds `NU1901;NU1902;NU1903;NU1904` | a newly disclosed vulnerability stays a warning, so the fix can be scheduled | +| `MSKit_TestingFramework`, `MSKit_TestsAssertions` | `xunit.v3`, `AwesomeAssertions` | [Testing](./testing.md) | +| `MSKit_RestrictPackageReference` | `Moq` as an error | [reference checks](./build.md#reference-checks) | + +**Another owner:** fork the kit, change `kit/.toolkit/msbuild/init.company.props` (and `.targets`) and `kit/.toolkit/res/package.icon.png`, publish releases from the fork and install with `update.sh --repo /` (recorded in `kit.json`, so later updates come from the fork). No part names an owner. + +## Extension files + +The kit imports these from the solution folder (`MSKit_SlnFileDirectory`) when they exist: + +| File | Phase | Use | +| --- | --- | --- | +| `Directory.Version.props` | props | `VersionPrefix` ([Versioning](./versioning.md)) | +| `Directory.GlobalUsings.props`, `Directory.GlobalUsings.targets` | props, targets | your own `` items | +| `Directory.Packages.Metadata.targets` | targets | `PackageReference Update` items with metadata such as `PrivateAssets` | +| `Directory.PackageAsProj.targets` | targets | the package-to-project switches ([PackageAsProj](./optional-parts.md#packageasproj)) | + +## Hooks around the kit + +Name your own files in `Directory.Build.props` above the kit import; each is imported only when it exists: + +| Property | Imported | +| --- | --- | +| `MSKit_BeforeInitProps` | before the owner layer and every part's props | +| `MSKit_AfterInitProps` | after every part's props, the version engine included | +| `MSKit_BeforeInitTargets` | before every part's targets | +| `MSKit_AfterInitTargets` | after every part's targets and audits | + +```xml + + $(MSBuildThisFileDirectory)build/after-kit.targets + + +``` + +`MSKit_BeforeInitTargets` and `MSKit_AfterInitTargets` are read in the targets phase, so they can also be set in the csproj. diff --git a/docs/roslyn.md b/docs/roslyn.md new file mode 100644 index 0000000..5757e98 --- /dev/null +++ b/docs/roslyn.md @@ -0,0 +1,79 @@ +# Roslyn components + +Four optional parts build [Roslyn](https://github.com/dotnet/roslyn) analyzers, code fixes and source generators and pack them the way the compiler loads them. Install the ones you need; each brings the parts it requires: + +```sh +sh .toolkit/update.sh --add Project.CodeAnalyzer --add Project.CodeFixer --add Project.SourceGenerator +``` + +## Wiring a project + +A project needs **both** of these: + +1. **Its name**, which sets the role: `*.Analyzers` → `IsCodeAnalyzer`, `*.CodeFixes` → `IsCodeFixer`, `*.SourceGenerator` → `IsSourceGenerator` (`MSKit_CodeAnalyzerProjectNameRegex`, `MSKit_CodeFixerProjectNameRegex`, `MSKit_SourceGeneratorProjectNameRegex`). The role turns on the packing targets. +2. **An import of the role's props**, which sets the target framework, the Roslyn references and the package shape. The kit does not import it for you: + +```xml + + + + Analyzers that catch misuse of the Acme masking API at compile time. + + +``` + +| Project | Import | +| --- | --- | +| analyzer (`Acme.Analyzers`) | `$(CodeAnalyzerCommonPropsPath)` | +| code fix (`Acme.CodeFixes`) | `$(CodeFixerCommonPropsPath)` | +| source generator (`Acme.SourceGenerator`) | `$(SourceGeneratorCommonPropsPath)` | + +The analyzer and code-fix props set their role themselves, so a project with another name only needs the import; a source generator with another name also sets `true` above it. Set your own `Description`: the analyzer's default, "Code analyzers description", fails [`MSKIT_PKG002`](./reference/codes.md#mskitpkg002). + +A project whose name matches a role but whose part is not installed fails with [`MSKIT_ROSLYN001`](./reference/codes.md#mskitroslyn001)-[`003`](./reference/codes.md#mskitroslyn003); a name that matches two roles fails with [`MSKIT_CORE001`](./reference/codes.md#mskitcore001). Detection runs in the props phase, so to stop it set `MSKit_DisableAutoDetect=true` (`MSKit_DisableCodeAnalyzerAutoDetect`, …) or narrow the regex in `Directory.Build.props`; set in the csproj, as the error text suggests, it comes too late. + +## What the props set + +Every role (`$(RoslynComponentCommonPropsPath)`, imported by the three above): + +| Setting | Value | +| --- | --- | +| `TargetFramework` | `netstandard2.0` (a shared `TargetFrameworks` is cleared, and the `MSKIT_SHARED008` override warning is skipped) | +| `IsRoslynComponent`, `EnforceExtendedAnalyzerRules`, `DevelopmentDependency` | `True` | +| `IsPackable` | `True` | +| `IncludeBuildOutput`, `IncludeSymbols` | `False`: the dll goes to `analyzers/dotnet/cs/`, not `lib/` | +| `NoPackageAnalysis` | `True` | +| `NoWarn` | adds `RS1036` | +| `PackageTags` | `roslyn;code-analysis` | +| references | `Microsoft.CodeAnalysis.Common`, `Microsoft.CodeAnalysis.CSharp` (private) | + +| Role | Adds | +| --- | --- | +| analyzer | `PackageTags` `analyzer`; packs the dll, its PDB and `AnalyzerReleases.Shipped.md` / `.Unshipped.md` (when present) into `analyzers/dotnet/cs/`; packs the sibling code-fix dll into the same package | +| code fix | `IsPackable=False` (it ships inside the analyzer's package); `Microsoft.CodeAnalysis.Workspaces.Common` | +| source generator | `PackageTags` `source-generator`; `Microsoft.CodeAnalysis.Analyzers`; packs the dll and PDB into `analyzers/dotnet/cs/` | + +Analyzer and source-generator projects get no [global usings](./build.md#global-usings). The analyzer never references `Workspaces` — that would trip RS1038 from [Microsoft.CodeAnalysis.Analyzers](https://github.com/dotnet/roslyn-analyzers) — which is why code fixes live in their own project. + +## Analyzer and code fix in one package + +An analyzer named `Acme.Analyzers` looks for `../Acme.CodeFixes/Acme.CodeFixes.csproj` and packs its dll next to its own. `MSKit_CodeFixer` points at another project; `MSKit_HasCodeFixer=False` turns the lookup off. There is deliberately no `ProjectReference` between the two (NuGet would reject the cycle), so build the solution before packing with `--no-build`. + +## Using an analyzer from the same repository + +A `ProjectReference` with `IsAnalyzer="True"` is turned into an analyzer reference (`OutputItemType="Analyzer"`, `ReferenceOutputAssembly="false"`): + +```xml + +``` + +## Seeing generated code + +A source generator project with `IncludeGenerateResultToProject=true` writes the generated files to `_generated/` in the project (`GenerateResultOutputPath`) and keeps them out of the compile, so they can be committed and reviewed. + +## Package versions + +| Packages | Property | Version | +| --- | --- | --- | +| `Microsoft.CodeAnalysis.Common`, `.CSharp`, `.Workspaces.Common` | `MSKit_PackageVersion_MicrosoftCodeAnalysis` | `4.14.0` — loads in the .NET 9.0.300 and .NET 10 SDK compilers and Visual Studio 17.14+; a newer one fails to load in older compilers | +| `Microsoft.CodeAnalysis.Analyzers` | `MSKit_PackageVersion_MicrosoftCodeAnalysisAnalyzers` | `3.11.0` | diff --git a/docs/testing.md b/docs/testing.md new file mode 100644 index 0000000..9d495b7 --- /dev/null +++ b/docs/testing.md @@ -0,0 +1,80 @@ +# Testing + +The Testing and Testing.XUnit.v3 parts turn a project into a test project by its name: [Microsoft.Testing.Platform](https://learn.microsoft.com/dotnet/core/testing/microsoft-testing-platform-intro) v2, [xUnit v3](https://xunit.net/docs/getting-started/v3/microsoft-testing-platform), code coverage and TRX reports, an assertion library, [NSubstitute](https://nsubstitute.github.io/), and `InternalsVisibleTo` from the code under test. A test project only references what it tests. + +## Which projects are test projects + +| Kind | Name matches | Becomes | +| --- | --- | --- | +| test project | `MSKit_TestsProjectNameRegex`, default `\.(Tests(\.Integration\|\.Unit)?\|IntegrationTests\|UnitTests)$`: `Acme.Tests`, `Acme.Tests.Unit`, `Acme.Tests.Integration`, `Acme.UnitTests`, `Acme.IntegrationTests` | `IsTestsProject=True`: an executable test host, never packable | +| test helper library | `MSKit_TestsLibProjectNameRegex`, default `\.(TestsSuite\|TestsFixtures\|Fixtures)$` | `IsTestsLibProject=True`: shared fixtures and base classes with the assertion and xUnit libraries, not runnable | + +Detection runs in the props phase, because the test framework's own targets read the result before `Directory.Build.targets`. So a project whose name does not match cannot just set `IsTestsProject` in its csproj — that fails with [`MSKIT_TEST013`](./reference/codes.md#mskittest013). Use the explicit endpoint instead: + +```xml + + + xunit.v3 + + + +``` + +`$(TestsLibProjectCommonPropsPath)` does the same for a helper library; a helper library may also set `IsTestsLibProject=True` directly. Override either regex in `Directory.Build.props`, or switch detection off with `MSKit_DisableTestsProjectAutoDetect=true` / `MSKit_DisableTestsLibProjectAutoDetect=true`. + +## What a test project gets + +- **Runner:** `OutputType=Exe`, `EnableMicrosoftTestingPlatform=True`, `xunit.v3.mtp-v2`, [`Microsoft.Testing.Extensions.CodeCoverage`](https://learn.microsoft.com/dotnet/core/testing/microsoft-testing-platform-extensions-code-coverage) and [`Microsoft.Testing.Extensions.TrxReport`](https://learn.microsoft.com/dotnet/core/testing/microsoft-testing-platform-extensions-test-reports). On net8.0 and net9.0 `TestingPlatformDotnetTestSupport=True` bridges SDK 8/9 `dotnet test` to it. `EnableMicrosoftTestingPlatform=False` falls back to [VSTest](https://learn.microsoft.com/dotnet/core/testing/unit-testing-platform-vs-vstest) (`Microsoft.NET.Test.Sdk`, `xunit.runner.visualstudio`, `xunit.v3`). +- **Exit code:** `TestingPlatformCommandLineArguments` defaults to `--ignore-exit-code 8`, so a project whose tests are all skipped does not fail the run. +- **Libraries:** the assertion library of `MSKit_TestsAssertions` and the mocking library of `MSKit_TestsMocking`, as package references with global usings, plus `global using Xunit` and `System.Diagnostics.CodeAnalysis`. `MSKit_TestsImplicitReferences=False` leaves all of them out. +- **Coverage:** `ExcludeFromCodeCoverage=True`, so the test assembly itself is not counted. +- **Config files:** `testconfig.json` (`MTPTestConfigFileName`) and `xunit.runner.json` (`XunitRunnerConfigFileName`) next to the csproj are copied to the output. `MSKit_ScaffoldTestConfig=True` writes starter files on a developer build when they are missing. + +| Property | Default | Values | +| --- | --- | --- | +| `MSKit_TestingFramework` | `xunit.v3` | `xunit.v3`, or your own wiring through `MSKit_TestingFramework_CommonPropsPath` / `MSKit_TestingFramework_LibCommonPropsPath` | +| `MSKit_TestsAssertions` | `AwesomeAssertions` | [`AwesomeAssertions`](https://awesomeassertions.org/), [`FluentAssertions`](https://fluentassertions.com/) (pinned to `[7.2.2]`, the last version under the Apache licence), [`Shouldly`](https://docs.shouldly.org/), `None` | +| `MSKit_TestsMocking` | `NSubstitute` | `NSubstitute` (with [NSubstitute.Analyzers](https://github.com/nsubstitute/NSubstitute.Analyzers)), `None` | + +## Running + +Add the runner to `global.json` (`"test": { "runner": "Microsoft.Testing.Platform" }`), then: + +```sh +dotnet test --solution MyRepo.slnx -c Release --coverage --coverage-output-format cobertura --report-trx --report-xunit-junit --results-directory TestResults +``` + +`--coverage` comes from the code-coverage extension, `--report-trx` from the TRX extension, `--report-xunit-junit` from xUnit itself. + +## `InternalsVisibleTo` + +Every project that is neither a test project nor a helper library exposes its internals to **every** test project and helper library found under `MSKit_TestsDir` (default: the solution folder, else the git root), and to `DynamicProxyGenAssembly2`, so NSubstitute can mock internal types. `InternalsVisibleToAllTestsProjects=False` in a csproj turns it off for that project. [`MSKIT_TEST030`](./reference/codes.md#mskittest030) and [`MSKIT_TEST031`](./reference/codes.md#mskittest031) warn when `MSKit_TestsDir` is empty or missing. + +## Package versions + +The kit provides these versions ([Build](./build.md#central-package-versions)); override one with its property: + +| Package | Property | Version | +| --- | --- | --- | +| `xunit.v3`, `xunit.v3.mtp-v2` | `MSKit_PackageVersion_XunitV3` | `4.0.1` | +| `xunit.v3.assert` | `MSKit_PackageVersion_XunitV3Assert` | `MSKit_PackageVersion_XunitV3` | +| `xunit.v3.extensibility.core` | `MSKit_PackageVersion_XunitV3ExtensibilityCore` | `MSKit_PackageVersion_XunitV3` | +| `xunit.runner.visualstudio` | `MSKit_PackageVersion_XunitRunnerVisualStudio` | `4.0.0` | +| `Microsoft.NET.Test.Sdk` | `MSKit_PackageVersion_MicrosoftNetTestSdk` | `18.10.1` | +| `Microsoft.Testing.Extensions.TrxReport` | `MSKit_PackageVersion_MicrosoftTestingExtensionsTrxReport` | `2.4.1` | +| `Microsoft.Testing.Extensions.CodeCoverage` | `MSKit_PackageVersion_MicrosoftTestingExtensionsCodeCoverage` | `18.11.2` | +| `AwesomeAssertions` | `MSKit_PackageVersion_AwesomeAssertions` | `9.6.0` | +| `FluentAssertions` | `MSKit_PackageVersion_FluentAssertions` | `[7.2.2]` | +| `Shouldly` | `MSKit_PackageVersion_Shouldly` | `4.3.0` | +| `NSubstitute` | `MSKit_PackageVersion_NSubstitute` | `6.2.0` | +| `NSubstitute.Analyzers.CSharp` | `MSKit_PackageVersion_NSubstituteAnalyzersCSharp` | `1.0.17` | + +## Checks + +| Code | When | +| --- | --- | +| [`MSKIT_TEST005`](./reference/codes.md#mskittest005) | an xUnit v3 test project targets a framework older than net8.0 | +| [`MSKIT_TEST010`](./reference/codes.md#mskittest010)-[`012`](./reference/codes.md#mskittest012), [`MSKIT_TEST020`](./reference/codes.md#mskittest020)-[`022`](./reference/codes.md#mskittest022) | the explicit endpoint was imported before `MSKit_TestingFramework` was set, the framework changed after it, or no wiring exists for it | +| [`MSKIT_TEST013`](./reference/codes.md#mskittest013) | a csproj sets `IsTestsProject` directly | +| [`MSKIT_TEST014`](./reference/codes.md#mskittest014) | a project named like a test project is marked as a helper library | +| [`MSKIT_TEST025`](./reference/codes.md#mskittest025), [`MSKIT_TEST026`](./reference/codes.md#mskittest026) | a test project has no `MSKit_TestingFramework`, or no installed part wires it | diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md new file mode 100644 index 0000000..b8e0c80 --- /dev/null +++ b/docs/troubleshooting.md @@ -0,0 +1,42 @@ +# Troubleshooting + +Symptoms, their cause, and the fix. A build message with an `MSKIT` code is explained in the [code reference](./reference/codes.md). + +## Updating + +| Symptom | Cause | Fix | +| --- | --- | --- | +| `update.sh` stops with `syntax error` or `unexpected EOF`; `kit.json` still names the old version | an update started from 0.2.0 or earlier runs the old script, which the new copy overwrites while it runs | run the same command once more | +| `SHA-256 mismatch … expected … (kit.json or --sha256)` | the release zip differs from the one pinned in `kit.json` for the same version | find out why the release changed before installing; `--sha256` with the new hash accepts it | +| `update.sh` without `--version` installs the version you already have | it reinstalls the pinned version | pass `--version ` ([Install and update](./install-and-update.md)) | +| `'' is a default part and cannot be removed` | default parts are always installed | — | + +## Building + +| Symptom | Cause | Fix | +| --- | --- | --- | +| Many [`MSKIT_DUP001`](./reference/codes.md#mskitdup001) errors right after installing | `Directory.Packages.props` repeats versions the kit provides | delete those `PackageVersion` lines, or override the kit's `MSKit_PackageVersion_*` property ([Build](./build.md#central-package-versions)) | +| The version is `9999.0.0` | a developer-machine build (no `GITHUB_RUN_ID`) | expected; `-p:MSKit_IsDevEnv=False` builds as CI ([Versioning](./versioning.md)) | +| `dotnet msbuild -getProperty:Version` prints `unknown placeholder: …` | a version template uses a placeholder the engine does not know | fix the template; a real build stops with [`MSKIT_VER002`](./reference/codes.md#mskitver002) | +| Restore fails on a tag build with [`MSKIT_VER006`](./reference/codes.md#mskitver006) | the tag is not a SemVer 2.0 version | delete the tag and the release, tag again (`v2.1.0`, `v2.1.0-beta.1`) | +| [`MSKIT_SHARED006`](./reference/codes.md#mskitshared006) / [`007`](./reference/codes.md#mskitshared007) after moving `TargetFrameworks` to `Directory.Build.props` | the csproj still repeats the value | delete it from the csproj | +| [`MSKIT_SHARED020`](./reference/codes.md#mskitshared020) locally, but CI is green | a csproj changes `TreatWarningsAsErrors`; the check runs on developer machines only | align the csproj, or set `MSKit_SkipAudit_TreatWarningsAsErrors=True` | +| [`MSKIT_ROSLYN001`](./reference/codes.md#mskitroslyn001)-`003` on a project that is not a Roslyn component | its name ends in `.Analyzers`, `.CodeFixes` or `.SourceGenerator` | rename it, or set `MSKit_DisableAutoDetect=true` in `Directory.Build.props` ([Roslyn](./roslyn.md#wiring-a-project)) | +| An `*.Analyzers` project builds for the shared target frameworks and references no Roslyn package | the role's props are not imported | add `` ([Roslyn](./roslyn.md#wiring-a-project)) | +| `IsNET8` is empty in a csproj `PropertyGroup` of a single-framework project | the constants are known in the props phase only for multi-targeted inner builds | test them in item and target conditions or in `Directory.Build.targets` ([Build](./build.md#target-framework-constants)) | + +## Testing + +| Symptom | Cause | Fix | +| --- | --- | --- | +| [`MSKIT_TEST013`](./reference/codes.md#mskittest013) | the csproj sets `IsTestsProject` | rename the project to `*.Tests`, or use `$(TestsProjectCommonPropsPath)` ([Testing](./testing.md#which-projects-are-test-projects)) | +| `dotnet test` on SDK 10 rejects `--solution`, `--coverage` or `--report-trx` | without the runner in `global.json` it runs in VSTest mode | add `"test": { "runner": "Microsoft.Testing.Platform" }` ([Microsoft.Testing.Platform mode](https://learn.microsoft.com/dotnet/core/testing/unit-testing-with-dotnet-test)) | +| NSubstitute cannot mock an internal type | the project under test is not exposing internals | keep `InternalsVisibleToAllTestsProjects` on and the test project under `MSKit_TestsDir` ([Testing](./testing.md#internalsvisibleto)) | + +## Packing + +| Symptom | Cause | Fix | +| --- | --- | --- | +| `dotnet pack` passes locally and fails on CI with `MSKIT_PKG` errors | the checks are warnings on a developer machine and errors on CI | fix the warnings the local pack prints ([Packaging](./packaging.md#checks)) | +| [`MSKIT_PKG016`](./reference/codes.md#mskitpkg016) on GitHub Enterprise, GitLab or another host | the release-notes default exists only for `github.com` | set `PackageReleaseNotes`, or generate the readme with `MSKit_PackageReadmeFrom` ([Package readme](./package-readme.md)) | +| [`MSKIT_PKG021`](./reference/codes.md#mskitpkg021) for every image of a self-hosted GitLab | nuget.org shows images only from its allowed hosts | host the images elsewhere, or point `MSKit_RepoRawUrlTemplate` at an allowed host | From 651df3f35cfc5ba4e2ffd1de3412579c72d2d121 Mon Sep 17 00:00:00 2001 From: VasiliyF <5789590+vfofanov@users.noreply.github.com> Date: Tue, 6 Oct 2026 06:49:18 +0200 Subject: [PATCH 6/8] Add the property and code references and run the docs check in the self-test docs/reference/properties.md lists every property and item the kit sets or reads; docs/reference/codes.md has one section per code, anchored by the id without the underscore. tests/docs.sh runs tools/docs-check.sh on the tree, proves it reports a broken copy, and that a code spelled without the underscore still matches. --- CONTRIBUTING.md | 9 +- docs/migrating-from-msbuild-routine.md | 20 ++ docs/reference/codes.md | 273 +++++++++++++++++++++++++ docs/reference/properties.md | 181 ++++++++++++++++ docs/roslyn.md | 2 +- docs/troubleshooting.md | 2 +- tests/docs.sh | 34 +++ tests/run.sh | 1 + 8 files changed, 517 insertions(+), 5 deletions(-) create mode 100644 docs/migrating-from-msbuild-routine.md create mode 100644 docs/reference/codes.md create mode 100644 docs/reference/properties.md create mode 100644 tests/docs.sh diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 77d26d1..c88392a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -8,6 +8,7 @@ Issues and pull requests are welcome. - `samples/MinimalLibrary` is a consumer with a library and a test project; its `.toolkit/` is installed by the self-test and not committed. - `tests/run.sh` is the self-test; `tests/fixtures/PackageChecks` breaks every package rule on purpose. - `tools/pack-kit.sh` builds the release zip and its SHA-256. +- `docs/` holds the user documentation, read in the order of [docs/README.md](./docs/README.md); `docs/reference/` lists every property and code. `tools/docs-check.sh` keeps them honest against the kit. ## Build and test @@ -17,12 +18,14 @@ You need the .NET 10 SDK and the .NET 8 runtime, plus `sh` (Git Bash on Windows) sh tests/run.sh ``` -It installs the working-tree kit into the sample and the fixtures with `update.sh`, then checks the computed versions for local, branch, pull-request and tag builds, runs the sample's tests with coverage, inspects the packed nuspec, and asserts that every `MSKIT_PKG` check fires on the fixtures. CI runs the same script on Linux and Windows. +It installs the working-tree kit into the sample and the fixtures with `update.sh`, then checks the computed versions for local, branch, pull-request and tag builds, runs the sample's tests with coverage, inspects the packed nuspec, asserts that every `MSKIT_PKG` check fires on the fixtures, and runs `sh tools/docs-check.sh`. CI runs the same script on Linux and Windows. ## Changing the kit -- A new property defaults with `Condition="'$(Name)'==''"`, so a consumer's value always wins, and gets a row in the README. -- A new check gets an `MSKIT_` code, a message that says how to fix it, a fixture that triggers it and a line in `tests/run.sh`. +- A new property defaults with `Condition="'$(Name)'==''"`, so a consumer's value always wins, and gets a row in [docs/reference/properties.md](./docs/reference/properties.md) plus a mention on its topic page. +- A new check gets an `MSKIT_` code (a shipped code is never renumbered or reused), a message that says how to fix it, a section in [docs/reference/codes.md](./docs/reference/codes.md) headed by the code id without the underscore, a fixture that triggers it and a line in `tests/run.sh`. +- `sh tools/docs-check.sh` fails on a property, item or code without its reference entry, on a name the docs mention that the kit lacks, and on a broken relative link; `--list properties|items|codes` prints the kit's inventory with the file and line of each. +- The README stays short: key features, install, links. Detail goes to the topic page in `docs/`. - A new part needs a line in `kit/.toolkit/kit.parts` and its `init.props` / `init.targets` imports in the entry points. - Keep scripts POSIX `sh` and PowerShell 7 equivalent; `update.sh` and `update.ps1` must produce the same `.toolkit/`. - Add a line to `CHANGELOG.md` under `Unreleased`. diff --git a/docs/migrating-from-msbuild-routine.md b/docs/migrating-from-msbuild-routine.md new file mode 100644 index 0000000..d90eab4 --- /dev/null +++ b/docs/migrating-from-msbuild-routine.md @@ -0,0 +1,20 @@ +# Migrating from MSBuild.Routine + +[MSBuild.Routine](https://github.com/DragoAnt/MSBuild.Routine) is the older, submodule-based predecessor of the kit. Moving a repository over: + +1. **Remove the submodule:** `git rm .msbuild`, delete `.gitmodules`, and drop `submodules:` from your workflows. +2. **Install the kit** ([Getting started](./getting-started.md)), with `--add PackageAsProj` if you use `Directory.PackageAsProj.targets`, and replace the `.msbuild\shared\init.props` / `init.targets` imports with the `.toolkit/msbuild/` ones. +3. **Drop what is now a default:** `Copyright`, `PackageLicenseExpression`, `RepositoryUrl`, `PackageReleaseNotes`, `TargetFrameworkStrategy`, and the `.msbuild\tfm.constants.props` import. +4. **Remove the versions the kit provides** from `Directory.Packages.props` (`xunit.v3*`, `xunit.runner.visualstudio`, `Microsoft.NET.Test.Sdk`, `Microsoft.Testing.Extensions.CodeCoverage`, `NSubstitute*`, your assertion library, `coverlet.collector`) and the explicit `Microsoft.Testing.Extensions.CodeCoverage` references from test projects; [`MSKIT_DUP001`](./reference/codes.md#mskitdup001) lists any you missed. To keep FluentAssertions 7, set `MSKit_TestsAssertions=FluentAssertions`. +5. **Add `Directory.Version.props`** with the next `VersionPrefix`, and publish releases with tags such as `v2.0.1` ([Versioning](./versioning.md)). + +Renamed properties: + +| MSBuild.Routine | MSBuildKit | +| --- | --- | +| `IncrementVersionType` | `MSKit_VersionStrategy` | +| `IsDevEnv`, `Branch`, `BuildNumber`, `CommitSha` | `MSKit_IsDevEnv`, `MSKit_Branch`, `MSKit_BuildNumber`, `MSKit_CommitSha` | +| `SlnSecretsId`, `SecretsTemplatesDir` | `MSKit_SlnSecretsId`, `MSKit_Templates` | +| `TestsDir` | `MSKit_TestsDir` | +| `SkipCheck_*` | `MSKit_SkipAudit_*` | +| `IsCodeAnalizerLib` | the `Project.CodeAnalyzer` part (`*.Analyzers` projects that import `$(CodeAnalyzerCommonPropsPath)`, [Roslyn](./roslyn.md)) | diff --git a/docs/reference/codes.md b/docs/reference/codes.md new file mode 100644 index 0000000..8430487 --- /dev/null +++ b/docs/reference/codes.md @@ -0,0 +1,273 @@ +# Code reference + +Every warning and error the kit reports, one section per code. The kit reports each code as `MSKIT_` (for example `MSKIT_VER006`); the section anchors use the id without the underscore (`#mskitver006`). Severity is the default; the section says how to change or skip it. + +| Family | Part | Codes | +| --- | --- | --- | +| [Packaging](#packaging) | Packaging | `PKG001`-`PKG022` | +| [Versioning](#versioning) | Trunk | `VER001`, `VER002`, `VER004`, `VER006`, `VER007` | +| [References](#references) | Trunk | `DUP001`, `PRE001`, `RES001`-`RES004` | +| [Shared properties](#shared-properties) | Trunk | `SHARED006`-`SHARED010`, `SHARED020` | +| [Project types](#project-types) | Core | `CORE001`, `ROSLYN001`-`ROSLYN003` | +| [Testing](#testing) | Testing, Testing.XUnit.v3 | `TEST005`, `TEST010`-`TEST014`, `TEST020`-`TEST022`, `TEST025`, `TEST026`, `TEST030`, `TEST031` | +| [PackageAsProj](#packageasproj) | PackageAsProj | `PAP001`, `PAP002` | + +## Packaging + +`PKG001`-`PKG019` run on `dotnet pack` for packable projects: **warnings on a developer machine, errors on CI** (`MSKit_PackageChecksAsErrors`). Skip one with `MSKit_SkipPackageChecks=` (several separated by `;`, all with `All`) or by adding the code to `NoWarn`. `PKG020`-`PKG022` come from the readme generator and stay warnings. Background: [Packaging](../packaging.md), [Package readme](../package-readme.md). + +### MSKITPKG001 + +`MSKIT_PKG001` — the package has no real `Description`: it is empty, the SDK's `Package Description`, the package id or the project name. Write one or two sentences on what the package does and what sets it apart; nuget.org search shows it first. + +### MSKITPKG002 + +`MSKIT_PKG002` — `Description` is shorter than `MSKit_PackageDescriptionMinLength` (30 characters). Say what it does and for whom, or lower the bar. + +### MSKITPKG003 + +`MSKIT_PKG003` — no README is packed. Add `package.readme.md` (or `README.md`) next to the csproj, or set `MSKit_PackageReadmeFrom`. + +### MSKITPKG004 + +`MSKIT_PKG004` — no `PackageTags`. Add a few search terms that are not already in the package id. + +### MSKITPKG005 + +`MSKIT_PKG005` — no icon. Set `PackageIconPath` to a 128×128 PNG; the owner layer sets one for every package. + +### MSKITPKG006 + +`MSKIT_PKG006` — no licence. Set `PackageLicenseExpression` to an [SPDX id](https://spdx.org/licenses/) or `PackageLicenseFile`. + +### MSKITPKG007 + +`MSKIT_PKG007` — the deprecated `PackageLicenseUrl` is set. Use `PackageLicenseExpression` or `PackageLicenseFile`. + +### MSKITPKG008 + +`MSKIT_PKG008` — the deprecated `PackageIconUrl` is set. Pack the image and use `PackageIcon` or `PackageIconPath`. + +### MSKITPKG009 + +`MSKIT_PKG009` — the package README has relative images, which nuget.org does not render. Use absolute `https` URLs from an [allowed host](https://learn.microsoft.com/nuget/nuget-org/package-readme-on-nuget-org#allowed-domains-for-images-and-badges), or generate the readme with `MSKit_PackageReadmeFrom`. + +### MSKITPKG010 + +`MSKIT_PKG010` — the package README contains HTML, which nuget.org does not render. Use Markdown. + +### MSKITPKG011 + +`MSKIT_PKG011` — the package README uses GitHub alerts (`> [!NOTE]`), which nuget.org shows as plain quotes. Use a bold lead-in such as `**Note:**`. + +### MSKITPKG012 + +`MSKIT_PKG012` — the package README loads images from a host nuget.org blocks. Host them on an allowed domain (`img.shields.io`, `raw.githubusercontent.com`, …). + +### MSKITPKG013 + +`MSKIT_PKG013` — the package version is not [SemVer 2.0](https://semver.org/) (`MSKit_SemVerRegex`). + +### MSKITPKG014 + +`MSKIT_PKG014` — no repository or project URL. Set `RepositoryUrl`, or build from a git clone whose `origin` remote Source Link can read. + +### MSKITPKG015 + +`MSKIT_PKG015` — the icon is not a PNG of `MSKit_PackageIconSize` × `MSKit_PackageIconSize` pixels (128). + +### MSKITPKG016 + +`MSKIT_PKG016` — no `PackageReleaseNotes`. The kit fills them on `github.com`, and on any host the generated readme knows with `MSKit_PackageReadmeFrom`; elsewhere set them (a link to the changelog is enough). + +### MSKITPKG017 + +`MSKIT_PKG017` — the package README has relative links, which break on nuget.org. Use absolute URLs, or generate the readme with `MSKit_PackageReadmeFrom`. + +### MSKITPKG018 + +`MSKIT_PKG018` — the package has an open-source licence expression, but its `Copyright` says "all rights reserved". Use `Copyright (c) YEAR OWNER`. + +### MSKITPKG019 + +`MSKIT_PKG019` — the package README contains a Mermaid diagram, which nuget.org shows as code. Link to the diagram on the repository host instead. + +### MSKITPKG020 + +`MSKIT_PKG020` (warning) — the readme cannot be generated as asked: the `MSKit_PackageReadmeFrom` file is missing, a `nuget:skip` / `nuget:only` marker is unbalanced, a link leaves the repository, or links cannot be rewritten (no repository URL, an unknown host or provider, no commit). The message names the line. + +### MSKITPKG021 + +`MSKIT_PKG021` (warning) — a generated readme loads an image from a host nuget.org does not render images from; the message names the image and its README line. + +### MSKITPKG022 + +`MSKIT_PKG022` (warning) — the repository is private or internal (`MSKit_RepositoryVisibility`, else GitLab's `CI_PROJECT_VISIBILITY`), so the readme's links will not open for package readers. + +## Versioning + +Background: [Versioning](../versioning.md). + +### MSKITVER001 + +`MSKIT_VER001` (error) — the csproj declares `` while a template strategy renders the version, so the value would be ignored. Remove it and declare `VersionPrefix` in `Directory.Version.props`, or set `MSKit_VersionStrategy=Manual`. + +### MSKITVER002 + +`MSKIT_VER002` (error) — a version template uses an unknown placeholder. The message lists the valid ones ([placeholders](../versioning.md#placeholders)). + +### MSKITVER004 + +`MSKIT_VER004` (error) — `MSKit_VersionStrategy=VersionTag` but `VersionTag` is empty. Pass `-p:VersionTag=1.2.3`, or use `ReleaseTag`. + +### MSKITVER006 + +`MSKIT_VER006` (error, stops restore) — a tag build whose tag, after removing a leading `v`, is not [SemVer 2.0](https://semver.org/) (`MSKit_ReleaseTagRegex`). Delete the tag and its release and tag again (`v2.1.0`, `v2.1.0-beta.1`). + +### MSKITVER007 + +`MSKIT_VER007` (warning) — the tag's `MAJOR.MINOR.PATCH` differs from the `VersionPrefix` the repository declares. Bump `VersionPrefix` after the release so branch builds sort above it; `MSKit_SkipAudit_ReleaseTagPrefix=True` silences it. + +## References + +Background: [reference checks](../build.md#reference-checks), [central package versions](../build.md#central-package-versions). + +### MSKITDUP001 + +`MSKIT_DUP001` (error, stops restore) — `Directory.Packages.props` declares a `PackageVersion` the kit already provides. Delete the line; to pin another version set the kit's `MSKit_PackageVersion_*` property, or turn the kit's versions off with `MSKit_ImplicitPackageVersions=False`. `MSKit_SkipAudit_ImplicitPackageDuplicates=True` skips the check. + +### MSKITPRE001 + +`MSKIT_PRE001` (warning) — a stable-branch build references a prerelease version of a package whose id starts with `MSKit_PrereleasePackagePrefix`. Use a stable version; `MSKit_PrereleasePackageCheckAsWarning=false` makes it an error. + +### MSKITRES001 + +`MSKIT_RES001` (error) — a referenced package is banned by an `MSKit_RestrictPackageReference` item with `Type="Error"`. Remove it or use the suggested alternative; `SkipGlobalRestriction="True"` on the one `PackageReference` turns it into a warning. + +### MSKITRES002 + +`MSKIT_RES002` (warning) — a referenced package is discouraged by an `MSKit_RestrictPackageReference` item with `Type="Warning"`. `SkipGlobalRestriction="True"` on the reference silences it. + +### MSKITRES003 + +`MSKIT_RES003` (error) — with `MSKit_RestrictProjectReferences=True` (or `MSKit_RestrictReferences=True`), a `ProjectReference` lacks `Allowed="True"`. + +### MSKITRES004 + +`MSKIT_RES004` (error) — with `MSKit_RestrictPackageReferences=True` (or `MSKit_RestrictReferences=True`), a `PackageReference` lacks `Allowed="True"`. + +## Shared properties + +Background: [target frameworks declared once](../build.md#target-frameworks-declared-once). + +### MSKITSHARED006 + +`MSKIT_SHARED006` (error) — the csproj declares the same `TargetFramework` as `Directory.Build.props`. Delete it from the csproj. + +### MSKITSHARED007 + +`MSKIT_SHARED007` (error) — the csproj declares the same `TargetFrameworks` as `Directory.Build.props`. Delete it from the csproj. + +### MSKITSHARED008 + +`MSKIT_SHARED008` (warning) — the csproj overrides the shared `TargetFramework` with another value. Remove it, or accept it with `MSKit_SkipAudit_TargetFrameworkOverride=True` in the csproj. + +### MSKITSHARED009 + +`MSKIT_SHARED009` (warning) — the csproj overrides the shared `TargetFrameworks` with another value. Remove it, or accept it with `MSKit_SkipAudit_TargetFrameworkOverride=True`. + +### MSKITSHARED010 + +`MSKIT_SHARED010` (error) — the csproj declares both `TargetFramework` and `TargetFrameworks` (an empty `` counts). Keep one. + +### MSKITSHARED020 + +`MSKIT_SHARED020` (error, developer machines only) — the csproj's final `TreatWarningsAsErrors` differs from the shared value. Align it, or set `MSKit_SkipAudit_TreatWarningsAsErrors=True`. + +## Project types + +Background: [Roslyn components](../roslyn.md). + +### MSKITCORE001 + +`MSKIT_CORE001` (error) — the project name matches more than one detection regex (analyzer, code fix, source generator). Rename the project, narrow a `MSKit_*ProjectNameRegex`, or set the matching `MSKit_Disable*AutoDetect=true` in `Directory.Build.props` above the kit import. + +### MSKITROSLYN001 + +`MSKIT_ROSLYN001` (error) — the name matches the analyzer regex, but the `Project.CodeAnalyzer` part is not installed. `sh .toolkit/update.sh --add Project.CodeAnalyzer`, or rename the project, or set `MSKit_DisableCodeAnalyzerAutoDetect=true` in `Directory.Build.props` above the kit import. + +### MSKITROSLYN002 + +`MSKIT_ROSLYN002` (error) — the name matches the code-fix regex, but `Project.CodeFixer` is not installed. Add the part, rename, or set `MSKit_DisableCodeFixerAutoDetect=true` in `Directory.Build.props` above the kit import. + +### MSKITROSLYN003 + +`MSKIT_ROSLYN003` (error) — the name matches the source-generator regex, but `Project.SourceGenerator` is not installed. Add the part, rename, or set `MSKit_DisableSourceGeneratorAutoDetect=true` in `Directory.Build.props` above the kit import. + +## Testing + +Background: [Testing](../testing.md). + +### MSKITTEST005 + +`MSKIT_TEST005` (error) — an xUnit v3 test project targets a framework older than net8.0. + +### MSKITTEST010 + +`MSKIT_TEST010` (error) — `$(TestsProjectCommonPropsPath)` was imported before `MSKit_TestingFramework` was set. Put the `PropertyGroup` above the `Import`. + +### MSKITTEST011 + +`MSKIT_TEST011` (error) — `MSKit_TestingFramework` changed after `$(TestsProjectCommonPropsPath)` was imported. Move the `PropertyGroup` above the `Import`. + +### MSKITTEST012 + +`MSKIT_TEST012` (error) — no wiring exists for the `MSKit_TestingFramework` of an explicit test project. Use `xunit.v3`, or point `MSKit_TestingFramework_CommonPropsPath` at your own props file. + +### MSKITTEST013 + +`MSKIT_TEST013` (error) — the csproj sets `IsTestsProject` directly, too late for the props-phase wiring. Rename the project to match `MSKit_TestsProjectNameRegex`, or use the explicit endpoint `$(TestsProjectCommonPropsPath)`. + +### MSKITTEST014 + +`MSKIT_TEST014` (error) — the name matches the test-project regex, but the project is marked as a test helper library. Rename it (`Acme.TestUtils`), narrow the regex, or drop the helper-library flag. + +### MSKITTEST020 + +`MSKIT_TEST020` (error) — `$(TestsLibProjectCommonPropsPath)` was imported before `MSKit_TestingFramework` was set. + +### MSKITTEST021 + +`MSKIT_TEST021` (error) — `MSKit_TestingFramework` changed after `$(TestsLibProjectCommonPropsPath)` was imported. + +### MSKITTEST022 + +`MSKIT_TEST022` (error) — no helper-library wiring exists for `MSKit_TestingFramework`. Use `xunit.v3`, or set `MSKit_TestingFramework_LibCommonPropsPath`. + +### MSKITTEST025 + +`MSKIT_TEST025` (error) — a test project or helper library has no `MSKit_TestingFramework`. Set it in `Directory.Build.props` (the owner layer sets `xunit.v3`). + +### MSKITTEST026 + +`MSKIT_TEST026` (error) — no installed part wires the `MSKit_TestingFramework` value. Use `xunit.v3` (part `Testing.XUnit.v3`), or set `MSKit_TestingFramework_CommonPropsPath`. + +### MSKITTEST030 + +`MSKIT_TEST030` (warning) — `MSKit_TestsDir` is empty, so `InternalsVisibleTo` cannot be added. Set it, or turn `InternalsVisibleToAllTestsProjects` off. + +### MSKITTEST031 + +`MSKIT_TEST031` (warning) — `MSKit_TestsDir` points at a folder that does not exist. + +## PackageAsProj + +Background: [PackageAsProj](../optional-parts.md#packageasproj). + +### MSKITPAP001 + +`MSKIT_PAP001` (error) — a package switched to a `ProjectReference` is still resolved from the package. Run `dotnet restore --force`. + +### MSKITPAP002 + +`MSKIT_PAP002` (error) — a package switched back from a project is not restored yet. Run `dotnet restore --force`, or set `PackageAsProj_SkipChecks=True`. diff --git a/docs/reference/properties.md b/docs/reference/properties.md new file mode 100644 index 0000000..7453fbb --- /dev/null +++ b/docs/reference/properties.md @@ -0,0 +1,181 @@ +# Property reference + +Every property and item the kit sets or reads, grouped by topic. **Set** = a value you may set (its default is shown); **out** = a value the kit computes for you to read; setting it yourself is not supported unless the row says so. Names without the `MSKit_` prefix are the kit's own too; standard SDK properties the kit only defaults (`Nullable`, `PackageId`, …) are on the topic pages. + +## Hooks + +| Name | Kind | Default | Meaning | +| --- | --- | --- | --- | +| `MSKit_BeforeInitProps`, `MSKit_AfterInitProps` | set | empty | A file imported before / after the kit's props ([Customizing](../customizing.md#hooks-around-the-kit)) | +| `MSKit_BeforeInitTargets`, `MSKit_AfterInitTargets` | set | empty | A file imported before / after the kit's targets | + +## Machine, CI and paths + +| Name | Kind | Default | Meaning | +| --- | --- | --- | --- | +| `MSKit_IsDevEnv` | set | `True`; `False` when `GITHUB_RUN_ID` is set | Developer machine or CI ([Build](../build.md#developer-machine-or-ci)) | +| `MSKit_IsGitHubCI` | out | | `True` on GitHub Actions | +| `MSKit_VcsProvider` | out | `GitHub` | The CI provider the Vcs part maps | +| `MSKit_CIPipelineId` | set | `GITHUB_RUN_ID`, else `0` | `{pipelineId}` | +| `MSKit_BuildNumber` | set | `GITHUB_RUN_NUMBER`, else `0` | `{buildNumber}` | +| `MSKit_CommitSha` | set | `GITHUB_SHA` on CI, else empty | `{commitShaShort}`, and the commit a generated readme pins links to when Source Link has none | +| `MSKit_Branch` | set | from GitHub Actions or `.git/HEAD`, else `unknown-branch` | [Roots, branch and commit](../build.md#roots-branch-and-commit) | +| `MSKit_IsStableBranchRegex` | set | owner layer `^(main\|release/.+)$`; kit `^(main\|master\|release/.+)$` | Branches that use `MSKit_StableVersionTemplate` and the prerelease check | +| `MSKit_IsStableBranch` | out | | `true` when `MSKit_Branch` matches | +| `MSKit_GitRoot` | set | the nearest folder with `.git` | | +| `MSKit_SlnFileDirectory` | set | the `.slnx` folder, else the folder holding `.toolkit/` | Where the extension files are looked up | +| `MSKit_SlnFileName` | set | the `.slnx` name | | +| `MSKit_ToolkitDir`, `MSKit_ToolkitMSBuildDir` | out | | `.toolkit/` and `.toolkit/msbuild/`, absolute | +| `MSKit_ProjectObjDir` | out | | The project's `obj` folder, absolute | +| `MSKit_Templates` | set | `.toolkit/.local/` | Templates for local files ([Local files](../local-files.md)) | +| `MSKit_Diagnostic` | set | `false` | Reserved; nothing reads it in this version | + +## Versioning + +| Name | Kind | Default | Meaning | +| --- | --- | --- | --- | +| `MSKit_VersionStrategy` | set | owner layer `ReleaseTag`; kit `SemVer` | `ReleaseTag`, `SemVer`, `SemVer4`, `DateBased`, `VersionTag`, `Manual` ([Versioning](../versioning.md#other-strategies)) | +| `MSKit_VersionTemplate`, `MSKit_StableVersionTemplate` | set | per strategy | Branch and stable-branch templates | +| `MSKit_ReleaseVersionTemplate`, `MSKit_PullRequestVersionTemplate` | set | `ReleaseTag`: `{releaseTag}`, `{prefix}-pr.{prNumber}.{buildNumber}` | Tag-build and pull-request templates | +| `MSKit_ExplicitVersion` | out | `Version` as it was before the kit loaded | When not empty the engine renders nothing | +| `MSKit_DeclaredVersionPrefix` | out | | `VersionPrefix` as declared, before a strategy default | +| `MSKit_IsReleaseTag` | set | `True` when `GITHUB_REF_TYPE=tag` | A tag build | +| `MSKit_ReleaseTagRaw`, `MSKit_ReleaseTag` | set | `GITHUB_REF_NAME`; the same without a leading `v` | | +| `MSKit_ReleaseTagRegex` | set | SemVer 2.0 | What a valid tag looks like | +| `MSKit_IsReleaseTagValid` | out | | | +| `MSKit_PullRequestNumber` | set | from `GITHUB_REF` / `GITHUB_REF_NAME` | `{prNumber}` | +| `MSKit_BuildDateTimeUtc` | set | the environment variable of that name, else each project's own clock | One date for a whole build | +| `MSKit_SkipAudit_ReleaseTagPrefix` | set | empty | `True` silences `MSKIT_VER007` | + +## Target frameworks + +| Name | Kind | Default | Meaning | +| --- | --- | --- | --- | +| `IsNET7`, `IsNET8`, `IsNET9`, `IsNET10`, `IsNET11`, `IsNET12`, `IsNET13`, `IsNET14` | out | | `True` for that `TargetFramework` ([constants](../build.md#target-framework-constants)) | +| `IsNET7_OR_GREATER`, `IsNET8_OR_GREATER`, `IsNET9_OR_GREATER`, `IsNET10_OR_GREATER`, `IsNET11_OR_GREATER`, `IsNET12_OR_GREATER`, `IsNET13_OR_GREATER`, `IsNET14_OR_GREATER` | out | | `True` for that version or later, up to `net14.0` | +| `IsNETSTANDARD20`, `IsNETSTANDARD21`, `IsNETSTANDARD` | out | | `netstandard2.0`, `netstandard2.1`, either | +| `IsNETFRAMEWORK`, `IsNETFRAMEWORK_OR_STANDARD` | out | | `net48`; `net48` or .NET Standard | +| `TargetFrameworkVersionMajor` | out | | `7` … `14` | +| `IsTfmConstantsImported` | out | | The constants file is loaded | +| `MSKit_TargetFramework_Shared`, `MSKit_TargetFrameworks_Shared` | out | | The value declared in `Directory.Build.props` ([declared once](../build.md#target-frameworks-declared-once)) | +| `MSKit_TargetFramework_Proj`, `MSKit_TargetFrameworks_Proj` | out | | The value declared in the csproj | +| `MSKit_SkipAudit_TargetFrameworkOverride` | set | empty | `True` accepts a different csproj value (`MSKIT_SHARED008` / `009`) | +| `MSKit_GuardXmlPeekRoutine`, `MSKit_GuardXmlPeekAudit` | set | empty (on) | `False` skips the text pre-check before parsing the csproj | + +## Build defaults and reference checks + +| Name | Kind | Default | Meaning | +| --- | --- | --- | --- | +| `MSKit_IncludeCodeAnalysisGlobalUsings` | set | `True` | The kit's [global usings](../build.md#global-usings) | +| `ExcludeFromCodeCoverage` | set | `True` for test projects | Adds `[ExcludeFromCodeCoverage]` to the assembly | +| `MSKit_TreatWarningsAsErrors_Shared` | out | | `TreatWarningsAsErrors` before the csproj body | +| `MSKit_SkipAudit_TreatWarningsAsErrors` | set | `True` on CI | Skips `MSKIT_SHARED020` | +| `MSKit_ImplicitPackageVersions` | set | `True` | The kit's [package versions](../build.md#central-package-versions) | +| `MSKit_SkipAudit_ImplicitPackageDuplicates` | set | `False` | `True` skips `MSKIT_DUP001` | +| `MSKit_RestrictPackageReference` | item | `Moq` (`Type="Error"`, owner layer) | A banned (`Type="Error"`) or discouraged (`Type="Warning"`) package, with a `Message` ([reference checks](../build.md#reference-checks)) | +| `MSKit_PackageReferenceNotAllowed`, `MSKit_ProjectReferenceNotAllowed` | item | | The checks' findings; read-only | +| `MSKit_RestrictReferences` | set | `False` | Allow-list mode for both reference kinds | +| `MSKit_RestrictProjectReferences`, `MSKit_RestrictPackageReferences` | set | `MSKit_RestrictReferences` | Allow-list mode for one kind (`Allowed="True"` required) | +| `MSKit_PrereleasePackagePrefix` | set | owner layer `DragoAnt.` | Ids the prerelease check covers | +| `MSKit_PrereleasePackageCheckAsWarning` | set | `true` | `false` makes `MSKIT_PRE001` an error | +| `MSKit_ProjectReferenceAsPrivateAssets`, `MSKit_PackageReferenceAsPrivateAssets` | set | empty | `True` makes every reference of that kind private ([Build](../build.md#private-references)) | +| `ManufacturerName`, `FullManufacturerName` | set | `DragoAnt` (owner layer, unconditional) | The owner ([Customizing](../customizing.md#the-owner-layer)) | + +## Package versions + +| Name | Kind | Default | Meaning | +| --- | --- | --- | --- | +| `MSKit_PackageVersion_XunitV3`, `MSKit_PackageVersion_XunitV3Assert`, `MSKit_PackageVersion_XunitV3ExtensibilityCore`, `MSKit_PackageVersion_XunitRunnerVisualStudio`, `MSKit_PackageVersion_MicrosoftNetTestSdk`, `MSKit_PackageVersion_MicrosoftTestingExtensionsTrxReport`, `MSKit_PackageVersion_MicrosoftTestingExtensionsCodeCoverage`, `MSKit_PackageVersion_AwesomeAssertions`, `MSKit_PackageVersion_FluentAssertions`, `MSKit_PackageVersion_Shouldly`, `MSKit_PackageVersion_NSubstitute`, `MSKit_PackageVersion_NSubstituteAnalyzersCSharp` | set | [Testing](../testing.md#package-versions) | The test stack's versions | +| `MSKit_PackageVersion_MicrosoftCodeAnalysis`, `MSKit_PackageVersion_MicrosoftCodeAnalysisAnalyzers` | set | [Roslyn](../roslyn.md#package-versions) | The Roslyn versions | + +## Packaging + +| Name | Kind | Default | Meaning | +| --- | --- | --- | --- | +| `IsPackable` | set | `True` (owner layer); `False` for test projects and code fixes | | +| `PackageIconPath` | set | `.toolkit/res/package.icon.png` (owner layer) | The icon, packed as `icon.png` | +| `MSKit_PackageIconSourcePath` | out | | The icon file the checks read | +| `MSKit_PackageReadmeSourcePath` | set | `package.readme.md`, else `README.md` next to the csproj | The README packed as `readme.md` | +| `MSKit_PackageValidationBaselineVersion` | set | empty | The release package validation compares against | +| `MSKit_DefaultReleaseNotes` | set | empty (on) | `False` turns the `PackageReleaseNotes` default off | +| `MSKit_PackageChecksAsErrors` | set | `True` on CI | The `MSKIT_PKG` checks as errors ([Packaging](../packaging.md#checks)) | +| `MSKit_SkipPackageChecks` | set | empty | Codes to skip, `;`-separated, or `All` | +| `MSKit_PackageDescriptionMinLength` | set | `30` | `MSKIT_PKG002` | +| `MSKit_PackageIconSize` | set | `128` | `MSKIT_PKG015` | +| `MSKit_SemVerRegex` | set | SemVer 2.0 | `MSKIT_PKG013` | + +## Package readme + +| Name | Kind | Default | Meaning | +| --- | --- | --- | --- | +| `MSKit_PackageReadmeFrom` | set | empty (off) | The README to generate from ([Package readme](../package-readme.md#properties)) | +| `MSKit_GeneratedPackageReadmePath` | set | `obj//package.readme.md` | Where the generated file goes | +| `MSKit_PackageReadmeTitle` | set | `auto` (the `PackageId`) | The first level-1 heading; empty keeps the README's | +| `MSKit_RepoProvider` | set | detected | `GitHub`, `GitLab`, `AzureDevOps`, `Bitbucket`, `Gitea` | +| `MSKit_RepoBlobUrlTemplate`, `MSKit_RepoRawUrlTemplate` | set | the provider's | File-link and image templates | +| `MSKit_ReleasesUrl`, `MSKit_IssuesUrl` | set | `auto` | The links added to the overview; empty leaves one out | +| `MSKit_RepositoryVisibility` | set | `CI_PROJECT_VISIBILITY` | `private` / `internal` raises `MSKIT_PKG022` | +| `MSKit_PackageReadmeAllowedImageHosts` | set | the hosts file | `;`-separated hosts that replace the file's list | +| `MSKit_PackageReadmeAllowedImageHostsFile` | set | the kit's `nuget.allowed-image-hosts.txt` | The hosts file | + +## Testing + +| Name | Kind | Default | Meaning | +| --- | --- | --- | --- | +| `IsTestsProject`, `IsTestsLibProject` | out | from the name | A test project, a test helper library ([Testing](../testing.md#which-projects-are-test-projects)); set `IsTestsLibProject` directly if you like, never `IsTestsProject` | +| `MSKit_TestsProjectNameRegex` | set | `\.(Tests(\.Integration\|\.Unit)?\|IntegrationTests\|UnitTests)$` | Test-project names | +| `MSKit_TestsLibProjectNameRegex` | set | `\.(TestsSuite\|TestsFixtures\|Fixtures)$` | Helper-library names | +| `MSKit_DisableTestsProjectAutoDetect`, `MSKit_DisableTestsLibProjectAutoDetect` | set | empty | `true` turns detection off | +| `MSKit_IsTestsProjectAutoDetected`, `MSKit_IsTestsLibProjectAutoDetected` | out | | The name matched | +| `TestsProjectCommonPropsPath`, `TestsLibProjectCommonPropsPath` | out | | The explicit endpoints to `Import` | +| `MSKit_TestingFramework` | set | `xunit.v3` (owner layer) | The framework wiring | +| `MSKit_TestingFrameworkAttached` | out | | An installed part wires the framework | +| `MSKit_TestingFramework_CommonPropsPath`, `MSKit_TestingFramework_LibCommonPropsPath` | set | the installed part's | Your own wiring for another framework | +| `MSKit_TestsAssertions` | set | `AwesomeAssertions` | `AwesomeAssertions`, `FluentAssertions`, `Shouldly`, `None` | +| `MSKit_TestsMocking` | set | `NSubstitute` | `NSubstitute`, `None` | +| `MSKit_TestsImplicitReferences` | set | empty (on) | `False` leaves the assertion and mocking references out | +| `InternalsVisibleToAllTestsProjects` | set | `True` for non-test projects | [`InternalsVisibleTo`](../testing.md#internalsvisibleto) to every test project | +| `MSKit_TestsDir` | set | the solution folder | Where test projects are looked for | +| `MSKit_ScaffoldTestConfig` | set | empty | `True` writes starter `testconfig.json` / `xunit.runner.json` | +| `MTPTestConfigFileName`, `XunitRunnerConfigFileName` | set | `testconfig.json`, `xunit.runner.json` | Config files copied to the output | + +## Roslyn components + +| Name | Kind | Default | Meaning | +| --- | --- | --- | --- | +| `IsCodeAnalyzer`, `IsCodeFixer`, `IsSourceGenerator` | set | from the name | The role ([Roslyn](../roslyn.md#wiring-a-project)) | +| `IsRoslynComponent` | out | `True` in a Roslyn project | | +| `MSKit_CodeAnalyzerProjectNameRegex`, `MSKit_CodeFixerProjectNameRegex`, `MSKit_SourceGeneratorProjectNameRegex` | set | `\.Analyzers$`, `\.CodeFixes$`, `\.SourceGenerator$` | Role names | +| `MSKit_DisableCodeAnalyzerAutoDetect`, `MSKit_DisableCodeFixerAutoDetect`, `MSKit_DisableSourceGeneratorAutoDetect` | set | empty | `true` above the kit import turns detection off | +| `MSKit_IsCodeAnalyzerAutoDetected`, `MSKit_IsCodeFixerAutoDetected`, `MSKit_IsSourceGeneratorAutoDetected` | out | | The name matched | +| `MSKit_AutoDetectedProjectType` | item | | Every role the name matched; more than one is `MSKIT_CORE001` | +| `MSKit_CodeAnalyzerSatelliteAttached`, `MSKit_CodeFixerSatelliteAttached`, `MSKit_SourceGeneratorSatelliteAttached` | out | | The role's part is installed | +| `CodeAnalyzerCommonPropsPath`, `CodeFixerCommonPropsPath`, `SourceGeneratorCommonPropsPath`, `RoslynComponentCommonPropsPath` | out | | The props a Roslyn project imports | +| `MSKit_HasCodeFixer` | set | `True` | Look for the sibling code-fix project | +| `MSKit_CodeFixer` | set | `../.CodeFixes/.CodeFixes.csproj` | The code-fix project packed with the analyzer | +| `IncludeGenerateResultToProject`, `GenerateResultOutputPath` | set | empty, `_generated` | Write generated sources into the project | + +## Local files and secrets + +| Name | Kind | Default | Meaning | +| --- | --- | --- | --- | +| `MSKit_LocalSecretsBaseDirectory` | set | the user-secrets folder | ([Local files](../local-files.md)) | +| `LocalSecretsDir` | set | `/` | A project's local folder | +| `LocalSecrets`, `LocalSecretsTemplate`, `LocalSecretsTemplateDir` | out | | `secrets.json` and its template | +| `UserSecretsLinkNamePrefix` | set | `appsettings` | The linked name, `.UserSecrets.json` | +| `MSKit_SecretsCleanUp` | set | `True` on CI | Delete `secrets.json` | +| `MSKit_CopyUserSecretsIdToOutput`, `MSKit_CopyUserSecretsIdToPublish` | set | empty | `true` copies `secrets.json` to the build / publish output | +| `MSKit_CopySecretsToProject`, `CopySecretsToProjectFileName` | set | `false`, empty | Copy `secrets.json` into the project folder (replaces its `.gitignore`) | +| `MSKit_SlnSecretsId` | set | `MSKit_SlnFileName` | The solution's folder under the user-secrets folder | +| `MSKit_UseSlnSecretsProps`, `MSKit_UseSlnSecretsTargets` | set | `False` | Import `Directory.Secrets.props` / `.targets` | +| `LocalSlnSecretsDir`, `LocalSlnSecretsProps`, `LocalSlnSecretsTargets` | out | | Their paths | +| `MSKit_SlnSecretsCleanUp` | set | `True` on CI | Delete them | +| `LocalCompileTemplateDir` | set | `LocalCompile/` | Templates for `LocalCompile` items | + +## Optional parts + +| Name | Kind | Default | Meaning | +| --- | --- | --- | --- | +| `PackageAsProj_SkipChecks` | set | empty | `True` skips `MSKIT_PAP002` ([PackageAsProj](../optional-parts.md#packageasproj)) | +| `ProjMetadataOutDir` | set | empty (off) | Where the metadata YAML goes ([ProjMetadata](../optional-parts.md#projmetadata)) | +| `MSKit_EFScriptsDir` | out | | The EF scripts folder ([EF](../optional-parts.md#ef)) | diff --git a/docs/roslyn.md b/docs/roslyn.md index 5757e98..e7b0eb4 100644 --- a/docs/roslyn.md +++ b/docs/roslyn.md @@ -30,7 +30,7 @@ A project needs **both** of these: The analyzer and code-fix props set their role themselves, so a project with another name only needs the import; a source generator with another name also sets `true` above it. Set your own `Description`: the analyzer's default, "Code analyzers description", fails [`MSKIT_PKG002`](./reference/codes.md#mskitpkg002). -A project whose name matches a role but whose part is not installed fails with [`MSKIT_ROSLYN001`](./reference/codes.md#mskitroslyn001)-[`003`](./reference/codes.md#mskitroslyn003); a name that matches two roles fails with [`MSKIT_CORE001`](./reference/codes.md#mskitcore001). Detection runs in the props phase, so to stop it set `MSKit_DisableAutoDetect=true` (`MSKit_DisableCodeAnalyzerAutoDetect`, …) or narrow the regex in `Directory.Build.props`; set in the csproj, as the error text suggests, it comes too late. +A project whose name matches a role but whose part is not installed fails with [`MSKIT_ROSLYN001`](./reference/codes.md#mskitroslyn001)-[`003`](./reference/codes.md#mskitroslyn003); a name that matches two roles fails with [`MSKIT_CORE001`](./reference/codes.md#mskitcore001). Detection runs in the props phase, so to stop it set `MSKit_DisableAutoDetect=true` (`MSKit_DisableCodeAnalyzerAutoDetect`, …) or narrow the regex, in `Directory.Build.props` above the kit import; set in the csproj, as the error text suggests, it comes too late. ## What the props set diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md index b8e0c80..29350ef 100644 --- a/docs/troubleshooting.md +++ b/docs/troubleshooting.md @@ -21,7 +21,7 @@ Symptoms, their cause, and the fix. A build message with an `MSKIT` code is expl | Restore fails on a tag build with [`MSKIT_VER006`](./reference/codes.md#mskitver006) | the tag is not a SemVer 2.0 version | delete the tag and the release, tag again (`v2.1.0`, `v2.1.0-beta.1`) | | [`MSKIT_SHARED006`](./reference/codes.md#mskitshared006) / [`007`](./reference/codes.md#mskitshared007) after moving `TargetFrameworks` to `Directory.Build.props` | the csproj still repeats the value | delete it from the csproj | | [`MSKIT_SHARED020`](./reference/codes.md#mskitshared020) locally, but CI is green | a csproj changes `TreatWarningsAsErrors`; the check runs on developer machines only | align the csproj, or set `MSKit_SkipAudit_TreatWarningsAsErrors=True` | -| [`MSKIT_ROSLYN001`](./reference/codes.md#mskitroslyn001)-`003` on a project that is not a Roslyn component | its name ends in `.Analyzers`, `.CodeFixes` or `.SourceGenerator` | rename it, or set `MSKit_DisableAutoDetect=true` in `Directory.Build.props` ([Roslyn](./roslyn.md#wiring-a-project)) | +| [`MSKIT_ROSLYN001`](./reference/codes.md#mskitroslyn001)-`003` on a project that is not a Roslyn component | its name ends in `.Analyzers`, `.CodeFixes` or `.SourceGenerator` | rename it, or set `MSKit_DisableAutoDetect=true` in `Directory.Build.props` above the kit import ([Roslyn](./roslyn.md#wiring-a-project)) | | An `*.Analyzers` project builds for the shared target frameworks and references no Roslyn package | the role's props are not imported | add `` ([Roslyn](./roslyn.md#wiring-a-project)) | | `IsNET8` is empty in a csproj `PropertyGroup` of a single-framework project | the constants are known in the props phase only for multi-targeted inner builds | test them in item and target conditions or in `Directory.Build.targets` ([Build](./build.md#target-framework-constants)) | diff --git a/tests/docs.sh b/tests/docs.sh new file mode 100644 index 0000000..50c82c0 --- /dev/null +++ b/tests/docs.sh @@ -0,0 +1,34 @@ +# Docs: the reference pages cover the kit, and tools/docs-check.sh catches what they miss. +# Sourced by tests/run.sh: uses its pass, bad, $out and $here. + +dc_log="$out/docs-check.log" +if sh "$here/tools/docs-check.sh" > "$dc_log" 2>&1; then pass "docs: $(tail -n 1 "$dc_log" | sed 's/^docs-check: //')" +else bad "docs: tools/docs-check.sh found problems (see $dc_log)"; cat "$dc_log"; fi + +# A copy with one property row, one code section, one name, one link and one anchor broken must fail on each. +dc_copy() { + rm -rf "$1"; mkdir -p "$1/kit/.toolkit" "$1/samples/MinimalLibrary" + cp -R "$here/kit/.toolkit/msbuild" "$1/kit/.toolkit/msbuild" + cp -R "$here/docs" "$1/docs" + for f in README.md CONTRIBUTING.md SECURITY.md CHANGELOG.md LICENSE; do cp "$here/$f" "$1/$f"; done + mkdir -p "$1/tools"; cp "$here/tools/docs-check.sh" "$1/tools/docs-check.sh" +} +dc_bad="$out/docs-check-broken" +dc_copy "$dc_bad" +grep -v '^| `MSKit_SemVerRegex` |' "$here/docs/reference/properties.md" > "$dc_bad/docs/reference/properties.md" +sed 's/^### MSKITVER004$/### Gone/; s/^### MSKITPAP002$/### MSKIT_PAP002/' "$here/docs/reference/codes.md" > "$dc_bad/docs/reference/codes.md" +printf '\nSee `MSKit_NoSuchProperty`, `MSKIT_VER099`, [gone](./no-such-page.md), [anchor](./build.md#no-such-heading) and [bare](build.md).\n' >> "$dc_bad/docs/troubleshooting.md" +sh "$dc_bad/tools/docs-check.sh" > "$out/docs-check-broken.log" 2>&1 && bad "docs: the check passed a broken copy (see $out/docs-check-broken.log)" +for needle in "property MSKit_SemVerRegex" "code MSKITVER004" "write the heading as MSKITPAP002" "mentions MSKit_NoSuchProperty" \ + "mentions MSKITVER099" "no-such-page.md points at a missing file" "no-such-heading names a heading" "build.md must start with ./"; do + grep -qF "$needle" "$out/docs-check-broken.log" && pass "docs: the check reports '$needle'" || bad "docs: the check missed '$needle' (see $out/docs-check-broken.log)" +done + +# The planned code rename (MSKIT_VER006 -> MSKITVER006) must not break the check. +dc_renamed="$out/docs-check-renamed" +dc_copy "$dc_renamed" +sed 's/MSKIT_VER006/MSKITVER006/g' "$here/kit/.toolkit/msbuild/DragoAnt.MSBuildKit/audit/audit.version.targets" \ + > "$dc_renamed/kit/.toolkit/msbuild/DragoAnt.MSBuildKit/audit/audit.version.targets" +grep -q 'Code="MSKITVER006"' "$dc_renamed/kit/.toolkit/msbuild/DragoAnt.MSBuildKit/audit/audit.version.targets" || bad "docs: the rename fixture did not rename MSKIT_VER006" +sh "$dc_renamed/tools/docs-check.sh" > "$out/docs-check-renamed.log" 2>&1 \ + && pass "docs: a code spelled without the underscore still matches its section" || { bad "docs: the renamed code broke the check (see $out/docs-check-renamed.log)"; cat "$out/docs-check-renamed.log"; } diff --git a/tests/run.sh b/tests/run.sh index cbae9c0..a979e1a 100644 --- a/tests/run.sh +++ b/tests/run.sh @@ -109,6 +109,7 @@ grep -q "MSKIT_PKG" "$out/checks-skip.log" && bad "MSKit_SkipPackageChecks=All d . "$here/tests/package-readme.sh" . "$here/tests/tfm-constants.sh" +. "$here/tests/docs.sh" echo if [ "$failures" -eq 0 ]; then echo "self-test: all checks passed"; else echo "self-test: $failures failure(s)"; exit 1; fi From 3288cb068553461070f351f189e1c9c8dce258e5 Mon Sep 17 00:00:00 2001 From: VasiliyF <5789590+vfofanov@users.noreply.github.com> Date: Tue, 6 Oct 2026 06:50:50 +0200 Subject: [PATCH 7/8] Changelog: documentation moved to docs/ --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 788b188..5c72fa5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ All notable changes to this project are documented here. The format follows [Kee ## [Unreleased] +### Changed + +- The documentation moved from the README into [docs/](./docs/README.md), one page per topic in reading order, with a [property reference](./docs/reference/properties.md) and a [code reference](./docs/reference/codes.md) that cover everything the kit sets, reads and reports. Corrected along the way: most packaging defaults apply to every project, not only packable ones; a Roslyn project imports its role's props itself; an update rewrites more than `.toolkit/msbuild/`; any tag build is a release build. + ## [0.2.1] - 2026-10-05 ### Added From ad9699a11ed332278bde6e2528b4271e8e1bb2f8 Mon Sep 17 00:00:00 2001 From: VasiliyF <5789590+vfofanov@users.noreply.github.com> Date: Tue, 6 Oct 2026 08:18:16 +0200 Subject: [PATCH 8/8] Drop the code prefix as a bare word from docs and the docs check --- docs/README.md | 2 +- docs/troubleshooting.md | 2 +- tools/docs-check.sh | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/README.md b/docs/README.md index c3d4cd5..9068739 100644 --- a/docs/README.md +++ b/docs/README.md @@ -17,7 +17,7 @@ Read in this order; each page stands on its own, so jump to the one you need. | 11 | [Roslyn components](./roslyn.md) | build analyzers, code fixes and source generators | | 12 | [Customizing](./customizing.md) | change the owner, import your own files, override a default | | 13 | [Troubleshooting](./troubleshooting.md) | fix a failing build or update | -| 14 | [Code reference](./reference/codes.md) | look up any `MSKIT` warning or error | +| 14 | [Code reference](./reference/codes.md) | look up any warning or error the kit reports | | 15 | [Property reference](./reference/properties.md) | look up any property the kit sets or reads | | 16 | [Migrating from MSBuild.Routine](./migrating-from-msbuild-routine.md) | move a repository off the older submodule | diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md index 29350ef..83bed30 100644 --- a/docs/troubleshooting.md +++ b/docs/troubleshooting.md @@ -1,6 +1,6 @@ # Troubleshooting -Symptoms, their cause, and the fix. A build message with an `MSKIT` code is explained in the [code reference](./reference/codes.md). +Symptoms, their cause, and the fix. A build message with an `MSKIT_` code is explained in the [code reference](./reference/codes.md). ## Updating diff --git a/tools/docs-check.sh b/tools/docs-check.sh index 55a7b35..f7bd11b 100644 --- a/tools/docs-check.sh +++ b/tools/docs-check.sh @@ -1,7 +1,7 @@ #!/bin/sh # Keeps the documentation honest against the kit: every property and item the kit defines has a row # in docs/reference/properties.md, every code it reports a section headed by the code id without the -# underscore in docs/reference/codes.md, every MSKit_ name and MSKIT code the docs mention exists in +# underscore in docs/reference/codes.md, every MSKit_ name and MSKIT_ code the docs mention exists in # the kit, and every relative link resolves (anchors included). # Usage: sh tools/docs-check.sh [--root DIR] [--list properties|items|codes] set -eu @@ -55,7 +55,7 @@ find kit/.toolkit/msbuild -type f \( -name '*.props' -o -name '*.targets' -o -na rest = out while (match(rest, /MSKIT_?[A-Z]+[0-9][0-9][0-9]/)) { print "C", id(substr(rest, RSTART, RLENGTH)), FILENAME ":" FNR; rest = substr(rest, RSTART + RLENGTH) } } - function id(c) { sub(/^MSKIT_/, "MSKIT", c); return c }' {} + > "$work/raw" + function id(c) { sub(/_/, "", c); return c }' {} + > "$work/raw" # Public names: MSKit_* (set or read) and Is* (set); never the kit's own _-prefixed state. # Each is listed with the first place that sets it, else the first place that reads it. @@ -109,7 +109,7 @@ awk -v dir="$work" ' rest = $0 while (match(rest, /MSKit_[A-Za-z0-9_]+[*<]?/)) { t = substr(rest, RSTART, RLENGTH); rest = substr(rest, RSTART + RLENGTH); if (t !~ /[*<]$/) print "N\t" f ":" FNR "\t" t } rest = $0 - while (match(rest, /MSKIT_?[A-Z]+[0-9][0-9][0-9]/)) { t = substr(rest, RSTART, RLENGTH); rest = substr(rest, RSTART + RLENGTH); sub(/^MSKIT_/, "MSKIT", t); print "M\t" f ":" FNR "\t" t } + while (match(rest, /MSKIT_?[A-Z]+[0-9][0-9][0-9]/)) { t = substr(rest, RSTART, RLENGTH); rest = substr(rest, RSTART + RLENGTH); sub(/_/, "", t); print "M\t" f ":" FNR "\t" t } }' $(cat "$work/docs.lst") > "$work/docs.tsv" awk -v dir="$work" -F'\t' ' @@ -131,7 +131,7 @@ awk -v dir="$work" -F'\t' ' $1 == "N" { if (!($3 in known)) problem($2 " mentions " $3 ", which the kit does not define or read"); next } $1 == "M" { if (!($3 in code)) problem($2 " mentions " $3 ", which the kit never reports"); next } $1 == "U" { problem($2 ": write the heading as " gensub_id($3) " so its anchor is the code id without the underscore"); next } - function gensub_id(c) { sub(/^MSKIT_/, "MSKIT", c); return c } + function gensub_id(c) { sub(/_/, "", c); return c } $1 == "L" { links[++nl] = $0; next } END { for (i = 1; i <= n; i++) if (!(order[i] in documented)) problem(what[order[i]] " " order[i] " (" known[order[i]] ") has no row in docs/reference/properties.md")