Home

Awesome

RelaxVersioner

RelaxVersioner

Japanese language

Status

Project Status: Active – The project has reached a stable, usable state and is being actively developed.

PackageLink
RelaxVersioner (MSBuild)NuGet RelaxVersioner (MSBuild)
rv-cli (CLI)NuGet RelaxVersioner (CLI)

What is this?

Have you ever wanted to embed the current version of .NET, its documentation, etc.? Furthermore, have you ever wanted to automate such an operation with CI, but found the additional work to be too opaque and numerous?

In C#, such information is conventionally described in the AssemblyInfo.cs file:

using System.Reflection;.

// Embed version number in assembly attribute.
[assembly: AssemblyVersion("1.0.21")]

However, this is as far as the standard goes. It was the developer's responsibility to properly update the embedded version number.

RelaxVersioner makes version embedding extremely simple by applying versions via Git tagging. Simply put, you can tag a Git tag with 1.0.21 and it will automatically generate and embed an assembly attribute like the one shown above!

The assembly with the embedded version number can be partially viewed in the Properties page of Explorer:

Assembly property at the explorer

If you look in ILSpy, you will also see all the information:

Assembly wide attributes at ILSpy

Sample output code

The following is a C# example, but F#, VB.net, and C++/CLI are also supported. The output attributes are the same.

In addition to the assembly attributes, a ThisAssembly symbol is also defined. This has the advantage that you can easily retrieve various version information without having to use reflection to search for the attributes:

using System.Reflection;

[assembly: AssemblyVersion("1.0.21")]
[assembly: AssemblyFileVersion("2020.12.20.33529")]
[assembly: AssemblyInformationalVersion("1.0.21-561387e2f6dc90046f56ef4c3ac501aad0d5ec0a")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyMetadata("AssemblyName","YourApp")]
[assembly: AssemblyMetadata("TargetFrameworkMoniker","net6.0")]
[assembly: AssemblyMetadata("Date","Sunday, April 23, 2023 9:42:21 PM 0900")]
[assembly: AssemblyMetadata("Branch","master")]
[assembly: AssemblyMetadata("Tags","")]
[assembly: AssemblyMetadata("Author","Kouji Matsui <k@kekyo.net>")]
[assembly: AssemblyMetadata("Committer","Kouji Matsui <k@kekyo.net>")]
[assembly: AssemblyMetadata("Subject","Merge branch 'devel'")]
[assembly: AssemblyMetadata("Body","")]
[assembly: AssemblyMetadata("Build","")]
[assembly: AssemblyMetadata("Generated","Sunday, April 23, 2023 9:42:21 PM 0900")]
[assembly: AssemblyMetadata("Platform","AnyCPU")]
[assembly: AssemblyMetadata("BuildOn","Unix")]
[assembly: AssemblyMetadata("SdkVersion","7.0.100")]
[assembly: AssemblyMetadata("ApplicationVersion","33529")]
[assembly: AssemblyMetadata("ApplicationDisplayVersion","1.0.21")]

namespace YourApp
{
  internal static class ThisAssembly
  {
    public const string AssemblyVersion = "1.0.21";
    public const string AssemblyFileVersion = "2020.12.20.33529";
    public const string AssemblyInformationalVersion = "1.0.21-561387e2f6dc90046f56ef4c3ac501aad0d5ec0a";
    public const string AssemblyConfiguration = "Release";
    public static class AssemblyMetadata
    {
      public const string AssemblyName = "YourApp";
      public const string TargetFrameworkMoniker = "net6.0";
      public const string Date = "Sunday, April 23, 2023 9:42:21 PM 0900";
      public const string Branch = "master";
      public const string Tags = "";
      public const string Author = "Kouji Matsui <k@kekyo.net>";
      public const string Committer = "Kouji Matsui <k@kekyo.net>";
      public const string Subject = "Merge branch 'devel'";
      public const string Body = "";
      public const string Build = "";
      public const string Generated = "Sunday, April 23, 2023 9:42:21 PM 0900";
      public const string Platform = "AnyCPU";
      public const string BuildOn = "Unix";
      public const string SdkVersion = "7.0.100";
      public const string ApplicationVersion = "33529";
      public const string ApplicationDisplayVersion = "1.0.21";
    }
  }
}

Getting started

Start guide

How to use

Example for development cycles

  1. You create new C#,F# or another project.
  2. Install "RelaxVersioner" from NuGet.
  3. (Optional): Comment outs default "AssemblyVersion" and "AssemblyFileVersion" declarations in AssemblyInfo.cs.
  4. Let's build now! Output binary applied version informations.
    • Default declaration of AssemblyVersion="0.0.1.0", AssemblyFileVersion="(Build date on 2sec prec.)" (ex:"2016.05.12.11523")
    • Applied AssemblyVersionMetadata from local Git repository (Author, Branch, Tags). But this example git repository not created , so there declarations containing "Unknown".
  5. Create Git local repository (command: git init). And commit with message.
  6. Build retry. Output binary applied Author, Branch, Tags now.
  7. You are tagging current commit. For example "0.5.4" or "v0.5.4". Rebuild, then contains AssemblyVersion is "0.5.4" now.
    • The auto increment feature: If your current commit doesn't apply any tags, RelaxVersioner will traverse committing history and auto increment commit depth for tail of version component. For example, tagged "0.5.4' at 2 older commit. The auto incrementer will calculate version "0.5.6".
    • RelaxVersioner will traverse first priority for primary parent and then next others. Therefore, if you're using branch strategy, you can apply auto increment with different version each branch when tagged different version each branch at bottom commit. For example: The tick-tock model for the master branch tagged "1.0.0" and devel branch tagged "1.1.0".
  8. All rights codes, tags. Push to remote repository, all done.
  9. Development cycles: next codes change and ready to release, you are tagging new version and then build, output binary auto tagged in AssemblyVersion and store informations.
    • We can apply with automated version number when "dotnet cli" for generate NuGet package (PackageVersion and PackageReleaseNotes attributes). You can use only dotnet pack command.

Command line interface (CLI)

RelaxVersioner supports the dotnet CLI tool. The rv command can be installed with:

$ dotnet tool install -g rv-cli

You can easily obtain the version string by using the rv command as follows:

$ rv .
1.2.3.4

You can use -f to get different output. For example:

# Standard version format
$ rv -f "{versionLabel}" .
1.2.3.4

# Commit ID
$ rv -f "{commitId}" .
01234567899abc ...

# Complex formats are also possible
$ rv -f "{commitId}:{commitDate:yyyyMMdd}/{branches}" .
0123456789 ...:20240123/develop,main

You can use the -o option to output directly to a file. In this case, no newlines are included at the end of the line:

# Output to the file
$ rv -o version.txt .
$ cat version.txt
1.2.3.4

If you use the -r or -i options, you will be in “replace mode”, which allows you to directly replace arbitrary text. Using this mode, you can perform bulk text replacements in a more direct way:

# Replace text on standard input
$ echo "@@@{commitId}@@@" | rv -r .
@@@0123456789abc ... @@@

# Replace specified file text
$ rv -i input.txt -o output.txt .

The standard bracket for the replacement keyword is in the form of braces '{ ... }', but different bracket characters can be used. For example, when applied to a document, braces can cause false positives, so a custom bracket is used to replace them:

# Replace with custom brackets
# Separate left and right bracket definitions with commas
$ echo "ABC#{commitId}#XYZ" | rv -r --bracket "#{,}#" .
ABC0123456789abc ... XYZ

With this CLI, you can use a combination of RelaxVersioner for different targets than .NET. For example, in a CI/CD environment like GitHub Actions, you can apply versions to NPM package generation or embed versions in your text documentation.


Using with Node.js projects

The CLI interface has special options for NPM (Node.js package manager). Change to the directory where the package.json file is located and run the CLI as follows:

$ rv --npm .
RelaxVersioner [3.8.0]: Generated versions code: Language=NPM, Version=12.34.56

The value of the version key in package.json will then be replaced with the correct version value.

Furthermore, if you use the --npmns option, you can also change the package versions that the package depends on at the same time. For example, if you are managing multiple packages and there are dependencies between the packages:

{
  "name": "webapi-interaction",
  "version": "0.0.1",             // <-- The version of this package (before updating)
  "dependencies": {
    "@foobar/helpers": "^1.4.0",  // <-- Version of the managed package that is a dependency
    "@foobar/common": "^1.7.2",   // <-- Version of the managed package that is a dependency
    "dayjs": "^1.2.3"
  }
}

Run the CLI specifying the namespace of the package name (@foobar) that is a dependency:

$ rv --npmns @foobar .

This will unify all the version numbers:

{
  "name": "webapi-interaction",
  "version": "13.24.5",             // <-- Automatically updated
  "dependencies": {
    "@foobar/helpers": "^13.24.5",  // <-- automatically updated (dependency)
    "@foobar/common": "^13.24.5",   // <-- automatically updated (dependency)
    "dayjs": "^1.2.3"               // <-- not updated
  }
}

This function is intended to be used with NPM workspaces and inside the CI process. In that case, please follow the NPM workspaces handling, such as using * as the version number of the referenced package. After updating the version of each package, run npm install in the workspace root directory to update the correct version numbers in package-lock.json.


Hints and Tips

How to use version numbers after building process

RelaxVersioner saves the files in the following location after build:

<your project dir>/obj/<configuration>/<tfm>/

To be precise:

For example, FooBarProject/obj/Debug/net6.0/ directory hierarchy. Here are the files to save:

If you want to reference this information from your program, you can get it from the version attributes or ThisAssembly. Other, XML or text files can be referenced by CI/CD (Continuous Integration and Continuous Deployment) to apply version information to the build process. For example, RelaxVersioner_ShortVersion.txt contains a string like 2.5.4, so you may be able to save your build artifacts with a version number when you upload them to the server.

If you want to reference this information from within the MSBuild target, you can use the properties as follows without having to access the text file:

  <Target Name="AB" AfterTargets="Compile">
    <Message Importance="High" Text="PropertiesPath: $(RelaxVersionerPropertiesPath)" />
    <Message Importance="High" Text="ResultPath: $(RelaxVersionerResultPath)" />
    <Message Importance="High" Text="ResolvedVersion: $(RelaxVersionerResolvedVersion)" />
    <Message Importance="High" Text="ResolvedShortVersion: $(RelaxVersionerResolvedShortVersion)" />
    <Message Importance="High" Text="ResolvedSafeVersion: $(RelaxVersionerResolvedSafeVersion)" />
    <Message Importance="High" Text="ResolvedIntDateVersion: $(RelaxVersionerResolvedIntDateVersion)" />
    <Message Importance="High" Text="ResolvedEpochIntDateVersion: $(RelaxVersionerResolvedEpochIntDateVersion)" />
    <Message Importance="High" Text="ResolvedCommitId: $(RelaxVersionerResolvedCommitId)" />
    <Message Importance="High" Text="ResolvedBranch: $(RelaxVersionerResolvedBranch)" />
    <Message Importance="High" Text="ResolvedTags: $(RelaxVersionerResolvedTags)" />
  </Target>

RelaxVersioner_Properties.xml contains a lot of very useful information, and you may be able to pull information from this XML file to meet your specific needs without having to write custom MSBuild scripts.

SourceLink integration

Sourcelink is a cool stuff debugger integration package for showing source code on-the-fly downloading from Git source code repository.

RelaxVersioner already supported Sourcelink integration. You can integrate using both RelaxVersioner and Sourcelink on simple step:

<!-- Example common properties for Sourcelink integration -->
<PropertyGroup>
  <!-- Will embed project untracked source files -->
  <EmbedUntrackedSources>true</EmbedUntrackedSources>

  <!-- Symbol embedding is recommended -->
  <DebugType>embedded</DebugType>

  <!-- Or you have to include symbol files (*.pdb) into same package -->
  <!-- <DebugType>portable</DebugType> -->
  <!-- <AllowedOutputExtensionsInPackageBuildOutputFolder>.pdb</AllowedOutputExtensionsInPackageBuildOutputFolder> -->

  <!-- Required: Will include Git repository information into assembly -->
  <PublishRepositoryUrl>true</PublishRepositoryUrl>
  <RepositoryType>git</RepositoryType>
  <RepositoryUrl>https://github.com/kekyo/Epoxy.git</RepositoryUrl>
</PropertyGroup>

<!-- Will build deterministic binary on GitHub CI Release building -->
<PropertyGroup Condition="'$(Configuration)' == 'Release'">
  <Deterministic>true</Deterministic>
  <ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
</PropertyGroup>

<ItemGroup>
  <!-- RelaxVersioner -->
  <PackageReference Include="RelaxVersioner" Version="2.12.0" PrivateAssets="All" />

  <!-- Root directory location of the solution file, if it exists. -->
  <!-- Refer: https://github.com/dotnet/roslyn/issues/37379 -->
  <SourceRoot Include="$(MSBuildThisFileDirectory)/"/>
</ItemGroup>

<!-- Required: Add Sourcelink package reference on only Release build -->
<ItemGroup Condition="'$(Configuration)' == 'Release'">
  <PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="All" />
</ItemGroup>

For more further informations, see Sourcelink documentation.

CI process trouble shooting

If your CI process causes error with this description like:

RelaxVersioner [2.12.0]: NotFoundException=object not found -
   no match for id (a2b834535c00e7b1a604fccc28cfebe78ea0ec31),
   Unknown exception occurred, ...

It means your clone repository contains only 1 depth commit or some. (And couldn't find these commit id.) You must clone all commits into CI workspace.

For example, GitHub Actions checkout@v2 task will clone only 1 depth for defaulted. Because it makes better fast cloning.

RelaxVersioner (and other automated versioning tool) requires all commits for calculating version depth. Apply fetch-depth: 0 predication into your build.yml script. You can understand with this real script.

Use nuspec file to generate NuGet package

When you are using a nuspec file to generate a NuGet package, there are additional symbols available besides the default placeholders, we can do automated building NuGet package with nuspec file. Please refer to the example below:

<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
  <metadata>
    <!-- Embedding package version -->
    <version>$PackageVersion$</version>

    <!-- Embedding branch name and commit ID -->
    <repository type="git" url="https://example.com/your/repo.git" branch="$RepositoryBranch$" commit="$RepositoryCommit$" />

    <!-- Embedding commit message -->
    <releaseNotes>$PackageReleaseNotes$</releaseNotes>
  </metadata>
</package>

Another topics


Sample custom rule set file (RelaxVersioner.rules):

<?xml version="1.0" encoding="utf-8"?>
<RelaxVersioner version="1.0">
  <WriterRules>
    <!-- Target languages -->
    <Language>C#</Language>
    <Language>F#</Language>
    <Language>VB</Language>
    <Language>C++/CLI</Language>

    <Import>System.Reflection</Import>

    <!--
        "versionLabel" extracts numerical-notate version string [1.2.3.4] or [v1.2.3.4] from git repository tags traverse start HEAD.
        If not found, use [0.0.1].
    -->
    <Rule name="AssemblyVersion">{versionLabel}</Rule>

    <!--
        "safeVersion" extracts committed date (from commmiter) from git repository HEAD.
        (The format is safe-numerical-notate version string [2016.2.14.12345]. (Last number is 2sec prec.))
    -->
    <Rule name="AssemblyFileVersion">{safeVersion}</Rule>

    <!--
        "commitId" extracts commit id from git repository HEAD.
        "commitId" alias to "commit.Hash".
    -->
    <Rule name="AssemblyInformationalVersion">{versionLabel}-{commitId}</Rule>

    <Rule name="AssemblyConfiguration">{Configuration}</Rule>

    <!--
        "key" attribute can only use with "AssemblyMetadataAttribute".
        "branch" can use field "Name". (Derived from GitReader)
        "author" and "committer" can use field "Name", "MailAddress", and "Date". (Derived from GitReader)
        "buildIdentifier" is passing from MSBuild property named "RelaxVersionerBuildIdentifier" or "BuildIdentifier".
        We can use in CI building.
        "generated" is generated date by RelaxVersioner.
        You can apply format directives same as string.Format().
    -->
    <Rule name="AssemblyMetadata" key="CommitId">{commitId}</Rule>
    <Rule name="AssemblyMetadata" key="Date">{commitDate:F} {commitDate.Offset:hhmm}</Rule>
    <Rule name="AssemblyMetadata" key="Branches">{branches}</Rule>
    <Rule name="AssemblyMetadata" key="Tags">{tags}</Rule>
    <Rule name="AssemblyMetadata" key="Author">{author}</Rule>
    <Rule name="AssemblyMetadata" key="Committer">{committer}</Rule>
    <Rule name="AssemblyMetadata" key="Subject">{commit.Subject}</Rule>
    <Rule name="AssemblyMetadata" key="Body">{commit.Body}</Rule>
    <Rule name="AssemblyMetadata" key="Build">{buildIdentifier}</Rule>
    <Rule name="AssemblyMetadata" key="Generated">{generated:F}</Rule>
    <Rule name="AssemblyMetadata" key="TargetFramework">{tfm}</Rule>

    <!--
        Both "ApplicationVersion" and "ApplicationDisplayVersion" are used for .NET MAUI versioning.
        "epochIntDateVersion" is a value calculated in the same way as `safeVersion`.
    -->
    <Rule name="AssemblyMetadata" key="ApplicationDisplayVersion">{shortVersion}</Rule>
    <Rule name="AssemblyMetadata" key="ApplicationVersion">{epochIntDateVersion}</Rule>

    <!--
        The "Platform" identity is a MSBuild property name.
        You can use "Platform" and another identities come from PropertyGroup definitions
        and process environments such as "RootNamespace", "Prefer32Bit", "NETCoreSdkVersion", "PATH" and etc.
        Each results are strictly string type, so format directives will be ignored.
    -->
    <Rule name="AssemblyMetadata" key="AssemblyName">{AssemblyName}</Rule>
    <Rule name="AssemblyMetadata" key="RootNamespace">{RootNamespace}</Rule>
    <Rule name="AssemblyMetadata" key="PlatformTarget">{PlatformTarget}</Rule>
    <Rule name="AssemblyMetadata" key="Platform">{Platform}</Rule>
    <Rule name="AssemblyMetadata" key="RuntimeIdentifier">{RuntimeIdentifier}</Rule>
    <Rule name="AssemblyMetadata" key="BuildOn">{OS}</Rule>
    <Rule name="AssemblyMetadata" key="SdkVersion">{NETCoreSdkVersion}</Rule>
  </WriterRules>
</RelaxVersioner>

License


History