Register | Login

Stacking Code

public interface IBlog { string Dump(Stream consciousness); }

Better Living Through Chemistry

Saturday, 29 January, 2011 @ 9:15 AM < Adam Boddington
Tags: Building Neno, Mercurial

This is post #37 in the Building Neno series. Please click here for a description of the Building Neno project and instructions on how to access the source code for this post.

Last week I wrote a post on getting Hg revision information into assembly versions. It works okay, but it has a small drawback. Because I'm putting the version information into two files, CommonAssemblyInfo.cs and Version.cs, I have to manually maintain the major, minor and build numbers in two places. And because I'm using all three assembly versions, I have to maintain those numbers on multiple lines as well. This morning I'm going to remove all the needless duplication.

Build Project

I want to consolidate the assembly version numbers into one place for easy maintenance. The best place to do that is probably in the build project itself. Here I'm going to set up properties for the major, minor and build numbers as well as create the three assembly versions as replacement values for template tokens.

<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" DefaultTargets="Build">
    <Import Project="$(MSBuildExtensionsPath)\MSBuildCommunityTasks\MSBuild.Community.Tasks.Targets"/>
    <UsingTask TaskName="HgVersionFile" AssemblyFile="References/MSBuildVersioning/MSBuildVersioning.dll" />
    <PropertyGroup>
        <SolutionName>StackingCode.Moja</SolutionName>
        <Major>1</Major>
        <Minor>0</Minor>
        <Build>1000</Build>
        <BuildText>Alpha</BuildText>
    </PropertyGroup>
    <ItemGroup>
        <Tokens Include="AssemblyVersion">
            <ReplacementValue>$(Major).$(Minor).$(Build).0</ReplacementValue>
        </Tokens>
        <Tokens Include="AssemblyFileVersion">
            <ReplacementValue>$(Major).$(Minor).$(Build).$REVNUM$</ReplacementValue>
        </Tokens>
        <Tokens Include="AssemblyInformationalVersion">
            <ReplacementValue>$(Major).$(Minor) $(BuildText) ($REVID$${Dirty$DIRTY$})</ReplacementValue>
        </Tokens>
        <Tokens Include="Dirty0">
            <ReplacementValue></ReplacementValue>
        </Tokens>
        <Tokens Include="Dirty1">
            <ReplacementValue>+</ReplacementValue>
        </Tokens>
    </ItemGroup>
    <!-- ... -->
</Project>

These are tokens that will be swapped in using the TemplateFile task from the MSBuild Community Tasks Project, before the HgVersionFile task from MSBuild Versioning gets a crack at doing its token replacement.

<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" DefaultTargets="Build">
    <!-- ... -->
    <Target Name="Version">
        <!-- Properties\CommonAssemblyInfo.cs -->
        <TemplateFile Template="Properties\CommonAssemblyInfo.template.cs" OutputFileName="CommonAssemblyInfo.temp1.cs" Tokens="@(Tokens)" />
        <HgVersionFile TemplateFile="Properties\CommonAssemblyInfo.temp1.cs" DestinationFile="Properties\CommonAssemblyInfo.temp2.cs" />
        <TemplateFile Template="Properties\CommonAssemblyInfo.temp2.cs" OutputFileName="CommonAssemblyInfo.cs" Tokens="@(Tokens)" />
        <Delete Files="Properties\CommonAssemblyInfo.temp1.cs;Properties\CommonAssemblyInfo.temp2.cs" />
        <!-- Version.cs -->
        <TemplateFile Template="Version.template.cs" OutputFileName="Version.temp1.cs" Tokens="@(Tokens)" />
        <HgVersionFile TemplateFile="Version.temp1.cs" DestinationFile="Version.temp2.cs" />
        <TemplateFile Template="Version.temp2.cs" OutputFileName="Version.cs" Tokens="@(Tokens)" />
        <Delete Files="Version.temp1.cs;Version.temp2.cs" />
    </Target>
    <!-- ... -->
</Project>

The template file is run through a TemplateFile task, then a HgVersionFile task, then another TemplateFile task.

  1. The first TemplateFile replaces ${} tokens with strings.
    ${AssemblyInformationalVersion} becomes
    1.0 Alpha ($REVID$${Dirty$DIRTY$})

  2. The HgVersionFile task replaces $$ tokens with values from Hg.
    1.0 Alpha ($REVID$${Dirty$DIRTY$}) becomes
    1.0 Alpha (875673902fee${Dirty1})

  3. The final TemplateFile task replaces any remaining ${} tokens.
    1.0 Alpha (875673902fee${Dirty1}) becomes
    1.0 Alpha (875673902fee+)

Temporary files are used instead of using the same file as input and output -- doing that causes problems in the HgVersionFile task. The temporary files are cleaned up with a Delete task at the end.

Hg Templates

The CommonAssemblyInfo.template.cs file now looks like this...

using System.Reflection;

[assembly: AssemblyCopyright("Copyright 2010 - 2011 Adam Boddington")]
[assembly: AssemblyVersion("${AssemblyVersion}")]
[assembly: AssemblyFileVersion("${AssemblyFileVersion}")]
[assembly: AssemblyInformationalVersion("${AssemblyInformationalVersion}")]

The Version.template.cs file now looks like this...

using System;

namespace StackingCode.Moja
{
    public static class Version
    {
        static Version()
        {
            DateTime buildDate = DateTime.Parse("$UTCDATETIME$");
            buildDate = DateTime.SpecifyKind(buildDate, DateTimeKind.Utc);
            HgBuildDate = new DateTimeOffset(buildDate);
        }

        public static readonly string AssemblyVersion = "${AssemblyVersion}";
        public static readonly string AssemblyFileVersion = "${AssemblyFileVersion}";
        public static readonly string AssemblyInformationalVersion = "${AssemblyInformationalVersion}";

        public static readonly DateTimeOffset HgBuildDate;
        public static readonly int HgRevisionNumber = $REVNUM$;
        public static readonly string HgChangesetId = "$REVID$";
        public static readonly bool HgIsBuildDirty = $DIRTY$ == 1;
        public static readonly string HgBranch = "$BRANCH$";
        public static readonly string HgTags = "$TAGS$";
    }
}

Overall a much cleaner and easier to maintain solution, without any need for preprocessor directives either.

Default Files

The StackingCode.Moja.csproj and StackingCode.Neno.csproj files still have the BeforeBuild tasks to copy a default file over CommonAssemblyInfo.cs and Version.cs (see the earlier post). I want to keep those tasks for users that don't have Hg installed, and for the AppHarbor build process which doesn't currently support Mercurial.

Happy versioning.

There are 0 comments.


Comments

Leave a Comment

Please register or login to leave a comment.


Older
Reasons Why I Avoid Entity Framework 4

Newer
IDependencyResolver and Generics

Older
Reasons Why I Avoid Entity Framework 4

Newer
IDependencyResolver and Generics

browse with Pivot


About


Projects

Building Neno


RSS
Recent Posts

Codility Nitrogenium Challenge
OS X Lock
HACT '13
Codility Challenges
Priority Queue


Tags

Architecture (13)
ASP.NET (2)
ASP.NET MVC (13)
Brisbane Flood (1)
Building Neno (38)
C# (4)
Challenges (3)
Collections (1)
Communicator (1)
Concurrency Control (2)
Configuration (1)
CSS (5)
DataAnnotations (2)
Database (1)
DotNetOpenAuth (2)
Entity Framework (1)
FluentNHibernate (2)
Inversion of Control (5)
JavaScript (1)
jQuery (4)
Kata (2)
Linq (7)
Markdown (4)
Mercurial (5)
NHibernate (20)
Ninject (2)
OpenID (3)
OS X (1)
Pivot (6)
PowerShell (8)
Prettify (2)
RSS (1)
Spring (3)
SQL Server (5)
T-SQL (2)
Validation (2)
Vim (1)
Visual Studio (2)
Windows Forms (3)
Windows Service (1)


Archives


Powered by Neno, ASP.NET MVC, NHibernate, and small furry mammals. Copyright 2010 - 2011 Adam Boddington.
Version 1.0 Alpha (d9e7e4b68c07), Build Date Sunday, 30 January, 2011 @ 11:37 AM