Question

I'm trying to create a build script for our code deployment to multiple environments. The code is as follows:

<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="4.0">

<PropertyGroup>
    <TargetEnv>Production</TargetEnv>
</PropertyGroup>

<ItemGroup Condition="'$(TargetEnv)' == 'Integration'">
    <Server Include="int1">
        <ip>172.0.0.1</ip>
    </Server>
</ItemGroup>

<ItemGroup Condition="'$(TargetEnv)' == 'Production'">
    <Server Include="prod1">
        <ip>172.0.2.1</ip>
    </Server>
    <Server Include="prod2">
      <ip>172.0.2.2</ip>
    </Server>
</ItemGroup>

<Target Name="Deploy">
    <CallTarget Targets="DeployIntegration" />
    <CallTarget Targets="DeployServers" />
</Target>

<Target Name="DeployIntegration" Condition="'$(TargetEnv)' == 'Integration'" Outputs="%(Server.Identity)">
    <Message Text="= specific int server thing need access to variable %(Server.Identity) =" Importance="high" />
</Target>

<Target Name="DeployServers" Condition="'$(TargetEnv)' != 'Integration'" Outputs="%(Server.Identity)">
    <Message Text="= specific prod thing here need access to variable %(Server.Identity) =" Importance="high" />
</Target>

<Target Name="RemoveServerFromLoadBalancer" AfterTargets="DeployServers" Condition="'$(TargetEnv)' != 'Integration'">
    <Message Text="= removing %(Server.Identity) from load balancer =" Importance="high" />
</Target>

<Target Name="IgnoreRemoveServerFromLoadBalancer" AfterTargets="DeployServers" Condition="'$(TargetEnv)' == 'Integration'">
    <Message Text="= ignore removing %(Server.Identity) from load balancer =" Importance="high" />
</Target>

<Target Name="CopyFilesAndCreateFolderLinks" AfterTargets="RemoveServerFromLoadBalancer;IgnoreRemoveServerFromLoadBalancer">
    <Message Text=" = creating and copying files %(Server.Identity) =" Importance="high" />
</Target>

<Target Name="SetWebFarmServerName"  AfterTargets="UpdateWebConfig" Condition="'$(TargetEnv)' != 'Integration'">
    <Message Text=" = app setting CMSWebFarmServerName set to  %(Server.Identity) =" Importance="high" />
</Target>

<Target Name="DisableWebFarmForIntegration" AfterTargets="UpdateWebConfig" Condition="'$(TargetEnv)' == 'Integration'">
    <Message Text=" = Disabled webfarm setting for Integration - %(Server.Identity) =" Importance="high" />  
</Target>

<Target Name="AddBackToLoadBalancer" AfterTargets="DisableWebFarmForIntegration" Condition="'$(TargetEnv)' != 'Integration'">
    <Message Text=" = Putting server %(Server.Identity) back on load balancer =" Importance="high" />
</Target>

</Project>

This code is in an xml(saved in the 11.0 folder) file and i run it using the msbuild command:

C:\Program Files (x86)\Microsoft Visual Studio 11.0>msbuild buildtest.xml /t:Deploy

This code returns this when i run the build task for production:

    DeployServers:
      = specific prod thing here need access to variable prod1 =
    DeployServers:
      = specific prod thing here need access to variable prod2 =
    RemoveServerFromLoadBalancer:
      = removing prod1 from load balancer =
      = removing prod2 from load balancer =
    CopyFilesAndCreateFolderLinks:
       = creating and copying files prod1 =
       = creating and copying files prod2 =

I basically want to make sure that, if i'm on integration, it doesn't run specific targets such as load-balancer related tasks as there is only one machine for it. I'm thinking, the returned value should look like this:

    DeployServers:
      = specific prod thing here need access to variable prod1 =
    RemoveServerFromLoadBalancer:
      = removing prod1 from load balancer =
    CopyFilesAndCreateFolderLinks:
      = creating and copying files prod1 =

    DeployServers:
      = specific prod thing here need access to variable prod2 =
    RemoveServerFromLoadBalancer:       
      = removing prod2 from load balancer =
    CopyFilesAndCreateFolderLinks:      
      = creating and copying files prod2 =

Sorry for the long post, this msbuild stuff is a little tricky. I appreciate your input.

Was it helpful?

Solution

The batching MSBuild performs here is correct, think about it: you ask it to Message for text %(Server.Identity) so it's going to do that for as many servers as it knows and there's no reason it's gonna wait for oter targets in between. So to get what you want you'll have to make it perform all required tasks once per server. Furthermore your general structure is a bit too complicated. The conditions on the targets are unmanageable: just the fact that you repeat the same condition x times is already a sign something's wrong as it, simply said, violates the DIY principle. Also what if you add another TargetEnv? And then another one? Yep you figured it: won't be nice :] Second possible future pitfall is the use of AfterTargets: it's nice when you only have a couple, but after a while you keep adding targets and you'll have no idea what the order is, you'll basically have to go through the entire file to grasp what's going on. Also what if you add more targets that are common for each TargetEnv? Or if you add another TargetEnv. Again won't be nice as you'll have to fix that in multiple places.

Now because you sort of mixed these two complications and threw batching on top of it, things got pretty unclear. Back to the start and think about what you really need: if TargetEnv is A you want to do X and Y and Z, if TargetEnv is B you want to do and Q and Z. That's it. You can consider that as two seperate responsabilities: selecting something based on a condition, and maintaining lists of actions per condition. So let's express this in an msbuild way.

Here's the condition part, now in the new Deploy target. The rest of the targets are moved to another file. Deploy will call a target (which one depends on the condition) in the other msbuild file called deploy.targets, in the same directory as the current file. Because the batching is now on a higher level, it will automatically be executed the way you want: once per server. Note the selected server is passed as a property to the other file. There are other ways to do this, but just like with code it's nice to have a couple of smaller files instead of one big do-it-all one.

<Target Name="Deploy">
  <PropertyGroup>
    <TargetsFile>$(MsBuildThisFileDirectory)deploy.targets</TargetsFile>
    <TargetToCall Condition="$(TargetEnv)=='Production'">DeployServers</TargetToCall>
    <TargetToCall Condition="$(TargetEnv)=='Integration'">DeployIntegration</TargetToCall>
  </PropertyGroup>
  <MSBuild  Projects="$(TargetsFile)" Targets="$(TargetToCall)" Properties="Server=%(Server.Identity)" />
</Target>

And here's the new file which has all the targets, and two 'master' targets that now specify exactly which other targets they want to have called, no more need for conditions, no more AfterTargets.

<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="4.0">

  <PropertyGroup>
    <CommonTargets>CopyFilesAndCreateFolderLinks</CommonTargets>
  </PropertyGroup>

  <Target Name="DeployIntegration">
    <Message Text="= specific int server thing need access to variable $(Server) =" Importance="high" />
    <CallTarget Targets="IgnoreRemoveServerFromLoadBalancer;$(CommonTargets)"/>
  </Target>

  <Target Name="DeployServers">
    <Message Text="= specific prod thing here need access to variable $(Server) =" Importance="high" />
    <CallTarget Targets="RemoveServerFromLoadBalancer;AnotherTargetJustForDeploy;$(CommonTargets)"/> 
  </Target>

  <Target Name="RemoveServerFromLoadBalancer">
    <Message Text="= removing $(Server) from load balancer =" Importance="high" />
  </Target>

  <Target Name="AnotherTargetJustForDeploy">
    <Message Text="= AnotherTargetJustForDeploy for $(Server) =" Importance="high" />
  </Target>

  <Target Name="IgnoreRemoveServerFromLoadBalancer">
    <Message Text="= ignore removing $(Server) from load balancer =" Importance="high" />
  </Target>

  <Target Name="CopyFilesAndCreateFolderLinks">
    <Message Text=" = creating and copying files $(Server) =" Importance="high" />
  </Target>

</Project>
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top