Versioning and Prerelease Packages
|
This section documents MSBuild 18.9, the .NET SDK 10.0.402 (with its This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production. The C# Reference bibliography lists the reference material consulted while preparing these pages. |
A package version is a promise: consumers and tools read 2.0.0 as "may break you", 1.1.0 as "new features" and
1.0.1 as "fixes only", and a -beta.1 suffix as "not stable". A .NET library actually has four version numbers,
and they serve different readers. This page sets them for Ledgerline by hand, from CI and from git tags, with
.NET SDK 10.0.402 (NuGet 7.9.0). How consumers express ranges is on
Package Versions and Ranges.
The four version numbers
| Property | Example | Read by |
|---|---|---|
|
|
NuGet: restore, ranges, nuget.org. SemVer, with an optional prerelease label. |
|
|
The runtime when binding references; part of a strong-named identity. |
|
|
Windows file properties, crash dumps, installers. Four numeric parts. |
|
|
Humans and diagnostics; any string. |
The library guidance:
-
✔️ CONSIDER SemVer 2.0.0 for the package version; ✔️ DO include a prerelease suffix for anything not stable; ✔️ DO use the package version in public documentation.
-
AssemblyVersion: ✔️ CONSIDER only including a major version (2.0.0.0) and keeping it in sync with the package’s major version; ❌ DO NOT have a fixed AssemblyVersion. (The newer compatibility rules say to change it on every release — the conflict is discussed on Designing a Library for NuGet.) -
FileVersion: ✔️ DO use Major.Minor.Build.Revision; ✔️ CONSIDER a CI build number as the revision. -
InformationalVersion: ❌ AVOID setting it yourself — the SDK fills it.
The SDK derives all four from Version, which is $(VersionPrefix) plus -$(VersionSuffix) when a suffix is set. A
target that prints them, run with and without a suffix on a project with <VersionPrefix>1.1.0</VersionPrefix>:
dotnet msbuild -t:ShowVersions -v:m
dotnet msbuild -t:ShowVersions -v:m -p:VersionSuffix=beta.2
PackageVersion=1.1.0 AssemblyVersion=1.1.0.0 FileVersion=1.1.0.0 InformationalVersion=1.1.0
PackageVersion=1.1.0-beta.2 AssemblyVersion=1.1.0.0 FileVersion=1.1.0.0 InformationalVersion=1.1.0-beta.2
The suffix changes only the package and informational versions — AssemblyVersion and FileVersion cannot carry
one. In a git repository, the .NET 8+ SDK appends the commit to InformationalVersion during the build (not at
evaluation, so the target above doesn’t see it); the compiled assembly contained:
1.1.0+6f1aa31e1083451648c6bef0c6a07957742e6ea8
IncludeSourceRevisionInInformationalVersion=false turns that off.
Prerelease versions and their order
Anything after - makes a version prerelease. NuGet attaches no meaning to the label, but the conventions are
-alpha (work in progress), -beta (feature complete, known bugs) and -rc (potentially final). Order follows
SemVer 2.0.0: a version without a label is higher than any prerelease of it; labels compare part by part, numerically
when a part is a number — rc.10 is higher than rc.2, but alpha10 is lower than alpha2, because it is compared
as a string. Dotted labels need NuGet 4.3 or later.
Consumers see prereleases only when they ask: dotnet package add --prerelease, the Include prerelease check box
in Visual Studio, a range whose lower bound is itself a prerelease, or a floating version such as 1.1.0-*. The library guidance: ✔️ DO publish a prerelease package when testing,
previewing or experimenting, and ✔️ DO publish a stable package when it’s ready, so other stable packages can depend
on it.
Setting the version from CI
Keep VersionPrefix (the next release) in the repository and add the suffix in CI:
dotnet pack -o out --version-suffix beta.2
dotnet pack -o out -p:Version=2.0.0-rc.1
dotnet pack -o out --version 2.0.0
Successfully created package '.../out/Ledgerline.Ver.1.1.0-beta.2.nupkg'.
Successfully created package '.../out/Ledgerline.Ver.2.0.0-rc.1.nupkg'.
Successfully created package '.../out/Ledgerline.Ver.2.0.0.nupkg'.
--version-suffix sets VersionSuffix for build and pack; -p:Version= replaces everything; --version (new in the
.NET 10 SDK, described by dotnet pack --help as "The version of the package to create") set the package version.
The online dotnet pack reference does not list --version yet. A PackageVersion set in the project overrides VersionSuffix, so
--version-suffix then has no effect.
Source: dotnet pack: Options, Pack target inputs.
Versions from git tags
Two widely used third-party packages compute the version from git history at build time, so nobody edits a version
number. Both are added as PrivateAssets="all" build-time dependencies.
MinVer 8.0.0 (third-party) reads the latest tag that is a SemVer version; between tags it produces the next patch as a prerelease with the height (commits since the tag):
dotnet pack -o out # no tag yet
git tag 1.0.0 && dotnet pack -o out
# one more commit
dotnet pack -o out
git tag 1.1.0-beta.1 && dotnet pack -o out
Ledgerline.MinVerDemo.0.0.0-alpha.0.nupkg
Ledgerline.MinVerDemo.1.0.0.nupkg
Ledgerline.MinVerDemo.1.0.1-alpha.0.1.nupkg
Ledgerline.MinVerDemo.1.1.0-beta.1.nupkg
For the last one MinVer set AssemblyVersion to 1.0.0.0 (major only, as the guidance suggests), FileVersion to
1.1.0.0 and InformationalVersion to 1.1.0-beta.1+37009328….
Nerdbank.GitVersioning 3.10.94 (third-party) reads the major and minor version from a committed version.json and
uses the git height as the patch; publicReleaseRefSpec lists the branches and tags that produce public versions:
{
"$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/main/src/NerdBank.GitVersioning/version.schema.json",
"version": "1.2-beta",
"publicReleaseRefSpec": [ "^refs/heads/main$", "^refs/tags/v\\d+\\.\\d+" ]
}
Ledgerline.NbgvDemo.1.2.2-beta.nupkg (on main, two commits)
Ledgerline.NbgvDemo.1.2.2-beta-g24ce3ae075.nupkg (on feature/x: the commit is appended)
with AssemblyVersion 1.2.0.0, FileVersion 1.2.2.9422 and InformationalVersion 1.2.2-beta+24ce3ae075. Both
compute from history, so CI needs enough of it: MinVer’s README explains that the single-commit clones of
actions/checkout and Azure Pipelines hide earlier tags, and recommends fetch-depth: 0.
Source: MinVer, Nerdbank.GitVersioning.
NU5104: a stable package with a prerelease dependency
warning NU5104: A stable release of a package should not have a prerelease dependency. Either modify the version spec of dependency "Ledgerline.Preview [1.0.0-beta.1, )" or update the version field in the nuspec.
A stable version must not require an unstable one; either wait for the dependency’s stable release or publish this package as a prerelease too.
Source: NU5104.
Common mistakes
-
A fixed
AssemblyVersion(1.0.0.0forever) — patched assemblies can’t be told apart on .NET Framework. -
Republishing the same version with changes — impossible on nuget.org, and confusing everywhere else.
-
A breaking change in a minor release — consumers' floating ranges pick it up.
-
Shallow clones with MinVer or Nerdbank.GitVersioning — versions such as
0.0.0-alpha.0. -
alpha10afteralpha2— use dotted numbers (alpha.10) so they sort numerically.
References
-
MinVer (third-party)
-
Nerdbank.GitVersioning (third-party)