Marcos Cobeña Morián: Si no lo tengo me lo invento

Regardless of how you generate your version numbers, it’s quite useful to embed
the git
repository information in an assembly (and/or its version information),
such as the branch and commit that generated it. It can be invaluable in
troubleshooting issues with a shipped product, and are opaque enough that they
don’t typically represent a secret you would care to hide. Logging code inside
your app/library could also trace this information, so that error logs or crash
reports could include this information too, further facilitating troubleshooting.
In addition, if you’re using GitFlow,
GitHubFlow or some other variation,
your branches or tags may also have additional meaning with regards to versioning,
and thus may also be relevant information you might want to consume as assembly
metadata or directly from code.
There is of course an infinite number of ways to do this, from powershell that
generate text files with the info, to MSBuild targets that generate JSON files,
continuous integration servers automatically patching files for you or manually
updating a version specifier somewhere in your source tree and having a pre-build
process patch everything locally.
In many cases I’ve seen in the wild, these are intended to be consumed from build
scripts that patch AssemblyInfo.cs files automatically and that’s about it. Maybe
expose some environment variable that you can consume in some way too from those
build scripts. Others would provide format strings and customization hooks to
tweak how the version numbers or assembly info is generated, requiring some
learning on the way. I’ve done so myself too at some point with
SemanticGit. In the case of
GitVersion,
it goes to the extreme of being awfully complicated.
I came to the conclusion that for .NET/MSBuild based projects, simple MSBuild
properties populated automatically (such as $(GitBranch)
, $(GitCommit)
,
$(GitSha)
and the like) coupled with source code exposure of the values via
generated constants, strikes the perfect balance of simplicity and flexibility.
Thus, GitInfo was born.
When you install the package in your project that lives in a git
repo, you
automatically get the following MSBuild properties, ready to be consumed
by any target that depends on the provided GitInfo
target:
$(GitRoot)
: the root directory (without trailing slash) of the git repository.$(GitBranch)
: the branch being built$(GitCommit)
: the short (7 chars) commit sha being built$(GitSha)
: the full commit sha being builtThat may be all the information you need if you’re not leveraging the commits
for versioning information. If so, in addition to consuming this from MSBuild
properties you can also consume it from from code (C# and VB projects only
for now). For this, GitInfo
leverages a fairly common pattern in the .NET
framework, which is to create a class named ThisAssembly
that holds these
values as constants.
For projects with a supported language, GitInfo
will generate a partial class
named ThisAssembly
in the intermediate output directory, like the following:
namespace Contoso
{
/// <summary>Provides access to the current assembly information.</summary>
partial class ThisAssembly
{
/// <summary>Provides access to the git information for the current assembly.</summary>
public partial class Git
{
/// <summary>Branch: master</summary>
public const string Branch = "master";
/// <summary>Commit: 5335c2f</summary>
public const string Commit = "5335c2f";
/// <summary>Sha: 5335c2f914b56ddd3dab4c0c71b44aa0e070f059</summary>
public const string Sha = "5335c2f914b56ddd3dab4c0c71b44aa0e070f059";
...
}
}
Notice how the generated values are also exposed as the member summary, so that
from anywhere in your codebase, you can easily find out what the current values
are by just hovering on any code consuming ThisAssembly.Git.Branch
for example.
Once thing you don’t want is this fancy addition ruining your carefully tunned
incremental builds, so I’ve been extremely vigilant of that aspect too. The
targets
have been carefully crafted with proper Inputs/Outputs
and smart caching of
the git information (you can peek at the GitInfo.cache
file in the intermediate
output directory) and refreshing it only when the current git HEAD changes
(i.e. you create a new commit, switch branches, pull commits, create a tag, etc.).
Additionally, this information is (by default) exposed as assembly metadata too
via AssemblyMetadataAttribute
s which are new in .NET 4.5, as follows:
[assembly: System.Reflection.AssemblyMetadata("GitInfo.Branch", Contoso.ThisAssembly.Git.Branch)]
[assembly: System.Reflection.AssemblyMetadata("GitInfo.Commit", Contoso.ThisAssembly.Git.Commit)]
[assembly: System.Reflection.AssemblyMetadata("GitInfo.Sha", Contoso.ThisAssembly.Git.Sha)]
It’s quite common to derive a product version number from a branch or tag name
plus the number of commits since the branch/tag was created, SemVer
style. The idea being that if the branch is named ‘v3.0.0’ and there have
been 40 commits to it since it was branched,
then the version number is ‘v3.0.40’. Pretty straightforward and very useful,
since now you can locally build any branch and commit (or tag) and generate a
fully equivalent product (including version!) from what a continuous integration
server would generate. The same technique is usually applied to a version file
in the repository root, which is updated after branching for a release (this is
how most of us do it at Xamarin, for example).
Git can easily tell you how many commits you have made since you created a tag or
branch or since you last commited to a particular file. This means that all the
information to create an automatic version number based on the combination of a
SemVer-named tag/branches (or a version file contents) plus commits on top of
it them readily available. All we need to do is automate its extraction and
expose that information to your code so you can build up whatever version
numbers you wish.
Rather than providing MSBuild properties or scripts with format strings you
have to learn in order to put together your version numbers, GitInfo
simply
exposes the determined version-related values via MSBuild and code constants
(through ThisAssembly.Git.BaseVersion
and ThisAssembly.Git.SemVer
) so
you can freely build the version number you want by just concatenating the
values yourself.
GitInfo
exposes two version structures:
BaseVersion
: this is the base version determined from the branch, tagMajor
, Minor
and Patch
,SemVer
: this is the determined version obtained by adding the number-pre
, -beta
). It’s also exposedMajor
, Minor
and Patch
but also addsLabel
(i.e. pre
or beta
), DashLabel
(i.e. -pre
or -beta
) andSource
(which can be Branch
, Tag
or File
). The DashLabel
allowsIn MSBuild, any target that depends on the provided GitVersion
target can
access the following properties for the above values:
The difference between GitTag
and GitBaseTag
is apparent in these two
sample values from an actual repo:
Code like the following is emitted for the ThisAssembly
partial class:
partial class ThisAssembly
{
/// <summary>Provides access to the git information for the current assembly.</summary>
public partial class Git
{
/// <summary>Branch: master</summary>
public const string Branch = "master";
/// <summary>Commit: 5335c2f</summary>
public const string Commit = "5335c2f";
/// <summary>Sha: 5335c2f914b56ddd3dab4c0c71b44aa0e070f059</summary>
public const string Sha = "5335c2f914b56ddd3dab4c0c71b44aa0e070f059";
/// <summary>Commits on top of base version: 145</summary>
public const string Commits = "145";
/// <summary>Tag: v0.2.63-145-g5335c2f</summary>
public const string Tag = "v0.2.63-145-g5335c2f";
/// <summary>Base tag: v0.2.63</summary>
public const string BaseTag = "v0.2.63";
/// <summary>Provides access to the base version information used to determine the <see cref="SemVer" />.</summary>
public partial class BaseVersion
{
/// <summary>Major: 0</summary>
public const string Major = "0";
/// <summary>Minor: 2</summary>
public const string Minor = "2";
/// <summary>Patch: 63</summary>
public const string Patch = "63";
}
/// <summary>Provides access to SemVer information for the current assembly.</summary>
public partial class SemVer
{
/// <summary>Major: 0</summary>
public const string Major = "0";
/// <summary>Minor: 2</summary>
public const string Minor = "2";
/// <summary>Patch: 208</summary>
public const string Patch = "208";
/// <summary>Label: </summary>
public const string Label = "";
/// <summary>Label with dash prefix: </summary>
public const string DashLabel = "";
/// <summary>Source: Tag</summary>
public const string Source = "Tag";
}
}
}
The generated ThisAssembly
file is in the intermediate output directory
in a file named ThisAssembly.GitInfo.g.cs
(or .vb).
Note that GitInfo
hasn’t generated a single [AssemblyVersion]
attribute :).
That is intentional! With the above information, you can trivially create one
yourself, share it however you like (linked files, shared asset project, common
targets imported from all your projects, etc.) among your projects. For example,
in Xamarin for Visual Studio, we have a GlobalAssemblyInfo.cs
like the
following:
// AssemblyVersion = full version info, since it's used to determine agents versions
[assembly: AssemblyVersion(Xamarin.ThisAssembly.Version)]
// FileVersion = release-like simple version (i.e. 3.11.2 for cycle 5, SR2).
[assembly: AssemblyFileVersion(Xamarin.ThisAssembly.SimpleVersion)]
// InformationalVersion = full version + branch + commit sha.
[assembly: AssemblyInformationalVersion(Xamarin.ThisAssembly.InformationalVersion)]
namespace Xamarin
{
partial class ThisAssembly
{
/// <summary>
/// Simple release-like version number, like 4.0.1 for a cycle 5, SR1 build.
/// </summary>
public const string SimpleVersion = Git.BaseVersion.Major + "." + Git.BaseVersion.Minor + "." + Git.BaseVersion.Patch;
/// <summary>
/// Full version, including commits since base version file, like 4.0.1.598
/// </summary>
public const string Version = SimpleVersion + "." + Git.Commits;
/// <summary>
/// Full version, plus branch and commit short sha, like 4.0.1.598-cycle6+39cf84e
/// </summary>
public const string InformationalVersion = Version + "-" + Git.Branch + "+" + Git.Commit;
}
}
You can of course just use the values directly in the version attributes,
rather than having separate constants, for simplicity:
[assembly: AssemblyVersion (ThisAssembly.Git.SemVer.Major + "." + ThisAssembly.Git.SemVer.Minor + "." + ThisAssembly.Git.SemVer.Patch)]
[assembly: AssemblyInformationalVersion (
ThisAssembly.Git.SemVer.Major + "." +
ThisAssembly.Git.SemVer.Minor + "." +
ThisAssembly.Git.SemVer.Patch + "-" +
ThisAssembly.Git.Branch + "+" +
ThisAssembly.Git.Commit)]
// i..e ^: 1.0.2-master+c218617
Extending the generated ThisAssembly
class with the formatted version strings
is quite useful however, since you can avoid repeating that formatting across your
codebase if you happen to consume it elsewhere, such as in logging.
Being a NuGet package, it’s trivial:
Install-Package GitInfo
If a base version can’t be determined, a warning will be issued and the
version values will default to 0.1.0
.
Right after installation, a readme will open up showing the available customization
points via MSBuild, copied here as of this writing:
$(GitThisAssembly): set to 'false' to prevent assembly
metadata and constants generation.
$(GitThisAssemblyMetadata): set to 'false' to prevent assembly
metadata generation only. Defaults
to 'false'.
$(ThisAssemblyNamespace): allows overriding the namespace
for the ThisAssembly class.
Defaults to the global namespace.
$(GitDefaultBranch): determines the base branch used to
calculate commits on top of current branch.
Defaults to 'master'.
$(GitVersionFile): determines the name of a file in the Git
repository root used to provide the base
version info.
Defaults to 'GitInfo.txt'.
$(GitInfoReportImportance): allows rendering all the retrieved
git information with the specified
message importance ('high',
'normal' or 'low').
Defaults to 'low'.
$(GitIgnoreBranchVersion) and $(GitIgnoreTagVersion): determines
whether the branch and tags (if any)
will be used to find a base version.
Defaults to empty value (no ignoring).
I find $(GitInfoReportImportance)
particularly useful in release
builds:
<PropertyGroup>
<GitInfoReportImportance Condition="'$(Configuration)' == 'Release'">high</GitInfoReportImportance>
</PropertyGroup>
Which causes the following msbuild log entry:
GitInfoReport:
Git Info:
GitRoot: C:/Contoso
GitBranch: master
GitCommit: 39cf84e
GitSha: 39cf84eb9027ca669c8aa6cb4fe5f238009d42ba
GitBaseVersion: 99.0.0
GitBaseVersionSource: C:ContosoContoso.Version
GitBaseVersionMajor: 99
GitBaseVersionMinor: 0
GitBaseVersionPatch: 0
GitCommits: 2611
GitTag:
GitBaseTag:
GitSemVerMajor: 99
GitSemVerMinor: 0
GitSemVerPatch: 2611
GitSemVerLabel:
GitSemVerDashLabel:
GitSemVerSource: File
This is nice especially if you just do it on the main build script for
a project, which can of course also benefit from GitInfo
even without
being a regular project containing code: it just needs to import the
targets and make its targets depend on
GitInfoReport!
But to me the best part is that all of the behavior is implemented in
a single .targets file, with plain native MSBuild with no custom tasks,
even adding the commits to the base patch
so it makes for an interesting read if you’re looking to learn some
MSBuild tricks too :).
Happy MSBuilding!
At Xamarin Evolve 2016 we announced that Xamarin.Forms is now open source and introduced a brand new Xamarin.Forms Previewer that renders a live preview of a page side-by-side with the corresponding XAML markup, directly within your Xamarin Studio environment. Xamarin.Forms has also had tons of new features added in the last several months to help […]
The post Contest: Add the Latest in Xamarin.Forms appeared first on Xamarin Blog.
Mobile-optimized DevOps has never been easier, and it’s setting Xamarin customers apart. With demanding users and high stakes delivery timelines, Coca-Cola Bottling Co. Consolidated’s (CCBCC) DevOps processes help its developers create apps that users love for their ease of use and business stakeholders love for their impact on sales. Here, Tarandeep Kaur, Sr. ISS Application […]
The post Mobile DevOps at Coca-Cola Bottling Co. Consolidated appeared first on Xamarin Blog.
The latest preview release of Xamarin.Forms (2.3.0) includes some great new features, including Themes, Data Pages, URL Navigation, and a brand new control, the CarouselView. You can think of the CarouselView as the successor to the CarouselPage, which had the restriction of a page that couldn’t be embedded easily. In contrast, the new CarouselView is […]
The post Flip through items with Xamarin.Forms CarouselView appeared first on Xamarin Blog.
In case you haven’t noticed, I’ve been rambling a lot lately on the topic of app monitoring and instrumentation, including a visit to Jesse Liberty’s podcast as well as my session at Xamarin Evolve.
For whatever reason I’ve been allowed to keep rambling about it since then, so I have some more links to share!
I was invited to speak at dotnetConf, which was an awesome virtual conference involving many great folks from Microsoft in addition to many others from around the community. There was a ton of great content, so I definitely encourage you to go check it out. I did a session named Monitoring Your Mobile Apps in the Wild, which was based on the previously mentioned session I did at Evolve.
It’s always a blast to talk to Carl and Richard, so it was an honor to be invited back to chat about this stuff. The episode, titled Instrumenting Mobile Apps with Greg Shackles
Okay, so this one is my own podcast and I’m cheating a little. Yesterday we published an episode with John-Daniel Trask of Raygun, talking about their new Real User Monitoring solution for the web and mobile apps called Pulse. The subject matter is right in line with all of this so I figured I’d give it an extra shout out.