Skip to content

fix(api-docs/dotnet): read package versions from MSBuild, not a csproj regex - #133

Merged
WomB0ComB0 merged 1 commit into
mainfrom
fix/dotnet-api-docs-version-from-msbuild
Sep 22, 2026
Merged

WomB0ComB0 merged 1 commit into
mainfrom
fix/dotnet-api-docs-version-from-msbuild

Conversation

@WomB0ComB0

Copy link
Copy Markdown
Member

What broke

The .NET API-docs template labels each package in the generated index by
regex-scraping <Version> / <VersionPrefix> out of the .csproj files,
falling back to the string unknown.

The .NET SDK repo has just moved to tag-derived versioning (MinVer), so it
deliberately carries no literal version anywhere — a literal in the tree is
exactly the thing an inbound sync can revert, and one did, twice. The scrape
therefore finds nothing and the generated reference now renders:

- `ResQ.Clients` — `vunknown`
- `ResQ.Core` — `vunknown`
- `ResQ.Protocols` — `vunknown`
- `ResQ.Blockchain` — `vunknown`
- `ResQ.Storage` — `vunknown`
- `ResQ.Simulation` — `vunknown`

Reproduced by running the template's own read_version() against a
full-history clone of the post-migration tree, not inferred.

Two things were wrong, and fixing either alone leaves it broken

1. The version came from the wrong source. Replaced with an MSBuild
query, which is authoritative for both repo shapes — it reports the literal
where one exists and the tag-derived value where it does not. The .csproj
scan stays behind it as a fallback for when the query is unavailable (no
-getProperty before SDK 8.0.200) or the project cannot be evaluated, then
unknown as before.

Two details the obvious version of this fix gets wrong:

  • -getProperty:Version on its own only evaluates the project, and a
    tag-derived version is computed inside a target. A bare query returns the
    SDK's implicit 1.0.0. Measured: dotnet msbuild <proj> -getProperty:Version
    1.0.0; dotnet build <proj> -getProperty:Version1.0.0 too, because
    -getProperty without an explicit target skips target execution. The query
    here runs -t:GetAssemblyVersion, a stock SDK target whose job is to settle
    the version → 0.6.1-alpha.0.42.
  • An MSBuild answer of exactly 1.0.0 is indistinguishable from the SDK
    default, so it is treated as inconclusive and the literal scan still runs.
    That preserves the existing multi-candidate behaviour the old comment
    describes, where the version lives only in the packaging variant of a
    duplicated .csproj.

2. The checkout was shallow. fetch-depth: 0 added. Tag-derived
versioning reads the tag graph at build time and, given a shallow tagless
checkout, does not fail — it silently settles on 0.0.0-alpha.<height>. So
after fixing the lookup alone the index would render v0.0.0-alpha.0, which
is worse than vunknown: a plausible-looking wrong version. persist-credentials: false
is unchanged, and the separate docs-repo checkout is untouched.

Verification

Against a full-history clone of the .NET SDK repo at e727403, with the
solution restored and built exactly as the workflow does.

Before (current read_version, post-migration tree):

- `ResQ.Clients` — `vunknown`
- `ResQ.Core` — `vunknown`
- `ResQ.Protocols` — `vunknown`
- `ResQ.Blockchain` — `vunknown`
- `ResQ.Storage` — `vunknown`
- `ResQ.Simulation` — `vunknown`

After (the run: block extracted from this PR's YAML and executed
verbatim, full history + tags):

- `ResQ.Clients` — `v0.6.1-alpha.0.42`
- `ResQ.Core` — `v0.6.1-alpha.0.42`
- `ResQ.Protocols` — `v0.6.1-alpha.0.42`
- `ResQ.Blockchain` — `v0.6.1-alpha.0.42`
- `ResQ.Storage` — `v0.6.1-alpha.0.42`
- `ResQ.Simulation` — `v0.6.1-alpha.0.42`

git describe at that commit is v0.6.0-46-ge727403; the .42 height is
what the tool actually computes, checked rather than assumed. With a release
tag applied to the same commit the same code returns v0.7.0, so the release
path — the one the on: push: tags: v* trigger exercises — is covered too.

Why fetch-depth: 0 is load-bearing. Same new code, same tree, depth-1
clone (1 commit, 0 tags):

- `ResQ.Clients` — `v0.0.0-alpha.0`
- `ResQ.Core` — `v0.0.0-alpha.0`
...

No regression for literal-version repos. ResQ.BuildingBlocks.Templates
in the building-blocks repo carries a literal <Version>0.1.0</Version>:
old logic → v0.1.0, new logic → v0.1.0. The fallback path itself was
exercised separately by making the MSBuild query fail (shimmed dotnet that
exits non-zero): still v0.1.0, via the regex scan. A project with no match
at all still yields unknown.

A synthetic fixture covers the two remaining branches: a project whose version
lives only in packages/<Proj>/<Proj>.csproj still resolves to 2.3.4, and a
project that genuinely declares 1.0.0 still reports 1.0.0 rather than being
swallowed by the SDK-default check.

Cost. ~0.5s per project, 2.9s for all six — the solution is already built
by the earlier step, so GetAssemblyVersion compiles nothing.

Lint. actionlint (with shellcheck available) is clean on the changed
template, exit 0, same as the pre-change baseline. The embedded Python
compiles and passes ruff check -select E,F,W.

Notes

  • Template only. No downstream .github/workflows/api-docs.yml copy was
    touched; the sync script propagates this.
  • The sibling templates read their versions from package.json,
    Cargo.toml, vcpkg.json and pyproject.toml, so none of them has this
    problem today. Worth revisiting if any of those repos moves to tag-derived
    versioning — they would need the same fetch-depth: 0.
  • Local verification ran on SDK 10.0.104 because that is what this machine
    has; CI pins 9.0.x via global.json. -getProperty and GetAssemblyVersion
    both predate 9.0, so the mechanism is the same, but a CI run on a real tag
    is the confirming test.

Test plan

  • Merge, sync the template out, and cut a v* tag on the .NET SDK repo
  • Confirm the resulting docs PR renders real versions in
    sdks/dotnet/api/README.mdx

…j regex

The .NET template scraped <Version>/<VersionPrefix> out of the .csproj
files to label each package in the generated API index. That only works
for repos that commit a literal version string. A repo that derives its
version from git tags carries no such literal on purpose -- a literal in
the tree is something an inbound sync can revert -- so the scrape fell
through to its "unknown" default and the index rendered `vunknown` for
every package.

Ask MSBuild instead, which is authoritative for both shapes: it reports
the literal where one exists and the tag-derived value where it does not.
The .csproj scan is kept behind it as a fallback for when the query is
unavailable (no -getProperty before SDK 8.0.200) or the project cannot be
evaluated, so nothing regresses for repos that still use a literal.

Two details that the obvious version of this fix gets wrong:

  - `-getProperty:Version` on its own only *evaluates* the project, and a
    tag-derived version is computed inside a target. A bare query returns
    the SDK's implicit 1.0.0. The query runs `-t:GetAssemblyVersion`, a
    stock SDK target whose job is to settle the version. It compiles
    nothing -- the solution is already built by an earlier step -- and
    costs ~0.5s per project, ~3s for the six.

  - An MSBuild answer of exactly 1.0.0 is indistinguishable from the SDK
    default, so it is treated as inconclusive and the literal scan still
    runs. That preserves the existing multi-candidate behaviour, where a
    version lives only in the packaging variant of a duplicated .csproj.

Also set fetch-depth: 0 on the source checkout. Tag-derived versioning
reads the tag graph at build time, and under the default shallow, tagless
checkout it does not fail -- it silently settles on 0.0.0-alpha.<height>.
Without this the index would render `v0.0.0-alpha.0` instead of
`vunknown`, which is worse: a plausible-looking wrong version. The
existing persist-credentials: false is unchanged.

Verified against a full-history clone of the .NET SDK repo: the old logic
yields "unknown" for all six packages, the new logic yields the real
MinVer version (0.6.1-alpha.0.42 at HEAD, 0.7.0 with a release tag
applied), and the same tree at depth 1 yields 0.0.0-alpha.0. A
literal-version repo returns its literal unchanged under both the old and
new logic.
@coderabbitai

coderabbitai Bot commented Sep 22, 2026

Copy link
Copy Markdown

Warning

Review limit reached

Next included review available in 56 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Advanced

Run ID: 912a6e3a-a24a-4268-b00b-405c7750ae13

📥 Commits

Reviewing files that changed from the base of the PR and between 5f24756 and cbeaf74.

📒 Files selected for processing (1)
  • automation/source-repo-templates/api-docs.dotnet.yml

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added the area:content MDX/MD documentation content label Sep 22, 2026
@WomB0ComB0
WomB0ComB0 merged commit 8814368 into main Sep 22, 2026
23 checks passed
WomB0ComB0 added a commit to resq-software/dotnet-sdk that referenced this pull request Sep 22, 2026
#106)

Every package in the generated reference is currently labelled
`vunknown`. The version lookup scrapes `<Version>`/`<VersionPrefix>`
out of the .csproj and returns "unknown" when neither is present --
and since this repo moved to MinVer, no project carries a literal
version by design. The scrape therefore misses on all of them.

This replaces that step with the version already fixed upstream in
resq-software/docs#133: ask MSBuild via
`-t:GetAssemblyVersion -getProperty:Version`. GetAssemblyVersion is
a stock SDK target, so MinVer runs and reports the tag-derived
value; the .csproj scrape is kept as a fallback for SDKs older than
8.0.200, which have no -getProperty.

The file is taken verbatim from the upstream template, so the next
template sync is a no-op here instead of a revert.

Verified locally against this tree at e727403 (full history, tags
present, after restore):

  ResQ.Storage   unknown -> 0.6.1-alpha.0.42
  ResQ.Core      unknown -> 0.6.1-alpha.0.42

Note the repo contains two ResQ.Core.csproj files -- the real
`ResQ.Core/` in the solution, and an orphaned `resq-core/` copy that
is not. The orphan is never restored, so MSBuild reports the SDK
default 1.0.0 for it. The upstream logic already handles this: it
skips any candidate whose value equals the SDK default and only
falls back to it last, so the real 0.6.1-alpha.0.42 wins. Confirmed
by replaying the selection over both candidates.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:content MDX/MD documentation content

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant