The build process could not be deserialized due to…

Background

At my current customer we’ve upgraded all our builds from TFS 2008 MSBuild tasks to TFS 2010 WorkFlow Activities. I must say that we’ve gone from several common target-files and per-build specific rsp-files to ONE build process template! That says enough about the power of the new build process in TFS 2010.

Following Microsoft’s recommendation here we’re using one Build Controller for our Default Collection and we’ve centralized our custom activities to one Team Project; lets call it Central.

In the Team Project Central the custom activities we’re using a stored at $/Central/BuildComponents and our Build Controller points out this location as the picture shows below:

image

In our Custom Activities we have some simple serializble objects that we use as part of our process to store some settings that we need in our build process. An of that would be an XCopyFileWrapper that we use to store 3 string properties:

image

We then use this wrapper in our Meta Data property in our DefaultTemplate.xaml file like the picture shown below:

image

Then we added the ParamWrapper as an array of XCopyFileWrapper and add it as an argument to DefaultTemplate.xaml like so:

image

This way we can have the same DefaultTemplate XCopy files depending on the input we add for each unique build definition in this rather nice centralized GUI for your build definitions:

image

image

And then in our DefaultTemplate.xaml we have an foreach loop that loops over our array of XCopyFileWrappers and invokes XCopy! This makes our build definitions really sweet, dynamic and expandable right!

The challenge

So the next Team Project comes along we can call it Project B and they want to use our Build Process. So they branch our build process template into their project and they create a new build definition and then KAPOW!

image

What happen here! Well my initial thoughts was another lesson I learned previously and blogged about here but specifying the assembly in the DefaultTepmplate.xaml didn’t solve this issue.

The solution

The very simple solution to this problem is that you need to give users READ permissions to your BuildComponents folder in our case the $/Central/BuildComponents folder (another way is to solve this is to give other users READ permissions to the Central Team Project).

That’s it!

Reflection

  • So if I want to centralize my custom build components then I have to give all users that will use these custom build components READ permission to the folder where the components are stored.
  • Or you could have some sort of deployment mechanism that deploys the build components to every Team Project in the Team Project Collection but then you’ll have to have one Build Controller per Team Project.

Cheers,

Hugo

Stuck on “Cannot create unknown type {clr-namespace:” in TFS Build?

Background

A couple of days ago I was upgrading some of my clients MSBuild tasks to WorkFlow Activities following the “Rewrite MSBuild task as a Workflow Activity” option in the guidelines from this excellent post by William Bartholomew.

I stumbled upon a custom Assembly Versioning MSBuild task and I took a moment of thought to see what I could do to improve this MSBuild task. I read this great post by Mike Fourie’s and yet another great post by John Robbins that I really encourage anyone doing custom versioning in TFS 2010 to read.

I decided to follow some of the posts recommendations and decided that for the purposes of my client I would not need to convert this (former) MSBuild task into a coded CodeActivity because a designed activity would be more then enough.

Anyway a simplified version of the clients workflow activity looked something like this:
image

With some input arguments that looked like this:

image

Everything is pretty straightforward if you’re custom to TFS Build and Windows Workflow Foundation except for the Activites in the Then/Else sections. In the Then/Else sections I’m using the AssemblyInfo Activity from Community TFS Build Extension here.

Challenge

So I check-in my custom assembly and make sure that the controller is pointing to the folder in the source control tree that contains my custom assemblies.
image

I then added my new Activity to the build process template and ran my first build against that build process template…BOOOOOM! Got this message: TF215097: An error occurred while initializing a build for build definition \Tailspin Toys\HugoHaggmark: Cannot create unknown type ‘{clr-namespace:HugoHaggmark.TfsBuild.Activities}Example’.

image

Solution

Are you in the same situation as me? Don’t despair a simple solution is close. Ler

  1. Firstly open up build process template as XML.
  2. Find the reference to your Custom Activity in the XML (probably in the top section of the XML), looks something like this:
    xmlns:local=”clr-namespace:<YourNameSpace>” or in my case I had this:
    xmlns:local=”clr-namespace:HugoHaggmark.TfsBuild.Activities”.
  3. Add ;assembly=<Name of your assembly> to that reference so in my case the end result looked like this:
    xmlns:local=”clr-namespace:HugoHaggmark.TfsBuild.Activities;assembly=HugoHaggmark.TfsBuild.Activities”
  4. Create a partial class right next to your Custom Activity and add the BuildActivity attribute like shown below:
    image
  5. Check In, rebuild and redeploy assemblies to the custom assemblies folder.
  6. Run the build and voila! The build error is gone!

Cheers,

Hugo

Build recipe for running unit tests using file masks in TFS Build 2008

Background

The background for this build script recipe can be found here and here. Instructions on how to install and configure a build server to run these build scripts can be found here and here.

The Challenge

The build recipe has to conform to these constraints/requirements:

  • No test lists
  • No maintenance of testsettings/testrunconfig files
  • Using file mask .Tests to identify which assemblies that contain unit tests
  • TFS 2008
  • Visual Studio 2010
  • BizTalk 2010
  • MSTest

The Dev.CI.testsettings file

As you might already know the testsettings file (formely know as testrunconfig) defines important configuration that tells MSTest various information; where to look for deployment items for instance.

I create one testsettings file per build that I’ve safely in source control and in that file I add this:

<Deployment>
  <DeploymentItem filename=\\bldserv\Bld\1\TestResults\DeplymentItems\/>
</Deployment>

These lines tells MSTest to look for deployment items in the specified directory/share. This works for now as we only have one build server that executes the CI-builds but when we add build servers later on we’ll have to come up with a better solution using environment variables for instance.

It would be great if TFS Build could automatically pick up the testsettings file for each solution in the build but as we only do ONE testrun per TFS Build that wouldn’t work either. The best would be if we ran MSTest / solution in the TFS Build.

The TFSBuild.proj file

Under the ItemGroup for testing I add the following line to tell MSTest to look for testcontainers with the “.Tests.dll” filemask.

<TestContainer Include="$(OutDir)\%2a.Tests.dll" />

The next step is to introduce a TFS Build override where we specify our testsettings file and do the copying to our deploymentitems folder specified by our testsettings file. There is also delete statement that I was added later because I found an assembly with the correct file mask but that wasn’t a unit test assembly, read more about that adventure here.

  <Target Name="BeforeTestConfiguration">

    <!–We need to set the path to our xml-files and schema-

    files for MSTest to include this can only be done from one

    single testsettings-file.–>

    <PropertyGroup>

      <RunConfigFile>$(SolutionRoot)Dev.CI.testsettings</RunConfigFile>

    </PropertyGroup>

   

    <!–Copy all testinstance-files.–>

    <CreateItem Include="$(SolutionRoot)\**\TestInstances\*">

      <Output TaskParameter ="Include" ItemName ="MyTestFiles"/>

    </CreateItem>

    <Copy SourceFiles="@(MyTestFiles)" 

          DestinationFolder="$(TestResultsRoot)\DeplymentItems"/>

    <!–Copy all schema-files.–>

    <CreateItem Include="$(SolutionRoot)\**\*.xsd">

      <Output TaskParameter ="Include" ItemName ="MySchemaFiles"/>

    </CreateItem>

    <Copy SourceFiles="@(MySchemaFiles)" 

          DestinationFolder="$(TestResultsRoot)\DeplymentItems"/>

    <!–Delete all non unittest-files.–>

    <CreateItem 

      Include="$(BinariesRoot)\**\[Customer].[BtsApp].OrchestrationsHelper.Tests.dll">

      <Output TaskParameter ="Include" ItemName ="MyNonUnitTestFiles"/>

    </CreateItem>

    <Delete Files="@(MyNonUnitTestFiles)"/>

  </Target>

The drawback to this approach is that your testinstance/schema files (often xml-files in BizTalk projects) need to be uniquely named or you would overwrite them in this step.

Lessons learned

  • You should start to think about automated build processes from day one in a dev project.
  • We need to find a way to not use hardcoded paths, maybe we could use environment variables.
  • Maybe we should look into other testing frameworks like XUnit, NUnit and see if they offer better support for this kind of situation

Please let me know what you think of this solution considering the constraints and requirements at hand. The examples for this post can be found here.

Cheers!

Hugo