Reference version
1.0.204-preview
Background and motivation
Buildvana currently uses Nerdbank.GitVersioning (NBGV) for version-number management. It is wired in at two points:
- In the MSBuild SDK, the
NerdbankGitVersioning module (src/Buildvana.Sdk/Modules/NerdbankGitVersioning/) auto-detects version.json in a consumer repo and injects a Nerdbank.GitVersioning package reference so that NBGV's MSBuild task populates $(Version), $(AssemblyVersion), $(FileVersion), and $(AssemblyInformationalVersion) during build.
- In the
bv tool, src/Buildvana.Tool/Services/Versioning/VersionService.cs shells out to the nbgv CLI (declared in .config/dotnet-tools.json) via dotnet tool run nbgv get-version --format json, parses the JSON, and exposes CurrentStr, IsPublicRelease, and IsPrerelease to the release workflow.
This arrangement has several costs:
- Consumer overhead. Every repo that uses Buildvana must keep NBGV installed as a local or global tool and keep its version in sync. A repo that cannot install a
dotnet tool in CI cannot use Buildvana's versioning.
- Feature mismatch. Buildvana uses a small subset of NBGV's capabilities: 3-part versions,
nuGetPackageVersion.semVer = 2.0, a simple publicReleaseRefSpec, firstUnstableTag. pathFilters is empty, assemblyVersion.precision is fixed. We pay for a full dependency to exercise ~20% of its surface.
- Subprocess + JSON at runtime.
bv launches nbgv as a subprocess every time it needs the current version, parses the JSON output, and extracts a handful of properties. This is slower than a direct call, more failure-prone, and couples the tool to a third-party CLI contract.
- No standalone version management. There is no Buildvana-native way to inspect or advance the current version outside of the release workflow. Operations that modify
version.json (preview→stable, bump minor) are embedded inside ReleaseTask and cannot be invoked independently.
Proposed enhancement
Replace NBGV end-to-end with an in-house implementation. The end state:
- A new shared library,
Buildvana.Versioning, contains all version-related pure logic: parsing version.json, constructing SemVer2 strings from (VersionSpec, height, isPublicRelease), matching branches against publicReleaseRefSpec patterns. It has no git and no MSBuild dependencies — just NuGet.Versioning for SemanticVersion.
Buildvana.Sdk.Tasks gains a new MSBuild task that shells out to the git CLI for height and current-branch detection, then calls Buildvana.Versioning to produce the version string. The task sets $(Version), $(AssemblyVersion), $(FileVersion), $(AssemblyInformationalVersion); the .NET SDK's built-in GenerateAssemblyInfo target emits the corresponding [assembly: ...] attributes as before — no custom attribute generation required.
- The SDK's
NerdbankGitVersioning module is replaced by a Versioning module that wires the new task into the same GetBuildVersion extension point. Auto-detection of version.json in the repo tree is preserved.
Buildvana.Tool no longer shells out to nbgv. VersionService uses Buildvana.Versioning for pure computation and GitService (LibGit2Sharp, already present) for height and branch data. The nbgv entry is removed from .config/dotnet-tools.json.
- A new top-level command
bv version exposes version operations directly: bv version show prints current version info; bv version advance <major|minor|stable|unstable> applies a VersionSpecChange to version.json. The underlying logic already exists in VersionFile.ApplyVersionSpecChange and VersionService.ComputeVersionSpecChange; the command just wires it to the CLI.
- Version semantics match NBGV's observable behavior for the feature set Buildvana actually uses:
- Height = number of commits from HEAD back to, but not including, the last commit that modified
version.json.
IsPublicRelease = HEAD's canonical ref matches any publicReleaseRefSpec regex.
- Output format is the SemVer2
NpmPackageVersion equivalent (no git-commit-hash build metadata).
pathFilters is not supported — Buildvana has never used it. A non-empty pathFilters in a consumer's version.json is ignored (with a build warning).
version.json schema remains readable as-is. Consumers upgrading the SDK do not need to change their version file.
Implementation proposals
Four sequenced phases, each shippable on its own.
Phase 0 — new shared library.
- Create
src/Buildvana.Versioning/Buildvana.Versioning.csproj targeting $(StandardTfm).
- Move
VersionSpec verbatim from src/Buildvana.Tool/Services/Versioning/ to the new project.
- Add
VersionFileData — an immutable record holding parsed version.json contents (version spec, first-unstable tag, public-release ref patterns). Built from a JsonDocument, no Cake or MSBuild dependencies.
- Add
VersionCalculator with a pure method approximately:
public static VersionInfo Compute(
VersionFileData fileData,
int height,
string canonicalBranchRef);
where VersionInfo carries the SemanticVersion, CurrentStr, IsPublicRelease, IsPrerelease.
- Add a
SemanticVersion-based public API. NuGet.Versioning was reviewed: SemanticVersion at v10.0.201 is strictly SemVer 2.0 (3-part only, full metadata support, spec-correct comparison); NuGet-specific quirks live in NuGetVersion, which we do not use.
- Reference from
Buildvana.Tool. VersionFile delegates parsing to VersionFileData; its Save/ApplyVersionSpecChange methods stay in the tool (Cake-dependent I/O).
Phase 1 — in-tool replacement of the nbgv subprocess.
GitService gains methods to compute height (via LibGit2Sharp: find the last commit that modified version.json, count commits on the path from HEAD back to it, exclusive) and to return HEAD's canonical ref (already available internally).
VersionService.GetVersionInformationFromNbgv is rewritten as GetVersionInformationFromGit, calling GitService + Buildvana.Versioning.VersionCalculator. The nbgv invocation is deleted.
.config/dotnet-tools.json drops the nbgv entry.
- At this point
bv release works without NBGV, but the SDK still uses NBGV for build-time versioning.
Phase 2 — MSBuild task and SDK module.
Buildvana.Sdk.Tasks references Buildvana.Versioning.
- New task, e.g.
Buildvana.Sdk.Tasks.Versioning.GetBuildVersion: takes version-file path and repo root as inputs; shells out to git via System.Diagnostics.Process for height (git rev-list -n 1 HEAD -- <version-file> for the base SHA, git rev-list --count <sha>..HEAD for the height) and for branch (git symbolic-ref --short HEAD, fallback to detached-HEAD handling); calls VersionCalculator; returns version components as task outputs.
- New SDK module:
src/Buildvana.Sdk/Modules/Versioning/. BeforeModules.targets performs the same version.json auto-detection as today. Module.targets defines a GetBuildVersion target that invokes the new task and sets $(Version), $(AssemblyVersion), $(FileVersion), $(AssemblyInformationalVersion). The existing BV_AdjustAssemblyInformationalVersion logic folds into the task's outputs.
- The old
NerdbankGitVersioning module is removed. Decision to resolve during review: keep the UseNerdbankGitVersioning property name for consumer compatibility (mildly misleading), rename to UseVersioning with a deprecated alias, or rename outright. Auto-detection from version.json presence makes the property rarely set explicitly, which limits the blast radius.
Phase 3 — bv version command.
- Add
bv version show and bv version advance <major|minor|stable|unstable>.
show prints: current version, latest version, latest stable, IsPublicRelease, IsPrerelease, current branch.
advance loads VersionFile, applies ApplyVersionSpecChange, saves, and optionally commits (gated by a flag).
- CLI shape depends on the state of the Cake-removal proposal. If this lands first (still on Cake.Frosting), use flat task names (
version-show, version-advance) since the current parser does not support subcommands; post-Cake, use Spectre.Console.Cli subcommand syntax (bv version show). Either way, the underlying logic is identical and the rename is cosmetic.
Self-hosting: each phase must leave dotnet bv build functional on the repo. Buildvana builds itself with the last published bv, so CI is insulated during the transition; the risk is local-dev only.
Usage examples
Consumer version.json is unchanged:
{
"version": "1.0-preview",
"publicReleaseRefSpec": [
"^refs/heads/main$",
"^refs/heads/v\\d+\\.\\d+$"
],
"release": {
"firstUnstableTag": "preview"
}
}
Consumer MSBuild integration is unchanged — the SDK auto-detects version.json and wires versioning on:
<Project Sdk="Buildvana.Sdk/1.x.y">
<!-- nothing version-specific to declare; auto-detected from version.json -->
</Project>
New CLI operations exposed by bv:
dotnet bv version show
# current: 1.0.0-preview.42
# latest: 1.0.0-preview.41
# latest stable: 0.9.3
# public release: true
# prerelease: true
# branch: main
dotnet bv version advance minor
# version.json: 1.0-preview -> 1.1-preview
dotnet bv version advance stable
# version.json: 1.1-preview -> 1.1
Build-time behavior is identical to today: dotnet build, direct msbuild, and dotnet bv build all produce the same $(Version), $(AssemblyVersion), $(FileVersion), $(AssemblyInformationalVersion) as the current NBGV-based flow.
Risks
- Version-semantics drift. The new height computation must yield the same number as NBGV for equivalent inputs. Off-by-one errors (inclusive/exclusive endpoints, boundary commits) would cause version jumps or stalls. Mitigation: a test suite that compares the new output against NBGV's
get-version across a representative set of commits and branches before NBGV is removed.
publicReleaseRefSpec semantics. NBGV uses .NET's System.Text.RegularExpressions, same as us, so patterns should match identically. However, anchoring and implicit flags must be preserved exactly — this is a direct porting concern, not a redesign.
pathFilters drop. Any consumer with a non-empty pathFilters will see height diverge from NBGV. Buildvana does not use this feature; a build warning and documentation note will cover the discrepancy.
- Git CLI availability. The new MSBuild task shells out to
git. In virtually every environment where MSBuild runs (dev machine, CI agent, container), git is present — but a stripped-down image could lack it. Mitigation: emit a clear diagnostic error pointing at the missing tool.
- Detached HEAD.
git symbolic-ref --short HEAD fails on a detached HEAD. GitService already returns an empty string for CurrentBranch in that case; the new task must do the same and treat detached HEAD as non-public-release.
- SDK property rename. If
UseNerdbankGitVersioning is renamed, explicit uses in consumer projects break. Since the property is auto-set from version.json presence, explicit uses should be rare; a backward-compat alias is low-cost if needed.
- Self-hosting during the transition. Each phase must leave
dotnet bv build working locally; CI is insulated because it builds with the last published SDK/tool.
- Dependency changes.
- Added (internal only):
Buildvana.Versioning project in the solution.
- Removed from consumer surface:
Nerdbank.GitVersioning package injection; nbgv from .config/dotnet-tools.json.
NuGet.Versioning stays (used in the new shared library; reviewed and confirmed strictly SemVer 2.0 compliant).
LibGit2Sharp stays (already used by GitService).
- Net consumer footprint is smaller: one fewer package reference injected, one fewer dotnet tool to install.
- No breaking changes for consumer version files.
version.json as currently used by Buildvana is fully supported; only the NBGV-specific pathFilters feature is dropped (Buildvana consumers don't use it).
Additional information
- The existing
VersionSpec, VersionFile, VersionSpecChange, and VersionIncrement types in src/Buildvana.Tool/Services/Versioning/ already model most of what's needed. The new code is primarily the height-and-branch calculator, the MSBuild task shell, and the shared-library extraction.
- NBGV is MIT-licensed. Where concrete implementation details are reproduced (the height algorithm for
pathFilters: [], publicReleaseRefSpec matching semantics), source comments will cite the NBGV source and preserve the copyright notice.
- This proposal is independent of the
Cake.Frosting removal proposal. Either can ship first; the only coordination point is the CLI shape for bv version's subcommand syntax, which is a cosmetic difference between the two landing orders.
- Follow-ups worth tracking separately once this lands:
bv version tag (create a git tag from the current version), bv version check (expose the consistency checks currently embedded in ReleaseTask.EnsureConsistency), and a richer bv version show output with JSON/verbose modes.
Reference version
1.0.204-preview
Background and motivation
Buildvana currently uses Nerdbank.GitVersioning (NBGV) for version-number management. It is wired in at two points:
NerdbankGitVersioningmodule (src/Buildvana.Sdk/Modules/NerdbankGitVersioning/) auto-detectsversion.jsonin a consumer repo and injects aNerdbank.GitVersioningpackage reference so that NBGV's MSBuild task populates$(Version),$(AssemblyVersion),$(FileVersion), and$(AssemblyInformationalVersion)during build.bvtool,src/Buildvana.Tool/Services/Versioning/VersionService.csshells out to thenbgvCLI (declared in.config/dotnet-tools.json) viadotnet tool run nbgv get-version --format json, parses the JSON, and exposesCurrentStr,IsPublicRelease, andIsPrereleaseto the release workflow.This arrangement has several costs:
dotnet toolin CI cannot use Buildvana's versioning.nuGetPackageVersion.semVer = 2.0, a simplepublicReleaseRefSpec,firstUnstableTag.pathFiltersis empty,assemblyVersion.precisionis fixed. We pay for a full dependency to exercise ~20% of its surface.bvlaunchesnbgvas a subprocess every time it needs the current version, parses the JSON output, and extracts a handful of properties. This is slower than a direct call, more failure-prone, and couples the tool to a third-party CLI contract.version.json(preview→stable, bump minor) are embedded insideReleaseTaskand cannot be invoked independently.Proposed enhancement
Replace NBGV end-to-end with an in-house implementation. The end state:
Buildvana.Versioning, contains all version-related pure logic: parsingversion.json, constructing SemVer2 strings from(VersionSpec, height, isPublicRelease), matching branches againstpublicReleaseRefSpecpatterns. It has no git and no MSBuild dependencies — justNuGet.VersioningforSemanticVersion.Buildvana.Sdk.Tasksgains a new MSBuild task that shells out to thegitCLI for height and current-branch detection, then callsBuildvana.Versioningto produce the version string. The task sets$(Version),$(AssemblyVersion),$(FileVersion),$(AssemblyInformationalVersion); the .NET SDK's built-inGenerateAssemblyInfotarget emits the corresponding[assembly: ...]attributes as before — no custom attribute generation required.NerdbankGitVersioningmodule is replaced by aVersioningmodule that wires the new task into the sameGetBuildVersionextension point. Auto-detection ofversion.jsonin the repo tree is preserved.Buildvana.Toolno longer shells out tonbgv.VersionServiceusesBuildvana.Versioningfor pure computation andGitService(LibGit2Sharp, already present) for height and branch data. Thenbgventry is removed from.config/dotnet-tools.json.bv versionexposes version operations directly:bv version showprints current version info;bv version advance <major|minor|stable|unstable>applies aVersionSpecChangetoversion.json. The underlying logic already exists inVersionFile.ApplyVersionSpecChangeandVersionService.ComputeVersionSpecChange; the command just wires it to the CLI.version.json.IsPublicRelease= HEAD's canonical ref matches anypublicReleaseRefSpecregex.NpmPackageVersionequivalent (no git-commit-hash build metadata).pathFiltersis not supported — Buildvana has never used it. A non-emptypathFiltersin a consumer'sversion.jsonis ignored (with a build warning).version.jsonschema remains readable as-is. Consumers upgrading the SDK do not need to change their version file.Implementation proposals
Four sequenced phases, each shippable on its own.
Phase 0 — new shared library.
src/Buildvana.Versioning/Buildvana.Versioning.csprojtargeting$(StandardTfm).VersionSpecverbatim fromsrc/Buildvana.Tool/Services/Versioning/to the new project.VersionFileData— an immutable record holding parsedversion.jsoncontents (version spec, first-unstable tag, public-release ref patterns). Built from aJsonDocument, no Cake or MSBuild dependencies.VersionCalculatorwith a pure method approximately:VersionInfocarries theSemanticVersion,CurrentStr,IsPublicRelease,IsPrerelease.SemanticVersion-based public API.NuGet.Versioningwas reviewed:SemanticVersionat v10.0.201 is strictly SemVer 2.0 (3-part only, full metadata support, spec-correct comparison); NuGet-specific quirks live inNuGetVersion, which we do not use.Buildvana.Tool.VersionFiledelegates parsing toVersionFileData; itsSave/ApplyVersionSpecChangemethods stay in the tool (Cake-dependent I/O).Phase 1 — in-tool replacement of the
nbgvsubprocess.GitServicegains methods to compute height (via LibGit2Sharp: find the last commit that modifiedversion.json, count commits on the path from HEAD back to it, exclusive) and to return HEAD's canonical ref (already available internally).VersionService.GetVersionInformationFromNbgvis rewritten asGetVersionInformationFromGit, callingGitService+Buildvana.Versioning.VersionCalculator. Thenbgvinvocation is deleted..config/dotnet-tools.jsondrops thenbgventry.bv releaseworks without NBGV, but the SDK still uses NBGV for build-time versioning.Phase 2 — MSBuild task and SDK module.
Buildvana.Sdk.TasksreferencesBuildvana.Versioning.Buildvana.Sdk.Tasks.Versioning.GetBuildVersion: takes version-file path and repo root as inputs; shells out togitviaSystem.Diagnostics.Processfor height (git rev-list -n 1 HEAD -- <version-file>for the base SHA,git rev-list --count <sha>..HEADfor the height) and for branch (git symbolic-ref --short HEAD, fallback to detached-HEAD handling); callsVersionCalculator; returns version components as task outputs.src/Buildvana.Sdk/Modules/Versioning/.BeforeModules.targetsperforms the sameversion.jsonauto-detection as today.Module.targetsdefines aGetBuildVersiontarget that invokes the new task and sets$(Version),$(AssemblyVersion),$(FileVersion),$(AssemblyInformationalVersion). The existingBV_AdjustAssemblyInformationalVersionlogic folds into the task's outputs.NerdbankGitVersioningmodule is removed. Decision to resolve during review: keep theUseNerdbankGitVersioningproperty name for consumer compatibility (mildly misleading), rename toUseVersioningwith a deprecated alias, or rename outright. Auto-detection fromversion.jsonpresence makes the property rarely set explicitly, which limits the blast radius.Phase 3 —
bv versioncommand.bv version showandbv version advance <major|minor|stable|unstable>.showprints: current version, latest version, latest stable,IsPublicRelease,IsPrerelease, current branch.advanceloadsVersionFile, appliesApplyVersionSpecChange, saves, and optionally commits (gated by a flag).version-show,version-advance) since the current parser does not support subcommands; post-Cake, use Spectre.Console.Cli subcommand syntax (bv version show). Either way, the underlying logic is identical and the rename is cosmetic.Self-hosting: each phase must leave
dotnet bv buildfunctional on the repo. Buildvana builds itself with the last publishedbv, so CI is insulated during the transition; the risk is local-dev only.Usage examples
Consumer
version.jsonis unchanged:{ "version": "1.0-preview", "publicReleaseRefSpec": [ "^refs/heads/main$", "^refs/heads/v\\d+\\.\\d+$" ], "release": { "firstUnstableTag": "preview" } }Consumer MSBuild integration is unchanged — the SDK auto-detects
version.jsonand wires versioning on:New CLI operations exposed by
bv:Build-time behavior is identical to today:
dotnet build, directmsbuild, anddotnet bv buildall produce the same$(Version),$(AssemblyVersion),$(FileVersion),$(AssemblyInformationalVersion)as the current NBGV-based flow.Risks
get-versionacross a representative set of commits and branches before NBGV is removed.publicReleaseRefSpecsemantics. NBGV uses .NET'sSystem.Text.RegularExpressions, same as us, so patterns should match identically. However, anchoring and implicit flags must be preserved exactly — this is a direct porting concern, not a redesign.pathFiltersdrop. Any consumer with a non-emptypathFilterswill see height diverge from NBGV. Buildvana does not use this feature; a build warning and documentation note will cover the discrepancy.git. In virtually every environment where MSBuild runs (dev machine, CI agent, container),gitis present — but a stripped-down image could lack it. Mitigation: emit a clear diagnostic error pointing at the missing tool.git symbolic-ref --short HEADfails on a detached HEAD.GitServicealready returns an empty string forCurrentBranchin that case; the new task must do the same and treat detached HEAD as non-public-release.UseNerdbankGitVersioningis renamed, explicit uses in consumer projects break. Since the property is auto-set fromversion.jsonpresence, explicit uses should be rare; a backward-compat alias is low-cost if needed.dotnet bv buildworking locally; CI is insulated because it builds with the last published SDK/tool.Buildvana.Versioningproject in the solution.Nerdbank.GitVersioningpackage injection;nbgvfrom.config/dotnet-tools.json.NuGet.Versioningstays (used in the new shared library; reviewed and confirmed strictly SemVer 2.0 compliant).LibGit2Sharpstays (already used byGitService).version.jsonas currently used by Buildvana is fully supported; only the NBGV-specificpathFiltersfeature is dropped (Buildvana consumers don't use it).Additional information
VersionSpec,VersionFile,VersionSpecChange, andVersionIncrementtypes insrc/Buildvana.Tool/Services/Versioning/already model most of what's needed. The new code is primarily the height-and-branch calculator, the MSBuild task shell, and the shared-library extraction.pathFilters: [],publicReleaseRefSpecmatching semantics), source comments will cite the NBGV source and preserve the copyright notice.Cake.Frostingremoval proposal. Either can ship first; the only coordination point is the CLI shape forbv version's subcommand syntax, which is a cosmetic difference between the two landing orders.bv version tag(create a git tag from the current version),bv version check(expose the consistency checks currently embedded inReleaseTask.EnsureConsistency), and a richerbv version showoutput with JSON/verbose modes.