Posts Tagged ‘MSBuild’

Change the default printing-output of an item @item

Normally, @(Item) is translated as a concatenation of the identity metadata of the elements in the item list Item – these values are delimitated by comma ‘,’  . We can customize this by using:

@(item->’…%(metaData)…’, ‘delim’ )

E.g., I often use the delimiter is %0A stands for a newline.

The difference between XmlRead vs XmlPeek ?

XmlPeek:
Read inner text.
Built-in support by MSBuild in dotNet Framework.

XmlRead:
Do not read inner text.
Need MSBuild Community Task.

Example:

<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" DefaultTargets="MAIN" ToolsVersion="4.0">
  <!--<Import Project="$(MSBuildExtensionsPath)\MSBuildContrib\MSBuildContrib.Tasks"/>-->
  <!--<Import Project="$(MSBuildExtensionsPath)\ExtensionPack\MSBuild.ExtensionPack.tasks"/>-->
  <Import Project="$(MSBuildExtensionsPath)\MSBuildCommunityTasks\MSBuild.Community.Tasks.Targets" />

  <Target Name="MAIN">
    <XmlPeek Namespaces="&lt;Namespace Prefix='msb' Uri='http://schemas.microsoft.com/developer/msbuild/2003'/&gt;"
             XmlInputPath="$(MSBuildProjectFile)"
             Query="/msb:Project/msb:Import">
      <Output TaskParameter="Result" ItemName="Peeked" />
    </XmlPeek>
    <Message Text="111: @(Peeked)"/>

    <XmlRead Prefix='msb' Namespace='http://schemas.microsoft.com/developer/msbuild/2003'
             XmlFileName="$(MSBuildProjectFile)"
             XPath="/msb:Project/msb:Import" >
      <Output TaskParameter="Value" ItemName="Peeked2" />
    </XmlRead>
    <Message Text="222: @(Peeked2)"/>
  </Target>
</Project>

Target Batching notes

When batching a target, we can not batch a series of calltarget since it will batch each calltarget one by one. See below sample script to see it.

<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" DefaultTargets="MAIN" >
  <Target Name="MAIN">
    <CallTarget Targets="t" />
    <CallTarget Targets="tt" />
    <CallTarget Targets="ttB" />
  </Target>

  <Target Name="t">
    <ItemGroup>
      <aa Include="1;22;333" />
    </ItemGroup>
  </Target>

  <Target Name="tt" Outputs="%(aa.Identity)">
    <Message Text="This batching won't work!" />
    <CallTarget Targets="ttt" />
    <CallTarget Targets="tttt" />
  </Target>

  <Target Name="ttt">
    <Message Text="1:%(aa.Identity)" />
  </Target>

  <Target Name="tttt">
    <Message Text="2:%(aa.Identity)" />
  </Target>

  <Target Name="ttB" Outputs="%(aa.Identity)">
    <Message Text="Batch it this way will work!" />
    <Message Text="1B:%(aa.Identity)" />
    <Message Text="2B:%(aa.Identity)" />
  </Target>
</Project>

For those who insist on calling a target in batching mode, then is the handy tool. See below sample script.

<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" DefaultTargets="MAIN" >
  <!--<Import Project="$(MSBuildExtensionsPath)\MSBuildContrib\MSBuildContrib.Tasks"/>-->
  <!--<Import Project="$(MSBuildExtensionsPath)\ExtensionPack\MSBuild.ExtensionPack.tasks"/>-->
  <!--<Import Project="$(MSBuildExtensionsPath)\MSBuildCommunityTasks\MSBuild.Community.Tasks.Targets" />-->

  <Target Name="MAIN">
    <CallTarget Targets="t" />
    <CallTarget Targets="tt" />
    <CallTarget Targets="ttB" />
  </Target>

  <Target Name="t">
    <ItemGroup>
      <aa Include="1;22;333" />
    </ItemGroup>
  </Target>

  <Target Name="tt" Outputs="%(aa.Identity)">
    <Message Text="This batching won't work!" />
    <CallTarget Targets="ttt" />
    <CallTarget Targets="tttt" />
  </Target>

  <Target Name="ttt">
    <Message Text="1:%(aa.Identity)" />
  </Target>

  <Target Name="tttt">
    <Message Text="2:%(aa.Identity)" />
  </Target>

  <Target Name="ttB" Outputs="%(aa.Identity)">
    <Message Text="Batch it this way will work!" />
    <Message Text="1B:%(aa.Identity)" />
    <Message Text="2B:%(aa.Identity)" />

    <Message Text="Still need to call another target? Belowings are the work-around." />
    <PropertyGroup>
      <aaList>@(aa)</aaList>
    </PropertyGroup>
    <MSBuild Projects="$(MSBuildProjectFile)"
             Targets="tttB"
             Properties="aaList=$(aaList)"
             />
  </Target>
  <Target Name="tttB">
    <CallTarget Targets="tttB1" />
    <CallTarget Targets="tttB2" />
  </Target>
  <Target Name="tttB1">
    <ItemGroup>
      <aa Include="$(aaList.Split(';'))" />
    </ItemGroup>
  </Target>
  <Target Name="tttB2" Outputs="%(aa.Identity)">
    <Message Text="3B:%(aa.Identity)" />
  </Target>

</Project>

Enjoy!

[MSBuild] How to implement nested loops

Let’s say we have nested loops through two list aa={1; 22; 333} and bb={a; bb; ccc} like:

foreach (i in aa) {
  foreach (j in bb) {
    print "i=? j=??";
  }
}

The solution is quite complicated and as below.

<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" DefaultTargets="MAIN" >
  <!--<Import Project="$(MSBuildExtensionsPath)\MSBuildContrib\MSBuildContrib.Tasks"/>-->
  <!--<Import Project="$(MSBuildExtensionsPath)\ExtensionPack\MSBuild.ExtensionPack.tasks"/>-->

  <Target Name="MAIN" >
    <CallTarget Targets="t" />
    <CallTarget Targets="tt" />
  </Target>

  <Target Name="t">
    <ItemGroup>
      <aa Include="1;22;333" />
    </ItemGroup>
  </Target>

  <Target Name="tt" Outputs="%(aa.Identity)">
    <Message Text="aa %(aa.Identity)" />

    <MSBuild Projects="$(MSBuildProjectFile)" Targets="ttt" Properties="aa=%(aa.Identity)" />
  </Target>

  <Target Name="ttt">
    <CallTarget Targets="ttt_1" />
    <CallTarget Targets="tttt" />
  </Target>

  <Target Name="ttt_1">
    <ItemGroup>
      <bb Include="a;bb;ccc" />
    </ItemGroup>
  </Target>

  <Target Name="tttt" Outputs="%(bb.Identity)">
    <Message Text="aa $(aa)" />
    <Message Text="bb %(bb.Identity)" />
  </Target>

</Project>

And for an even more complex nested loops (3 nested), see sample script below.

<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" DefaultTargets="MAIN" >
  <Target Name="MAIN">
    <CallTarget Targets="t"/>
    <CallTarget Targets="tt"/>
  </Target>

  <Target Name="t" Outputs="%(aaList.Identity)">
    <ItemGroup>
      <aaList Include="1; 22; 333;" />
    </ItemGroup>
    <Message Text="Foreach aa in aaList:" />
  </Target>

  <Target Name="tt">
    <MSBuild Projects="$(MSBuildProjectFile)"
             Targets="ttt"
             Properties="aa=%(aaList.Identity)"
    />
  </Target>

  <Target Name="ttt">
    <CallTarget Targets="tttt" />
    <CallTarget Targets="ttttt" />
  </Target>

  <Target Name="tttt">
    <ItemGroup>
      <bbList Include="a; bb; ccc;" />
    </ItemGroup>
  </Target>

  <Target Name="ttttt" Outputs="%(bbList.Identity)">
    <Message Text="aa:$(aa)" />
    <MSBuild Projects="$(MSBuildProjectFile)"
             Targets="t2"
             Properties="bb=%(bbList.Identity); aa=$(aa)"
    />
  </Target>
  <Target Name="t2">
    <CallTarget Targets="tt2" />
    <CallTarget Targets="ttt2" />
  </Target>

  <Target Name="tt2">
    <ItemGroup>
      <ccList Include="x; yy; zzz;" />
    </ItemGroup>
  </Target>
  <Target Name="ttt2" Outputs="%(ccList.Identity)">
    <Message Text="aa:$(aa) bb:$(bb)" />
    <Message Text="cc:%(ccList.Identity)" />
  </Target>

</Project>

[MSBuild] Note when working with Condition

The < and > comparision may be tricky!

Example:

<Target Name="ttt">
<Message Text="1" Condition="2>1" />
<Message Text="22" Condition="2&gt;1" />
<Message Text="a" Condition="2>=2" />
<Message Text="bb" Condition="2&gt;=2" />

<Message Text="1x" Condition="1<2" /> <!--error-->
<Message Text="22x" Condition="1&lt;2" />
<Message Text="ax" Condition="2<=2" /> <!--error-->
<Message Text="bbx" Condition="2&lt;=2" />
</Target>

[MSBuild] Custom Tasks

MSBuild Community Tasks

  1. Download at msbuildtasks.tigris.org
  2. Usage:
<Import Project="$(MSBuildExtensionsPath)\MSBuildCommunityTasks\MSBuild.Community.Tasks.Targets"/>

MSBuild Extension Pack

  1. Download at MSBuild Extension Pack
  2. Usage:
<Import Project="$(MSBuildExtensionsPath)\ExtensionPack\MSBuild.ExtensionPack.tasks"/>

MSBuild SDC Tasks

  1. Download at SDC Tasks Library
  2. Usage: You need to copy the task manually.

[MSBuild] Caution on properties's value when calling another target

Cautions:

  1. Property values updated in a called target can be visible in another called target but not in the current target.
  2. Programmatic properties & items are not emitted (can not be passed) to CallTarget invocation down the stream until the target that created these properties & items completes its execution. In order to pass a newly created property to subsequent targets, you need to create it outside the context of the current target.  In other words, you need to create a new target and place the creation in that target.

[MSBuild] Overwrite global property.

<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" DefaultTargets="uu" >
  <PropertyGroup>
    <aa>1</aa>
  </PropertyGroup>

  <Target Name="t"> <!--try to overwrite value of 'aa'-->
    <PropertyGroup>
      <aa>22</aa>
    </PropertyGroup>

    <Message Text="tt:$(aa) - print 'aa' insdie of t." />

    <CallTarget Targets="tt" />
  </Target>

  <Target Name="tt">
    <Message Text="tt:$(aa) - print 'aa' outside of t, t is not done." />
  </Target>

  <Target Name="u">
    <Message Text="u:$(aa) - print 'aa' outside of t, after t is done." />
  </Target>

  <Target Name="uu">
    <CallTarget Targets="t"/> <!--update 'aa' then print 'aa' inside t-->

    <CallTarget Targets="u"/> <!--print 'aa' outside of t-->
  </Target>
</Project>

[MSBuild] Nested loops in MSBuild

  <Target Name="t">
    <ItemGroup>
      <item Include="1;22"/>
    </ItemGroup>
    <Message Text="foreach:@(item)" />
    <MSBuild Projects="$(MSBuildProjectFile)" Targets="tt" Properties="t=%(item.Identity)" />
  </Target>

  <Target Name="tt">
    <ItemGroup>
      <item Include="a;bb;ccc"/>
    </ItemGroup>
    <Message Text="t=$(t  ), foreach:@(item)" />
    <MSBuild Projects="$(MSBuildProjectFile)" Targets="ttt" Properties="t=$(t); tt=%(item.Identity)" />
  </Target>

  <Target Name="ttt">
    <Message Text="t:$(t) tt:$(tt)" />
 </Target>

[MSBuild] Input/Output parameters when calling a Target

Principle to work with targets in MSBuild.

They design MSBuild to call targets using attribute ‘DependsOnTargets’ .

Passing input parameters as properties.

Defining the property using PropertyGroup will NOT work! We need to create a new target, define the property overthere, then call the concerned target. It works that way.

Passing input parameters as batching value from an item list.

We have the tips to pass input as property to a called target, but how can we pass to it a property which is a batched value? -> Use <MSBuild …> to batch the item list & pass the input through its Properties attribute.

See below example.


<Target Name="t">
 <ItemGroup>
   <package Include="1;22;333"/>
 </ItemGroup>
 <Message Text="$(MSBuildProjectFile)" />
 <MSBuild Projects="$(MSBuildProjectFile)" Targets="transformPackage" Properties="packageName=%(package.Identity)" />
</Target>

<Target Name="transformPackage">
 <Message Text="2: $(packageName)" />
</Target>

Creating output parameters.

Just create whatever values you need but NOTE that only “not-the-caller” targets can read the new value.

Passing input parameters as an item list.