In MSBuild 4 we introduced several performance improvements, particular for large interdependent builds. By and large they are automatic and you receive their benefit without making any changes to the way your build process in authored. However, there are still some cases where we are unable to make the best decision. One such case is when there is a particular external tool which is invoked as part of the build but which takes a significant amount of time. An example of such a tool would be cl.exe, the C++ compiler. This article discusses how to use the new yield mechanism for external tools to improve the performance of your builds.
There are a few ways MSBuild can be made to execute external, command-line tools:
- Write a task which derives from ToolTask.
- Use the Exec task to call your command.
- Use the XamlTaskFactory.
All of these methods ultimately use the ToolTask class in Microsoft.Build.Utilities.v4.0.dll to handle executing a command-line task and deal with the output in the MSBuild way. Like all tasks, however, they block any other work from happening in MSBuild while they are executing. In cases where the task is very short, such as touching a log file or copying a file from one place to another this is perfectly acceptable. But in the original example of invoking the C++ compiler, the amount of time MSBuild itself sits idle can be lengthy and in some cases it may be a significant impediment to good parallelization of your build.
The problem has to do with the way MSBuild utilizes its worker nodes. Whenever a project is scheduled to be built, it is assigned to one of the worker nodes. This node will then execute that project from start to finish, and will not accept more work until the project is either finished or the project makes an MSBuild call (for instance to satisfy a project-to-project reference.) This is in large part because a node can only execute one task at a time, as tasks must be guaranteed their environment and current directory will not be modified during execution.
However, command-line tools do not execute in-process, and therefore their environment cannot be polluted by the running of additional tasks in parallel on the same node. We can take advantage of this behavior to let the MSBuild node execute tasks in other projects while our long-running tool completes its work. This is done using the YieldDuringToolExecution parameter.