Skip to content

AshleighAdams/Verlite

Repository files navigation

Logo

Verlite

Verlite.MsBuild Verlite.CLI Verlite.Core
Codecov Mutation testing score

Versioning with SemVer 2 Git tags. Automatically set the version from Git tags for .NET Core SDK-style projects, or use the CLI tool for all others. Platform agnostic.

Usage

Add the following to your Directory.Build.props or csproj:

<ItemGroup>
  <PackageReference Include="Verlite.MsBuild" Version="x.y.z" PrivateAssets="All" />
</ItemGroup>

Optionally, if your CI/CD pipelines use shallow clones (such as GitHub Actions by default), add build steps to automatically deepen the repository to—and fetch—the nearest tag:

dotnet tool install --global Verlite.CLI --version "x.y.z"
verlite --auto-fetch

Goals and Scope

Verlite aims to fall somewhere between MinVer and GitVersion—using the same versioning scheme as MinVer, with a slightly richer and more flexible feature set.

Verlite is aimed at continuous delivery workflows, not continuous deployment workflows—where versions are denoted from a branching model or commit messages. Instead with Verlite, tags are the source of truth for versions. Any versions with height attached (see version calculation) are intended only for development purposes and to not be released to the primary feed.

Versioning based upon commit messages or branches is out of scope. Such can be done via passing different options into Verlite by your build process, but keep in mind this is not a supported workflow of Verlite, so shouldn't be done for release critical aspects.

Version Calculation

Take the head commit, if one or more version tags exist, use the highest version, otherwise, keep following all parents until a version tag is found, taking the highest version tag, then bumping the version and appending the "commit height" onto the end.

To bump the version, the patch is by default incremented by 1. The version part to bump can be configured via VerliteAutoIncrement/--auto-increment option.

The commit height is applied by concatenating the prerelease tag, a separator ("."), and the height together, where the prerelease tag is either the last tagged version's prerelease, or if not found/was not a prerelease, using the VerliteDefaultPrereleasePhase/--default-prerelease-phase option.

See docs/VersionCalculation.md for further reading.

Options

Description CLI Short, CLI Long, MsBuild Property Default
Disable invoking Verlite. VerliteDisabled false
Tags starting with this represent versions. -t, --tag-prefix, VerliteTagPrefix v
Disable the version prefix. VerliteDisableTagPrefix false
The default phase for the prerelease label -d, --default-prerelease-phase, VerliteDefaultPrereleasePhase alpha
The minimum RTM version that's a core version without a prerelease. -m, --min-version, VerliteMinimumVersion 0.1.0
The height for continuous deliverable auto heights should begin at. -p, --prerelease-base-height, VerlitePrereleaseBaseHeight 1
Force the calculated version to be this version. --version-override, VerliteVersionOverride
Logging level. --verbosity, VerliteVerbosity normal
Set the build data to this value. -b, --build-metadata, VerliteBuildMetadata
Part of the version to print. -s, --show all
Automatically fetch commits and a tag for shallow clones. --auto-fetch false
Create a lightweight tag instead of fetching the remote's. --enable-lightweight-tags false
Set which version part should be bumped after an RTM release. -a, --auto-increment, VerliteAutoIncrement patch
A command to test whether a tag should be ignored via exit code. -f, --filter-tags, VerliteFilterTags
The remote endpoint to use when fetching tags and commits. -r, --remote, VerliteRemote origin
Generate version strings and embed them via a source generator. VerliteEmbedVersion true
Use a shadow repo (partial, only commits) to read commit history. --enable-shadow-repo, VerliteEnableShadowRepo false

Comparison with GitVersion

GitVersion has a focus on branches, and is well suited for a Continuous Deployment workflow, where releases are triggered based upon branches or commit messages. Shallow repositories are not supported.

Verlite cares only about tags, and is well suited for Continuous Delivery workflows, where official releases happen by tagging.

Comparison with MinVer

MinVer's behavior is a subset of Verlite, and so we can configured Verlite to behave the same with the following properties set:

<PropertyGroup>
	<VerliteDisableTagPrefix>true</VerliteDisableTagPrefix>
	<VerliteDefaultPrereleasePhase>alpha.0</VerliteDefaultPrereleasePhase>
<PropertyGroup>

Additionally, Verlite has some extra features, some of which I required or desired, hence the creation of this project. These are:

  • Shallow repositories are fully supported.
    • Fetch tags and commits needed for calculating the version with verlite --auto-fetch.
    • Error out if the repository is too shallow instead of silently returning an incorrect version.
  • Continuous Delivery versions can start at the first prelease ordinal to reduce long version fatigue. That is to say, after tagging 1.0.0, the next CD version by default is 1.0.1-alpha.1 instead of 1.0.1-alpha.0.1.
    • CD releases after a tagged prerelease behave identical to MinVer, for example, the commit after 1.0.0-rc.1 becomes 1.0.0-rc.1.1 and not 1.0.0-rc.2.
  • The default base height after a tag can be set, such as 1.0.0 -> 1.0.1-alpha.0.
  • Scripts can query Verlite for a specific version part.

FAQ

Why Verlite?

For if you find GitVersion too complex and MinVer too minimal for your needs. Verlite is a superset of MinVer, but takes on a small amount of complexity to provide a simpler to use tool.

Can I bump the major or minor parts after an RTM tag?

Yes, the VerliteAutoIncrement option will specify which version part should be bumped after an RTM tag.

Can I change the default phase?

Yes, the the default phase of alpha can be changed using the VerliteDefaultPrereleasePhase option.

Why is the default phase "alpha" and not "alpha.0"?

To reduce fatigue. The first commits after a stable tag are likely to be either: hotfixes that are quickly released, or a merge window opening up, resulting in a flurry of change that developers directly consume from the CD pipeline. Therefore, the default behavior is to omit the leading zero, and keep these particular versions as short as possible.

Should the you prefer alpha.0.n be used after a stable release instead of alpha.n, the VerliteDefaultPrereleasePhase can be set to alpha.0.

Can prereleases be tagged?

You should only release tagged prereleases. Then for subsequent untagged commits, they will be versioned with the tagged version with the height appended. For example, the next commit after 2.0.0-rc.1 may be versioned as 2.0.0-rc.1.1.

Can I get the commit hash?

Yes, the commit hash is exposed in the $(VerliteCommit) property which can be used after the Verlite task has run.

Can I use a branching strategy?

Sort of. Verlite is intended for tags to be the cause of a release, not an effect of a release. Verlite is not aware of named branches, and will not natively take them into account for version calculation, instead using only the commit graph and tags for version calculation.

Should you chose to, Verlite can be configured to produce different versions using MsBuild's Condition attribute under CI pipelines, for example:

<!-- apply the PR number for PR builds -->
<PropertyGroup Condition="$(GITHUB_ACTIONS.StartsWith('refs/pull/'))">
  <VerliteBuildMetadata>pr.$(GITHUB_REF.Substring(10))</VerliteBuildMetadata>
</PropertyGroup>
<!-- apply the branch name for branch builds -->
<PropertyGroup Condition="$(GITHUB_ACTIONS.StartsWith('refs/heads/'))">
  <VerliteBuildMetadata>branch.$(GITHUB_REF.Substring(11))</VerliteBuildMetadata>
</PropertyGroup>
<!-- mark locally build builds with +local -->
<PropertyGroup Condition="'$(GITHUB_ACTIONS)' == ''">
  <VerliteBuildMetadata>local</VerliteBuildMetadata>
</PropertyGroup>

Can Verlite be used elsewhere?

Yes, the command line tool can be used elsewhere, for example, in Conan packages:

from six import StringIO

def Project(ConanFile):
    # ...
    def set_version(self):
        buf = StringIO()
        self.run(f"verlite --auto-fetch {self.recipe_folder}", output=buf)
        self.version = buf.getvalue()

What is the default tag prefix?

The default tag prefix is v, so a tag of v1.2.3 is interpreted as SemVer 1.2.3.

The default prefix can be set to nothing by setting VerliteDisableTagPrefix to true for MsBuild, or --tag-prefix="" for the CLI. It can be changed to an arbitrary value setting VerliteTagPrefix or --tag-prefix.

Can multiple versions be nested?

Yes, by setting a unique VerliteTagPrefix for each project.

Can shallow clones be used?

Yes, with a caveat—for performance reasons verlite --auto-fetch must be invoked to deepen the repository prior to building. To avoid footguns, auto-fetching is not exposed under MsBuild as to the need to query the remote for each project would slow down build times too much.

What happens if auto fetch isn't used?

Nothing unsafe. In the event a clone is not deep enough, an exception will be thrown and the build will fail, instead of calculating an incorrect version number silently.

Footguns not included.

Can I use only signed/specific/arbitrary tags?

Yes, this can be done by supplying a command in --filter-tags/VerliteFilterTags that Verlite will execute and then read the exit code. In the supplied command {} will be substituded with the tag under question, along with a number of environment variables set. As an example, the following will only take into account tags signed with a trusted private key, ignoring all others:

<PropertyGroup>
	<VerliteFilterTags>git tag --verify {}</VerliteFilterTags>
</PropertyGroup>

See docs/FilteringTags.md for further reading.

What is a good changelog strategy?

While not related to Verlite per se, a good method for which changes to include in your release notes is: include all changes since the last version which was as equally stable or better.

Changelogs for _____ releases should contain changes since the last _____ build.
stable stable
release candidate release candidate or stable
beta beta, release candidate, or stable
alpha alpha, beta, release candidate, or stable

What happens if tag buildmeta and options buildmeta are set?

If a tag with height is being used, the build metadata is discarded, and the options's build metadata used if present. In the case of both tag without height and options having build metadata, they are concatenated together with a hash.

Tagging a release as say, v1.2.3+abc and also specifying --build-metadata xyz will result in a final version of 1.2.3+abc-xyz.

Can I access computed versions in my assembly?

Yes, since v2.1, the computed versions are added by default using a source generator. This can be disabled by setting the VerliteEmbedVersion property to false.

The embedded version is structured as follows:

namespace Verlite
{
	internal static class Version
	{
		public const string Full;
		public const string Core;
		public const string Major;
		public const string Minor;
		public const string Patch;
		public const string Prerelease;
		public const string BuildMetadata;
		public const string Commit;
		public const string Height;
	}
}

Can others access computed versions in the assembly?

No, the generated structure is marked as internal, and only visible to your assembly. Should you wish to expose this version, it is recomended you use a public static property:

namespace MyLibrary;
public static class Version
{
	public string Version => Verlite.Version.FullVersion;
}

What is a shadow repo?

Using a shadow repo is an experimental method of allowing shallow depth=1 clones without causing any issues with Verlite. A special repo within your repositories .git directory will be created and updated as needed, where only commits are fetched (via --filter=tree:0).

Can I query the version from the command line for a project?

Yes, this has been possible since .NET 8 with the following command:

$ dotnet msbuild $ProjectPath -getproperty:Version -target:Restore,Verlite