Using Two Version Control Software Systems On One Project

This seems to be a novel idea.

Version control has two main aspects:

  • Strategic – release builds for bug fixes and formal testing
  • Tactical – for development

Typically these two needs conflict.  Developers that are refactoring frequently don’t check in there changes until they are finished.

This means that one step too far and you have a lot of rework.  In addition it is hard to pass a changeset to another developer/machine without compromising the Strategic version control.  This may only be an experiment to identify the shape of future work.

Typically these have been solved by having branches in the vcs. However branching is rather expensive.

In a corporate environment you can have a standardised system imposed due to its provision of work item tracking.

You would not want developers checkin in lots of small changes to the strategic branch – just to back them out later.

A neater solution is to use two distinct systems.  Keep the corporate vcs for the strategic use (integration testing, structured builds, work item tracking) but allow the devs to have local version control using a distributed version control system such as Mercurial/Hg.

This allows the developer to check in every time that the local code compiles successfully.  It allows you to go back that 5 mins rather than spend that hour redoing your work.

Combined with a suitable suit of unit tests this gives a greater degree of freedom to experiment. In addition you can pass these tactical version control changesets between developers or machines. (How do you take tfs away with you so that you could develop on a plane?).

Installer Rant

Microsoft are not very good at writing installers.

Here is a good example of an epic fail:

I want to add Visual C++ to the Visual Studio 2008 installation on our build server.

I start with the setup.exe.

This fails (after a long wait).

Some Google searches  later and I find that the next step is to use the add/remove programs to trigger the same program.

This fails.  Apparently a service packed visual studio cannot have a product added. This is a slow classic partial install then full rollback without a useful message.

So I remove Visual Studio Service pack 1 (which is a slow process).

I then install Visual C++

Then I attempt to reinstall the service pack.  Somehow this needs 5 GB on the C: Drive (something the server I am using does not have free)  the D: drive has over 80GB of free space, but no the C: drive is the only option.  What the hell needs 5GB?  I could install an entire operating system with full build chain in less.

So I uninstall Visual C++ and reload the service pack.

Now I am theoretically back where I started (only have lost some time).

The build machine has two build servers on it: tfs and cruise control.Net. (one is for integration testing, the other for deployment builds)

Cruise Control.Net can build our entire source tree.  tfs fails on the Silverlight application.

After some more searching I find that the uninstall of VS.Net 2008 SP1 also silently uninstalled the Silverlight VS.NET service pack.

As the build machine does not have direct internet access (don’t ask – the security department is clearly insane).

So I download the service pack installer (70MB) on another machine and then copied to the deployment box.

At this point I find that the installer itself needs to download more.

This means a call to our server team to get the internet access added to the locked down box.

Finally I get the items installed.

I have several complaints:

Why is it so hard to add a product to visual studio?

Why is there no failure log in the installer?

Why must the installer use the C: drive?

Why can’t you download everything that you need to install in one hit?

Validation of viewstate MAC failed

 

I got the following error on an ASP.Net application:

Validation of viewstate MAC failed. If this application is hosted by a Web Farm or cluster, ensure that <machineKey> configuration specifies the same validationKey and validation algorithm. AutoGenerate cannot be used in a cluster.

 

However
does not solve the problem.
The fix can be simpler:
Add PostBackUrl=’#’ to the control causing the postback.

Mercurial

Here is the Wikipedia article on Mercurial.

Mercurial/Hg is a great little distributed version control system.

The beauty of this is that there is no central server to get in the way.

A number of times I have worked in places where the central version control server is down (for hours or days) which caused a significant amount of disruption to the development process.

Useful links on T4

Here are a series of useful links for the T4 code generation tool.

http://msdn2.microsoft.com/en-us/library/microsoft.visualstudio.texttemplating.texttransformation.texttransformation.aspx

http://www.olegsych.com/2008/02/t4-text-blocks/

http://msdn2.microsoft.com/en-us/library/microsoft.visualstudio.texttemplating.texttransformation.write.aspx

http://msdn2.microsoft.com/en-us/library/microsoft.visualstudio.texttemplating.texttransformation.writeline.aspx

http://msdn2.microsoft.com/en-us/library/microsoft.visualstudio.texttemplating.texttransformation.generationenvironment.aspx

http://www.olegsych.com/2008/03/how-to-generate-multiple-outputs-from-single-t4-template/

T4 is useful yet rather limited.  You can normally only generate one file per template.

Custom Tool to build protobuff files in Visual Studio

Here is the source to a visual studio addin for compiling protobuf files directly to cs source.
This is a great starting point for using an external compiler as a code generator that fits directly into visual studio.
This allows the definition of a Custom Tool that creates a code behind file that is automatically regenerated every time the top level item is saved.
It does require references to some items in the VS.NET SDK.
The error handling is fairly simplistic: Errors are embedded in place of the generated code.
The resulting dll needs to be registered for COM.  The registration code is as minimal as possible – even having the attribute use a constant.
using System;
using System.Globalization;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
using System.Text.RegularExpressions;
using Microsoft.VisualStudio.TextTemplating.VSHost;
using Microsoft.Win32;
using System.Diagnostics;
namespace ProtogenGenerator
{
[Guid(CustomGuidString)]
[ComVisible(true)]
public class MinimalTool : BaseCodeGeneratorWithSite
{
//This allows this to be defined once – the com registration routines need to match the declared guid
#warning Replace this with your own guid…
public const string CustomGuidString = “99C3D237-70D3-498c-BD54-CD108CC5E82A”;
private static Guid CustomToolGuid = new Guid( “{” + CustomGuidString + “}”);
private const string CustomToolName = “ProtogenGenerator”; // Need to update this…
private const string CustomToolDescription = “Generates ProtoGen”; // Need to update this…
private const string SourceFileExtension = “.proto”;
StringBuilder _errorBuffer = new StringBuilder();
StringBuilder _outputBuffer = new StringBuilder();
protected override byte[] GenerateCode(string inputFileName, string inputFileContent)
{
string rootName = Path.GetFileNameWithoutExtension(inputFileName);
string inputPath = Path.GetDirectoryName(inputFileName);
string toolFolder = Path.GetDirectoryName(typeof(MinimalTool).Assembly.Location);
ProcessStartInfo psi = new ProcessStartInfo( toolFolder + @”protoc.exe”);
psi.CreateNoWindow = true;
psi.WorkingDirectory = toolFolder;
psi.WindowStyle = ProcessWindowStyle.Hidden;
psi.RedirectStandardError = true;
psi.UseShellExecute = false;
StringBuilder arguments = new StringBuilder();
arguments.AppendFormat(@” –proto_path=””{0};{1}”””, toolFolder, inputPath);
arguments.AppendFormat(@” –descriptor_set_out=””{0}{1}.pb”””, toolFolder, rootName);
arguments.AppendFormat(@” “”{0}googleprotobuf{1}”””, toolFolder, “descriptor.proto”);
arguments.AppendFormat(@” “”{0}{1}”””, toolFolder, “csharp_options.proto”);
arguments.AppendFormat(@” “”{0}”””, inputFileName);
psi.Arguments = arguments.ToString();
int result;
using (Process proc = Process.Start(psi))
{
proc.EnableRaisingEvents = true;
proc.ErrorDataReceived += ProcErrorDataReceived;
proc.BeginErrorReadLine();
proc.WaitForExit();
result = proc.ExitCode;
proc.ErrorDataReceived -= ProcErrorDataReceived;
}
if (result != 0)
{
StringBuilder results = new StringBuilder();
results.Append(“There was a problem with the protoc compiler.”);
results.AppendLine();
results.Append(psi.FileName);
results.AppendLine();
results.Append(arguments.ToString());
results.AppendLine();
results.Append(_errorBuffer.ToString());
return Encoding.ASCII.GetBytes(results.ToString());
}
// Reset the string builder
_errorBuffer = new StringBuilder();
// We now have our “prebuild selector”
psi = new ProcessStartInfo(toolFolder + @”protogen.exe”);
psi.CreateNoWindow = true;
psi.WorkingDirectory = toolFolder;
psi.WindowStyle = ProcessWindowStyle.Hidden;
psi.RedirectStandardError = true;
psi.RedirectStandardOutput = true;
psi.UseShellExecute = false;
arguments = new StringBuilder();
string pbFileName = string.Format(@”{0}{1}.pb”, toolFolder, rootName);

 

arguments.AppendFormat(” ” + pbFileName);
psi.Arguments = arguments.ToString();
using (Process proc = Process.Start(psi))
{
proc.EnableRaisingEvents = true;
proc.ErrorDataReceived += ProcErrorDataReceived;
proc.OutputDataReceived += ProcOutputDataReceived;
proc.BeginOutputReadLine();
proc.BeginErrorReadLine();
proc.WaitForExit();
proc.ErrorDataReceived -= ProcErrorDataReceived;
proc.OutputDataReceived -= ProcOutputDataReceived;
result = proc.ExitCode;
}
if (result != 0)
{
StringBuilder results = new StringBuilder();
results.AppendFormat(“There was a problem with the protogen compiler {0}”, result);
results.AppendLine();
results.Append(psi.FileName);
results.AppendLine();
results.Append(arguments.ToString());
results.AppendLine();
results.AppendLine(“Error:”);
results.Append(_errorBuffer.ToString());
results.AppendLine();
results.AppendLine(“Output:”);
results.Append(_outputBuffer.ToString());
return Encoding.ASCII.GetBytes(results.ToString());
}
// I now need to find the name of the generated file
string sourceContent = File.ReadAllText(inputFileName);
string outputNamePrefix = null;
// I need to know the name of the generated cs file.
// Now I know the rules:
// 1. It can be specified in option
//     (google.protobuf.csharp_file_options).umbrella_classname = “PositionBuffer”;
// 2. If not specified there then:
//    take the file name, remove the extension
//    Convert to PascalCase, removing punctuation. Numbers and punctuation trigger new words.

Regex re = new Regex(“umbrella_classname[ ]*=[ ]*”(.*)”[ ]*;”);

if (re.IsMatch(sourceContent))
{
// We have pulled the umbrella_classname definition from the source.
outputNamePrefix = re.Match(sourceContent).Groups[1].Value;
}
else
{
outputNamePrefix = UnderscoresToPascalOrCamelCase(rootName, true);
}

if (outputNamePrefix == null)
{
return Encoding.ASCII.GetBytes(“Unable to determine the umbrella_classname”);
}
string outputFileName = toolFolder + “\” + outputNamePrefix + “Description.cs”;
if (!File.Exists(outputFileName))
{
// Try again
outputFileName = toolFolder + “\” + outputNamePrefix + “.cs”;
if (!File.Exists(outputFileName))
{
return Encoding.ASCII.GetBytes(“Unable to find file ” + outputFileName);
}
}
Byte[] resultBuffer = File.ReadAllBytes(outputFileName);
// This is the tidy up – only do this when the compile works.
File.Delete(pbFileName);
File.Delete(outputFileName);
return resultBuffer;
}
// This was taken from the FileDescriptor
private static string UnderscoresToPascalOrCamelCase(string input, bool pascal)
{
StringBuilder result = new StringBuilder();
bool capitaliseNext = pascal;
for (int i = 0; i < input.Length; i++)
{
char c = input[i];
if (‘a’ <= c && c <= ‘z’)
{
if (capitaliseNext)
{
result.Append(char.ToUpper(c, CultureInfo.InvariantCulture));
}
else
{
result.Append(c);
}
capitaliseNext = false;
}
else if (‘A’ <= c && c <= ‘Z’)
{
if (i == 0 && !pascal)
{
// Force first letter to lower-case unless explicitly told to capitalize it.
result.Append(char.ToLower(c, CultureInfo.InvariantCulture));
}
else
{
// Capital letters after the first are left as-is.
result.Append(c);
}
capitaliseNext = false;
}
else if (‘0’ <= c && c <= ‘9’)
{
result.Append(c);
capitaliseNext = true;
}
else
{
capitaliseNext = true;
}
}
return result.ToString();
}
void ProcOutputDataReceived(object sender, DataReceivedEventArgs e)
{
_outputBuffer.AppendLine(e.Data);
}
void ProcErrorDataReceived(object sender, DataReceivedEventArgs e)
{
_errorBuffer.AppendLine(e.Data);
}
public override string GetDefaultExtension()
{
return “.cs”;
}
#region Registration
private static Guid CSharpCategory =
new Guid(“{FAE04EC1-301F-11D3-BF4B-00C04F79EFBC}”);
private const string KeyFormat
= @”SOFTWAREMicrosoftVisualStudio{0}Generators{1}{2}”;
protected static void Register(Version vsVersion, Guid categoryGuid)
{
string subKey = String.Format(KeyFormat,
vsVersion, categoryGuid.ToString(“B”), CustomToolName);
using (RegistryKey key = Registry.LocalMachine.CreateSubKey(subKey))
{
key.SetValue(“”, CustomToolDescription);
key.SetValue(“CLSID”, CustomToolGuid.ToString(“B”));
key.SetValue(“GeneratesDesignTimeSource”, 1);
}
subKey = String.Format(KeyFormat,
vsVersion, categoryGuid.ToString(“B”), SourceFileExtension);
//This automates the association of the custom key with this tool.
using (RegistryKey key = Registry.LocalMachine.CreateSubKey(subKey))
{
key.SetValue(“”, CustomToolName);
}
}
protected static void Unregister(Version vsVersion, Guid categoryGuid)
{
string subKey = String.Format(KeyFormat,
vsVersion, categoryGuid.ToString(“B”), CustomToolName);
Registry.LocalMachine.DeleteSubKey(subKey, false);
subKey = String.Format(KeyFormat,
vsVersion, categoryGuid.ToString(“B”), SourceFileExtension);
Registry.LocalMachine.DeleteSubKey(subKey, false);
}
[ComRegisterFunction]
public static void RegisterClass(Type t)
{
// Register for both VS.NET 2002, 2003, 2008 and 2010  (C#)
Register(new Version(8, 0), CSharpCategory);
Register(new Version(9, 0), CSharpCategory);
Register(new Version(10, 0), CSharpCategory);
}
[ComUnregisterFunction]
public static void UnregisterClass(Type t)
{ // Unregister for both VS.NET 2002, 2003, 2008 and 2010 (C#)
Unregister(new Version(8, 0), CSharpCategory);
Unregister(new Version(9, 0), CSharpCategory);
Unregister(new Version(10, 0), CSharpCategory);
}
#endregion
}
}