Skip to content

Swap Nerdbank.GitVersioning for native versioning - #308

Merged
rdeago merged 8 commits into
Tenacom:mainfrom
rdeago:native-versioning
Jul 31, 2026
Merged

Swap Nerdbank.GitVersioning for native versioning#308
rdeago merged 8 commits into
Tenacom:mainfrom
rdeago:native-versioning

Conversation

@rdeago

@rdeago rdeago commented Jul 31, 2026

Copy link
Copy Markdown
Member

Part of #296 (Phase 7 of #267); the issue stays open until the post-publish tail lands.

This is the atomic swap: the version source flips from Nerdbank.GitVersioning to Buildvana's native versioning in one change, with no PR state carrying two active versioning systems.

  • SDK: new Versioning module, auto-detected by a repository-root VERSION file, computing Version/PackageVersion/AssemblyVersion/FileVersion/InformationalVersion via a new ComputeVersion task that runs VersioningService in-process (LibGit2Sharp height — no git CLI). Results are cached per home directory in the task host, keyed on repository state. UseNerdbankGitVersioning remains as a warned alias of UseVersioning (BVSDK2001); the GetBuildVersion target name is preserved.
  • ThisAssembly: versioned C# projects generate ThisAssembly by default, now carrying SimpleVersion, SemVer, IsPublicRelease, IsPrerelease, and GitCommitId alongside the Phase 6 defaults (differences vs NBGV's class are noted in the changelog).
  • Packaging: LibGit2Sharp's managed + native assets ship in the SDK package bin/; Unix natives are renamed (lib prefix stripped) to match LibGit2Sharp's composed probe name, and a one-time bootstrap points it at runtimes/{rid}/native. Spike-verified from the packed layout under dotnet build; msbuild.exe uses the same .NET task host (verification on the dev machine blocked by an SDK/VS version pairing that equally affects all existing tasks).
  • Tool: bv release computes versions natively; the nbgv subprocess and the tool's own versioning types are gone. Release policy (consistency checks, bump computation) lives in a thin VersionService over the library.
  • Self-host transition: root VERSION + buildvana.json added; version.json and the nbgv tool entry are retained until the new SDK is published (post-publish tail: roll pins, delete version.json/nbgv, update CI comments and the .vscode schema mapping).
  • Also: new Buildvana.Core.Testing shared test-harness library, and a false-positive ReSharper inspection suppressed in Buildvana.Core.JsonSchema.

Verified: 141 tests (36 new), dotnet bv pack self-hosts end to end, ReSharper inspectcode clean.

🤖 Generated with Claude Code

rdeago and others added 6 commits July 31, 2026 03:42
…ourceMap

ReSharper's solution-wide analysis inspects the JsonSourceMap.Frame.cs partial
part in isolation and misses the instantiation in JsonSourceMap.cs, so it
claims the class (which has a private constructor and a static factory method)
cannot be instantiated.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
It starts with the TempGitRepo harness (moved from the versioning tests, where
the SDK task tests would otherwise have needed a copy) and a TempHome harness
for tests exercising file-based code that resolves paths against a home
directory.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…e.Versioning

In preparation for replacing the tool-side versioning types:

- VersionSpec gains Stable/Unstable/NextMinor/NextMajor/ApplyChange extension
  methods, porting the advance semantics of the tool's version.json-based
  VersionSpec (new version lines start as prereleases).
- A new VersionFile type becomes the single reader and writer of the VERSION
  file; VersioningService now reads the version specification through it. The
  writer only emits the configured prerelease tag as informational text when it
  fits the version file grammar, falling back to a bare dash.
- VersioningService computes FileVersion (MAJOR.MINOR.HEIGHT.0).
- GitHeightCalculator can describe the repository state Calculate depends on
  as an opaque token, so callers can cache computed versions; its static
  constructor points LibGit2Sharp at the runtimes/{rid}/native directory next
  to the managed assembly when present, for hosts without a dependency context
  (e.g. MSBuild task hosts).

The versioning and task test projects now use the harnesses from
Buildvana.Core.Testing instead of project-local copies.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The new Versioning module is auto-detected by the presence of a VERSION file
in the home directory and computes Version, PackageVersion, AssemblyVersion,
FileVersion, and InformationalVersion through the new ComputeVersion task,
which runs VersioningService in-process using the engine-backed reporter.
Computed versions are cached per home directory for the lifetime of the task
host, keyed on a fingerprint of the VERSION file, the configuration file, and
the repository state, so multi-project builds pay for a single Git graph walk.

UseNerdbankGitVersioning remains as a deprecated alias of UseVersioning
(warning BVSDK2001); the GetBuildVersion target name is kept, real or stub,
for targets that depend on it. Versioned C# projects enable
GenerateThisAssemblyClass by default, and the module contributes
SimpleVersion, SemVer, IsPublicRelease, IsPrerelease, and GitCommitId
constants to the generated class.

LibGit2Sharp's managed assembly and native binaries ship in the package's bin
directory; the Unix binaries are renamed at pack time (the lib prefix is
stripped) because LibGit2Sharp composes the file name without it when probing
the explicitly-set native library path, which is the only resolution path
available without a deps.json.

The SDK's own version props now read PackageVersion, which is set by both the
old and the new versioning, instead of NBGV's NuGetPackageVersion.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The tool's own versioning types (VersionSpec, VersionSpecChange, VersionFile,
VersionIncrement) are deleted in favor of the library's, and the nbgv
subprocess is gone: VersionService is now a thin release-flow facade over a
recomputable VersioningService (ServerRelease refreshes the version after
creating the release commit), keeping the tool-only policy methods
(EnsureConsistency, ComputeVersionSpecChange) and the latest-version data from
GitService. VersionIncrement survives as a private nested enum of the facade.

The release flow reads and rewrites the VERSION file through the library's
VersionFile, passing the configured versioning.prereleaseTag as the
informational tag text.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The repository gets its own VERSION file and buildvana.json, mirroring the
policy in the retained version.json. version.json and the nbgv tool entry stay
for now: the repository still self-hosts on the last published (NBGV-based)
SDK, and they will be removed after the new SDK is published and the pins
rolled (the post-publish tail of the migration).

Documentation follows: the Versioning module's diagnostics replace the
NerdbankGitVersioning section (BVSDK2000 reworded, BVSDK2001 added), the
VERSION file is documented in the directory structure, and the changelog gains
the native-versioning feature entries and the breaking changes for the NBGV
removal, the UseNerdbankGitVersioning rename, the version.json migration, and
the dropped version.json-only features.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@rdeago rdeago added enhancement [issue/PR] requests / implements new or improved functionality. refactor [issue/PR] requests / implements a refactor of existing code without affecting functionality. labels Jul 31, 2026
@github-actions github-actions Bot added area:docs [issue/PR] affects documentation (excluding XML documentation that is part of source code). area:code [issue/PR] affects project code (excluding tests). labels Jul 31, 2026
@rdeago

rdeago commented Jul 31, 2026

Copy link
Copy Markdown
Member Author

@claude please review this PR.

@claude

claude Bot commented Jul 31, 2026

Copy link
Copy Markdown

Claude finished @rdeago's task in 5m 36s —— View job


Claude's review

  • Gather context (PR description, changed files, repo rules)
  • Read diff against origin/main
  • Review SDK versioning module changes
  • Review Buildvana.Core.Versioning changes
  • Review Buildvana.Sdk.Tasks (ComputeVersion task + caching)
  • Review Buildvana.Tool versioning/release changes
  • Review packaging (LibGit2Sharp native assets)
  • Review tests
  • Post final review

Overall this is a clean, well-scoped swap. The design holds together: VersionFile / VersionSpecExtensions in Buildvana.Core.Versioning are shared by both the SDK task and bv release, the GetBuildVersion target name and UseNerdbankGitVersioning alias preserve compatibility, and the per-home-directory task-host cache (keyed on a HEAD-ref+SHA token plus VERSION/buildvana.json(c) contents) looks correct — it only skips caching (never serves stale data) when the fingerprint can't be computed. Test coverage for the new pieces (VersionFile, VersionSpecChange, ComputeVersion incl. cache-hit/invalidation) is solid.

A few things worth a look, roughly in priority order:

1. Stale comment referencing Nerdbank.GitVersioningsrc/Buildvana.Tool/Subcommands/ReleaseCommand.cs:182-184:

// Ensure that the release tag doesn't already exist.
// This assumes that full repo history has been checked out;
// however, that is already a prerequisite for using Nerdbank.GitVersioning.

This file's imports were already updated for the swap (Buildvana.Core.JsonBuildvana.Core.Versioning), but the comment still describes NBGV as the current dependency needing full history. Minor, but worth fixing since it's misleading in a PR whose whole point is removing that dependency.
Fix this →

2. Case-insensitive cache key on case-sensitive filesystemssrc/Buildvana.Sdk.Tasks/Tasks/ComputeVersion-caching.cs:17:

private static readonly ConcurrentDictionary<string, CachedVersion> Cache = new(StringComparer.OrdinalIgnoreCase);

The per-task-host cache is keyed by the full home directory path with OrdinalIgnoreCase. On a case-sensitive filesystem (Linux/macOS), two genuinely distinct home directories differing only by case would collide and could serve one repo's cached version data for another. It's an unlikely scenario in practice (repo paths rarely differ only by case), but worth a second look — is this deliberate for cross-platform path normalization, or should it be Ordinal (or platform-conditional)?

3. Design question — versioning constants gated by EnableDefaultThisAssemblyConstantssrc/Buildvana.Sdk/Modules/Versioning/Module.Core.targets:53:

<Target Name="BV_DefineVersioningThisAssemblyConstants"
        BeforeTargets="BV_WriteThisAssemblyConstantsFile"
        DependsOnTargets="GetBuildVersion"
        Condition="'$(EnableDefaultThisAssemblyConstants)' != 'false'">

EnableDefaultThisAssemblyConstants is owned by the ThisAssemblyClass module and gates its own bucket (AssemblyVersion, AssemblyProduct, AssemblyCompany, etc. — see Modules/ThisAssemblyClass/Module.Core.targets:44). Reusing it here means setting it to false also suppresses SimpleVersion/SemVer/IsPublicRelease/IsPrerelease/GitCommitId, which are a different, versioning-specific set of constants. If that's meant as "one switch to disable all built-in constants," fine — just flagging the cross-module coupling in case it's accidental reuse rather than a deliberate single opt-out.

Nits (no action needed unless you want them): RuntimeInformation.RuntimeIdentifier in GitHeightCalculator-nativeLibrary.cs relies on the portable RID reporting introduced in modern .NET, which matches the runtimes/{rid}/native layout you're packaging — just noting the assumption since the PR description says on-device MSBuild.exe verification was blocked. The catch (LibGit2SharpException) around GlobalSettings.NativeLibraryPath matches LibGit2Sharp's actual behavior (the setter throws only once the native library is already resolved/locked, and doesn't validate the path itself), so that fallback is safe as written.

No correctness issues found in the version computation logic itself (VersionFile, VersionSpecExtensions, VersioningService.FileVersion), the MSBuild module wiring (UseVersioning/UseNerdbankGitVersioning coalescing order, BVSDK2000/BVSDK2001 diagnostics), or the LibGit2Sharp native-asset repackaging (lib prefix stripping for Unix .so/.dylib).

rdeago and others added 2 commits July 31, 2026 04:47
The full-history requirement for the tag-existence check still holds, but it
now comes from the native Git height computation, not from NBGV.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Case-insensitive keying could make two case-differing home directories share a
cache slot on case-sensitive filesystems. The fingerprint check already keeps
that from ever serving stale data, but the collision would thrash the shared
entry; ordinal keying gives each directory its own.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@rdeago

rdeago commented Jul 31, 2026

Copy link
Copy Markdown
Member Author

@claude Thanks for the review. Both actionable points are addressed:

  1. The stale Nerdbank.GitVersioning comment in ReleaseCommand.cs now attributes the full-history requirement to the native Git height computation (b3609e9).
  2. The ComputeVersion cache is now keyed with StringComparer.Ordinal (c551990). Note that the fingerprint check already prevented stale data from ever being served — a case-collision could only cause the two directories to thrash a shared cache slot — but ordinal keying removes even that.

Point 3 is as intended: EnableDefaultThisAssemblyConstants is deliberately a single switch that turns off all Buildvana-provided constants, versioning ones included.

Please take another look.

@claude

claude Bot commented Jul 31, 2026

Copy link
Copy Markdown

Claude finished @rdeago's task in 1m 9s —— View job


Claude's review (follow-up)

  • Read trigger comment and identify the three points to re-check
  • Verify fix for stale NBGV comment (b3609e9)
  • Verify fix for cache key comparer (c551990)
  • Confirm point 3 design rationale
  • Post final review

Both fixes check out, and the third point's rationale is sound.

1. Stale comment (b3609e9)src/Buildvana.Tool/Subcommands/ReleaseCommand.cs:182. The comment now correctly attributes the full-history requirement to native Git height computation instead of NBGV. Confirmed there's no other stray NBGV/Nerdbank reference left in source comments or targets — the remaining hits (Module.Core.targets, BeforeModules.targets, GitHeightCalculator.cs) are all intentional: the GetBuildVersion compatibility-name note, the UseNerdbankGitVersioning deprecated-alias documentation, and the height-algorithm attribution comment. Everything outside src/ (version.json, .config/dotnet-tools.json, CI workflow comments, .vscode/settings.json) is explicitly called out in the PR description as the post-publish tail, so untouched by design.

2. Cache comparer (c551990)src/Buildvana.Sdk.Tasks/Tasks/ComputeVersion-caching.cs:17 now uses StringComparer.Ordinal. I re-read ComputeFingerprint/GetOrComputeVersion: the fingerprint (repo state token + VERSION/buildvana.json(c) contents) is checked on every lookup before a cached entry is returned, so the agreement in the commit message is accurate — case-insensitive keying could only have caused two distinct directories to thrash a shared slot, never serve stale data. Ordinal keying removes that possibility entirely. Good fix.

3. EnableDefaultThisAssemblyConstants reuse — acknowledged as intentional (single opt-out switch for all Buildvana-provided constants). No further action needed; makes sense as a deliberate design choice.

No new issues found. This PR looks good to merge from my side.

@rdeago
rdeago merged commit e879ff3 into Tenacom:main Jul 31, 2026
6 checks passed
@rdeago
rdeago deleted the native-versioning branch July 31, 2026 06:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:code [issue/PR] affects project code (excluding tests). area:docs [issue/PR] affects documentation (excluding XML documentation that is part of source code). enhancement [issue/PR] requests / implements new or improved functionality. refactor [issue/PR] requests / implements a refactor of existing code without affecting functionality.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant