From 57cc0a1e2b6054f63dd916e9a60862e2fd0ff4e5 Mon Sep 17 00:00:00 2001
From: VasiliyF <5789590+vfofanov@users.noreply.github.com>
Date: Mon, 5 Oct 2026 14:49:41 +0200
Subject: [PATCH 1/4] Test the TFM constants of single-TFM test projects
Net8/9/10 test projects with one TargetFramework must see IsNETxx
and pass MSKIT_TEST005; a net6 one must still fail it; multi-TFM
inner builds keep the props-phase values.
---
tests/fixtures/TfmConstants/.gitignore | 1 +
.../TfmConstants/Directory.Build.props | 5 ++
.../TfmConstants/Directory.Build.targets | 5 ++
.../Multi.Tests/Multi.Tests.csproj | 8 ++++
.../Net10.Tests/Net10.Tests.csproj | 7 +++
.../TfmConstants/Net6.Tests/Net6.Tests.csproj | 7 +++
.../TfmConstants/Net8.Tests/Net8.Tests.csproj | 7 +++
.../TfmConstants/Net9.Tests/Net9.Tests.csproj | 7 +++
tests/run.sh | 4 +-
tests/tfm-constants.sh | 46 +++++++++++++++++++
10 files changed, 96 insertions(+), 1 deletion(-)
create mode 100644 tests/fixtures/TfmConstants/.gitignore
create mode 100644 tests/fixtures/TfmConstants/Directory.Build.props
create mode 100644 tests/fixtures/TfmConstants/Directory.Build.targets
create mode 100644 tests/fixtures/TfmConstants/Multi.Tests/Multi.Tests.csproj
create mode 100644 tests/fixtures/TfmConstants/Net10.Tests/Net10.Tests.csproj
create mode 100644 tests/fixtures/TfmConstants/Net6.Tests/Net6.Tests.csproj
create mode 100644 tests/fixtures/TfmConstants/Net8.Tests/Net8.Tests.csproj
create mode 100644 tests/fixtures/TfmConstants/Net9.Tests/Net9.Tests.csproj
create mode 100644 tests/tfm-constants.sh
diff --git a/tests/fixtures/TfmConstants/.gitignore b/tests/fixtures/TfmConstants/.gitignore
new file mode 100644
index 0000000..428d735
--- /dev/null
+++ b/tests/fixtures/TfmConstants/.gitignore
@@ -0,0 +1 @@
+.toolkit/
diff --git a/tests/fixtures/TfmConstants/Directory.Build.props b/tests/fixtures/TfmConstants/Directory.Build.props
new file mode 100644
index 0000000..95daf71
--- /dev/null
+++ b/tests/fixtures/TfmConstants/Directory.Build.props
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/tests/fixtures/TfmConstants/Directory.Build.targets b/tests/fixtures/TfmConstants/Directory.Build.targets
new file mode 100644
index 0000000..07200a8
--- /dev/null
+++ b/tests/fixtures/TfmConstants/Directory.Build.targets
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/tests/fixtures/TfmConstants/Multi.Tests/Multi.Tests.csproj b/tests/fixtures/TfmConstants/Multi.Tests/Multi.Tests.csproj
new file mode 100644
index 0000000..6c52674
--- /dev/null
+++ b/tests/fixtures/TfmConstants/Multi.Tests/Multi.Tests.csproj
@@ -0,0 +1,8 @@
+
+
+
+ net8.0;net10.0
+ True
+
+
+
diff --git a/tests/fixtures/TfmConstants/Net10.Tests/Net10.Tests.csproj b/tests/fixtures/TfmConstants/Net10.Tests/Net10.Tests.csproj
new file mode 100644
index 0000000..4340f62
--- /dev/null
+++ b/tests/fixtures/TfmConstants/Net10.Tests/Net10.Tests.csproj
@@ -0,0 +1,7 @@
+
+
+
+ net10.0
+
+
+
diff --git a/tests/fixtures/TfmConstants/Net6.Tests/Net6.Tests.csproj b/tests/fixtures/TfmConstants/Net6.Tests/Net6.Tests.csproj
new file mode 100644
index 0000000..dbc1517
--- /dev/null
+++ b/tests/fixtures/TfmConstants/Net6.Tests/Net6.Tests.csproj
@@ -0,0 +1,7 @@
+
+
+
+ net6.0
+
+
+
diff --git a/tests/fixtures/TfmConstants/Net8.Tests/Net8.Tests.csproj b/tests/fixtures/TfmConstants/Net8.Tests/Net8.Tests.csproj
new file mode 100644
index 0000000..58990cd
--- /dev/null
+++ b/tests/fixtures/TfmConstants/Net8.Tests/Net8.Tests.csproj
@@ -0,0 +1,7 @@
+
+
+
+ net8.0
+
+
+
diff --git a/tests/fixtures/TfmConstants/Net9.Tests/Net9.Tests.csproj b/tests/fixtures/TfmConstants/Net9.Tests/Net9.Tests.csproj
new file mode 100644
index 0000000..1f059d7
--- /dev/null
+++ b/tests/fixtures/TfmConstants/Net9.Tests/Net9.Tests.csproj
@@ -0,0 +1,7 @@
+
+
+
+ net9.0
+
+
+
diff --git a/tests/run.sh b/tests/run.sh
index d617556..b42d5b7 100644
--- a/tests/run.sh
+++ b/tests/run.sh
@@ -9,6 +9,7 @@ kit="$here/kit"
sample="$here/samples/MinimalLibrary"
fixtures="$here/tests/fixtures/PackageChecks"
readme_fixture="$here/tests/fixtures/PackageReadme"
+tfm_fixture="$here/tests/fixtures/TfmConstants"
out="$here/dist/selftest"
run_tests=1
[ "${1:-}" = "--skip-tests" ] && run_tests=0
@@ -18,7 +19,7 @@ failures=0
pass() { echo "PASS $*"; }
bad() { echo "FAIL $*"; failures=$((failures+1)); }
-for root in "$sample" "$fixtures" "$readme_fixture"; do
+for root in "$sample" "$fixtures" "$readme_fixture" "$tfm_fixture"; do
sh "$kit/.toolkit/update.sh" --source "$kit" --root "$root" > "$out/install.log" || { cat "$out/install.log"; exit 1; }
done
pass "update.sh installed the kit into the sample and the fixtures"
@@ -106,6 +107,7 @@ $clean_env dotnet pack "$fixtures/BadPackage/BadPackage.csproj" -c Release -nolo
grep -q "MSKIT_PKG" "$out/checks-skip.log" && bad "MSKit_SkipPackageChecks=All did not silence the checks" || pass "MSKit_SkipPackageChecks=All silences the checks"
. "$here/tests/package-readme.sh"
+. "$here/tests/tfm-constants.sh"
echo
if [ "$failures" -eq 0 ]; then echo "self-test: all checks passed"; else echo "self-test: $failures failure(s)"; exit 1; fi
diff --git a/tests/tfm-constants.sh b/tests/tfm-constants.sh
new file mode 100644
index 0000000..66012e3
--- /dev/null
+++ b/tests/tfm-constants.sh
@@ -0,0 +1,46 @@
+# TfmConstants: IsNETxx follows the TargetFramework a csproj sets, single or multi-targeted.
+# Sourced by tests/run.sh: uses its pass, bad, $out, $here, $clean_env and $tfm_fixture.
+
+tfm_prop() {
+ $clean_env dotnet msbuild "$tfm_fixture/$1/$1.csproj" -nologo "-getProperty:$2" ${3:+"$3"} 2>&1 | tr -d '\r' | tail -n 1
+}
+tfm_expect() {
+ actual=$(tfm_prop "$1" "$2" "${4:-}")
+ [ "$actual" = "$3" ] && pass "tfm $1${4:+ $4}: $2='$actual'" || bad "tfm $1${4:+ $4}: $2 expected '$3', got '$actual'"
+}
+tfm_validate() {
+ name="$1"; expect="$2"; shift 2
+ if $clean_env dotnet msbuild "$tfm_fixture/$name/$name.csproj" -nologo -t:ValidateXUnitV3TFM "$@" > "$out/tfm-$name.log" 2>&1; then
+ [ "$expect" = ok ] && pass "tfm $name passes MSKIT_TEST005" || bad "tfm $name: expected MSKIT_TEST005, the check passed"
+ elif grep -q "MSKIT_TEST005" "$out/tfm-$name.log"; then
+ [ "$expect" = fail ] && pass "tfm $name fails with MSKIT_TEST005" || bad "tfm $name: false MSKIT_TEST005 (see $out/tfm-$name.log)"
+ else
+ bad "tfm $name: the check failed without MSKIT_TEST005 (see $out/tfm-$name.log)"
+ fi
+}
+
+for v in 8 9 10; do
+ tfm_expect "Net$v.Tests" IsNET8_OR_GREATER True
+ tfm_expect "Net$v.Tests" IsNET$v True
+ tfm_expect "Net$v.Tests" TargetFrameworkVersionMajor "$v"
+ tfm_validate "Net$v.Tests" ok
+done
+tfm_expect Net8.Tests TestingPlatformDotnetTestSupport True
+tfm_expect Net9.Tests TestingPlatformDotnetTestSupport True
+tfm_expect Net10.Tests TestingPlatformDotnetTestSupport ""
+
+tfm_expect Net6.Tests IsNET8_OR_GREATER ""
+tfm_validate Net6.Tests fail
+
+# Multi-targeted: the outer build has no TargetFramework; each inner build gets it as a global
+# property, so the constants are right in the props phase too (a csproj property condition).
+tfm_expect Multi.Tests IsNET8 ""
+tfm_expect Multi.Tests IsNET8 True -p:TargetFramework=net8.0
+tfm_expect Multi.Tests FixturePropsPhaseIsNet8 True -p:TargetFramework=net8.0
+tfm_expect Multi.Tests IsNET10_OR_GREATER True -p:TargetFramework=net10.0
+tfm_expect Multi.Tests IsNET8 "" -p:TargetFramework=net10.0
+tfm_validate Multi.Tests ok -p:TargetFramework=net8.0
+
+$clean_env dotnet build "$tfm_fixture/Net10.Tests/Net10.Tests.csproj" -c Release -nologo > "$out/tfm-build.log" 2>&1 \
+ && pass "tfm a single-TFM net10.0 xunit.v3 test project builds" \
+ || { bad "tfm Net10.Tests build (see $out/tfm-build.log)"; grep -m 3 "error" "$out/tfm-build.log"; }
From a21221bc8753a1600cd699e3402b95c4c6e5d9df Mon Sep 17 00:00:00 2001
From: VasiliyF <5789590+vfofanov@users.noreply.github.com>
Date: Mon, 5 Oct 2026 14:55:22 +0200
Subject: [PATCH 2/4] Evaluate the TFM constants again in the targets phase
A single is set by the csproj body, after the props
phase, so IsNETxx were empty there: MSKIT_TEST005 fired on net8-10
test projects and TestingPlatformDotnetTestSupport was never set.
tfm.constants.targets clears and redefines them from TfmConstants'
init.targets; the props-phase values stay for inner builds.
---
CHANGELOG.md | 4 ++
README.md | 2 +-
.../init.targets | 5 ++
.../tfm.constants.targets | 69 +++++++++++++++++++
kit/.toolkit/msbuild/init.targets | 1 +
tests/tfm-constants.sh | 12 ++++
6 files changed, 92 insertions(+), 1 deletion(-)
create mode 100644 kit/.toolkit/msbuild/DragoAnt.MSBuildKit.TfmConstants/init.targets
create mode 100644 kit/.toolkit/msbuild/DragoAnt.MSBuildKit.TfmConstants/tfm.constants.targets
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3e45543..5823d99 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -13,6 +13,10 @@ All notable changes to this project are documented here. The format follows [Kee
- nuget.org's allowed image hosts moved from a property default to `nuget.allowed-image-hosts.txt` in the Packaging part, read only on pack; `MSKit_PackageReadmeAllowedImageHosts` still replaces it.
+### Fixed
+
+- A test project with a single `` (net8.0, net9.0, net10.0) no longer fails with a false `MSKIT_TEST005`, and on net8.0/net9.0 gets `TestingPlatformDotnetTestSupport`: the TfmConstants part now evaluates `IsNETxx` again in the targets phase, after the csproj body has set the framework. The props-phase values stay for multi-targeted inner builds.
+
## [0.1.1] - 2026-10-04
### Fixed
diff --git a/README.md b/README.md
index a1a43d0..d7a387e 100644
--- a/README.md
+++ b/README.md
@@ -69,7 +69,7 @@ Default parts are always installed; add optional ones with `--add`.
| `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 |
+| `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 |
diff --git a/kit/.toolkit/msbuild/DragoAnt.MSBuildKit.TfmConstants/init.targets b/kit/.toolkit/msbuild/DragoAnt.MSBuildKit.TfmConstants/init.targets
new file mode 100644
index 0000000..6c58f27
--- /dev/null
+++ b/kit/.toolkit/msbuild/DragoAnt.MSBuildKit.TfmConstants/init.targets
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/kit/.toolkit/msbuild/DragoAnt.MSBuildKit.TfmConstants/tfm.constants.targets b/kit/.toolkit/msbuild/DragoAnt.MSBuildKit.TfmConstants/tfm.constants.targets
new file mode 100644
index 0000000..d68b6f9
--- /dev/null
+++ b/kit/.toolkit/msbuild/DragoAnt.MSBuildKit.TfmConstants/tfm.constants.targets
@@ -0,0 +1,69 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ True
+
+ True
+ True
+ True
+
+ True
+
+ True
+ True
+ True
+ True
+ True
+ True
+ True
+ True
+
+ True
+ True
+ True
+ True
+ True
+ True
+ True
+ True
+
+
+
+ 7
+ 8
+ 9
+ 10
+ 11
+ 12
+ 13
+ 14
+
+
+
diff --git a/kit/.toolkit/msbuild/init.targets b/kit/.toolkit/msbuild/init.targets
index 0385214..f4e11ab 100644
--- a/kit/.toolkit/msbuild/init.targets
+++ b/kit/.toolkit/msbuild/init.targets
@@ -3,6 +3,7 @@
+
diff --git a/tests/tfm-constants.sh b/tests/tfm-constants.sh
index 66012e3..e65e60e 100644
--- a/tests/tfm-constants.sh
+++ b/tests/tfm-constants.sh
@@ -19,6 +19,18 @@ tfm_validate() {
fi
}
+tfm_dir="$here/kit/.toolkit/msbuild/DragoAnt.MSBuildKit.TfmConstants"
+grep 'Condition=' "$tfm_dir/tfm.constants.props" > "$out/tfm-props.defs"
+grep 'Condition=' "$tfm_dir/tfm.constants.targets" > "$out/tfm-targets.defs"
+cmp -s "$out/tfm-props.defs" "$out/tfm-targets.defs" && [ -s "$out/tfm-props.defs" ] \
+ && pass "tfm tfm.constants.targets defines the same constants as tfm.constants.props" \
+ || bad "tfm tfm.constants.targets and tfm.constants.props differ (diff $out/tfm-props.defs $out/tfm-targets.defs)"
+sed -n 's/.*<\([A-Za-z0-9_]*\) Condition=.*/\1/p' "$out/tfm-props.defs" | sort -u > "$out/tfm-defined.names"
+sed -n 's/^ *<\([A-Za-z0-9_]*\)><\/\1>$/\1/p' "$tfm_dir/tfm.constants.targets" | sort -u > "$out/tfm-cleared.names"
+cmp -s "$out/tfm-defined.names" "$out/tfm-cleared.names" \
+ && pass "tfm tfm.constants.targets clears every constant before redefining it" \
+ || bad "tfm cleared and defined constants differ (diff $out/tfm-defined.names $out/tfm-cleared.names)"
+
for v in 8 9 10; do
tfm_expect "Net$v.Tests" IsNET8_OR_GREATER True
tfm_expect "Net$v.Tests" IsNET$v True
From dc942d75fc906b63564c7209bb22fb11b1ff1b25 Mon Sep 17 00:00:00 2001
From: VasiliyF <5789590+vfofanov@users.noreply.github.com>
Date: Mon, 5 Oct 2026 14:58:06 +0200
Subject: [PATCH 3/4] Test the release-notes default off GitHub
With the readme generator on, GitLab, self-hosted GitLab and an
explicit MSKit_ReleasesUrl must fill PackageReleaseNotes and keep
MSKIT_PKG016 quiet, incrementally too; an empty releases URL, an
unknown host or MSKit_DefaultReleaseNotes=False still raise it.
---
tests/package-readme.sh | 26 +++++++++++++++++++++++++-
1 file changed, 25 insertions(+), 1 deletion(-)
diff --git a/tests/package-readme.sh b/tests/package-readme.sh
index a958aa6..dc444d8 100644
--- a/tests/package-readme.sh
+++ b/tests/package-readme.sh
@@ -25,6 +25,11 @@ rf_count() {
n=$(grep -cF -- "$2" "$out/readme-$1.md" || true)
[ "$n" = "$3" ] && pass "readme $1 has $2 $3 time(s)" || bad "readme $1 has $2 $n time(s), expected $3"
}
+rf_notes() {
+ nupkg=$(ls "$out/readme-$1"/*.nupkg 2>/dev/null | grep -v '\.snupkg$' | head -n 1)
+ actual=$([ -n "$nupkg" ] && unzip -p "$nupkg" '*.nuspec' | tr -d '\r' | sed -n 's:.*\(.*\).*:\1:p')
+ [ "$actual" = "$2" ] && pass "readme $1 release notes '$actual'" || bad "readme $1 release notes: expected '$2', got '$actual'"
+}
generated="$readme_fixture/Observer/obj/Release/package.readme.md"
# Pack only: a build never writes the generated readme.
@@ -55,7 +60,8 @@ rf_has github "[fenced](./fenced.md)"
rf_has github ""
awk '/^## /{exit} /\[Release notes\]/{found=1} END{exit !found}' "$out/readme-github.md" \
&& pass "readme github: the release-notes link is in the overview" || bad "readme github: the release-notes link is not in the overview"
-for code in MSKIT_PKG009 MSKIT_PKG012 MSKIT_PKG017 MSKIT_PKG020 MSKIT_PKG021 MSKIT_PKG022; do rf_quiet github $code; done
+for code in MSKIT_PKG009 MSKIT_PKG012 MSKIT_PKG016 MSKIT_PKG017 MSKIT_PKG020 MSKIT_PKG021 MSKIT_PKG022; do rf_quiet github $code; done
+rf_notes github "https://github.com/DragoAnt/Fixture/releases"
# Incremental: the same inputs leave the generated file alone; a new commit regenerates it.
stamp1=$(stat -c %Y "$generated" 2>/dev/null || echo none)
@@ -93,12 +99,25 @@ rf_has gitlab "[Release notes](https://gitlab.com/dragoant/sub/fixture/-/release
rf_has gitlab "[Issues](https://gitlab.com/dragoant/sub/fixture/-/issues)"
rf_quiet gitlab MSKIT_PKG021
rf_quiet gitlab MSKIT_PKG017
+rf_quiet gitlab MSKIT_PKG016
+rf_notes gitlab "https://gitlab.com/dragoant/sub/fixture/-/releases"
+rf_pack gitlab-again Observer $gl
+rf_quiet gitlab-again MSKIT_PKG016
+rf_notes gitlab-again "https://gitlab.com/dragoant/sub/fixture/-/releases"
+rf_pack gitlab-no-releases Observer $gl -p:MSKit_ReleasesUrl=
+rf_warns gitlab-no-releases MSKIT_PKG016
+rf_pack gitlab-no-default Observer $gl -p:MSKit_DefaultReleaseNotes=False
+rf_warns gitlab-no-default MSKIT_PKG016
+rf_pack gitlab-notes Observer $gl "-p:PackageReleaseNotes=See the changelog."
+rf_notes gitlab-notes "See the changelog."
# Self-hosted GitLab recognised through SourceLinkGitLabHost: its raw images are not on nuget.org's list.
rf_pack gitlab-self Observer -p:RepositoryUrl=https://git.example.org/team/fixture -p:RepositoryCommit=$sha -p:FixtureGitLabHost=git.example.org
rf_has gitlab-self "[the guide](https://git.example.org/team/fixture/-/blob/$sha/$rel/docs/guide.md)"
rf_has gitlab-self ""
rf_has gitlab-self "[Issues](https://git.example.org/team/fixture/-/issues)"
+rf_quiet gitlab-self MSKIT_PKG016
+rf_notes gitlab-self "https://git.example.org/team/fixture/-/releases"
rf_warns gitlab-self MSKIT_PKG021
grep "warning MSKIT_PKG021" "$out/readme-gitlab-self.log" | grep -q "line 11" \
&& pass "readme gitlab-self: MSKIT_PKG021 names the README line" || bad "readme gitlab-self: MSKIT_PKG021 does not name line 11"
@@ -109,7 +128,12 @@ grep "warning MSKIT_PKG021" "$out/readme-gitlab-self.log" | grep -q "img.shields
rf_pack unknown Observer -p:RepositoryUrl=https://code.example.net/team/fixture -p:RepositoryCommit=$sha
rf_warns unknown MSKIT_PKG020
rf_warns unknown MSKIT_PKG017
+rf_warns unknown MSKIT_PKG016
rf_has unknown "[the guide](./docs/guide.md)"
+rf_pack unknown-releases Observer -p:RepositoryUrl=https://code.example.net/team/fixture -p:RepositoryCommit=$sha \
+ "-p:MSKit_ReleasesUrl=https://code.example.net/team/fixture/changes"
+rf_quiet unknown-releases MSKIT_PKG016
+rf_notes unknown-releases "https://code.example.net/team/fixture/changes"
# Overrides: provider, templates, an empty releases URL (link omitted), a custom issues URL.
rf_pack provider Observer -p:RepositoryUrl=https://code.example.net/team/fixture -p:RepositoryCommit=$sha -p:MSKit_RepoProvider=GitLab
From a90265afa176296b52b9b9ce97a4a8b1dd1dcc94 Mon Sep 17 00:00:00 2001
From: VasiliyF <5789590+vfofanov@users.noreply.github.com>
Date: Mon, 5 Oct 2026 15:03:39 +0200
Subject: [PATCH 4/4] Default the release notes to the releases page the readme
links
PackageReleaseNotes defaulted only on GitHub, so MSKIT_PKG016 fired
on GitLab and the other hosts even with the readme generator on. The
generator now writes the releases URL it resolved beside the readme,
and an empty PackageReleaseNotes takes it before the checks run.
---
CHANGELOG.md | 1 +
README.md | 2 +-
docs/package-readme.md | 2 +-
.../audit/audit.package.targets | 2 +-
.../package.readme.targets | 16 +++++++++++++++-
.../package.tasks.cs.txt | 3 +++
6 files changed, 22 insertions(+), 4 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5823d99..9ac40b2 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -16,6 +16,7 @@ All notable changes to this project are documented here. The format follows [Kee
### Fixed
- A test project with a single `` (net8.0, net9.0, net10.0) no longer fails with a false `MSKIT_TEST005`, and on net8.0/net9.0 gets `TestingPlatformDotnetTestSupport`: the TfmConstants part now evaluates `IsNETxx` again in the targets phase, after the csproj body has set the framework. The props-phase values stay for multi-targeted inner builds.
+- `MSKIT_PKG016` no longer fires on GitLab and the other hosts the generated readme knows: with `MSKit_PackageReadmeFrom` set, an empty `PackageReleaseNotes` defaults to the releases page the readme links (`MSKit_ReleasesUrl`). GitHub keeps its tag release page; `MSKit_DefaultReleaseNotes=False` still turns the default off.
## [0.1.1] - 2026-10-04
diff --git a/README.md b/README.md
index d7a387e..2008df9 100644
--- a/README.md
+++ b/README.md
@@ -103,7 +103,7 @@ Defaults apply to projects with `IsPackable=True` and yield to any value you set
| `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 | [release notes](https://learn.microsoft.com/nuget/create-packages/package-authoring-best-practices#release-notes) |
+| `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) |
diff --git a/docs/package-readme.md b/docs/package-readme.md
index a773aea..4ff4ed4 100644
--- a/docs/package-readme.md
+++ b/docs/package-readme.md
@@ -51,7 +51,7 @@ A self-hosted GitLab is recognised through the same item Source Link uses:
| `MSKit_RepoProvider` | detected | `GitHub`, `GitLab`, `AzureDevOps`, `Bitbucket` or `Gitea` |
| `MSKit_RepoBlobUrlTemplate` | the provider's | File links; a `/blob/` in it becomes `/tree/` for folders |
| `MSKit_RepoRawUrlTemplate` | the provider's | Images |
-| `MSKit_ReleasesUrl` | `auto` | The release-notes page; empty leaves the link out |
+| `MSKit_ReleasesUrl` | `auto` | The release-notes page, also the default `PackageReleaseNotes` off GitHub; empty leaves the link out |
| `MSKit_IssuesUrl` | `auto` | The issues page; empty leaves the link out |
| `MSKit_RepositoryVisibility` | `$(CI_PROJECT_VISIBILITY)` | `private` or `internal` raises `MSKIT_PKG022` |
| `MSKit_GeneratedPackageReadmePath` | `obj//package.readme.md` | Where the generated file goes |
diff --git a/kit/.toolkit/msbuild/DragoAnt.MSBuildKit.Packaging/audit/audit.package.targets b/kit/.toolkit/msbuild/DragoAnt.MSBuildKit.Packaging/audit/audit.package.targets
index 2eb368d..563bc1e 100644
--- a/kit/.toolkit/msbuild/DragoAnt.MSBuildKit.Packaging/audit/audit.package.targets
+++ b/kit/.toolkit/msbuild/DragoAnt.MSBuildKit.Packaging/audit/audit.package.targets
@@ -93,7 +93,7 @@
Text="Package '$(PackageId)' icon is not a $(MSKit_PackageIconSize)x$(MSKit_PackageIconSize) PNG (PNG: $(_MSKit_IconIsPng), size: $(_MSKit_IconWidth)x$(_MSKit_IconHeight))."
Rule="$(_MSKit_PkgRules)#icon" />
<_MSKit_PkgFinding Include="MSKIT_PKG016" Condition="'$(PackageReleaseNotes)'==''"
- Text="Package '$(PackageId)' has no PackageReleaseNotes. Set them, or a link to the changelog (on GitHub the kit links the releases page by default)."
+ Text="Package '$(PackageId)' has no PackageReleaseNotes. Set them, or a link to the changelog (the kit links the releases page by default on GitHub, and on any host the generated readme knows with MSKit_PackageReadmeFrom)."
Rule="$(_MSKit_PkgRules)#release-notes" />
<_MSKit_PkgFinding Include="MSKIT_PKG017" Condition="'$(_MSKit_RelativeLinks)'!=''"
Text="The package README of '$(PackageId)' has relative links, which break on nuget.org: $(_MSKit_RelativeLinks). Use absolute URLs."
diff --git a/kit/.toolkit/msbuild/DragoAnt.MSBuildKit.Packaging/package.readme.targets b/kit/.toolkit/msbuild/DragoAnt.MSBuildKit.Packaging/package.readme.targets
index ec44f4c..c94651e 100644
--- a/kit/.toolkit/msbuild/DragoAnt.MSBuildKit.Packaging/package.readme.targets
+++ b/kit/.toolkit/msbuild/DragoAnt.MSBuildKit.Packaging/package.readme.targets
@@ -9,6 +9,7 @@
$(MSBuildThisFileDirectory)nuget.allowed-image-hosts.txt
<_MSKit_ReadmeStampPath>$(MSKit_GeneratedPackageReadmePath).inputs
<_MSKit_ReadmeFindingsBase>$(MSKit_GeneratedPackageReadmePath).findings
+ <_MSKit_ReadmeReleasesUrlPath>$(MSKit_GeneratedPackageReadmePath).releases
@@ -28,7 +29,7 @@
+ DependsOnTargets="_MSKit_PackageReadmeInputs;_MSKit_WritePackageReadme;_MSKit_ReportPackageReadmeFindings;_MSKit_DefaultReleaseNotesFromReadme" />
+
+
+
+
+
+
+ $(_MSKit_ReadmeReleasesUrl)
+
+
+
diff --git a/kit/.toolkit/msbuild/DragoAnt.MSBuildKit.Packaging/package.tasks.cs.txt b/kit/.toolkit/msbuild/DragoAnt.MSBuildKit.Packaging/package.tasks.cs.txt
index 94de646..d590633 100644
--- a/kit/.toolkit/msbuild/DragoAnt.MSBuildKit.Packaging/package.tasks.cs.txt
+++ b/kit/.toolkit/msbuild/DragoAnt.MSBuildKit.Packaging/package.tasks.cs.txt
@@ -118,6 +118,7 @@ public sealed class MSKit_PackageAssets : Task
public string OutputPath { get; set; }
public string FindingsBase { get; set; }
+ public string ReleasesUrlPath { get; set; }
public string RepoRoot { get; set; }
public string PackageId { get; set; }
public string RepositoryUrl { get; set; }
@@ -410,5 +411,7 @@ public sealed class MSKit_PackageAssets : Task
File.WriteAllText(OutputPath, string.Join("\n", result) + "\n", utf8);
foreach (var code in codes)
File.WriteAllText(FindingsBase + "." + code + ".txt", string.Join("\n", findings[code]) + (findings[code].Count > 0 ? "\n" : ""), utf8);
+ if (!string.IsNullOrWhiteSpace(ReleasesUrlPath))
+ File.WriteAllText(ReleasesUrlPath, releasesUrl.Length > 0 ? releasesUrl + "\n" : "", utf8);
}
}