How to: Ensure that files are buildable when they are being checked in to Team Foundation Version Control.

Posted by: Subodh Sohoni , on 7/14/2009, in Category VSTS & TFS (Azure DevOps)
Views: 42061
Abstract: This article demonstrates how to ensure that files are built locally before they can be checked in.
How to: Ensure that files are buildable when they are being checked in to Team Foundation Version Control.
 
I was recently approached by a world renowned software development company in Hyderabad to conduct training on TFS. Their special requirement was that I should cover the topic of Version Control with an example to show how to ensure that files are built locally before they can be checked in. A few days back there also was a question on the MSDN forums to know how to ensure that files are buildable at the time of check-in. These two requirements was a starting point for me to think about a component to fulfil this requirement.
In TFS 2010, this functionality is implemented by the concept of ‘Gated Check-in’ where a configured build executes to ensure that the files being checked in are buildable for the check-in to succeed. It puts those files in a temporary shelveset and executes the build. If the build succeeds files are automatically checked in and otherwise the check-in fails.
The best way to ensure any condition fulfilment for check-in is to put it in the check-in policy. To ensure that the files are buildable we need to write a custom check-in policy. In this article I am going to discuss about code for the specific condition that is encountered and not about general solution for Custom Check-in Policy creation. On DotNetCurry I had written an article about how to write custom check-in policy in an article last year and there are quite a few resources on MSDN and Internet for in depth information about it. One of them which guided me long time back is written by Marcel de Vreis in his blog.
The earlier article that I had written used PolicyBase abstract class as the base class whereas in this article we will write a class to implement two interfaces that are implemented by PolicyBase. These interfaces are IPolicyDefinition and IPolicyEvaluation. The most important method that we will be writing in this Evaluate which is specified by IPolicyEvaluation interface.
Faced with the problem of ensuring that the files are buildable at the time of check-in, my first thought was to find if there is any flag set by the compiler when it successfully compiles the file and then if further editing is done on it that flag is reset. I did not find any such flag. I tried to find if the project file maintains any such record and found that it also does not keep such data. After a lot of research I came to the conclusion that there is no way to tell if a file is successfully compiled and not further edited.
Now the only option to ensure that the file is compiled at the time of check-in is to compile it on the fly when it is required to be checked in. This can be done by using System.CodeDom.Compiler.CodeDomProvider class which allows us to select any language to compile the code. We can compile a small number of code files using CodeDomProvider class but in the realistic situation where we have large solution consisting of many different projects with complex hierarchy of folders and files as well as referenced assemblies, this option is less likely to succeed. It becomes a ‘build’ rather than only ‘compile’ operation.
Another option is to emulate what Visual Studio does at the time of compilation. It builds the assemblies from project files using well known technology of MSBuild. In turn, MSBuild can accept the project file like a .csproj or .vbproj file which is an XML file to provide configuration like target platform, version, referenced assemblies and the files to be built into a single assembly. If we can use MSBuild to build our project before check-in then we ensure that when check-in happens at that time those files are buildable. We can call MSBuild.exe through a command prompt which means we can also do it using code. We can also get the result of the build. What we cannot get are the details like where the build process failed and what sort of error was encountered.
We will get all the data of the build with errors and results if we execute the build using object model provided for MSBuild by Microsoft. We can use the build engine object to execute the build and use a custom logger object to know the details of the build. By default logger sends the output to ‘stdout’ which is the console but we want the output to be sent to our check-in policy and then it can do whatever necessary with that output. For this purpose we will have to build a custom logger class and in the event of compilation error, send the error object back to the check-in policy.
Let us begin by creating the check-in policy class and implement the interfaces IPolicyDefinition and IPolicyEvaluation. We give a reference to Microsoft.TeamFoundation.VersionControl.Client.dll and use namespace Microsoft.TeamFoundation.VersionControl.Client from it. For the sake of simplicity I am going to assume that our test project (for which this check-in policy is going to work is written in C# although we can do some additional coding to take care of projects in other languages too.
 
C#
 
[Serializable]
public class BuildPolicyClass : IPolicyDefinition, IPolicyEvaluation
{
    [NonSerialized]
    private IPendingCheckin m_PendingCheckin = null;
 
    public string Description
    {
        get { return StringMsgs.Description; }
    }
 
    public bool Edit(IPolicyEditArgs policyEditArgs)
    {
        return false;
    }
 
    public string Type
    {
        get { return StringMsgs.PolicyType; }
    }
 
    public string TypeDescription
    {
        get { return StringMsgs.TypeDescription; }
    }
 
    public bool CanEdit
    {
        get { return false; }
    }
 
    public string InstallationInstructions
    {
        get { return StringMsgs.InstallationInstructions; }
    }
 
    public void Activate(PolicyFailure failure)
    {
MessageBox.Show("There is minimum one compilation error in your code which will break the build. Please correct those errors before checking the code in", "Build Custom Checkin Policy", MessageBoxButtons.OK, MessageBoxIcon.Stop);
    }
 
    public void DisplayHelp(PolicyFailure failure)
    {
        MessageBox.Show(failure.Message);
    }
 
    public void Initialize(IPendingCheckin pendingCheckin)
    {
        if (m_PendingCheckin != null)
        {
            throw new InvalidOperationException("This Policy is Already             Active");
        }
        this.m_PendingCheckin = pendingCheckin;
        this.m_PendingCheckin.PendingChanges.CheckedPendingChangesChanged += new EventHandler(PendingChanges_CheckedPendingChangesChanged);
    }
 
void PendingChanges_CheckedPendingChangesChanged(object sender, EventArgs e)
    {
        OnPolicyStateChanged(Evaluate());
    }
 
    public event PolicyStateChangedHandler PolicyStateChanged;
 
    public void Dispose()
    {
        this.m_PendingCheckin.PendingChanges.CheckedPendingChangesChanged -= new EventHandler(PendingChanges_CheckedPendingChangesChanged);
        m_PendingCheckin = null;
    }
 
    private void OnPolicyStateChanged(PolicyFailure[] failures)
    {
        if (PolicyStateChanged != null)
        {
            PolicyStateChanged(this, new PolicyStateChangedEventArgs(failures, this));
        }
    }
 
}
 
 
public class StringMsgs
{
    public static string PolicyType = "Build Checkin Policy";
    public static string Description = "This policy is to build .cs file in every checkin";
    public static string TypeDescription = "Build Checkin Policy: Builds .cs file if present in checkin";
    public static string InstallationInstructions = "Copy the assembly BuildCustomCheckinPolicy.dll and make necessary registry entry";
}
                  
VB.NET
 
<Serializable> _
Public Class BuildPolicyClass
      Implements IPolicyDefinition, IPolicyEvaluation
            <NonSerialized> _
            Private m_PendingCheckin As IPendingCheckin = Nothing
            Public ReadOnly Property Description() As String
                  Get
                        Return StringMsgs.Description
                  End Get
            End Property
 
Public Function Edit(ByVal policyEditArgs As IPolicyEditArgs) As Boolean
                  Return False
            End Function
 
            Public ReadOnly Property Type() As String
                  Get
                        Return StringMsgs.PolicyType
                  End Get
            End Property
 
            Public ReadOnly Property TypeDescription() As String
                  Get
                        Return StringMsgs.TypeDescription
                  End Get
            End Property
 
            Public ReadOnly Property CanEdit() As Boolean
                  Get
                        Return False
                  End Get
            End Property
 
            Public ReadOnly Property InstallationInstructions() As String
                  Get
                        Return StringMsgs.InstallationInstructions
                  End Get
            End Property
 
            Public Sub Activate(ByVal failure As PolicyFailure)
MessageBox.Show("There is minimum one compilation error in your code which will break the build. Please correct those errors before checking the code in","Build Custom Checkin Policy",MessageBoxButtons.OK,MessageBoxIcon.Stop)
            End Sub
 
            Public Sub DisplayHelp(ByVal failure As PolicyFailure)
                  MessageBox.Show(failure.Message)
            End Sub
 
            Public Sub Initialize(ByVal pendingCheckin As IPendingCheckin)
                  If m_PendingCheckin IsNot Nothing Then
Throw New InvalidOperationException("This Policy is Already Active")
                  End If
                  Me.m_PendingCheckin = pendingCheckin
                  AddHandler m_PendingCheckin.PendingChanges.CheckedPendingChangesChanged, AddressOf PendingChanges_CheckedPendingChangesChanged
            End Sub
 
Private Sub PendingChanges_CheckedPendingChangesChanged(ByVal sender As Object, ByVal e As EventArgs)
                  OnPolicyStateChanged(Evaluate())
            End Sub
 
            Public Event PolicyStateChanged As PolicyStateChangedHandler
            Public Sub Dispose()
RemoveHandler m_PendingCheckin.PendingChanges.CheckedPendingChangesChanged, AddressOf PendingChanges_CheckedPendingChangesChanged
                  m_PendingCheckin = Nothing
            End Sub
 
Private Sub OnPolicyStateChanged(ByVal failures() As PolicyFailure)
RaiseEvent PolicyStateChanged(Me, New PolicyStateChangedEventArgs(failures, Me))
            End Sub
 
End Class
 
 
Public Class StringMsgs
 
            Public Shared PolicyType As String = "Build Checkin Policy"
 
Public Shared Description As String = "This policy is to build .cs file in every checkin"
 
Public Shared TypeDescription As String = "Build Checkin Policy: Builds .cs file if present in checkin"
Public Shared InstallationInstructions As String = "Copy the assembly BuildCustomCheckinPolicy.dll and make necessary registry entry"
 
End Class
 
Now we come to the code where we will check if the files being checked in are buildable by actually building them in the code itself. We may have the files from various projects being checked in at the same time. Moreover these files may depend upon some code from some other .cs files from the same project or on the referred assemblies. To achieve it, in the Evaluate method, we will get hold of .csproj files which are present for those projects. This is done by first listing the files which are being checked in and then getting the .csproj files for those. We have to use the MSBuild object model and for that we have to give reference to:
·         Microsoft.Build.Engine.dll
·         Microsoft.Build.Framework.dll
·         Microsoft.Build.Utility.dll
 
C#
 
public PolicyFailure[] Evaluate()
{
    ArrayList changes = new ArrayList();
    try
    {
        List<string> csProjFiles = new List<string>();
PendingChange[] CheckedChanges = m_PendingCheckin.PendingChanges.CheckedPendingChanges;
//all the files being checked in
        foreach (PendingChange pChange in CheckedChanges)
//for each of the file being checked in
        {
string[] FilesInProjectDir = Directory.GetFiles(pChange.LocalOrServerFolder);
// Get all files in the same folder
            foreach (string fileName in FilesInProjectDir)
//each file in the directory which contains the
            {
                if (fileName.EndsWith(".csproj"))
                {
                    //check if this .csproj file is already in the array
                    bool fileInArray = false;
                    foreach (string csproj in csProjFiles)
                    {
                        if (csproj == fileName)
                        {
                            fileInArray = true;
                            break;
                        }
                    }
                    if (fileInArray == false)
                    {
                        //if it is not then add it to array
                        csProjFiles.Add(fileName);
                    }
                }
            }
        }
    }
 
}
 
VB.NET
 
    Public Function Evaluate() As PolicyFailure()
        Dim changes As New ArrayList()
        Try
            Dim csProjFiles As New List(Of String)()
Dim CheckedChanges() As PendingChange = m_PendingCheckin.PendingChanges.CheckedPendingChanges
'all the files being checked in
            For Each pChange As PendingChange In CheckedChanges
'for each of the file being checked in
Dim FilesInProjectDir() As String = Directory.GetFiles(pChange.LocalOrServerFolder)
' Get all files in the same folder
                For Each fileName As String In FilesInProjectDir
'each file in the directory which contains the
                    If fileName.EndsWith(".csproj") Then
                        'check if this .csproj file is already in the array
                        Dim fileInArray As Boolean = False
                        For Each csproj As String In csProjFiles
                            If csproj = fileName Then
                                fileInArray = True
                                Exit For
                            End If
                        Next csproj
                        If fileInArray = False Then
                            'if it is not then add it to array
                            csProjFiles.Add(fileName)
                        End If
                    End If
                Next fileName
            Next pChange
        End Try
 
    End Function
 
Now we have the project files for all the projects in the solution so we can take them one by one and build them using build engine. We will also create a custom logger to get the details of the error which we will make a public property of the logger class so that we can get hold of it if it is created and get further details of error from that object. When we have the details of compilation error then we can add it to the PolicyFailures collection so that it is reflected back in the check-in dialogue box.
 
C#
 
foreach (string csProjFile in csProjFiles)
{
    Engine BEngine = new Engine();
    BuildLogger blogger = new BuildLogger();
    BEngine.RegisterLogger(blogger);
   
    bool compilationSuccessfull = BEngine.BuildProjectFile(csProjFile);
    if (!compilationSuccessfull)
    {
Microsoft.Build.Framework.BuildErrorEventArgs BError = blogger.ErrorObject;
PolicyFailure failure = new PolicyFailure("Error: \"" + BError.Message + "\" in " + BError.File + " on Line " + BError.LineNumber, this);
        changes.Add(failure);
    }
    BEngine.UnregisterAllLoggers();
 
}
}
catch (Exception ex)
{
PolicyFailure failure = new PolicyFailure(ex.Message, this);
changes.Add(failure);               
}           
return (PolicyFailure[])changes.ToArray(typeof(PolicyFailure));
}
 
VB.NET
 
For Each csProjFile As String In csProjFiles
      Dim BEngine As New Engine()
      Dim blogger As New BuildLogger()
      BEngine.RegisterLogger(blogger)
 
Dim compilationSuccessfull As Boolean = BEngine.BuildProjectFile(csProjFile)
      If (Not compilationSuccessfull) Then
Dim BError As Microsoft.Build.Framework.BuildErrorEventArgs = blogger.ErrorObject
Dim failure As New PolicyFailure("Error: """ & BError.Message & """ in " & BError.File & " on Line " & BError.LineNumber, Me)
            changes.Add(failure)
      End If
      BEngine.UnregisterAllLoggers()
 
Next csProjFile
}
Catch ex As Exception
Dim failure As New PolicyFailure(ex.Message, Me)
changes.Add(failure)
End Try
Return CType(changes.ToArray(GetType(PolicyFailure)), PolicyFailure())
}
 
What remains are the details of custom logger class.
 
C#
 
class BuildLogger : Microsoft.Build.Utilities.Logger
{
 
    private Microsoft.Build.Framework.BuildErrorEventArgs error;
 
    public Microsoft.Build.Framework.BuildErrorEventArgs ErrorObject
    {
        get
        {
            return error;
        }
    }
 
public override void Initialize(Microsoft.Build.Framework.IEventSource eventSource)
    {
eventSource.ErrorRaised += new Microsoft.Build.Framework.BuildErrorEventHandler(eventSource_ErrorRaised);
    }
 
void eventSource_ErrorRaised(object sender, Microsoft.Build.Framework.BuildErrorEventArgs e)
    {
        error = e;
    }
}
 
VB.NET
 
Friend Class BuildLogger
      Inherits Microsoft.Build.Utilities.Logger
 
Private [error] As Microsoft.Build.Framework.BuildErrorEventArgs
 
Public ReadOnly Property ErrorObject() As Microsoft.Build.Framework.BuildErrorEventArgs
                  Get
                        Return [error]
                  End Get
            End Property
 
Public Overrides Sub Initialize(ByVal eventSource As Microsoft.Build.Framework.IEventSource)
AddHandler eventSource.ErrorRaised, AddressOf eventSource_ErrorRaised
            End Sub
 
Private Sub eventSource_ErrorRaised(ByVal sender As Object, ByVal e As Microsoft.Build.Framework.BuildErrorEventArgs)
                  [error] = e
            End Sub
End Class
 
Summary: What this check-in policy will achieve is to build the code on the client side to ensure that the files are buildable i.e. there are no syntactical errors in the code and methods usage is correct. What it cannot do is, as it stands it will not be able to work on the VB.NET code, you will have to modify the code of the check-in policy to ensure that. Second limitation is due to the behaviour of MSBuild. It stops the build when it encounters an error. It means that we will have to work in the check-in policy error by error and will not get all the errors at the same time.

If you liked the article,  Subscribe to the RSS Feed or Subscribe Via Email

  
Subodh Sohoni is a VSTS MVP, MCTS - Microsoft Team Foundation Server - Configuration and Development and also is a Microsoft Certified Trainer(MCT) since 2004. Subodh works as VP, Technology with SEED Infotech Ltd

 

This article has been editorially reviewed by Suprotim Agarwal.

Absolutely Awesome Book on C# and .NET

C# and .NET have been around for a very long time, but their constant growth means there’s always more to learn.

We at DotNetCurry are very excited to announce The Absolutely Awesome Book on C# and .NET. This is a 500 pages concise technical eBook available in PDF, ePub (iPad), and Mobi (Kindle).

Organized around concepts, this Book aims to provide a concise, yet solid foundation in C# and .NET, covering C# 6.0, C# 7.0 and .NET Core, with chapters on the latest .NET Core 3.0, .NET Standard and C# 8.0 (final release) too. Use these concepts to deepen your existing knowledge of C# and .NET, to have a solid grasp of the latest in C# and .NET OR to crack your next .NET Interview.

Click here to Explore the Table of Contents or Download Sample Chapters!

What Others Are Reading!
Was this article worth reading? Share it with fellow developers too. Thanks!
Share on LinkedIn
Share on Google+

Author

Subodh is a Trainer and consultant on Azure DevOps and Scrum. He has an experience of over 33 years in team management, training, consulting, sales, production, software development and deployment. He is an engineer from Pune University and has done his post-graduation from IIT, Madras. He is a Microsoft Most Valuable Professional (MVP) - Developer Technologies (Azure DevOps), Microsoft Certified Trainer (MCT), Microsoft Certified Azure DevOps Engineer Expert, Professional Scrum Developer and Professional Scrum Master (II). He has conducted more than 300 corporate trainings on Microsoft technologies in India, USA, Malaysia, Australia, New Zealand, Singapore, UAE, Philippines and Sri Lanka. He has also completed over 50 consulting assignments - some of which included entire Azure DevOps implementation for the organizations.

He has authored more than 85 tutorials on Azure DevOps, Scrum, TFS and VS ALM which are published on www.dotnetcurry.com.Subodh is a regular speaker at Microsoft events including Partner Leadership Conclave.You can connect with him on LinkedIn .


Page copy protected against web site content infringement 	by Copyscape




Feedback - Leave us some adulation, criticism and everything in between!
Comment posted by mona on Thursday, October 3, 2013 5:25 AM
would be nice if we can download the code :) ... and thanks this is helpful