Tag Archives: MSBuild

Some tips for .Net developers using git on cygwin

Here are some tips that i want to share with fellow .Net developers that use git on cygwin.

First of all i defined some aliases in my ~/.bashrc:

# open explorer in the current working directory
alias explorer='explorer.exe "`cygpath -aw \"$PWD\"`"'

# invoke MSBuild
alias msbuild='/cygdrive/c/Windows/Microsoft.NET/Framework/v4.0.30319/MSBuild.exe'

Because i don’t like the TFS source control story i use git-tfs. As a .Net developer you want to add the following to your .git/info/exclude file:

#OS junk files
[Tt]humbs.db
*.DS_Store

#Visual Studio files
*.[Oo]bj
*.exe
*.pdb
*.user
*.aps
*.pch
*.vspscc
*.vssscc
*_i.c
*_p.c
*.ncb
*.suo
*.tlb
*.tlh
*.bak
*.[Cc]ache
*.ilk
*.log
*.lib
*.sbr
*.sdf
ipch/
obj/
[Bb]in
[Dd]ebug*/
[Rr]elease*/
Ankh.NoLoad

#Tooling
_ReSharper*/
*.resharper
[Tt]est[Rr]esult*

#Subversion files
.svn

Whenever i work online i usually run these two commands consecutively: git -a -m “commit message” and git-tfs checkin -m “commit message”. Here is a small ~/bin/commit script that combines these:

#!/bin/bash
git commit -a -m "$1";
git-tfs checkin -m "$1";

Invoke PowerShell script from MSBuild

Here is a small MSBuild target that allows you to invoke a PowerShell script, eg: powershell.exe & ‘script.ps1′ -SomeParam ‘x’

<Target Name="InvokePowerShell">
 <PropertyGroup>
  <PowerShellCommand>"$(PowerShellTool)" "&amp; '$(ScriptFile)' -SomeParam '$(SomeParam)' "</PowerShellCommand>
 </PropertyGroup>
 <Exec Command="$(PowerShellCommand)" />
</Target>

Build your solution with Visual Studio from MSBuild

Unfortunately MSBuild and BIDS Helper are not able to build an .asdatabase from our Analysis Services project (.dwproj). Here is a task which invokes Visual Studio to build such a solution:

<Target Name="DevEnvBuild">
 <Error Condition="'$(SolutionFile)'==''" Text="Missing SolutionFile" />
 <PropertyGroup>
  <DevEnvTool Condition="'$(DevEnvTool)'==''">C:\Program Files\Microsoft Visual Studio 9.0\Common7\IDE\devenv.exe</DevEnvTool>
  <DevEnvSwitch Condition="'$(DevEnvSwitch)'==''">Build</DevEnvSwitch>
  <DevEnvBuildCommand>"$(DevEnvTool)" "$(SolutionFile)" /$(DevEnvSwitch)</DevEnvBuildCommand>
 </PropertyGroup>
 <Exec Command="$(DevEnvBuildCommand)" />
</Target>

What i dislike about the Web.config Transformation in VS2010

There are a couple of things that i strongly dislike about the Web.config transformation in VS2010:

  • Only works with XML files (eg: Can’t be used to generate a release notes.txt file)
  • Does not seem to support externalized sections, eg: log4net.config in a separate file
  • No support to copy/paste transform files
  • Only works when Visual Studio 2010 is installed (And i am still not convinced a build server should have this).
  • Ties environment to build configuration

Lesson learned: Don’t trust your co-workers, always double-check!

  • Having multiple transformations is easy-peasy, just invoke the TransformXml task for all your config files and make sure your transformation files are correct. For log4net this would look like:
  • <log4net xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
     <root>
      <level value="ERROR" xdt:Transform="Replace"/>
     </root>
    </log4net>
    
  • The support for copy/paste can be achieved by removing the DependentUpon tag in your proj file (At the cost that you don’t have the + sign in solution explorer which ‘hides’ the transforms files)

Making the TemplateFileTask easier to use…

One of the disadvantages of the TemplateFile task (msbuildtasks) is the fact that it requires a lot of typing to define template values:

<ItemGroup Condition= " '$(ConfigurationEnvironment)'=='build' ">
 <Tokens Include="a">
  <ReplacementValue>localhost</ReplacementValue>
 </Tokens>
 <Tokens Include="b">
  <ReplacementValue><mynode/></ReplacementValue>
 </Tokens>
</ItemGroup>

Here is a format proposition to make this a lot more finger friendly:

<configuration>
 <variables env="build">
  <x name="a">localhost</x>
  <x name="b><mynode/></x>
 </variables>
</configuration>

Here is the msbuild script we need to achieve that:

<PropertyGroup>
 <ConfigurationFile>configuration.xml</ConfigurationFile>
 <ConfigurationEnvironment>build</ConfigurationEnvironment>
</PropertyGroup>

<!-- Retreive all template values for the specific environment -->
<XmlQuery XmlFileName="$(ConfigurationFile)" XPath = "//variables[@env='$(ConfigurationEnvironment)']/*">
 <Output TaskParameter="Values" ItemName="Values" />
</XmlQuery>

<!-- Construct @Tokens -->
<ItemGroup>
 <Tokens Include="%(Values.name)">
  <ReplacementValue>%(Values._innerxml)</ReplacementValue>
 </Tokens>
</ItemGroup>

<!-- Generate the configuration files -->
<Message Text="Available variables:" />
<Message Text="====================" />
<Message Text="%(Tokens.Identity): %(Tokens.ReplacementValue)" />

Happy coding!

Clean TemplateFile hack

A while ago i wrote about a Clever TemplateFile hack to use some xml block as ReplacementValue. Today i realized there is a clean way to achieve this by defining the value as CDATA:

<TemplateTokens Include="mex">
 <ReplacementValue>
  <![CDATA[<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />]]>
 </ReplacementValue>
</TemplateTokens>

Setting up a self-contained build

Here is something you may have experienced already: As a newcomer on an existing project, you check out the code from source-control and discover that the build is broken. When you ask around no-one else seems to have that problem but a helpful collegue is kind enough to tell you that you can find the installers for the missing dependencies at location X (Let’s not even mention the places where those installers are not available *sigh*).

Anway, in order to avoid such a situation you could organize your solution in such a way that all the dependencies (libraries and tools) are part of it. A typical folder structure would look like this:

screenshot of typical solution folder organization

In order to get those files out of the installer and in your solution (instead of installed under %Program Files%) you could do an administrative install of the msi (eg: msiexec /a Blah.msi) but i find it easier to use Qwerty.Msi.

Here are a couple of settings you may want to add to your build configuration in order to make your self-contained build work:

<!-- Configure solution directories -->
<basePath Condition="'$(BasePath)'==''">$(MSBuildThisFileDirectory)..</basePath>
<buildPath>$(BasePath)\build</buildPath>
<sourcePath>$(BasePath)\src</sourcePath>
<toolsPath>$(BasePath)\tools</toolsPath>

<!-- Configure tool directories -->
<!-- the ending \ is required for the extension pack -->
<extensionTasksPath>$(ToolsPath)\MSBuild.ExtensionPack\</extensionTasksPath>
<msbuildCommunityTasksPath>$(ToolsPath)\MSBuildCommunityTasks</msbuildCommunityTasksPath>
<ilmergeToolPath>$(ToolsPath)\ILMerge</ilmergeToolPath>
<svnToolPath>$(ToolsPath)\Subversion\bin</svnToolPath>
<!-- wix will use this property to determine the location of other files -->
<wixToolPath>$(ToolsPath)\Wix</wixToolPath>

<!-- Configure target file paths -->
<commonTargetsPath>$(BuildPath)\common.targets</commonTargetsPath>
<msbuildCommunityTasksTargetsPath>$(MSBuildCommunityTasksPath)\MSBuild.Community.Tasks.Targets</msbuildCommunityTasksTargetsPath>
<extensionPackTargetsPath>$(ExtensionTasksPath)MSBuild.ExtensionPack.tasks</extensionPackTargetsPath>

<!-- Configure WIX -->
<wixTargetsPath>$(WixToolPath)\Wix.targets</wixTargetsPath>
<wixTasksPath>$(WixToolPath)\WixTasks.dll</wixTasksPath>
<wixTargetsPath>$(WixToolPath)\Wix.targets</wixTargetsPath>
<wixTasksPath>$(WixToolPath)\WixTasks.dll</wixTasksPath>
<luxTargetsPath>$(WixToolPath)\Lux.targets</luxTargetsPath>
<luxTasksPath>$(WixToolPath)\LuxTasks.dll</luxTasksPath>
<luxTargetsPath>$(WixToolPath)\Lux.targets</luxTargetsPath>
<luxTasksPath>$(WixToolPath)\LuxTasks.dll</luxTasksPath>

With this solution in place the next ‘new guy’ does not have to waste time trying to figure out where those dependencies are ;)

Convention over configuration with MSBuild

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 ;)

Clever TemplateFile hack

In my current project i use TemplateFileTask (MSBuild Community Tasks Project) to generate configuration files. I ran into the problem that i don’t want to expose a MEX endpoint in production. This is my initial template file:

 <endpoint address="" binding="ws2007HttpBinding" contract="DemoService.IFileService" />
 ${MexEndpoint}
</service>

And here is my initial msbuild task:

<Import Project="$(MSBuildExtensionsPath)\MSBuildCommunityTasks\MSBuild.Community.Tasks.Targets"/>
 <Target Name="GenerateConfigFiles">
  <PropertyGroup>
   <MexEndpoint>
    <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
   </MexEndpoint>
  </PropertyGroup>

  <PropertyGroup Condition=" '$(Env)'=='Production' ">
   <MexEndpoint></MexEndpoint>
  </PropertyGroup>

  <ItemGroup>
   <Tokens Include="MexEndpoint">
    <ReplacementValue>$(MexEndpoint)</ReplacementValue>
   </Tokens>
  </ItemGroup>

  <TemplateFile Template="Web.template.config" OutputFileName="Web.config" Tokens="@(Tokens)" />
 </Target>
</Project>

This results in the following configuration file: (WCF does not like the xml namespace declaration):

 <endpoint address="" binding="ws2007HttpBinding" contract="DemoService.IFileService" />
 <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" />
</service>

I noticed that a smart collegue of mine came up with the following template file:

 <endpoint address="" binding="ws2007HttpBinding" contract="DemoService.IFileService" />
 <${MexBegin}endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" /${MexEnd}>
</service>

And this is how he defines the MexBegin and MexEnd properties in msbuild:

<PropertyGroup>
 <MexBegin></MexBegin>
 <MexEnd></MexEnd>
</PropertyGroup>

<PropertyGroup Condition=" '$(Env)'=='Production' ">
 <MexBegin>!--</MexBegin>
 <MexEnd>--</MexEnd>
</PropertyGroup>

This leads to a nice MEX endpoint for all environments and in Production we get the following:

<!--endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" /-->

Perhaps it is cleaner to implement my own TemplateFileTask but untill then this clever hack does the job ;)