Friday, May 01, 2009

MSBuild UsingTask gotchas

One significant drawback of MSBuild UsingTask element is that you must specify exactly the task name you are importing. That is if the assembly you are importing contains 200 tasks, you will have to import them explicitly one by one. And since you probably do not want to do that in every project you author, usually these 200 tasks will be defined in separate project file that can be imported whenever the tasks are needed.

While there is no workaround for specifying the task name, there is another, somewhat easier way to make sure that the tasks are available to your projects without explicitly importing tasks project file.

Let’s suppose that you have created MSBuild project file that contains UsingTask statements for all custom tasks you want to have available in your projects. Then if you rename this project file to have .tasks extension and place it in .NET framework folder (e.g. C:\WINDOWS\Microsoft.NET\Framework\v3.5 folder for .NET 3.5), the tasks defined there will be available in any project using that version of MSBuild without explicit import statement.

This is the mechanism used to make tasks shipped with MSBuild by default available to all projects (look into Microsoft.Common.tasks file to see these tasks defined there). No magick required!

By the way, looking into Microsoft.Common.tasks file imparts two additional pieces of wisdom (to quote):

NOTE: Listing a <UsingTask> tag in a *.tasks file like this one rather than in a project or targets file can give a significant performance advantage in a large build, because every time a <UsingTask> tag is encountered, it will cause the task to be rediscovered next time the task is used.

Another useful comment relates to the way the tasks are defined in UsingTask – you can either specify fully-qualified task name (including namespaces) or a short one; however, (again, quote from Microsoft.Common.tasks file):

NOTE: Using the fully qualified class name in a <UsingTask> tag is faster than using a partially qualified name.

In addition to performance win, you will also be able to disambiguate the task used. For example, both SDC tasks and MSBuild Community tasks packages define a bunch of tasks that differ only by name. In such cases you will have to be explicit both in UsingTask statement and when using the imported task:

<!-- Import SDC Sleep task -->
<UsingTask AssemblyFile="Microsoft.Sdc.Tasks.dll" 
          TaskName="Microsoft.Sdc.Tasks.Sleep"/>
<!-- Import MSBuild Community Sleep task -->
<UsingTask AssemblyFile="MSBuild.Community.Tasks.dll" 
          TaskName="MSBuild.Community.Tasks.Sleep" />
<!-- Use SDC Sleep task, full name to disambiguate -->
<Target Name="Sleep">
  <Microsoft.Sdc.Tasks.Sleep SleepTimeout="1"/>
</Target>

Mirror from MSDN blog

1 comment:

Anonymous said...

The collision issue can also occur with the "Delete" task defined in multiple places in SDC (related to BizTalk).

The IncrementalClean and CoreClean targets in Microsoft.Common.targets invoke the "Delete" task in a non-qualified way. Once the SDC tasks are imported, it is one of these the MSBuild stumbles upon first.

Our solution was to redeclare the ... <UsingTask TaskName="Delete" AssemblyName="Microsoft.Build.Tasks.v3.5, Version=3.5.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"/>
... after the SDC <Import/> task.

Ignoring the performance issues clarified in this blog entry, it seems to work well.

Cheers!

Sam.