Since BizTalk Server 2009, BizTalk Server project files are MSBuild .btproj XML files.
For some reason, BizTalk projects need to be compiled in two phases.
The first pass compiles schemas, maps and pipelines, and produces an intermediate managed assembly. The second pass deletes the intermediate assembly, compiles the orchestrations and produces the final output assembly.
This behavior is hardcoded in the $(MSBuildExtensionsPath)\Microsoft\BizTalk\BizTalkC.targets file.
Unfortunately, this breaks incremental compilation of large solutions that make use of BizTalk Server projects because it forces a recompile each time a project is being built. Fortunately, there is a solution.
MSBuild being extensible, it is possible to override the default (incorrect) targets and fix this. First, create an appropriate file, say BizTalkCustom.targets to host your customizations, in any location of your choice :
<!-- Rerun the build process (second pass) if the project hosts orchestrations --> <!-- The extra << and @(CSharpOutputFromXLang)!='' >> condition prevents this task --> <!-- from taking place if the project does not contain any orchestrations. --> <Target Name="SecondPass" Condition="$(SecondBuild)!=true and $(TempAssemblyOnly)!=true and @(XLang)!=''"> <MSBuild Projects="$(MSBuildProjectFile)" Properties="SecondBuild=true" /> </Target> <!-- Compile XLang/s orchestration --> <!-- Notice that is the MSBuild system has decided to run this Target, --> <!-- both intermediately compiled files and intermediately compiled assembly are deleted. --> <Target Name="CompileODX" Condition="$(SecondBuild)==true" Inputs="@(XLang);$(MSBuildAllProjects);$(ClrTypesAssembly)" Outputs="$(BuildDone)"> <!-- Delete previously generated C# files from XLang compilation --> <Delete Files="@(IntermediateAssembly)" /> <Delete Files="@(CSharpOutputFromXLang)" /> <XLangTask XLangItems="@(XLang)" ProjectReferences="@(ReferencePath)" WarningLevel="$(WarningLevel)" BpelCompliance="$(BpelCompliance)" DefineConstants="$(DefineConstants)" TreatWarningsAsErrors="$(TreatWarningsAsErrors)" TempAssembly="$(ClrTypesAssembly)" OutputDirectory="$(XLangOutputPath)"> </XLangTask> </Target>
The first part of the solution is to modify the <SecondPass> Target in order to add an extra test in the <Condition> attribute. This prevents the SecondPass from even taking place in case your .btproj does not host orchestrations.
The second part of the solution is to defer the removal of the intermediate managed assembly to the <CompileODX> target. Indeed, this target correctly handles incremental compilation, so it does not run if all files are already up-to-date.
In order to use the overriden targets in your compilations, you need to slightly modify your .btproj project files, and import the preceding .targets file, like so:
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" /> <Import Project="$(MSBuildExtensionsPath)\Microsoft\BizTalk\BizTalkC.targets" /> <Import Condition=" Exists('$(CustomExtensionsPath)BizTalkCustom.targets') " Project="$(CustomExtensionsPath)BizTalkCustom.targets" />
Where $(CustomExtensionsPath) represents the path to your custom extensions project files.