Welcome to MSDN Blogs Sign in | Join | Help

How to call a custom target after building each individual solution (sln) in Team Build?

 

Issue: -

I would like to invoke a specific target just after each solution is compiled. Unfortunately, team build performs a build including all solution in an atomic MSBuild call. I don’t want to make changes in my sln or csproj files but I am OK with modifying the tfsbuild.proj. What can I do to get the desired behavior?

 

Solution:-  

This is possible in Team Build but is slightly complicated. You need to override the default CoreCompile target with your own version.

To give you the idea of what needs to be done, please refer this simplified script -

In this proj file, all the three projects (classlibrary1, classlibrary2, classlibrary3) are build for each configuration (Debug|AnyCPU, Release|AnyCPU) and the target CommonPostCompileStep is invoked after project is build for a particular configuration.  

 

<?xml version="1.0" encoding="utf-8"?>

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

 

 

  <ItemGroup>

    <!-- List of projects to build -->

    <SolutionToBuild Include=".\ClassLibrary1\ClassLibrary1.sln" />

    <SolutionToBuild Include=".\ClassLibrary2\ClassLibrary2.sln" />

    <SolutionToBuild Include=".\ClassLibrary2\ClassLibrary2.sln" /> 

  </ItemGroup>

 

 

  <ItemGroup>

    <!-- Configuration to build -->

    <ConfigurationToBuild Include="Release|Any CPU">

      <FlavorToBuild>Release</FlavorToBuild>

      <PlatformToBuild>Any CPU</PlatformToBuild>

    </ConfigurationToBuild>

 

    <ConfigurationToBuild Include="Debug|Any CPU">

      <FlavorToBuild>Debug</FlavorToBuild>

      <PlatformToBuild>Any CPU</PlatformToBuild>

    </ConfigurationToBuild> 

  </ItemGroup>

 

 

  <Target Name="CoreCompile">

 

    <!-- Call the compile for each configuration -->

    <MSBuild

            Projects="$(MSBuildProjectFile)"

            Targets="RunCoreCompileWithConfiguration"

            Properties="Platform=%(ConfigurationToBuild.PlatformToBuild);Flavor=%(ConfigurationToBuild.FlavorToBuild);" />

 

  </Target>

 

  <Target Name="RunCoreCompileWithConfiguration">

 

    <!-- Call the compile for each project (per configuration), with your custom target -->

    <MSBuild

            Projects="$(MSBuildProjectFile)"

            Targets="RunCoreCompileForProject; CommonPostCompileStep"

            RunEachTargetSeparately="true"

            Properties="Platform=$(Platform);Flavor=$(Flavor);

ProjectToBuild=%(SolutionToBuild.Identity)" />

 

  </Target>

 

  <Target Name="RunCoreCompileForProject">

 

    <!-- Actual compile for a particular {configuration, project} pair -->

    <MSBuild

          Condition="'$(ProjectToBuild)'!='' "

          Projects="$(ProjectToBuild)"

          Properties="Configuration=$(Flavor);Platform=$(Platform);

SkipInvalidConfigurations=true;"

          Targets="ReBuild" />

  </Target>

 

  <Target Name="CommonPostCompileStep">

 

    <!-- Custom target to execute after building each project/solution-->

    <Message Text="Common postcompile target to execute after building solution"/>

  </Target>

 

 

</Project>

   

 

Note the following issues when making changes in your tfsbuild.proj file

 

  1. In msbuild task, Projects property – use $(SolutionRoot)\TeamBuildTypes\$(BuildType)\tfsbuild.proj instead of using "$(MSBuildProjectFile). Otherwise the teambuild logger will crash
  2. As you are spanning msbuild process for each project (per configuration) separately, this will have performance implication.
  3. If making change in your csproj file to include custom post build step is easier. However if you are building large number of solutions and making changes in individual files is painful then this might be an better approach.

Please feel free to use this example as hint and customize your CoreCompile target.

 

 

 

Different behavior of StopOnFirstFailure flag with respect to slns and xxprojs in Team Build

 

Issue -

StopOnFirstFailure flag is respected when we are building multiple solutions and find the error in 1st solution and stop building the rest of solutions. However when we have a scenario where a solution contains multiple projects (csproj’s) and 1st project has error, the build continues to build the remaining projects and this flag does not have any effect.

Explaination

StopOnFirstFailure flag says  -

    … for each of the “things” I passed into the Projects parameter of the <MSBuild> task, please build them all even if one of them fails (StopOnFirstFailure=false), or please stop as soon as any of them fails (StopOnFirstFailure=true).  In team build case, the “thing” you’re passing in is a .SLN.  So <MSBuild> task will attempt to build the whole .SLN, regardless of your StopOnFirstFailure setting.  The behavior of .SLN builds is defined as attempting to build all projects within it, even if one of them fails, and there’s no easy way to change that behavior. 

If you still want to immediately stop the build on getting the build break, then you need to know the list of projects that are contained within the SLN. You need to pass list of projects to the Projects parameter of <MSBuild> task. Since team build does not have this information at disposal, there is no easy way to fix it.  

 

 

Stopping the build after first (compilation) error in team build

 

Scenario -

We are building multiple solutions in our Team Build project file. We want to halt the build when the first compile error is encountered.

 

Answer

 

If you are building multiple solutions (sln's) and you encounter error in any sln, the build process will not stop after that sln and will continue to build the rest of solutions. The reason is that MSBuild task in core compile will be invoked for each solution specified in the item group due (@(SolutionToBuild))

 

To stop the build after getting the build break and not continue building rest of solutions, you need to make the following changes –

  • Modify the file Microsoft.TeamFoundation.Build.targets file to add the additional option of "StopOnFirstFailure" (specified by yellow text)

ADD -

<!-- If user has not specified the option anything default behavior should be to build all slns -->
<
PropertyGroup>

<StopOnFirstFailure>false</StopOnFirstFailure>

</PropertyGroup>

<!-- In target CORECLEAN -->

<MSBuild

Projects="@(SolutionToBuild)"

StopOnFirstFailure="$(StopOnFirstFailure)"

Properties="Configuration=$(FlavorToBuild); ..."

Targets="Clean" /> 

<!-- In target CORECOMPILE -->

<MSBuild

Condition=" '@(SolutionToBuild)'!='' "

Projects="@(SolutionToBuild)"

StopOnFirstFailure="$(StopOnFirstFailure)"

Properties="Configuration=$(FlavorToBuild);..."

Targets="Build" />

  • Now if you want to stop the build in first failure, edit your build type (tfsbuild.proj) and override/redefine the property "StopOnFirstFailure" (specified by yellow text).

<PropertyGroup>

     <StopOnFirstFailure>true</StopOnFirstFailure>

</PropertyGroup>

 

 

If you are building a solution with multiple projects (.xxproj's) and you encounter an error in first .csproj, then build will not stop immediately but will iterate over all the remaining projects (csproj). 

 

 

 

FAQ

 

Is it a good idea to use the same machine as "build machine" and "team foundation server"?

 

It is not a good idea to make Team Foundation Server machine as build machine for real life deployment (ok for demos/trials etc). The main reason is for debugging build failures etc, many people in org may need access to build machine and that could pose a security threat to the server.

 

Where all do I need to install the fxcop tool (that came with the VSTS for developer DVD) to enable static analysis in Team Build?

 

Only build machine.

 

 

Configuring destop builds for building specific solutions in Team Build

 

Scenario

I have set up a Team Build Type which builds an entire system consisting of multiple solutions. I want an easy way for developers to be able to invoke builds on there dev machines (for only the solutions they are working on) using Team Build. We do not want developers to build the entire system (all solutions) as this will result in lot of time wastage. Additionally we do not want to create a seperate Team Build Type for each developer for supporting the desktop scneario for scalability and mantainability issues. Is it possible?

Yes, it is possible. Developers don’t need to define new build type customize for there needs. By doing simple customization in tfsbuild.proj file and passing desktop properties scenario can be easily enabled.

For example we are building two solutions (App1.sln and App2.sln) in team build but I want to build only App1.sln solution for my dev box (for desktop build). However my changes will not affect the non desktop scenarios.

The changes are –

In tfsbuild.proj

Change lines –

<ItemGroup>

    <!-- List of solutions to build for

  - desktop scenarios

  - non desktop scenarios

     -->

    <SolutionToBuild Include="$(SolutionRoot)\App1\App1.sln" />

    <SolutionToBuild Include="$(SolutionRoot)\App2\App2.sln" />

</ItemGroup>

To lines -

<ItemGroup>

 

    <!-- List of solutions to build for non desktop scenarios only. -->

    <SolutionToBuild

      Condition=" '$(IsDesktopBuild)'!='true'"

      Include="$(SolutionRoot)\App2\App2.sln" />

 

    <!-- List of solutions to build for

       - non desktop scenarios

       - desktop scenario with ComputerNameParameter set to name of

         current computer name

    -->

    <SolutionToBuild

Include="$(SolutionRoot)\App1\App1.sln"

Condition=" '$(IsDesktopBuild)'!='true' OR

            '$(COMPUTERNAME)'=='$(ComputerNameParameter)'" />

 

  </ItemGroup>

Execution after change

1.      msbuild TFSBuild.proj – no project will be build for desktop build

2.      msbuild TFSBuild.proj /p:ComputerNameParameter=<MyComputerName>

Only App1.sln project will be build for the desktop scenario. It assumes that your build machine for desktop build is represented by <MyComputerName> variable.

 

 

How to get changesets/workitems belonging to a particular sub folder (only)?

  1. Edit the tfsbuild.proj and over ride the label task definition. Do note that changes in Name and Files argument. The Files attribute is used to define the scope of labels incase it is disjoint. Name of the label now contains the explicit scope.
  2. Initialize the value of the property GetGhangesetForFolder. The folder should not be team project. For example (1) if your folder is $/teamproject/myfolder, then the value should be "myfolder", (2) if your folder is $/teamproject/myfoldder/component1 then the calue should be "myfolder/component1". 

Add the following stuff to your tfsbuild.proj

<PropertyGroup 

<GetChangesetForFolder>_Component1_</GetChangesetForFolder>

</PropertyGroup>

<Target Name="CoreLabel"

          Condition=" '$(IsDesktopBuild)'!='true' "

          DependsOnTargets="$(CoreLabelDependsOn)" >

    <!-- Label all the latest non deleted files in workspace -->

    <Label

       Condition=" '$(SkipLabel)'!='true' "

       Workspace="$(WorkspaceName)"

       Name="$(BuildNumber)@$/$(TeamProject)/$(GetChangesetForFolder)"

       Version="W$(WorkspaceName)"

       Files="$/$(TeamProject)/$(GetChangesetForFolder)"

       Child="$(ChildLabel)"

       Comments="$(LabelTaskComment)"

       Recursive="$(RecursiveLabel)" />

 </Target>

Additionally note that you do not need to override the LabelQueryScope property.

 

How to customize the drop location in team build?

For eaxmple in your tfsbuild.proj, the value of DropLocation is \\machine\drop1 and you want to change it to \\machine\drop2.

  • Edit your build type definition (tfsbuild.proj) file and overload the BeforeEndToEndIteration

<Target Name="BeforeEndToEndIteration">

    <CreateProperty Value="$(CustomDropLocation)" >

        <Output TaskParameter="Value" PropertyName="DropLocation"/>

    </CreateProperty>

</Target>

  • Edit the tfsbuild.rsp file to pass the custom value of drop location.

Add the following line to tfsbuild.rsp. This along with changes in step (1) will reset your DropLocation.

/p:CustomDropLocation=\\machine\drops2

For more details about the overriding precedence for msbuild properties, refer this post.

 

custom msbuild task to get changesets and workitems based on AreaPath

 

 

Objective of sample task

 

Get all the affected change sets between this build and the previous successful build that has the work items with Area Path according to input parameter specified in the .proj file.

 

You need to make the following changes in your tfsbuild.proj

 

1.    Compile the code and create the assembly (dll)

2.    check-in the task assembly in the build type folder (along with tfsbuild.proj file)

3.    Add using task tag just before the <Import Project… statement. For example

<UsingTask

 TaskName="Microsoft.TeamFoundation.Build.Tasks.Samples.GetChangesetsWorkItemsList"

 AssemblyFile="GetChangesetsWorkItemsList.dll" />

4.    Disable the old gencheckinnotes task by setting the following property inside tfsbuild.proj 

<PropertyGroup>

<SkipPostBuild>true</SkipPostBuild>

</PropertyGroup>

5.    Reset the old gencheckinnotes task on build break by redefining the target definition inside tfsbuild.proj –

<Target Name="GetChangeSetsOnBuildBreak"

        Condition=" '$(IsDesktopBuild)'!='true' " >

<!-- Get associated changesets even when build fails. Set the AreaPath property-->

<GetChangesetsWorkItemsList

AssemblyPath="$(TeamBuildRefPath)"

Recursive="false"

AreaPath=""

TeamFoundationServerUrl="$(TeamFoundationServerUrl)"

BuildId="$(BuildNumber)"

CurrentLabel="L$(BuildNumber)$(LabelQueryScope)"

LastLabel="L$(LastGoodBuildNumber)$(LabelQueryScope)"

UpdateWorkItems="false"

ContinueOnError="true" />

</Target> 

6.    Invoke the new task by redefining the target definition inside tfsbuild.proj –

<Target Name="AfterCompile">

<GetChangesetsWorkItemsList

AssemblyPath="$(TeamBuildRefPath)"

Recursive="true"

AreaPath="TeamProject1\Area 0"

TeamFoundationServerUrl="$(TeamFoundationServerUrl)"

BuildId="$(BuildNumber)"

CurrentLabel="L$(BuildNumber)$(LabelQueryScope)"

LastLabel="L$(LastGoodBuildNumber)$(LabelQueryScope)"

UpdateWorkItems="$(UpdateAssociatedWorkItems)"

ContinueOnError="true" />

</Target> 

7.    Make sure to add the targets towards the end of the tfsbuild.proj (just before </Projects> tag)

 

 

Important Points about the custom task:-

 

1.                If the area path is empty, then the task falls back to the default behavior where it will get all the change sets and the associated work items between the last successful build and the current build.

2.                If the area path is invalid, you will not get any work item but we will not indicate that you have passed invalid/non existent area path.

3.                If the area path is valid and recursive flag is set, then we will get all the change sets and the work items that belong to the area path note or its children.

4.                If the area path is valid and recursive flag is not set, then we will get only the change sets and the work items that belong to the area path note.

5.                The custom task should be invoked after Core Label target. It is safe to use AfterCompile target

6.                The UpdateWorkItem flag can be used to enable or disable the task from updating the work items.

7.                If the last label is empty, then the task will show bad performance because it will query for all the change sets.

8.                Work item can be associated with multiple change sets, we will not display duplicates.

9.                We will display the sorted list of change sets and work items.

10.            AssemblyPath property should point to the folder containing Microsoft.TeamFoundation.Build.Tasks.VersionControl.dll.

 

Tested Scenarios

1.                Multiple projects of different types with multiple configuration with

a.    Area path not specified – got everything

b.    Area path with recursive false, update flag false – got cs and wi belonging to specified node.

c.    Area path with recursive false, update flag true – got cs and wi. Additionally updated wi belonging to specified node.

d.    Area path with recursive true, update flag false – got all cs and wi.

e.    Area path with recursive true, update flag true – got all cs and wi. Additionally updated all wi belonging to specified node.

2.                Building project for the first time – working as expected

 

Source Code

 

[Download - sources

 

 

 

Redirecting the copy of output assemblies for individual solutions to specified subfolders at drop site in Team Build

 

Team Build overrides the output directories that are specified in the individual project files, and thus places all build output at $(BinariesRoot)\$(Platfom)\$(Configuration)\

 

  1. Is there a way disable copying the assemblies at drop site? 

Yes, you need to set the SkipDropBuild property to true inside your tfsbuild.proj file. 

 

  1. Disable override behavior and have team Build use the relative output paths specified in each project properties?

No it is not possible now. We are working on it.

 

Unfortunately Team Build can not use the relative output paths specified in each project properties. We wanted to use TargetOutputs item which captures the relative path of output assembly (based on project settings). We can not use it because –

    1. The item is populated only when we call MSbuild on .XXPROJ files. It does not get initialized when we call MSbuild on .SLN files.  In Team Build basic unit of building is .SLN.
    2. The TargetOutputs item does not include the PDB files and other output configuration files for different type of projects.

  1. Specify override for individual projects during the build without any impact on desktop scenario?

There is no easy way to do it but I do have this workaround –

 

Expected actions from the user -  

  1. Override the core compile target and core clean target. Just past the following code in your tfsbuild.proj (towards the end , just before </project> tag). Note that you should not make modifications to Microsoft.Teamfoundation.Build.targets file.  

<Target Name="CoreClean"

          DependsOnTargets="$(CoreCleanDependsOn)"

          Condition=" '$(SkipClean)'!='true' ">

 

 

  <!-- Clean for desktop build -->

  <MSBuild

        Condition="'$(IsDesktopBuild)'=='true'"

        Projects="$(MSBuildProjectFile)"

        Targets="RunCoreCleanWithConfiguration"

        Properties="RunCodeAnalysis=$(RunCodeAnalysis); BinariesRoot=$(BinariesRoot);FxCopDir=$(FxCopDir);Platform=%(ConfigurationToBuild.PlatformToBuild);Flavor=%(ConfigurationToBuild.FlavorToBuild); " />

 

  <!-- Clean SolutionRoot only for end to end build -->

  <RemoveDir

        Condition="Exists('$(SolutionRoot)') and '$(IsDesktopBuild)'!='true'"

        Directories="$(SolutionRoot)" />

 

  <RemoveDir

        Condition="Exists('$(BinariesRoot)') and '$(IsDesktopBuild)'!='true'"

        Directories="$(BinariesRoot)" />

 

  <RemoveDir

        Condition="Exists('$(TestResultsRoot)') and '$(IsDesktopBuild)'!='true'"

        Directories="$(TestResultsRoot)" />

 

</Target>

 

<Target Name="RunCoreCleanWithConfiguration" >

 

  <!-- OutDirForClean for not Any CPU -->

  <CreateItem

        Condition=" '$(Platform)'!='Any CPU' "

        Include="$(BinariesRoot)\$(Platform)\$(Flavor)\" >

    <Output TaskParameter="Include" ItemName="OutDirForClean" />

  </CreateItem>

 

  <!-- OutDirForClean for Any CPU -->

  <CreateItem

        Condition=" '$(Platform)'=='Any CPU' "

        Include="$(BinariesRoot)\$(Flavor)\" >

    <Output TaskParameter="Include" ItemName="OutDirForClean" />

  </CreateItem>

 

  <!-- OutDir - to ensure we have absolute path as property -->

  <CreateProperty Value="%(OutDirForClean.FullPath)" >

    <Output TaskParameter="Value" PropertyName="OutDir" />

  </CreateProperty>

 

  <!-- Call MSBuild /t:Clean for desktop build -->

  <MSBuild

        Projects="@(SolutionToBuild)"         

 Properties="Configuration=$(Flavor);Platform=$(Platform);RunCodeAnalysis=$(RunCodeAnalysis);SkipInvalidConfigurations=true;FxCopDir=$(FxCopDir);OutDir=$(OutDir)%(SolutionToBuild.OutputFolder)\"

 

        Targets="Clean" />

 

</Target>

 

 

<Target Name="CoreCompile"

          DependsOnTargets="$(CoreCompileDependsOn)">

 

  <MakeDir

        Directories="$(BinariesRoot)"

        Condition="!Exists('$(BinariesRoot)')" />

 

  <TeamBuildMessage

        Tag="Configuration"

        Condition=" '$(IsDesktopBuild)'!='true' "

        Value="%(ConfigurationToBuild.FlavorToBuild)" />

 

  <TeamBuildMessage

        Tag="Platform"

        Condition=" '$(IsDesktopBuild)'!='true' "

        Value="%(ConfigurationToBuild.PlatformToBuild)" />

 

  <!-- Need proper location of build type otherwise logger fail (file not in enlistment)-->

  <MSBuild

        Condition="'$(IsDesktopBuild)'!='true' "

      Projects="$(SolutionRoot)\TeamBuildTypes\$(BuildType)\TfsBuild.proj"

 

        Targets="RunCoreCompileWithConfiguration"

        Properties="Platform=%(ConfigurationToBuild.PlatformToBuild);Flavor=%(ConfigurationToBuild.FlavorToBuild);RunCodeAnalysis=$(RunCodeAnalysis);BinariesRoot=$(BinariesRoot);FxCopDir=$(FxCopDir);TeamBuildConstants=$(TeamBuildConstants);SolutionRoot=$(SolutionRoot)" />

 

  <!-- Destop build, need this because $(Buildtype) not defined in desktop scenario-->

  <MSBuild

            Condition="'$(IsDesktopBuild)'=='true' "

            Projects="$(MSBuildProjectFile)"

            Targets="RunCoreCompileWithConfiguration"

            Properties="Platform=%(ConfigurationToBuild.PlatformToBuild);Flavor=%(ConfigurationToBuild.FlavorToBuild);RunCodeAnalysis=$(RunCodeAnalysis);BinariesRoot=$(BinariesRoot);FxCopDir=$(FxCopDir);TeamBuildConstants=$(TeamBuildConstants);SolutionRoot=$(SolutionRoot)" />

 

  <OnError ExecuteTargets="OnBuildBreak;" />

</Target>

 

 

<Target Name="RunCoreCompileWithConfiguration" >

 

  <!-- OutDirForCompile for not Any CPU -->

  <CreateItem

        Condition=" '$(Platform)'!='Any CPU' "

        Include="$(BinariesRoot)\$(Platform)\$(Flavor)\" >

    <Output TaskParameter="Include" ItemName="OutDirForCompile" />

  </CreateItem>

 

  <!-- OutDirForCompile for Any CPU -->

  <CreateItem

        Condition=" '$(Platform)'=='Any CPU' "

        Include="$(BinariesRoot)\$(Flavor)\" >

    <Output TaskParameter="Include" ItemName="OutDirForCompile" />

  </CreateItem>

 

  <!-- OutDir property - This is to ensure we have absolute path as property -->

  <CreateProperty Value="%(OutDirForCompile.FullPath)" >

    <Output TaskParameter="Value" PropertyName="OutDir" />

  </CreateProperty>

 

  <!-- First part of VCOverride file -->

  <CreateProperty Value="%3C?xml version=%221.0%22?%3E%0D%0A%3CVisualStudioPropertySheet ProjectType=%22Visual C++%22 Version=%228.00%22 Name=%22Team Build Overrides%22 OutputDirectory=%22$(OutDir)%22%3E%0D%0A" >

 

    <Output TaskParameter="Value"

        PropertyName="VCOverridesString1"/>

  </CreateProperty>

 

  <!-- Third part of VCOverride file -->

  <CreateProperty Value="%3C/VisualStudioPropertySheet%3E" >

    <Output TaskParameter="Value" 

        PropertyName="VCOverridesString3"/>

  </CreateProperty>

 

  <!-- RunCodeAnalysis option -->

  <CreateProperty

        Condition=" '$(RunCodeAnalysis)'=='Always' "

        Value="RunCodeAnalysis=true" >

    <Output TaskParameter="Value"

        PropertyName="CodeAnalysisOption" />

  </CreateProperty>

 

  <!— 2nd part of VCOverride file when RunCodeAnalysis is always -->

  <CreateProperty

        Condition=" '$(RunCodeAnalysis)'=='Always' "

 

        Value="%09%3CTool Name=%22VCCLCompilerTool%22 EnablePREfast=%22true%22 /%3E%0D%0A%09%3CTool Name=%22VCFxCopTool%22 EnableFxCop=%22true%22 /%3E%0D%0A" >

 

    <Output TaskParameter="Value"

        PropertyName="VCOverridesString2"/>

  </CreateProperty>

 

  <CreateProperty

        Condition=" '$(RunCodeAnalysis)'=='Never' "

        Value="RunCodeAnalysis=false" >

    <Output TaskParameter="Value"

        PropertyName="CodeAnalysisOption" />

  </CreateProperty>

 

  <!-- Second part of VCOverride file when RunCodeAnalysis is never -->

  <CreateProperty

        Condition=" '$(RunCodeAnalysis)'=='Never' "

 

        Value="%09%3CTool Name=%22VCCLCompilerTool%22 EnablePREfast=%22false%22 /%3E%0D%0A%09%3CTool Name=%22VCFxCopTool%22 EnableFxCop=%22false%22 /%3E%0D%0A" >

 

    <Output TaskParameter="Value"

        PropertyName="VCOverridesString2"/>

  </CreateProperty>

 

  <!-- ReferencePath option -->

  <CreateProperty

        Condition=" '@(AdditionalReferencePath)'!='' "

        Value="$(OutDir);@(AdditionalReferencePath)" >

    <Output TaskParameter="Value" PropertyName="ReferencePath" />

  </CreateProperty>

 

  <CreateProperty

        Condition=" '@(AdditionalReferencePath)'=='' "

        Value="$(OutDir)" >

    <Output TaskParameter="Value" PropertyName="ReferencePath" />

  </CreateProperty>

 

  <!-- Generate VCOverride file for C++ projects -->

  <WriteLinesToFile

        File="TFSBuild.vsprops"

        Lines="$(VCOverridesString1)$(VCOverridesString2)$(AdditionalVCOverrides)$(VCOverridesString3)"

 

        Overwrite="true" />

 

  <!-- Build using MSBuild task -->

  <MSBuild

        Condition=" '@(SolutionToBuild)'!='' "

        Projects="@(SolutionToBuild)"

        Properties="Configuration=$(Flavor);Platform=$(Platform);SkipInvalidConfigurations=true;VCBuildOverride=$(MSBuildProjectDirectory)\TFSBuild.vsprops;FxCopDir=$(FxCopDir);OutDir=$(OutDir)%(SolutionToBuild.OutputFolder)\;ReferencePath=$(ReferencePath);TeamBuildConstants=$(TeamBuildConstants);$(CodeAnalysisOption)"

 

        Targets="Build" />

 

 

 

  <!-- Specify SolutionToPublish ItemGroup if you have ClickOnce based solutions or projects that you want to publish. The task below will generate manifest and deployment package. -->

  <MSBuild

        Condition=" '@(SolutionToPublish)'!='' "

        Projects="@(SolutionToPublish)"

        Properties="Configuration=$(Flavor);Platform=$(Platform);SkipInvalidConfigurations=true;VCBuildOverride=$(MSBuildProjectDirectory)\TFSBuild.vsprops;FxCopDir=$(FxCopDir);OutDir=$(OutDir)%(SolutionToPublish.OutputFolder)\;PublishDir=$(OutDir)%(SolutionToPublish.OutputFolder)\;ReferencePath=$(ReferencePath);TeamBuildConstants=$(TeamBuildConstants);$(CodeAnalysisOption)"

 

        Targets="Publish" />

</Target>

  1. In the build type file you need to make the following changes –

  <ItemGroup>   

    <SolutionToBuild Include="$(SolutionRoot)\ClassLibrary1\ClassLibrary1.sln">

      <OutputFolder>Lib</OutputFolder>

    </SolutionToBuild>

    <SolutionToBuild Include="$(SolutionRoot)\ConsoleApplication1\ConsoleApplication1.sln">

      <OutputFolder>App</OutputFolder>

    </SolutionToBuild>

</ItemGroup>

 

Please note the OutputFolder attribute will point to the sub folder under BinariesRoot\<platform>\<configuration>. For example in the above scenario we are interested in building for AnyCPU|Debug, then the output assemblies will be @ $(BinariesRoot)\Debug\Lib\Classlibrary1.dll, $(BinariesRoot)\Debug\App\ClassApplication1.exe.

 

It is not advisable to change the structure to BinariesRoot\<subfolder>\<platform>\<configuration>.

 

Advantages of this approach

  1. Desktop build will not get impacted
  2. If the OutputFolder attribute is not specified, then we revert back to the original behavior
  3. User does not need to make any modifications to the individual .csproj files (big +)

 Disadvantages of this approach

  1. I am not sure of the impact on project to project reference.  
  2. You will get a (dummy) entry under compile steps in report for building tfsbuild.proj. You can just ignore it.
  3. Solution will not work for unmanaged projects (using VCBuild.exe) that uses overrides file. The reason is that we generate the overrides file once and then pass it for all solutions. We can not do task batching while generating the overrides file (because it will impact the order in which solutions will be build) (big -)
  4. Not tested the test task. User might need to specify (hardcode) the correct value of SearchPathRoot property. (big -)

Tested scenarios

  1. Desktop clean/build/rebuild without specifying the OutputFolder attribute
  2. Desktop clean/build/rebuild with OutputFolder attribute defined
  3. End to end build with OutputFolder attribute for all solutions
  4. End to end build with OutputFolder attribute for some solutions

 

Issues in create the instance of singilton class using reflection in C#

 

Scenario -

 

I have a public class (named class1) with only internal constructors. Assume this class exists in assembly class1.dll. Now I want to create the instance of class1 inside another public class (named class2) that exist in another assembly (class2.exe).

 

Case A

 

Class1.dll

 

namespace ns1

{

    public class class1 {

 

        internal class1(int i)

        {

            Console.WriteLine("init ns1.Class1, args value: " + i);

        }

 

        public void print()

        {

            Console.WriteLine("executing ns1.class1.print()");

        }       

    }

}

 

Class2.exe

 

namespace ns2

{

    class Class2

    {

        static void Main(string[] args)

        {

            Assembly assembly = Assembly.LoadFrom(@"c:\Class1.dll");

            Type type = assembly.GetType("ns1.class1", true, true);

           

            ConstructorInfo[] constructors = type.GetConstructors(

BindingFlags.NonPublic | BindingFlags.Instance);

 

            object o = constructors[0].Invoke(

                BindingFlags.Instance | BindingFlags.NonPublic,

                null,

                new object[] { 1 },

                System.Globalization.CultureInfo.InvariantCulture);

 

            // typecasting does not work

            ns1.class1 instance = o as ns1.class1;                    

        }

    }

}

 

The type cast of object o to ns1.class1 fails. Program things that ns1.class1 defined in assembly loaded form c:\class1.dll is different that the one declared in the statement but they are exactly the same. Can anyone explain the reason? Is it because I am not using Binder?

 

Case B

 

Class1.dll

 

namespace ns1

{

    public class class1:Stack {

 

        internal class1(int i)

        {

            Console.WriteLine("init ns1.Class1, args value: " + i);

        }

 

        public void print()

        {

            Console.WriteLine("executing ns1.class1.print()");

        }       

    }

}

 

Class2.exe

 

namespace ns2

{

    class Class2

    {

        static void Main(string[] args)

        {

            Assembly assembly = Assembly.LoadFrom(@"c:\Class1.dll");

            Type type = assembly.GetType("ns1.class1", true, true);

           

            ConstructorInfo[] constructors = type.GetConstructors(

BindingFlags.NonPublic | BindingFlags.Instance);

 

            object o = constructors[0].Invoke(

                BindingFlags.Instance | BindingFlags.NonPublic,

                null,

                new object[] { 1 },

                System.Globalization.CultureInfo.InvariantCulture);

 

            // typecasting works

            Stack instance = o as Stack;

 

            MethodInfo[] methods = type.GetMethods();

            methods[0].Invoke(o, null);                       

        }

    }

}

 

The type casting of object o to Stack works because the type is predefined and compiler has no issue is finding the standard definition or maybe binding works by default. I can even invoke print method on the object. Another interesting observation is that there is no object slicing happening in C# unlike C++. This is because the everything is passed by reference in C# (thanks to Abhinab who pointed out the reason).  

 

Overriding precedence for properties in MSBuild

 

You can set the value of property in the following manner

  1. pass the value in proj/targets file inside PropertyGroup tag
  2. pass the value in the msbuild command line using /p switch
  3. pass the value of the property in rsp file using /property switch
  4. pass the value by using CreateProperty task

Precedence order

  1. Value specified using CreateProperty task will always have the highest precedence.
  2. Value specified in task/targets file under PropertyGroup tag will have the least precedence
  3. Value specified using command line or rsp file have equal precedence. The one passed later will override the earlier one. For example if rsp file set Name = “RspName” and the command line set Name = “CmdName”.
    1. Calling msbuild @rspfile /p:Name=CmdName will set the value of Name property to CmdName
    2. Calling msbuild /p:Name=CmdName @rspfile will set the value of Name property to RspName

Problem referencing different versions of same assembly in Team Build (issue with file references)

 

Problem - Build two projects referencing the assembly of same name but of different version fails in Team Build

Scenario - Consider two projects (Proj1 and Proj2) that refer to the same assembly (lib.dll) of different versions (1.0.0.0 and 2.0.0.0). For example

$/TP1/Common/v1/lib.dll

$/TP1/Common/v2/lib.dll

$/TP1/Proj1/Proj1.sln

$/TP1/Proj1/Proj1/Proj1.csproj (file reference to ..\..\Common\v1\lib.dll,1.0.0.0)

...

$/TP1/Proj2/Proj2.sln

$/TP1/Proj2/Proj2/Proj2.csproj (file reference to ..\..\Common\v2\lib.dll,2.0.0.0) 

...

Investigation result - This works fine on desktop scenario but is broken inside Team Build.

The user has to make sure

  1. Specific Version” flag is be set for the file references
  2. User has common folder checked in at the correct location inside source control
  3. Private” flag (for copy local for the referenced assembly) is set to false.  

The undesirable effect for the above mentioned changes is that user will not be able to run the assembly from the drop site. For example in our scenario –

  • Build will be successful.
  • At the drop site you will have binaries for proj1 and proj2.
  • Assemblies will not be usable because they will need lib.dll (1.0.0.0 & 2.0.0.0) at drop site but we can not have two files with same name at drop site
  • If we set “Private” flag to true for proj1.csproj and proj2.csproj then also assemblies will not be usable because as part of build process for proj1.csproj, the lib.dll (1.0.0.0) will be copied to $(outdir) (binaries folder). The build for proj2.csproj will override the lib.dll (2.0.0.0). This will result in unpredictable results.
    • If lib.dll (both v1 and v2) contain same methods that does different things, then assemblies for proj2 and proj1 will show wrong behavior.
    • If lib.dll (both v1 and v2) contains different method signatures then application will crash.

Recommendations/workarounds

  1. Avoid using file references
  2. If you need to use file references – different version of same assembly, then you can not run the assembly directly from the drop site and you need to have custom steps to copy the assembly to separate folder (with correct versions of referenced assemblies)

Related [post 

 

Confusing behavior of MSBuild logger on raising LogError event

 

Today I found that raising the error message was not enough for the msbuild to stop and invoke the targets specified by OnError tag. For example in the given sample -   

 

Custom task:- MSBuildTask.dll

 

namespace MSBuildTask

{

    public class MSBuildTask : Task

    {

        public override bool Execute()

        {

 

            string msg = " message before raising error event";

            base.BuildEngine.LogMessageEvent(

new BuildMessageEventArgs( msg, String.Empty,

String.Empty, MessageImportance.Normal));

 

            msg = " error message ";

            base.BuildEngine.LogErrorEvent(

                  new BuildErrorEventArgs(String.Empty, String.Empty,

String.Empty, 0, 0, 0, 0, msg, String.Empty,

String.Empty));

 

            msg = "error message after error event";

            base.BuildEngine.LogMessageEvent(

               new BuildMessageEventArgs(msg, String.Empty,

                  String.Empty, MessageImportance.Normal));               

 

            return true;

 

        }

    }

}

 

Project file

 

<Project

xmlns="http://schemas.microsoft.com/developer/msbuild/2003">

<UsingTask TaskName="MSBuildTask.MSBuildTask"

      AssemblyFile="MSBuildTask.dll" />  

 

  <Target Name="TestTarget">

       <Message Text=" Before starting the custom task "/> 

       <MSBuildTask ContinueOnError="true"/>      

       <Message Text=" After running the custom task "/>

 

       <OnError ExecuteTargets="TargetExecOnError;" />

  </Target>

  <Target Name="TargetExecOnError">

     <Message Text=" Task to be executed on error "/> 

  </Target> 

</Project>

 

Irrespective of whether you set ContinueOnError to true or false, you will never be able to execute targets specified by OnError tag. Moreover you will get the message “error message after error eventon console. This is contrary to believe I had that raising error event in MSBuild is equivalent to raising an exception in code and the rest of task code/following targets will not execute. This is not true.

 

Findings

  1. The targets specified by OnError tag will be executed only when task returns false. If you are raising the error event in your custom msbuild task then it is your responsibility to return false status and make task fails.
  2. Raising the error event is not equivalent to raising the exception. As shown in the example above, you will always get the message “After running the custom task”

  

AT degradation when multiple instances of GenCheckinNotesUpdateWortItem task are happening concurrently ...

 

Investigation for the observation made in post.

 

    "CPU consumption on AT went up to the maximum of 38% (w3wp.exe) while the GenCheckinNotesUpdateWorkitem task was executing on the build machine."

 

Case 1 - 3 msbuild processes in separate window - running GenCheckinNotesUpdateWorkitem on same team project for the first time

  • 5000 changesets distributed over 5000 files
  • Results
    • Max CPU spike – 29% (on AT)
    • Avg CPU  ~ 7% to 14%
    • Total time taken - ~ 17 mins

 

Case 2  - 3 msbuild processes in separate window – running GenCheckinNotesUpdateWorkitem on same different project for the first time

  • 5000 changesets distributed over 5000/400/4 files
  • Results
    • Max CPU spike – 34% (on AT)
    • Avg CPU  ~ 7% to 14%
    • Total time taken - ~ 15 mins

Conclusion -

 

There are no issues in running multiple instances of builds concurrently for the same AT.

 

Why GenCheckinNotesUpdateWorkitem task is expensive !!!

 

Performance data for GenCheckinNotesUpdateWortItem task (all times in ms)

1.       For MSTV project (~ 17 MB)

a.       Failed 1st build -

Time in Task – 110848

QueryLabel – 990

AnalyzeChangeset – 105870

QueryHistory – 105800

UpdateWorkItem – 3737

Get_WorkItem - 3737

b.       Successful 1st build -

Time in Task – 121108

QueryLabel – 1053

AnalyzeChangeset – 117615

QueryHistory – 117458.51

UpdateWorkItem – 2184

Get_WorkItem - 2184

c.       Successful 2nd build (17 edits, 4 deletes, 3 adds) -

Time in Task – 14250

QueryLabel – 2005

AnalyzeChangeset – 3177

QueryHistory – 3127.65

UpdateWorkItem – 7513

Get_WorkItem - 7512

Learning

·         AnalyzeChangesets() function is the most expensive function call. Analyzing further, most of the cost is in QueryHistory(). Moreover QueryHistory is getting called once per file making cumulative cost high.

o        Why is QueryHistory() call expensive? Because it uses “yield” construct to give better response time in UI. Please refer the documentation for more details.

·         If the number of files is less, the performance of task is limited by cost of QueryLabel(). Again this is dependent of the number of labels that exist on AT

Machine Details 

  •  Client/BM on the same machine with configuration @ 2.79 GHz, 512 MB RAM
  •  AT configuration @ 3 GHz, 1 GB RAM

  

More Posts Next page »
 
Page view tracker