TFS Custom Build Activities and Friends

Well, it should not be that strange. It is pretty common, that you need custom activity to perform your build. However sometimes you run into strange situations, where you might not find any help on the net… So I tryed to collect my experiences about this here.

(There are also many articles about this topic… I don’t want to repeat them – you can find links for some of them at the bottom.)

Project structure

Activities and Controls

Usually it is a good practice to keep the things together, and put your classes (activities, controls, argument classes) into a single assembly. Obviously in some cases it is not possible, or not preferrable – but in many cases makes your (and the config manager’s ) life easier. It is also elminates the issue of not loading an assembly when it is necessary.

However it is also a good practice to separate classes based on their function and level – so you can do it with namespaces. I’m using the below namespaces currently in order to separate my classes:

  • MyProject
  • MyProject.Controls
  • MyProject.Activities
  • MyProject.ViewModels

An exception is the assembly with the unit tests – obviously it is separated into another DLL, as it won’t get deployed to the productive TFS server.

Adding the Build Template (XAML)

Create a separate project (simple windows library) for the XAML files. (If it is in the same project as your activities, then VS may forgot to include the assembly explicitly when you import the namespace to the xaml. However don’t forget to add the required TFS and project references to the workflow project.

You can set the Build Action property of the xaml file to XamlAppDef – then VS will try to interpret it during the build, and may drive your attention to some error during the local build.

How the activity should look like

I will write details about the build activity types and their usages later on.

Testing and Debugging

Yes. The build runs on the Build Controller/Build Agent and not on the developer’s PC. To set up server environment on the dev PC might be too complex, sometimes maybe impossible, and testing on the server directly is not the most convenient way – but don’t worry, there are other possibilities also.

Unit Tests

Unit Tests are supported in most VS editions (if not in all). It runs locally, and can be debugged pretty easy. This can be automated also, so you can reuse your unit test to ensure that your code is always working. So let’s call your custom activity from a unit test, and mock the surrounding environment (or create a test environment). You can use the WorkflowInvoker for this.

        using Microsoft.VisualStudio.TestTools.UnitTesting;
        // ...

        [TestMethod]
        public void MyActivitytest()
        {
            var testObject = new object();
            var activityInputArguments = new Dictionary<stringobject>
                                {
                                    {"Argument1""Value1"},
                                    {"Argument2", testObject }
                                };
            var activityOutputArguments = WorkflowInvoker.Invoke(new MyCustomActivity(), activityInputArguments);

            var res = activityOutputArguments["Result"as MyResultClass;

            Assert.IsNotNull(res, "Activity returned with null result.");
            //...
        }

Remote Debugging

Run a remote debugger (msvsmon.exe) on the Build Controller/Build Agent, and attach your Visual Studio to the remote TFS service process: TFSBuildServiceHost.exe. Then you can debug your code directly. Howevere unfortunatelly it does not always work, and may not stop at your breakpoint. (Any further suggestion, experience is welcomed.)

Old Times Are Back – Debug messages

When there is no other way you can use the old-style – add debug and trace output to your workflow/activity.

using Microsoft.TeamFoundation.Build.Workflow.Activities;

public class MyCustomActivity : CodeActivity
{
    protected override void Execute(CodeActivityContext context)
    {
        // ...
        context.TrackBuildMessage("My build message");
        // ...
    }
}

  

Signing Custom Assemblies

When you sign your custom assembly (and check in to the custom assemblies folder…), you might receive the below error message:

TF215097: An error occurred while initializing a build for build definition \MyProject\MyBuild: Cannot create unknown type ‘{clr-namespace:MyProject.Activities;assembly=MyProject}MyTestActivity’.

Well. fancy message, and there is no other trace usually to pick up the root cause. If you google the error code (TF215097), you will find plenty of pages, where several scenarios are described with the same error code. A reason behind this in this case, that the assembly is not linked properly from your build template xaml. If you have a strong-named assembly, then you need to specify the assembly properly, including version number and the public key token.

The proper syntax in the xaml file is below:

<Activity  ...
  xmlns:mpa="clr-namespace:MyProject.Activities;assembly=MyProject, Version=1.0.0.0Culture=neutralPublicKeyToken=123456789abcdef0"
  ... >

After doing this, the build should work.

How custom DLLs are loaded

Hehh.. Maybe not as you’d expect… 🙂 Assemblies are seached in the below order:

  • Specified CustomAssemby Path (on Build controller)
  • Visual Studio’s PrivateAssemblies folder
  • Global Assembly Cache

When you run the build, TSF loads all the assemblies from the CustomAssemblies folder, that contains activity in the specific HostEnvironment. Other assemblies from the CustomAssemblies folder might be not loaded at all!

Assemblies from PrivateAssemblies folder and GAC can be loaded on-demand.

Best practice: custom build assemblies should be kept in CustomAssemblies folder, and not in e.g. GAC. Otherwise the maintenance of these assemblies will be more complex (e.g. you have to deploy your DLLs to each build controllers/agents separately, although when it is kept in CustomAssemblies folder, they will be updated automatically for all build servers).

Troubleshooting

Sometimes you may see, that locally everything is fine, however the build server does not work as expected. In this case this checklist can help to find, what has been dropped out of the “deployment workflow”:

  • Always ensure that your workflow (xaml) and custom DLLs are checked in
  • Ensure that the custom assembly folder has been set up properly on the build controller
  • Ensure that assemblies are referenced properly in your xaml (with version and public key token for signed assemblies)
  • Ensure that no older copy exists in the GAC and/or in VS PrivateAssemblies folder

Also you may experience, that sometimes an assembly is just cannot be loaded from the CustomAssemblies folder, although it is referenced properly from xaml. In such case defining a dummy activity in the assembly with attribute [BuildActivity(HostEnvironmentOption.All)] may solve the problem.

References

Here comes some pretty good articles about the topic.

Advertisements
About

Mainly a developer from the .Net and also the PHP world. Many others staffz are attached also.

Tagged with: , , , , , ,
Posted in TFS, TFS 2010, TFS Build

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: