默认生成过程(如 DefaultTemplate.xaml 中所述)会将其从所有代码项目中编译的二进制文件放入单个目录中。 但是,在某些情况下,您需要将二进制文件组织到更细化、更具组织性的文件夹结构中。
您可以使用本主题中的技术来创建自定义生成过程,以便将您的二进制文件放入您设计的目录结构中。 您还可以根据相同的原则通过不同的方式自定义生成过程。 本主题将说明以下技术:
- 自定义生成过程的 Windows 工作流段。 您应更改此段以自定义除二进制文件编译和处理之外的生成过程的大多数方面。 具体而言,本主题将介绍如何执行以下任务:
- 通过修改默认模板 (DefaultTemplate.xaml) 的副本创建自定义生成过程。
- 声明和使用参数以将数据传入工作流中。
- 声明和使用变量以通过工作流收集和传递数据。
- 修改工作流使用 MSBuild 活动调用 MSBuild 的方式。
- 将文件下载到生成服务器并使用 ConvertWorkspaceItem 活动将该文件提供给生成过程。
- 自定义生成过程的 MSBuild 段。 通过更改此段,您可以更有效地自定义二进制文件的编译和处理方式。具体而言,本主题将介绍如何执行以下任务:
- 将参数传递到 MSBuild,然后在代码项目中使用它们来更改已编译的二进制文件的处理方式。
- 设置您自己的 MSBuild 元素(如属性组或目标)的集中式通用代码库。 通过设置此类型的库,您的团队就能轻松地重用和修改一些关键的生成过程逻辑。
注意 |
本主题涵盖三种类型的代码项目:C#、C++ 和 Visual Basic。 但是,您或许能够使用其他类型的代码项目的技术。 |
主题内容
若要执行以下过程,必须将以下权限设置为“允许”:
- 编辑生成定义。
- 相关版本控制目录的签出和签入。
- 将生成排队。
有关更多信息,
无论您的工作如何划分到代码项目和解决方案中,默认生成过程都会将所有已编译的二进制文件放入您的放置文件夹中的单个子目录。例如,您可以具有以下解决方案和代码项目:
- SolutionA
- CPPWin32ConsoleApp(Visual C++ 控制台应用程序)
- CSharpConsoleApp(Visual C# 控制台应用程序)
- SolutionB
- VBConsoleApp(Visual Basic 控制台应用程序)
下图演示了将二进制文件作为 DefaultTemplate.xaml 中指定的过程的一部分进行编译后,MSBuild 放置这些文件的方式和位置。
您可以指定将已编译的二进制文件放入与您的解决方案和代码项目的结构匹配的子目录结构中。
过程概述
下图演示如何在高级别实现这类生成过程:
您要遵循的步骤概述
简而言之,您可以执行以下步骤以根据 DefaultTemplate.xaml 创建此类自定义生成过程:
- 生成定义和生成过程模板
- 创建生成定义(例如,名为 OurTeamBuild)。在“过程”选项卡上,使生成定义基于新的生成过程模板(例如,名为 CustomOutputDirInline.xaml)。
- 在 CustomOutputDirInline.xaml 中,在编译代码的 Run MSBuild for Project 活动的实例中执行以下步骤:
- 删除 Run MSBuild for Project 活动的 OutDir 属性的值使其成为空字符串,以禁用该属性。
注意 | |
如果未进行此更改,OutDir 属性将覆盖在代码项目中实现的任何已自定义的目录结构逻辑。 |
- 从 BinariesDirectory 变量收集包含生成代理的放置文件夹的字符串值,并将其传入到 MSBuild 参数(例如,名为 TeamBuildOutDir)。
- 代码项目
- 在 OurTeamBuild 生成所编译的每个代码项目中,添加相应的元素(OutputPath 或OutDir)以定义将在您的放置文件夹中创建的子目录结构。
以下小节详细说明了如何执行这些步骤。
创建生成定义和 CustomOutputDirInline 自定义生成过程模板
通过创建生成定义并使其基于新的生成过程模板,建立生成过程的基础。
创建生成定义和生成过程模板
- 创建生成定义。
- 在“常规”选项卡上,为生成定义指定名称(如 OurTeamBuild)。
- 在“过程”选项卡上,添加要生成的解决方案。
- 在 OurTeamBuild 生成定义的“过程”选项卡上,将生成过程模板设置为名为CustomOutputDirInline.xaml 且基于默认模板 (DefaultTemplate.xaml) 的新生成过程模板。
有关更多信息,在“源代码管理资源管理器”中,打开团队项目,并显示包含生成过程模板的文件夹。
默认情况下,此子目录名为 BuildProcessTemplates。 - 签出并双击在此过程前面创建的 CustomOutputDirInline.xaml 文件。
- 在工作流设计器中,查找 Run MSBuild for Project 活动的第二个实例,该实例位于以下结构中:
- 序列 (Sequence) >
- 在代理上运行 (AgentScope) >
- 尝试编译、测试和关联变更集和工作项 (TryCatch [Try]) >
- 序列 (Sequence) >
- 编译、测试和关联变更集和工作项 (Parallel) >
- 尝试编译和测试 TryCatch [Try] >
- 编译和测试 Sequence >
- 针对 BuildSettings.PlatformConfigurations 中的每个配置ForEach [Body] >
- 针对配置编译和测试 Sequence >
- 如果 BuildSettings.HasProjectsToBuild If [Then] >
- 针对 BuildSettings.ProjectsToBuild 中的每个项目ForEach [Body] >
- 尝试编译项目 TryCatch [Try] >
- 编译项目 Sequence >
- 针对项目运行 MSBuild MSBuild
- 有关如何导航此结构的信息右击 Run MSBuild for Project 活动,然后单击“属性”。
- 在“属性”窗格中,删除 OutDir 框中的数据以将此属性设置为空字符串。
- 在“属性”窗格中,将 CommandLineArguments 属性设置为以下值:
String.Format("/p:SkipInvalidConfigurations=true;TeamBuildOutDir=""{0}"" {1}", BinariesDirectory, MSBuildArguments) - 保存 CustomOutputDirInline.xaml。
- 在“源代码管理资源管理器”中,将更改签入此文件。
将放置文件夹逻辑嵌入您的代码项目中
既然已创建生成定义和自定义生成过程模板,那么必须在此生成过程所编译的每个代码项目中嵌入目录结构逻辑。
注意 |
您不能将此逻辑嵌入工作流本身中,因为 DefaultTemplate.xaml 工作流不会通过 MSBuild 编译器循环访问和运行每个项目。相反,工作流会调用一次 MSBuild 以编译所有解决方案和项目。 |
嵌入放置文件夹逻辑
- 在“源代码管理资源管理器”中,打开 OurTeamBuild 生成定义生成的解决方案。
- 向解决方案中的每个代码项目添加必需的输出目录元素。对于托管代码项目(如 Visual C# 或 Visual Basic),此属性为 OutputPath。对于 Visual C++ 项目,此属性为 OutDir。对于解决方案中的每个代码项目,请按照以下步骤进行操作:
- 在“解决方案资源管理器”中右击项目。如果“卸载项目”命令可用,则单击它。
- 右击项目,然后单击“编辑 ProjectName”。
- 执行以下步骤之一:
- 如果该项目是托管代码项目(如 Visual C# 或 Visual Basic):添加 OutputPath 元素。您必须将此元素放在已位于代码项目中的最后一个 OutputPath 元素之后,如下面的示例所示:
<Project DefaultTargets="Build" xmlns="http://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> - 如果该项目是 Visual C++ 项目:添加 OutDir 元素。您必须将此元素放在导入Microsoft.Cpp.targets 的元素之前,如下面的示例所示:
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003 ..."> ... <PropertyGroup Condition="$(TeamBuildOutDir) != '' "> <OutDir>$(TeamBuildOutDir)\$(SolutionName)\$(MSBuildProjectName)\$(Configuration)\</OutDir> </PropertyGroup> <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" /> </Project>
- 保存代码项目。
- 在解决方案资源管理器中,右击解决方案,然后单击“签入”。
- 针对 OurTeamBuild 生成的每个解决方案,重复上述步骤。
如果要维护多个代码项目,则可以通过将 OutputPath 和 OutDir 元素保留在两个共享文件中来改进上一节所述的过程。如果采用此方法,则通过修改两个集中文件(而不是每个代码项目),可以更轻松地更改放置文件夹的目录结构。
过程概述
下图演示如何在高级别实现这类生成过程:
您要遵循的步骤概述
简而言之,您必须执行以下步骤以根据 DefaultTemplate.xaml 创建此类自定义生成过程:
- 在“解决方案资源管理器”中,创建目录(例如,名为 $/OurTeam/BuildProcessMSBuild)来包含通用 MSBuild 代码。在此目录中,创建并存储用于定义将在您的放置文件夹中创建的子目录结构的 MSBuild 文件。
- 生成定义和生成过程模板
- 通过执行以下步骤更新生成定义(例如,名为 OurTeamBuild):
- 在“工作区”选项卡上,映射 $/OurTeam/BuildProcessMSBuild 目录。
- 在“过程”选项卡上,使生成定义基于新的生成过程模板(例如,名为CustomOutputDirImport.xaml)。
- 在 CustomOutputDirImport.xaml 中执行以下步骤:
- 将 LocalPathToMSBuildCode 声明为范围限制到 Run On Agent 活动的 String 变量。
- 将 ServerPathToMSBuildCode 声明为参数。
添加此参数后,必须修改 OurTeamBuild 定义。在“过程”选项卡中,键入$/OurTeam/BuildProcessMSBuild 作为此生成过程参数的值。 - 在 Run on Agent > 活动中,在 Try Compile, Test, and Associate Changesets and Work Items [Try] 活动之前添加一个 ConvertWorkspaceItem活动以将 ServerPathToMSBuildCode 参数转换为 MSBuild 可以处理的生成代理上的本地路径。然后,将此值放入 LocalPathToMSBuildCode 变量。
- 在用于编译代码的 Run MSBuild for Project 活动的实例中,按照以下步骤操作:
- 删除 Run MSBuild for Project 活动的 OutDir 属性的值使其成为空字符串,以禁用该属性。
注意 | |
如果未进行此更改,OutDir 属性将覆盖在代码项目中实现的任何已自定义的目录结构逻辑。 |
- 从 BinariesDirectory 变量收集包含生成代理的放置文件夹的字符串值,并将该值作为参数传入到 MSBuild(例如,名为 TeamBuildOutDir)。
- 将 LocalPathToMSBuildCode 的值作为参数传入到 MSBuild(例如,名为CommonMSBuildCode)。
- 代码项目
- 在 OurTeamBuild 编译的每个代码项目中,在相应的位置添加 <Import /> 元素。
以下小节详细说明了如何按这些步骤进行操作。
创建包含放置文件夹逻辑的 MSBuild 通用代码文件
对于此方法,首先要创建一个目录和两个 MSBuild 项目文件。这些文件包含用于定义将在您的放置文件夹中创建的子目录结构的逻辑。
创建文件
- 在“源代码管理资源管理器”中,执行下列步骤之一:
- 查找存储 MSBuild 通用代码的目录。
- 创建用于存储 MSBuild 通用代码的目录并为其命名,例如$/OurTeam/BuildProcessMSBuild。
- 如果“源代码管理资源管理器”顶部的“本地路径”标签旁边出现“未映射”链接,请单击该链接以将服务器目录映射到本地工作区中的相应目录。
- 创建以下文件并将这些文件保存和嵌入到 $/OurTeam/BuildProcessMSBuild 中:
- 包含托管代码项目(如 Visual C# 或 Visual Basic)的放置文件夹逻辑的文件(例如,名为ManagedCodeProjectOutDir.targets)。
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <PropertyGroup Condition="$(TeamBuildOutDir) != '' "> <OutputPath>$(TeamBuildOutDir)\$(SolutionName)\$(MSBuildProjectName)\$(Configuration)</OutputPath> </PropertyGroup> </Project> - 包含 Visual C++ 代码项目的放置文件夹逻辑的文件(例如,名为CPPCodeProjectOutDir.targets)。
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <PropertyGroup Condition="$(TeamBuildOutDir) != '' "> <OutDir>$(TeamBuildOutDir)\$(SolutionName)\$(MSBuildProjectName)\$(Configuration)\</OutDir> </PropertyGroup> </Project>
创建生成定义和 CustomOutputDirImport 自定义生成过程模板
你可以重用在本主题前面创建的名为 OurTeamBuild 的生成定义。您将使其基于新的生成过程模板并进行其他调整。
创建生成定义和生成过程模板
- 在 团队资源管理器 中,右击 OurTeamBuild,然后单击“编辑”。
- 单击“过程”选项卡,然后将生成过程模板设置为名为 CustomOutputDirImport.xaml 且基于默认模板 (DefaultTemplate.xaml) 的新生成过程模板。
有关更多信息,在“源代码管理资源管理器”中,打开团队项目,并显示包含生成过程模板的文件夹。默认情况下,此子目录的名称为 BuildProcessTemplates。 - 签出并双击在此过程前面创建的 CustomOutputDirImport.xaml 文件。
- 在工作流设计器中,查找 Run on Agent 活动,该活动位于以下结构中:
- 序列 (Sequence) >
- 在代理上运行 (AgentScope) >
- 有关如何导航此结构的信息,在窗口底部,单击“参数”。
- 在“参数”窗格中创建一个参数,并将其命名为 ServerPathToMSBuildCode。
- 在“属性”窗格中,选中“IsRequired”复选框。
- 在窗口底部,单击“变量”。
- 在“变量”窗格中声明一个变量,该变量的名称为 LocalPathToMSBuildCode,类型为 String,作用范围为 Run On Agent。
- 将 ConvertWorkspaceItem 活动从“工具箱”的“Team Foundation Build 活动”部分拖动到Initialize Workspace 和 If CreateLabel 活动之间的位置。
注意 | |
如果“Team Foundation Build 活动”部分未显示在“工具箱”中,则可以从Microsoft.TeamFoundation.Build.Workflow.dll 程序集手动添加此部分。有关更多信息,请参见How to: Add Activities to the Toolbox。 |
- 右击 ConvertWorkspaceItem 活动,然后单击“属性”。
- 在“属性”窗格中,设置以下属性值:
- 显示名称:Get Local Path to MSBuild Code
- 输入:ServerPathToMSBuildCode
- 结果:LocalPathToMSBuildCode
- 工作区:Workspace
- 查找 Run MSBuild for Project 活动的第二个实例,该活动位于以下结构中:
- 序列 (Sequence) >
- 在代理上运行 (AgentScope) >
- 尝试编译、测试和关联变更集和工作项 (TryCatch [Try]) >
- 序列 (Sequence) >
- 编译、测试和关联变更集和工作项 (Parallel) >
- 尝试编译和测试 TryCatch [Try] >
- 编译和测试 Sequence >
- 针对 BuildSettings.PlatformConfigurations 中的每个配置ForEach [Body] >
- 针对配置编译和测试 Sequence >
- 如果 BuildSettings.HasProjectsToBuild If [Then] >
- 针对 BuildSettings.ProjectsToBuild 中的每个项目ForEach [Body] >
- 尝试编译项目 TryCatch [Try] >
- 编译项目 Sequence >
- 针对项目运行 MSBuild MSBuild
- 有关如何导航此结构的信息,请参见右击 Run MSBuild for Project 活动,然后单击“属性”。
- 在“属性”窗格中,删除 OutDir 框中的数据以将此属性设置为空字符串。
- 在“属性”窗格中,将 CommandLineArguments 属性设置为以下值:
String.Format("/p:SkipInvalidConfigurations=true;CommonMSBuildCode=""{0}"";TeamBuildOutDir=""{1}"" {2}", LocalPathToMSBuildCode, BinariesDirectory, MSBuildArguments) - 保存 CustomOutputDirImport.xaml。
在“源代码管理资源管理器”中,将更改签入此文件。
更新 OurTeamBuild 生成定义
接下来,您必须更改为 OurTeamBuild 生成定义。
更新生成定义
- 在“团队资源管理器”中,展开正在使用的团队项目,展开“生成”文件夹,右击 OurTeamBuild 生成定义,然后单击“编辑生成定义”。
- 单击“工作区”选项卡,然后添加具有以下值的项:
- 状态:活动
- 源代码管理文件夹:$/OurTeam/BuildProcessMSBuild
- 生成代理文件夹:$(SourceDir)\BuildProcessMSBuild
- 单击“过程”选项卡,在“ServerPathToMSBuildCode”框中键入$/OurTeam/BuildProcessMSBuild。
- 保存生成定义。
将放置文件夹逻辑导入您的代码项目中
创建生成定义和自定义生成过程模板后,您必须更新代码项目以导入目录结构逻辑。
导入放置文件夹逻辑
- 在“源代码管理资源管理器”中,双击 OurTeamBuild 生成的解决方案。
- 对于解决方案中的每个代码项目,请按照以下步骤进行操作:
- 在“解决方案资源管理器”中右击项目。如果“卸载项目”命令可用,则单击它。
- 右击项目,然后单击“编辑 ProjectName”。
- 执行以下步骤之一:
- 如果该项目是托管代码项目(如 Visual C# 或 Visual Basic):在已位于代码项目中的最后一个 OutputPath 元素之后添加一个 Import 元素,如下面的示例所示。
<Project DefaultTargets="Build" xmlns="http://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"/> - 如果该项目是 Visual C++ 项目:则会在导入 Microsoft.Cpp.targets 的元素之前添加一个 Import 元素,如下面的示例所示:
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003 ..."> ... <Import Condition=" $(CommonMSBuildCode) != ''" Project="$(CommonMSBuildCode)\CPPCodeProjectOutDir.targets"/> <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" /> </Project>
- 保存代码项目。
- 在“解决方案资源管理器”中,右击解决方案,然后单击“签入”。
- 针对 OurTeamBuild 生成的每个解决方案,重复上述步骤。
接下来,您可能要执行下列任务:
- 修改放置文件夹逻辑。若要满足您的团队的要求,您可以修改前面的章节中提到的 OutputPath 和OutDir 元素的内容。
- 将自定义的代码项目另存为模板。如果您的团队将创建很多代码项目,则您可以自动将自定义 MSBuild 逻辑包含在新代码项目中。在“解决方案资源管理器”中,单击代码项目,打开“文件”菜单,然后单击“导出模板”。