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<string, object> { {"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.
- On activity level you can simply use the out-of-the box WriteBuildMessage, WriteBuildWarning, WriteBuildError or WriteBuildInformation<> activities, which will write the output to the build log (according to log verbosity).
- On code level you can call the context.TrackBuildMessage(), TrackBuildWarning() and TrackBiuldError() methods:
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.0, Culture=neutral, PublicKeyToken=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.
- http://www.ewaldhofman.nl/post/2010/04/20/Customize-Team-Build-2010-e28093-Part-1-Introduction.aspx
- http://msmvps.com/blogs/rfennell/archive/2010/03/08/lessons-learnt-building-a-custom-activity-to-run-typemock-isolator-in-vs2010-team-build.aspx
Leave a Reply