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