Control Where the Build System Places Your Binaries

The default build process (as defined in DefaultTemplate.xaml), drops the binaries that it compiles from all your code projects into a single directory. However, in some cases, you want to organize the binaries into a more granular and organized folder structure.

You can use techniques in this topic to create a custom build process that drops your binaries into a directory structure that you design. You can also customize build processes in different ways by using the same principles. This topic explains the following techniques:

  • Customize the Windows Workflow segment of the build process. You should make changes in this segment to customize most aspects of your build process except for compilation and handling of binaries. Specifically, this topic describes how to perform the following tasks:

    • Create a custom build process by modifying a copy of the Default Template (DefaultTemplate.xaml).

    • Declare and use arguments to pass data into the workflow.

    • Declare and use variables to collect and pass data throughout the workflow.

    • Modify how the workflow uses the MSBuild activity to call MSBuild.

    • Download a file to the build server and use the ConvertWorkspaceItem activity to make that file available to the build process.

  • Customize the MSBuild segment of the build process. By making changes in this segment, you can more effectively customize how binaries are compiled and handled. Specifically, this topic describes how to perform the following tasks:

    • Pass arguments to MSBuild and then use them in your code projects to change how your compiled binaries are handled.

    • Set up a centralized common code library of your own MSBuild elements, such as property groups or targets. By setting up this type of library, you enable your team to easily reuse and modify key pieces of your build process logic.

Note

This topic covers three types of code projects: C#, C++, and Visual Basic. However, you might be able to use the techniques for other types of code projects.

In this topic

  • Required Permissions

  • Where the default build process puts compiled binaries

  • Organize compiled binaries by using logic that is embedded in each code project

    • Overview of the process

    • Overview of the steps that you follow

    • Create the build definition and the CustomOutputDirInline custom build process template

    • Embed the drop folder logic in your code projects

  • Organize compiled binaries by using logic that is maintained in two centralized files

    • Overview of the process

    • Overview of the steps that you follow

    • Create the MSBuild common code files that contain the drop folder logic

    • Create the build definition and the CustomOutputDirImport custom build process template

    • Update the OurTeamBuild build definition

    • Import the drop folder logic into your code projects

  • Next Steps

Required Permissions

To perform the following procedures, you must have the following permissions set to Allow:

  • Edit build definition.

  • Check out and Check in for the relevant version control directories.

  • Queue builds.

For more information, see Team Foundation Server Permissions.

Where the default build process drops compiled binaries

No matter how your work is divided into code projects and solutions, the default build process puts all the compiled binaries into a single subdirectory in your drop folder. For example, you could have the following solutions and code projects:

  • SolutionA

    • CPPWin32ConsoleApp (a Visual C++ console application)

    • CSharpConsoleApp (a Visual C# console application)

  • SolutionB

    • VBConsoleApp (a Visual Basic console application)

The following diagram illustrates how and where MSBuild drops the binaries after they have been compiled as part of the process that is specified in DefaultTemplate.xaml.

Default flow

Organize compiled binaries by using logic that is embedded in each code project

You can specify that the compiled binaries will be dropped into a subdirectory structure that matches the structure of your solutions and code projects.

Overview of the process

The following diagram illustrates at a high level how you can implement such a build process:

Flow with custom output logic embedded

Overview of the steps that you follow

In short, you can perform the following steps to create this kind of custom build process based on DefaultTemplate.xaml:

  • Step 1 Build definition and build process template

    • Create a build definition (named, for example, OurTeamBuild). On the Process tab, base the build definition on a new build process template (named, for example, CustomOutputDirInline.xaml).

    • In CustomOutputDirInline.xaml, perform the following steps in the instance of the Run MSBuild for Project activity that compiles the code:

      • Disable the OutDir property of the Run MSBuild for Project activity by removing its value to make it an empty string.

        Note

        If you do not make this change, the OutDir property overrides any customized directory structure logic that you implement in the code project.

      • Collect the string value that contains the drop folder of the build agent from the BinariesDirectory variable, and pass it into an MSBuild argument (named, for example, TeamBuildOutDir).

  • Step 2 Code projects

    • In each code project that the OurTeamBuild build compiles, add the appropriate element (either OutputPath or OutDir) to define the subdirectory structure that will be created in your drop folders.

The following subsections explain in detail how to perform these steps.

Create the build definition and the CustomOutputDirInline custom build process template

You lay the foundation of your build process by creating a build definition and basing it on a new build process template.

To create the build definition and the build process template

  1. Create a build definition.

    1. On the General tab, give the build definition a name (for example, OurTeamBuild).

    2. On the Process tab, add the solutions that you want to build.

      For more information, see Create a Basic Build Definition.

  2. On the Process tab of the OurTeamBuild build definition, set the Build process template to a new build process template that is named CustomOutputDirInline.xaml and that is based on the Default Template (DefaultTemplate.xaml).

    For more information, see Create and Work with a Custom Build Process Template.

  3. In Source Control Explorer, open your team project, and display the folder that contains your build process templates.

    By default, this subdirectory is named BuildProcessTemplates.

  4. Check out and then double-click the CustomOutputDirInline.xaml file that you created earlier in this procedure.

  5. In the workflow designer, find the second instance of the Run MSBuild for Project activity, which is located in the following structure:

    1. Sequence (Sequence) >

    2. Run On Agent (AgentScope) >

    3. Try Compile, Test, and Associate Changesets and Work Items (TryCatch [Try]) >

    4. Sequence (Sequence) >

    5. Compile, Test, and Associate Changesets and Work Items (Parallel) >

    6. Try Compile and Test TryCatch [Try] >

    7. Compile and Test Sequence >

    8. For Each Configuration in BuildSettings.PlatformConfigurationsForEach [Body] >

    9. Compile and Test for Configuration Sequence >

    10. If BuildSettings.HasProjectsToBuild If [Then] >

    11. For Each Project in BuildSettings.ProjectsToBuildForEach [Body] >

    12. Try to Compile the Project TryCatch [Try] >

    13. Compile the Project Sequence >

    14. Run MSBuild for Project MSBuild

    For information about how to navigate this structure, see Navigate in a Complex Windows Workflow.

  6. Right-click the Run MSBuild for Project activity, and then click Properties.

  7. In the Properties pane, remove the data in the OutDir box to set this property to an empty string.

  8. In the Properties pane, set the CommandLineArguments property to the following value:

    String.Format("/p:SkipInvalidConfigurations=true;TeamBuildOutDir=""{0}"" {1}",
    BinariesDirectory, MSBuildArguments)
    
  9. Save CustomOutputDirInline.xaml.

  10. In Source Control Explorer, check in your changes to this file.

Embed the drop folder logic in your code projects

Now that you have created your build definition and custom build process template, you must embed the directory structure logic in each code project that this build process compiles.

Note

You cannot embed this logic in the workflow itself because the DefaultTemplate.xaml workflow does not iterate and run each project through the MSBuild compiler. Instead, the workflow calls MSBuild once to compile all the solutions and projects.

To embed the drop folder logic

  1. In Source Control Explorer, open a solution that the OurTeamBuild build definition builds.

  2. Add the required output directory element to each code project in the solution. For managed code projects such as Visual C# or Visual Basic, this property is OutputPath. For Visual C++ projects, this property is OutDir. For each code project in the solution, follow these steps:

    1. In Solution Explorer, right-click the project. If the Unload Project command is available, click it.

    2. Right-click the project, and then click Edit ProjectName.

    3. Perform one of the following steps:

      • If the project is a managed code project, such as Visual C# or Visual Basic: add an OutputPath element. You must position this element after the last OutputPath element that already is in the code project, as the following example shows:

        <Project DefaultTargets="Build" xmlns="https://schemas.microsoft.com/developer/msbuild/2003 ...">
         ...
        <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x86'">
         ...
         <OutputPath>bin\Debug\</OutputPath>
         ...
        </PropertyGroup>
        <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x86' ">
         ...
         <OutputPath>bin\Release\</OutputPath>
         ...
        </PropertyGroup>
        
        <PropertyGroup Condition="$(TeamBuildOutDir) != '' ">
        <OutputPath>$(TeamBuildOutDir)\$(SolutionName)\$(MSBuildProjectName)\$(Configuration)</OutputPath>
        </PropertyGroup>
        
      • If the project is a Visual C++ project: add an OutDir element. You must position this element before the element that imports Microsoft.Cpp.targets, as the following example shows:

        <Project DefaultTargets="Build" xmlns="https://schemas.microsoft.com/developer/msbuild/2003 ...">
        ...
        
        <PropertyGroup Condition="$(TeamBuildOutDir) != '' ">
         <OutDir>$(TeamBuildOutDir)\$(SolutionName)\$(MSBuildProjectName)\$(Configuration)\</OutDir>
        </PropertyGroup>
        
        <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
        </Project>
        
    4. Save the code project.

  3. In Solution Explorer, right-click the solution, and then click Check In.

  4. Repeat these steps for each solution that OurTeamBuild builds.

Organize compiled binaries by using logic that is maintained in two central files

If you have many code projects to maintain, you can improve the process that the previous section describes if you keep the OutputPath and OutDir elements in two shared files. If you take this approach, you can change the directory structure of your drop folder more easily by modifying two centralized files instead of each code project.

Overview of the process

The following diagram illustrates at a high level how you can implement such a build process:

Flow with custom output logic imported

Overview of the steps that you follow

In short, you must perform the following steps to create this kind of custom build process based on DefaultTemplate.xaml:

  • In Source Control Explorer, create a directory (named, for example, $/OurTeam/BuildProcessMSBuild) to contain your common MSBuild code. In this directory, create and store the MSBuild files that define the subdirectory structure that will be created in your drop folders.

  • Step 1 Build definition and build process template

    • Update the build definition (named, for example, OurTeamBuild) by following these steps:

      • On the Workspace tab, map the $/OurTeam/BuildProcessMSBuild directory.

      • On the Process tab, base the build definition on a new build process template (named, for example, CustomOutputDirImport.xaml).

    • In CustomOutputDirImport.xaml, follow these steps:

      • Declare LocalPathToMSBuildCode as a String variable that is scoped to the Run On Agent activity.

      • Declare ServerPathToMSBuildCode as an argument.

        After you add this argument, you must modify the OurTeamBuild definition. On the Process tab, type $/OurTeam/BuildProcessMSBuild as the value of this build process parameter.

      • In the Run on Agent > activity, before the Try Compile, Test, and Associate Changesets and Work Items [Try] activity, add a ConvertWorkspaceItem activity to convert the ServerPathToMSBuildCode argument into a local path on the build agent that can MSBuild can process. You then put this value in the LocalPathToMSBuildCode variable.

      • In the instance of the Run MSBuild for Project activity that compiles the code, follow these steps:

        • Disable the OutDir property of the Run MSBuild for Project activity by removing its value to make it an empty string.

          Note

          If you do not make this change, the OutDir property overrides any customized directory structure logic that you implement in the code project.

        • Collect the string value that contains the drop folder of the build agent from the BinariesDirectory variable, and pass that value into MSBuild as an argument (named, for example, TeamBuildOutDir).

        • Pass the value of LocalPathToMSBuildCode into MSBuild as an argument (named, for example, CommonMSBuildCode).

  • Step 2 Code projects

    • In each code project that OurTeamBuild compiles, add an <Import /> element in the appropriate location.

The following subsections explain in detail how to follow these steps.

Create the MSBuild common code files that contain the drop folder logic

For this approach, you start by creating a directory and two MSBuild project files. These files contain the logic that defines the subdirectory structure that will be created in your drop folders.

To create the files

  1. In Source Control Explorer, perform one of the following steps:

    • Locate the directory where you store your MSBuild common code.

    • Create a directory to store your MSBuild common code, and name it, for example, $/OurTeam/BuildProcessMSBuild.

  2. If the Not mapped link appears next to the Local Path label at the top of Source Control Explorer, click the link to map the server directory to the appropriate directory in your local workspace.

  3. Create, save, and check the following files in to $/OurTeam/BuildProcessMSBuild:

    • A file to contain the drop folder logic for managed code projects, such as Visual C# or Visual Basic (named, for example, ManagedCodeProjectOutDir.targets).

      <Project xmlns="https://schemas.microsoft.com/developer/msbuild/2003">
      <PropertyGroup Condition="$(TeamBuildOutDir) != '' ">
       <OutputPath>$(TeamBuildOutDir)\$(SolutionName)\$(MSBuildProjectName)\$(Configuration)</OutputPath>
      </PropertyGroup>
      </Project>
      
    • A file to contain the drop folder logic for Visual C++ code projects (named, for example, CPPCodeProjectOutDir.targets).

      <Project xmlns="https://schemas.microsoft.com/developer/msbuild/2003">
      <PropertyGroup Condition="$(TeamBuildOutDir) != '' ">
       <OutDir>$(TeamBuildOutDir)\$(SolutionName)\$(MSBuildProjectName)\$(Configuration)\</OutDir>
      </PropertyGroup>
      </Project>
      

Create the build definition and the CustomOutputDirImport custom build process template

You can re-use the build definition that you created earlier in this topic and named OurTeamBuild. You will base it on a new build process template and make additional adjustments.

To create the build definition and the build process template

  1. In Team Explorer, right-click OurTeamBuild, and then click Edit.

  2. Click the Process tab, and then set the Build process template to a new build process template that is named CustomOutputDirImport.xaml and that is based on the Default Template (DefaultTemplate.xaml).

    For more information, see Create and Work with a Custom Build Process Template.

  3. In Source Control Explorer, open your team project, and display the folder that contains your build process templates. By default, the name of this subdirectory is BuildProcessTemplates.

  4. Check out and then double-click the CustomOutputDirImport.xaml file that you created earlier in this procedure.

  5. In the workflow designer, find the Run on Agent activity, which is located in the following structure:

    1. Sequence (Sequence) >

    2. Run On Agent (AgentScope) >

    For information about how to navigate this structure, see Navigate in a Complex Windows Workflow.

  6. At the bottom of the window, click Arguments.

  7. In the Arguments pane, create an argument, and name it ServerPathToMSBuildCode.

  8. In the Properties pane, select the IsRequired check box.

  9. At the bottom of the window, click Variables.

  10. In the Variables pane, declare a variable that is named LocalPathToMSBuildCode with a type of String and a Scope of Run On Agent.

  11. Drag the ConvertWorkspaceItem activity from the Team Foundation Build Activities section of the Toolbox to the location between the Initialize Workspace and If CreateLabel activities.

    Note

    If the Team Foundation Build Activities section does not appear in the Toolbox, you can manually add this section from the Microsoft.TeamFoundation.Build.Workflow.dll assembly. For more information, see How to: Add Activities to the Toolbox.

  12. Right-click the ConvertWorkspaceItem activity, and then click Properties.

  13. In the Properties pane, set the following property values:

    • Display Name: Get Local Path to MSBuild Code

    • Input: ServerPathToMSBuildCode

    • Result: LocalPathToMSBuildCode

    • Workspace: Workspace

  14. Find the second instance of the Run MSBuild for Project activity, which is located in the following structure:

    1. Sequence (Sequence) >

    2. Run On Agent (AgentScope) >

    3. Try Compile, Test, and Associate Changesets and Work Items (TryCatch [Try]) >

    4. Sequence (Sequence) >

    5. Compile, Test, and Associate Changesets and Work Items (Parallel) >

    6. Try Compile and Test TryCatch [Try] >

    7. Compile and Test Sequence >

    8. For Each Configuration in BuildSettings.PlatformConfigurationsForEach [Body] >

    9. Compile and Test for Configuration Sequence >

    10. If BuildSettings.HasProjectsToBuild If [Then] >

    11. For Each Project in BuildSettings.ProjectsToBuildForEach [Body] >

    12. Try to Compile the Project TryCatch [Try] >

    13. Compile the Project Sequence >

    14. Run MSBuild for Project MSBuild

    For information about how to navigate this structure, see Navigate in a Complex Windows Workflow.

  15. Right-click the Run MSBuild for Project activity, and then click Properties.

  16. In the Properties pane, remove the data in the OutDir box to set this property to an empty string.

  17. In the Properties pane, set the CommandLineArguments property to the following value:

    String.Format("/p:SkipInvalidConfigurations=true;CommonMSBuildCode=""{0}"";TeamBuildOutDir=""{1}"" {2}",
    LocalPathToMSBuildCode, BinariesDirectory, MSBuildArguments)
    
  18. Save CustomOutputDirImport.xaml.

    In Source Control Explorer, check in your changes to this file.

Update the OurTeamBuild build definition

Next, you must changes to the OurTeamBuild build definition.

To update the build definition

  1. In Team Explorer, expand the team project that you are working in, expand the Builds folder, right-click the OurTeamBuild build definition, and then click Edit Build Definition.

  2. Click the Workspace tab, and then add an entry that has the following values:

    • Status: Active

    • Source Control Folder: $/OurTeam/BuildProcessMSBuild

    • Build Agent Folder: $(SourceDir)\BuildProcessMSBuild

  3. Click the Process tab, and type $/OurTeam/BuildProcessMSBuild in the ServerPathToMSBuildCode box.

  4. Save the build definition.

Import the drop folder logic into your code projects

After you create your build definition and your custom build process template, you must update your code projects to import the directory structure logic.

To import the drop folder logic

  1. In Source Control Explorer, double-click a solution that OurTeamBuild builds.

  2. For each code project in the solution, follow these steps:

    1. In Solution Explorer, right-click the project. If the Unload Project command is available, click it.

    2. Right-click the project, and then click Edit ProjectName.

    3. Perform one of the following steps:

      • If the project is a managed code project such as Visual C# or Visual Basic: add an Import element after the last OutputPath element that is already in the code project, as the following example shows:

        <Project DefaultTargets="Build" xmlns="https://schemas.microsoft.com/developer/msbuild/2003 ...">
         ...
        <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x86'">
         ...
         <OutputPath>bin\Debug\</OutputPath>
         ...
        </PropertyGroup>
        <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x86' ">
         ...
         <OutputPath>bin\Release\</OutputPath>
         ...
        </PropertyGroup>
        
        <Import Condition=" $(CommonMSBuildCode) != ''" Project="$(CommonMSBuildCode)\ManagedCodeProjectOutDir.targets"/>
        
      • If the project is a Visual C++ project: add an Import element before the element that imports Microsoft.Cpp.targets, as the following example shows:

        <Project DefaultTargets="Build" xmlns="https://schemas.microsoft.com/developer/msbuild/2003 ...">
        ...
        <Import Condition=" $(CommonMSBuildCode) != ''" Project="$(CommonMSBuildCode)\CPPCodeProjectOutDir.targets"/>
        
        <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
        </Project>
        
    4. Save the code project.

  3. In Solution Explorer, right-click the solution, and then click Check In.

  4. Repeat the previous steps for each solution that OurTeamBuild builds.

Next Steps

To follow up, you might perform the following tasks:

  • Modify the drop folder logic. To meet the requirements of your team, you can modify the contents of the OutputPath and OutDir elements that earlier sections offered.

  • Save your customized code project as a template. If your team will create many code projects, you can automatically include your custom MSBuild logic in new code projects. In Solution Explorer, click the code project, open the File menu, and then click Export Template.

Additional Resources

You can find additional information in the following topics on the Microsoft website:

See Also

Concepts

Using Source Control Explorer

Create a Workspace to Work with your Team Project

Other Resources

MSBuild Reference