Thursday, August 12, 2010

MSBuild Presentation - May 2010 UDNUG Meeting

This last week while thinking about going to the UDNUG (Utah .NET User Group) meeting this week, I realized that I never posted my notes and such from my MSBuild presentation at the UDNUG in May.  So without further ado, here are links to the final demo files and below are my raw notes.  Enjoy!

Why MSBuild?
.csproj files are MSBuild files
MSBuild is distributed with the .NET framework so a VS installation is not required on your build server.

NAnt

Primary alternative is NAnt - was pretty much dead (very little activity in the last 4 years, mostly because it had matured and partly because MSBuild released with VS2005/.NET 2.0)
Earlier in 2010, .9 version is being worked on (in RC1) which I haven't looked at.

Another is Ruby/Rake
I plan on looking into this some at some point as it would be nice to have a first class language, instead of XML configuation.
info on lostechies.com from Derick Bailey on this topic if you are interested (Derick Bailey on Ruby/Rake)
But even this uses MSBuild to build the actual projects, just uses Ruby to do additional work around MSBuild

While I don't usually use this reason (I have chosen NHibernate over Entity Framework, nUnit over msUnit, etc) the fact that MSBuild is an MS product is useful.  Also it means that your build will be similar to a VS build (but not identical) Also it’s installed wherever the .NET framework is installed.

Start with the classic "Hello World" demo

Add Target with Message

  <Target Name="Hello">
    <Message Text="Hello Utah .NET User Group"/>
  </Target>

Add Another Target

  <Target Name="Before Hello">
    <Message Text="Before Hello"/>
  </Target>

Now I want Before to run and then Hello.

Add "Also Before Hello"
  <Target Name="Also Before Hello">
    <Message Text="Also Before Hello"/>
  </Target>

Move Hello to the end
Note that the first target still runs by default.  Can specify any target to run on the command line.

Command Line Reference on MSDN

Also can specify the "default target" using "DefaultTargets" (default Hello)

  <Target Name="Hello Again">
    <Message Text="Hello Again UDNUG!" />
  </Target>

Add Hello Again to the default targets.  Note that the targets run in order that they are listed.

Add a depends on target to Hello Again:

  <Target Name="Hello Again" DependsOnTargets="Before Hello">
    <Message Text="Hello Again UDNUG!" />
  </Target>

Run: note that Before Hello ONLY RUNS ONCE.
the assumption is that you only want to build dependencies once.

Properties
  <PropertyGroup>
    <MessageFor>Utah .NET User Group (from a Property)</MessageFor>
  </PropertyGroup>

  <PropertyGroup>
    <MessageFor>Utah .NET User Group (from a Property)</MessageFor>
    <Today>$([System.DateTime]::Now)</Today>
  </PropertyGroup>

  <Target Name="HelloProperties">
    <Message Text="Hello $(MessageFor) on $(Today)"/>
  </Target>
  There are some property names that are reserved for MSBuild.

Conditions

    <MessageFor Condition="$(MF) == 'UDNUG'">Utah .NET User Group (from a Property)</MessageFor>
    <MessageFor Condition="$(MF) == 'UCC'">Utah Code Camp (from a Property)</MessageFor>
    <MessageFor Condition="$(MF) == ''">Nobody</MessageFor>

Items/Item Groups

  <ItemGroup>
    <SamplePhotos Include="C:\Users\Public\Pictures\Sample Pictures\*.jpg" />
  </ItemGroup>
  <Target Name="Photos">
    <Message Text="Photos: %0A%0D@(SamplePhotos, '%0A%0D')" />
  </Target>

Exclude
  <ItemGroup>
    <SamplePhotos Include="C:\Users\Public\Pictures\Sample Pictures\*.jpg" Exclude="C:\Users\Public\Pictures\Sample Pictures\*house*"/>
  </ItemGroup>

Well known metadata 
Well Known Item Metadata on MSDN

  <Target Name="PhotosMeta">
    <Message Text="Photo filename: %(SamplePhotos.Filename)%(SamplePhotos.Extension)" />
  </Target>

Transforms

      <Message Text="Photo filenames: @(SamplePhotos->'%(Filename)%(Extension)')" />

Incremental Builds

  <Target Name="Text" Inputs="@(TextFiles)" Outputs="@(TextFiles->'CopyOfTextFiles\%(Filename)%(Extension)')">
    <Message Text="hooray for changeme.txt"/>
    <Copy SourceFiles="@(TextFiles)" DestinationFolder="CopyOfTextFiles"/>
  </Target>

Import

Move the following to Demo.Extra.proj

<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="4.0">
  <Target Name="Also Before Hello">
    <Message Text="Also Before Hello"/>
  </Target>
</Project>

  <Import Project="Demo.Extra.proj"/>

Run the Hello target to show that it's using the other file too.

Task Reference on MSDN

MSBuild Community Tasks
http://msbuildtasks.tigris.org/

Show MSBuild.Community.Tasks.Targets
Show that it is using the UsingTask tag to import all the tasks.

If you didn't use the MSI (in my case I want to have it xCopy buildable), you need to make sure to set MSBuildCommunityTasksPath so that the imported targets file can find the tasks.

    <SourcePath>$(MSBuildProjectDirectory)</SourcePath>
    <MSBuildCommunityTasksPath Condition="'$(MSBuildCommunityTasksPath)' == ''">$(SourcePath)\MSBuildCommunityTasks</MSBuildCommunityTasksPath>

    <Import Project="$(MSBuildCommunityTasksPath)\MSBuild.Community.Tasks.Targets" />

  <Target Name="Zip">
    <Zip Files="@(SamplePhotos)"
        ZipFileName="SamplePhotos.zip" />
  </Target>
Actually build a project!

  <Target Name="Build">
    <MSBuild Projects="@(Projects)">
      <Output TaskParameter="TargetOutputs" ItemName="AssembliesBuilt"/>
    </MSBuild>
    <Message Text="Built Assemblies: @(AssembliesBuilt)" />
    <Copy SourceFiles="@(AssembliesBuilt)" DestinationFolder="."/>
  </Target>

  <ItemGroup>
    <Projects Include="C:\Users\Mike\Demos\MSBuildPrototype\MSBuildPrototype\MSBuildPrototype.csproj"/>
  </ItemGroup>

New in MSBuild 4.0
Not a lot of experience here, but there are a few features that look pretty exciting.

New in MSBuild 4 on MSDN

Property Functions

String (instance) property functions

  <Target Name="String">
    <Message Text="MessageFor Length: $(MessageFor.Length)"/>
    <Message Text="MessageFor First 2: $(Messagefor.Substring(0,2))"/>
  </Target>

Static property functions

Getting the date before is an example of this.

  <Target Name="Math">
    <Message Text="Square root of $(Value) = $([System.Math]::Sqrt($(Value)))" />
  </Target>

MSBuild property functions

  <Target Name="Add">
    <Message Text="Sum of $(x) and $(y) is $([MSBuild]::Add($(x), $(y)))"/>
  </Target>

  Can do x and y on separate /p: or use "" to wrap a ; separated pairs
RunBeforeTargets and RunAfterTargets

Modify this:

  <Target Name="Math" AfterTargets="Add" BeforeTargets="String">
    <Message Text="Square root of $(Value) = $([System.Math]::Sqrt($(Value)))" />
  </Target>

Math runs After Add and Before String when you run either.

Inline Tasks

  <UsingTask
    TaskName="HelloWorld"
    TaskFactory="CodeTaskFactory"
    AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v4.0.dll" >
    <ParameterGroup />
    <Task>
      <Using Namespace="System"/>
      <Using Namespace="System.IO"/>
      <Code Type="Fragment" Language="cs">
        <![CDATA[
// Display "Hello, world!"
Log.LogError("Hello, world!");
]]>
      </Code>
    </Task>
  </UsingTask>

  <Target Name="Hello">
    <HelloWorld />
  </Target>
Show parameters

  <UsingTask
    TaskName="HelloWorld"
    TaskFactory="CodeTaskFactory"
    AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v4.0.dll" >
    <ParameterGroup>
      <Value ParameterType="System.Int32" Required="true"/>
    </ParameterGroup>
    <Task>
      <Using Namespace="System"/>
      <Using Namespace="System.IO"/>
      <Code Type="Fragment" Language="cs">
        <![CDATA[
// Display "Hello, world!"
Console.WriteLine("hello");
Console.WriteLine(Value * Value);
//Log.LogError("Hello, world!");
]]>
      </Code>
    </Task>
  </UsingTask>

  <Target Name="Hello">
    <HelloWorld Value="4"/>
  </Target>

Original design by andrastudio
Blogger port by Blogger Templates