Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to avoid repetition in MSBuild?

Tags:

msbuild

dry

I don't mind an occasional repetition of something when it's necessary, but in MSBuild I really don't know how to ever avoid repetition. It doesn't offer "functions" in the usual sense; a target can only ever get called once, even via CallTarget, and <Import> only works on Project level.

Here's a specific example I'm trying to de-"repetize":

<Target Name="Tgt1">
  <PropertyGroup><Conf1>Twiddle</Conf1><Conf2>Thing</Conf2></PropertyGroup>

  <PropertyGroup><xxxxxxxxxxExePath>$(xxxxxxxBuildRoot)\$(Conf1)Console-xxxxxxxxed</xxxxxxxxorExePath></PropertyGroup>
  <MSBuild Projects="$(BuildSingleProj)" Targets="Build;Merge"
           Properties="Configuration=$(Conf1)$(Conf2);Platform=$(Platform);CompiledFileName=$(CompiledFileName);ProjectName=$(ProjectName);SolutionFile=$(SolutionFile);Root=$(Root);Caller=$(MSBuildProjectFullPath)"/>
  <MakeDir Directories="$(xxxxxxxxorExePath)" />
  <WriteLinesToFile File="$(xxxxxxxxorExePath)\xxxxxxx.IsPortable.txt" />
  <WriteLinesToFile File="$(xxxxxxxxorExePath)\xxxxxxx.Global.Settings.xml" Lines="@(xxxxxxxLicense)" Overwrite="true" />
  <Exec Command='$(xxxxxxxxorExePath)\xxxxxxx.exe -a "$(xxxxxxxBuildRoot)\$(Conf1)$(Conf2)-Merged\xxxxxxx.exe" "$(xxxxxxxBuildRoot)\$(Conf1)$(Conf2)-xxxxxxxxed\xxxxxxx.exe"'/>
</Target>

I have four such targets, Tgt1, Tgt2, Tgt3, Tgt4. The only thing that differs between these four targets is the first line, the one that defines Conf1 and Conf2.

The only more or less workable de-duplication idea that I'm aware of is by moving the shared code to a new target and calling it via the MSBuild task. This, unfortunately, requires a loooooong string of properties to be manually passed in, and this task uses rather a few (I counted 11 properties and 1 item group).

An additional requirement is that I can invoke the script with an arbitrary subset of these targets, e.g. \t:Tgt2,Tgt3.

Is there any sensible alternative to just copy/pasting this chunk of code - that doesn't involve copying around huge lists of properties instead?

like image 545
Roman Starkov Avatar asked Jan 26 '11 22:01

Roman Starkov


2 Answers

This is a perfect scenario to use Batching.

You'll need to create custom Items with the appropriate metadata and then create a single Target to reference the new Items.

You can wrap each Item in it's own target like so:

<Target Name="Tgt1">
  <ItemGroup>
    <BuildConfig Include="Tgt1">
      <Conf1>Twiddle</Conf1>
      <Conf2>Thing</Conf2>
    </BuildConfig>
  </ItemGroup>
</Target>

<Target Name="Tgt2">
  <ItemGroup>
    <BuildConfig Include="Tgt2">
      <Conf1>Twaddle</Conf1>
      <Conf2>Thing 1</Conf2>
    </BuildConfig>
  </ItemGroup>
</Target>

<Target Name="Tgt3">
  <ItemGroup>
    <BuildConfig Include="Tgt3">
      <Conf1>Tulip</Conf1>
      <Conf2>Thing 2</Conf2>
    </BuildConfig>
  </ItemGroup>
</Target>

You'll then need a core target to call that will perform all of the work like so:

<Target Name="CoreBuild" Outputs="%(BuildConfig.Identity)">
  <Message Text="Name  : %(BuildConfig.Identity)" />
  <Message Text="Conf1 : %(BuildConfig.Conf1)" />
  <Message Text="Conf2 : %(BuildConfig.Conf2)" />
</Target>

Adding Outputs="%(BuildConfig.Identity)" to the target will make sure you batch at the target level instead of at the task level.

You can execute this from msbuild with passing arbitrary combinations of the targets as long as the last target is your core target. For example executing this command MSBuild.exe test.msbulid /t:Tgt1,Tgt3,CoreBuild will give you the following output:

Name  : Tgt1
Conf1 : Twiddle
Conf2 : Thing

Name  : Tgt3
Conf1 : Tulip
Conf2 : Thing 2
like image 74
Aaron Carlson Avatar answered Sep 22 '22 08:09

Aaron Carlson


DRY is not a tenet of MSBuild. With that being said its not good to repeat yourself in any case, when it is reasonably avoidable. The answer that Aaron gave regarding batching is a good one. This is one means to prevent duplication.

One thing that I would like to point out is that at a higher level it seems like you are thinking of MSBuild as a procedural language (i.e. having functions that you can call and what not). MSBuild is much more declarative than procedural though. If you are creating MSBuild scripts and you have the mindset 'Create function X so that I can call it at point Y', then you're entering a world of pain. Instead you should think of MSBuild as phases. For example; gather files, compile, publish, etc. When you think of it in this way then it makes total sense why targets are skipped after they have been executed once (which you've obviously observed during your trials).

Also after having been working with MSBuild for as long as I have I've figured out that it can really be a PITA to do things in a generic/uber-reusable way. It can be done, but I would reserve that type of effort for .targets files that you know for sure will be re-used many times. Now a days instead of going through that I am much more pragmatic and I land somewhere in between totally hacking scripts & doing things the way I used to do. I have a set of scripts that I re-use, but besides those I try and keep things simple. One big reason for this is that there are a lot of people how know the basics of MSBuild, but very few who have a very deep knowledge of it. Creating good generic scripts requires a deep knowledge of MSBuild, so when you leave a project the person who comes in behind you will have no idea what you were doing (perhaps good if you are a contractor? lol).

In any case I've got a bunch of resources on batching at: http://sedotech.com/Resources#Batching.

like image 44
Sayed Ibrahim Hashimi Avatar answered Sep 21 '22 08:09

Sayed Ibrahim Hashimi