A while ago i blogged that i was using the TemplateFile task from the MSBuild Community Tasks Project to generate configuration files. Each project that required templating would have modified it’s csproj file as following:
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets. -->
<Import Project="$(MSBuildProjectDirectory)\config.msbuild" />
<Target Name="BeforeBuild">
<CallTarget Targets="GenerateConfigurationFiles" />
</Target>
And each of these config.msbuild files looked as following:
<TemplateFile Template="web.template.config" OutputFileName="web.config" Tokens="@(TemplateTokens)" />
<TemplateFile Template="Config\WcfClients.config" OutputFileName="Config\WcfClients.config" Tokens="@(TemplateTokens)" />
</Target>
As you can notice the convention here is that each template file has ‘.template.’ in it’s name, and the name of an output file is the template file name without ‘.template.’.
<!-- valide input -->
<Error Condition="'$(SourceFile)'==''" Text="Missing SourceFile" />
<!-- calculate destination file -->
<RegexReplace Input="$(SourceFile)" Expression="(\.template)\." Replacement="." Count="1">
<Output TaskParameter="Output" PropertyName="DestinationFile" />
</RegexReplace>
<!-- generate file -->
<TemplateFile Template="$(SourceFile)" OutputFileName="$(DestinationFile)" Tokens="@(TemplateTokens)" />
</Target>
Now that we can do it for one file, we can do it for many files too:
<!-- valide input -->
<Error Condition="'$(SourceDir)'==''" Text="Missing SourceDir" />
<!-- find all template files -->
<ItemGroup>
<TemplateFiles Include="$(SourceDir)\**\*.template.*" Exlude="$(SourceDir)\**\*.svn*" />
</ItemGroup>
<!-- process each template file -->
<MSBuild Projects="$(MSBuildProjectFile)" Targets="ProcessTemplate" Properties="SourceFile=%(TemplateFiles.FullPath)" />
</Target>
After these core improvements we wrote a common.proj.targets file as following:
<!-- import global variables -->
<Import Project="$(MSBuildThisFileDirectory)\configuration.proj" />
<PropertyGroup>
<BuildDependsOn>CommonBeforeBuild;$(BuildDependsOn);CommonAfterBuild</BuildDependsOn>
</PropertyGroup>
<Target Name="CommonBeforeBuild">
<MSBuild Projects="$(CommonTargetsPath)" Targets="ProcessTemplates" Properties="SourceDir=$(MSBuildProjectDirectory)" />
</Target>
<Target Name="CommonAfterBuild">
<!--<MSBuild Projects="$(CommonBuildTargetsPath)" Targets="PEVerify" Properties="SourceFile=$(TargetPath)" />-->
</Target>
</Project>
Now we only need to import our common.proj.targets file in projects that have template files and focus on real business problems