MsBuild finding directories if not given as parameter
I’m a lazy developer. Being lazy does not mean I avoid to work. It means that I like to reflect things I am doing and optimize and atomize stuff to get more time on the valuable tasks. Code generation is a tool I tend to use quite regularly and T4 is at most my generator of choice.
Generated files can cause a lot of merging conflicts. So they are not to be checked into my source control system (currently my choice is HG/Mercurial).
For the build server this means the files do not exist when the repository is freshly checked out – they need to be generated during the build right before the compile happens.
With code generation I usually tend to use a pattern that is for example also used by ASP.NET MVC Views/Controller generation: If there is a local directory containing code generation templates, use it. Otherwise utilize the system wide templates.
MsBuild version 4.0 comes with a feature called property functions. This allows to place for instance a “find directories” inside a property group.
I use them to:
- Allow to set a system wide template directory using the command line/the MsBuild API.
- If no directory is set use a local directory.
- If non of the above is applied set a fallback default.
Here is the XML snippet that shows how to use the pattern:
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Generate" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<RootFolder>..\src</RootFolder>
<CodeGeneratorDir Condition=" '$(CodeGeneratorDir)' == '' " />
<LocalCodeGeneratorDir> CodeGenerationTemplates </LocalCodeGeneratorDir>
<CodeGeneratorDirFallback> C:\CodeGenerationTemplates </CodeGeneratorDirFallback>
</PropertyGroup>
<ItemGroup>
<ModelFiles Include="..\src\**\*.xdml" />
<TemplateDirectory Include="$(CodeGeneratorDir);" />
<TemplateDirectory Condition=" '$(CodeGeneratorDir)' == '' " Include="$([System.IO.Directory]::GetDirectories( "$(RootFolder)", "$(LocalCodeGeneratorDir)", System.IO.SearchOption.AllDirectories))" />
<TemplateDirectory Condition=" '@(TemplateDirectory)' == '' " Include="$(CodeGeneratorDirFallback)" />
</ItemGroup>
<Target Name="Generate">
<Message Text="@(TemplateDirectory)" />
<Message Text="@(ModelFiles)" />
</Target>
</Project>