65.9K
CodeProject 正在变化。 阅读更多。
Home

QuickBuild .NET 解决方案

starIconstarIconstarIconstarIconstarIcon

5.00/5 (3投票s)

2012年5月2日

CPOL

15分钟阅读

viewsIcon

174797

在本地构建多个项目

摘要

在复杂的应用程序中,我们通常需要构建大量的项目,然后才能在本地进行运行/调试/测试。在本地构建所有项目的过程通常是手动的,开发者需要获取最新版本的代码,按照特定顺序逐个构建项目/解决方案,并确保不破坏项目引用。这个过程通常耗时且容易出错,因为它涉及多个手动步骤。

本文介绍了用于为包含大量项目/解决方案的复杂应用程序创建本地构建的方法。该方法使得开发/调试/测试过程更高效、高度准确且耗时更少。如果一个应用程序有大量的项目/解决方案,并且错误修复和交付非常频繁,那么这种方法将是确保交付的修复程序准确且与其他模块兼容的最佳方式。本文中使用的方法,即在本地构建整个应用程序,将是一个单击即可完成的过程,构建文件将位于应用程序运行的单个文件夹中。

我们目前在项目中使用所提出的方法,取得了非常成功的结果,大大减少了初始设置问题和间歇性构建所花费的时间。这种方法在整个开发团队中得到推广,从而为整个项目节省了大量时间。我们使用 Microsoft .net Framework 3.5 附带的 Microsoft Build 工具、Microsoft Visual Studio 2008 开发环境以及 Team Foundation Server 2005 作为配置和源代码管理工具实现了这个解决方案。

引言

当应用程序涉及大量项目时,设置用于错误修复/调试/测试的环境通常需要本地构建应用程序。在本地构建应用程序的过程如下:

  • 开发者将最新的源代码获取到本地机器的工作区中。

  • 在本地构建项目/解决方案。

    • 确定项目/解决方案的位置。

    • 在 Visual Studio 中打开项目/解决方案并进行构建。

    • 将构建输出(.dll/.exe)复制到一个公共位置。

  • 对应用程序中的所有项目/解决方案重复相同的过程,确保不要遗漏顺序。

  • 在应用程序准备好进行本地调试后,开发者可以开始修复错误。

  • 一旦错误修复完成,开发者需要按照上述步骤构建整个应用程序,以确保他的修复不会引起与其他模块的兼容性问题。

  • 构建成功后,他会签入代码,以进行后续的部署和发布过程。

  • 团队中的所有开发者都需要遵循相同的过程。

正如我们所见,上述过程涉及手动步骤,如果操作不当,将导致代码不兼容和错误。最终在发布过程中,由于需求功能无法正常工作,构建可能会失败或导致应用程序失败。以下提到的问题在开发者签入代码的过程中非常常见。

  • 获取最新版本后,开发者需要确定项目/解决方案的位置,并逐一构建它们。
  • 在构建过程中,即使只有一个项目没有构建成功,也存在无法完成整个构建的风险,从而导致需要重新执行构建过程。

  • 如果开发者在没有本地构建的情况下签入修复,可能会导致用于部署的服务器构建失败。这就引发了诸如“是谁的修复破坏了代码”以及“需要采取什么措施来修复”等问题。

  • 如果应用程序包含大量项目,在本地构建它们是一个耗时的过程,并且整个团队都需要遵循这个过程,导致时间的累积损失。

本文试图通过提供一种只需单击即可在本地构建所有项目/解决方案的方法来解决上述问题,这里称之为 QuickBuild。本文讨论的在 .net Framework 下使用 MSBuild 平台进行本地构建的方法,提供了一种涉及多个项目的高度灵活和快速的构建模式。这种方法已经过测试和实施。新的流程有助于节省开发时间并确保修复的正确性。

QuickBuild 的需求

开发这种快速构建方法的需求源于我们遵循迭代交付模型,需要非常频繁地进行错误修复和功能增强。我们的项目包含大约 360 个项目,分布在 19 个解决方案中。一个开发者在提供修复或功能增强时,需要构建整个应用程序,以确保他签入的代码与其他模块兼容,因为一个项目的更改会导致其他依赖项目的更改,因此一个典型的修复也需要构建整个应用程序。考虑到这样一个大规模的项目大约有十几名开发者。每个开发者都在自己的本地机器上面临相同的构建问题,每个人都花费很长时间,这促使我们创建了快速构建。在切换到快速构建流程之前,我们分析了几个要点,用于在本地机器上生成整个应用程序的构建输出,以及两种流程中涉及的步骤。

常规构建流程

快速构建流程

获取最新代码。

逐个构建项目,将输出复制到特定位置。确保遵循顺序。

完成一个功能增强或修复。

逐个构建项目,将输出复制到特定位置。确保遵循顺序。

如果构建失败,重复该过程。

团队中的每个开发者都需要遵循相同的流程。

为每个新团队成员提供耗时的知识转移。

获取最新代码。

启动快速构建流程。

完成功能增强或修复。

启动快速构建流程。

开发者无需遵循任何流程,只需点击构建文件即可。

无需打开 Visual Studio 进行构建。

不需要太多的知识转移。新开发者可以立即开始工作。

表 1 - 常规构建和快速构建流程中的步骤

总而言之,以下是这种方法背后的主要驱动因素:

  1. 由于迭代模型,发布频繁。

  2. 项目复杂,包含许多项目和解决方案。

  3. 对每个开发者来说都是耗时的过程。

  4. 手动逐个构建项目容易出错。

  5. 由 TFS 管理员一次性设置,开发者永久构建。

使用 MSBuild 设置 QuickBuild 的详细信息

  1. 实施快速构建流程
  2. 我们的项目基于 .net Framework 3.0 构建,使用 C# 作为编程语言,TFS 作为配置管理工具。

    上述任何一项都可以修改,例如,此流程可用于任何 .net 平台、任何 .net 编程语言以及任何其他配置管理工具。当开发者签入他的代码时,他需要确保他的代码不会破坏系统中的任何其他东西。为确保这一点,他必须在签入代码之前在本地机器上构建整个应用程序。我们还在团队内部制定了一条规则,即任何人都不能在没有构建日志的情况下签入。收集 360 个项目的构建日志并将其粘贴到签入注释中是件麻烦事。相反,只用一条整合的构建声明,说明构建成功,会是一种更高效的方式。

    以下小节详细介绍了配置快速构建流程所需的步骤,这些步骤适用于所有 .net 版本、语言和版本控制系统。

  3. 先决条件
  4. 在开始快速构建流程之前,我们需要确保具备以下先决条件。

    • 已安装 .NET Framework

    • 所有解决方案/项目已加载到本地机器

    • 项目输出设置为一个公共文件夹。

    • 创建一个包含特定顺序解决方案列表的构建文件。

准备公共文件夹

由于我们有多个项目,并且按照标准,构建输出默认存储在 /bin/debug 文件夹中。构建每个项目并将输出复制到公共文件夹以供引用将是一个耗时且有风险的过程。相反,将每个项目的输出路径更改为一个公共文件夹,如“C:\builds\”。这样,每个正在构建的项目都会将程序集复制到指定的输出文件夹中。因此,其他引用这些程序集的项目将需要从这个位置引用。如果应用程序中只有一个解决方案,则程序集无需放置在公共位置;而如果像我们这样有多个解决方案,那么输出需要在公共位置,以便其他解决方案中的项目引用它们。

图 1 - 更改构建输出

为什么需要一个公共文件夹?

默认情况下,输出路径将是 bin\Debug\。这意味着一旦项目成功构建,程序集将被复制到此文件夹。每个项目都将有自己的 bin\Debug\ 文件夹。当每个项目构建时,所有的程序集都将被加载到每个项目的 bin\Debug\ 文件夹中。为安装/部署项目或运行应用程序而收集它们将很困难。指定一个公共文件夹将允许程序集被复制到同一个文件夹中,从而消除了查找文件夹和提取程序集的任务。

为每个模块的项目准备解决方案

构建与特定模块相关的项目很容易。我们无需担心解决方案中项目的构建顺序。解决方案会处理构建顺序。将所有相关的项目放在一个解决方案中。同时,我们不能将所有项目放在一个解决方案中,原因是机器无法处理在一个解决方案中打开如此多的项目。我可以在一个解决方案中加载所有 360 个项目,并使用 Visual Studio 中可用的标准构建命令。事实上,解决方案中的项目数量没有限制。我的一个解决方案中有 55 个项目。但标准做法是不超过 10-15 个。项目数量越少,构建时间越短。

确保所有项目都在某个解决方案中。列出解决方案的构建顺序。

图 2 - 在解决方案下分类的项目

准备构建 XML

构建文件是一个扩展名为 .build 的 xml 文件。该构建文件明确包含要构建的解决方案列表、源文件夹和目标文件夹位置以及任何其他附加任务。 

定义源文件夹

下面的示例 XML 说明了如何指定源文件夹。“..\”表示源文件夹位于当前位置的上一级。

SourceFolder 是在整个 XML 中使用的变量。

<PropertyGroup>
<SourceFolder>..\</SourceFolder>

指定目标

构建的目标是解决方案文件。我们需要按以下格式指定解决方案文件。

<Target Name="Build">
  <MSBuild Projects="$(SourceFolder)\Creehan3\Framework\Framework.sln"  StopOnFirstFailure="true"   >
     <Output TaskParameter="TargetOutputs" ItemName="BuildOutput" />
   </MSBuild>
</Target>

MSBuild 标签中的 Projects 属性指定了解决方案的位置。请注意变量 $(SourceFolder) 的使用方式。

StopOnFirstFailure 属性指定如果出现任何故障,则停止构建。同样,在 Target 标签中按需要构建的顺序列出所有解决方案。整个构建文件如下所示:

表 2 - 构建文件
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" >
<PropertyGroup>
<SourceFolder>..\</SourceFolder>
</PropertyGroup>
<Target Name="Build">
<MSBuild Projects="$(SourceFolder)\solution1.sln"  StopOnFirstFailure="true"   />
<MSBuild Projects="$(SourceFolder)\solution2.sln" StopOnFirstFailure="true" />
<MSBuild Projects="$(SourceFolder)\solution3.sln" StopOnFirstFailure="true"  />
<MSBuild Projects="$(SourceFolder)\solution4.sln" StopOnFirstFailure="true"  />
</Target>
</Project>

这是一个非常基础的脚本。它只构建你的解决方案。但你可以轻松地对其进行增强,以添加其他重要任务,例如运行单元测试、打包输出、代码覆盖率等。

运行 Quick Build

可以从 Visual Studio 命令提示符使用以下命令运行 Quick Build:

Msbuild /t:Build /l:FileLogger,Microsoft.Build.Engine; logfile=build.log application.build
  • /t 指定要对目标执行构建操作。
  • /l 指定记录来自 MSBuild 的事件。
  • Logfile 指定日志的文件名。
  • application.build 指定构建文件(在第 3.6 节中创建)。

创建一键式方法

为此,我们将创建一个触发 MSBuild 命令的批处理文件。

除了调用 MSBuild 命令,我们还可以执行其他操作,如将必备程序集复制到公共文件夹、将构建输出写入日志等。

del C:\builds\*.* /Q > dependency.log
@echo Cleaning N: drive Complete

C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\msbuild.exe /t:Build 
   /l:FileLogger,Microsoft.Build.Engine;logfile=build.log application.build 
@echo Build complete. 
pause

此批处理文件执行以下任务。

  • 删除公共文件夹中的所有文件,使我们每次都能进行全新的构建。

  • 将工作目录设置为 Visual Studio 命令提示符。

  • 运行 MSBuild 命令。

  • 将日志写入 build.log 文件。

构建文件和批处理文件的创建方式使其可以从应用程序根文件夹执行。

将此批处理文件放置在应用程序根文件夹并签入到 TFS 中,使开发者能够在他们的本地机器上进行构建。

图 3 - 包含 Quick Build 的 TFS 项目结构

上图显示了我的项目结构,其中 QuickBuild 文件夹包含一个构建文件和一个批处理文件。

构建之后

构建成功后,我们可以在公共文件夹中找到输出。

如果构建失败,我们可以在 QuickBuild 批处理文件所在的文件夹中找到日志。日志文件包含解决方案中每个项目的完整构建输出。利用这个日志,我们可以确定构建失败的位置/原因。

开发者需要做什么?

在他的本地机器上设置一个 N:\ 驱动器(公共文件夹),这是一项一次性任务。将最新代码获取到本地机器的工作区中。找到 Quick Build 文件夹并双击批处理文件。构建过程将被触发,构建输出将放置在指定的公共文件夹中。

后续步骤

考虑到同一应用程序内项目之间的引用,QuickBuild 是获取构建输出的最快方法之一。它可以通过多种方式进行增强。

  • 我们可以修改构建文件以包含创建安装/部署 MSI 的过程。

  • 我们还可以编写脚本将应用程序部署到指定位置。

  • 可以将 Quick Build 集成到 Visual Studio 菜单项中,以便可以从 Visual Studio 内部运行。

  • 根据项目需求添加或删除一个或多个解决方案。

面临的挑战

为每位开发者设置公共文件夹

每个开发者都应该有相同的公共文件夹,并且在相同的位置。这对所有开发者来说可能无法实现。由于每个项目的输出文件夹都硬编码到公共文件夹,如果开发者的机器上不存在该公共文件夹,构建就会失败。

为了消除这个挑战,我们创建了一个文件夹并将其映射到本地机器的 N:\ 驱动器。每个项目都将输出指向 N:\ 驱动器。每个开发者都可以在任何位置创建任何文件夹,但将其映射到 N:\ 驱动器解决了这个问题。

数据样本

表 2 显示了我们应用程序目前拥有的不同类型的项目。

项目类型

项目数量

VB.NET

10

C#.net

350

解决方案

19

表 3 - 示例应用程序中的项目类型

下面的表 3 比较了手动构建和快速构建在最佳和最差情况下的耗时。

 最佳情况下的耗时最差情况下的耗时
手动构建4 小时 12 小时
快速构建 10 分钟30 分钟
表 4 - 快速构建与手动构建的平均耗时比较

考虑一个典型场景,一位新开发者需要修复一个 bug 并将代码签入 TFS,同时确保系统中的其他部分不会被破坏,需要采取以下步骤:

  1. 从 TFS 获取最新代码。

  2. 执行本地构建,以便在本地运行/调试应用程序。

  3. 修复代码。

  4. 执行本地构建,以确保修复不会破坏任何其他模块。

  5. 签入代码。

在最佳情况下,一个开发者完成上述步骤大约需要 8-10 小时。

而使用指定的方法,他不会超过 30 分钟。

考虑到一个 10 名开发者的团队,执行上述相同的场景,总工时将是 100 小时。

而遵循指定的方法,总工时将是 5 小时,从而节省了超过 11 个人天的工作量。

在快速构建的情况下,最佳情景是指没有发生“第 4 节 - 面临的挑战”中提到的人为错误。最差情景是发生了一些人为错误,比如没有获取最新版本、没有映射公共输出文件夹等。在手动构建的情况下,最差情景更为严重,因为在构建顺序出错时可能会发生错误。在这种情况下,通常很难调试错误发生的原因,有时整个周期都会被重复。

上述示例也可能因组件的大小和数量而变化,但自动化构建的时间几乎是相同的。因此,在我们的交付中引入这种做法,使得本地构建比我们之前采用的常规做法快得多。这不仅为开发者减少了构建时间,而且每次他有代码更改时也是如此。

Quick Build 的优势

以下是我们通过实施这种方法获得的主要优势:

  • 无需担心构建顺序,因为构建顺序是预定义的。
  • 无需在 Visual Studio 中加载多个解决方案/项目。
  • 无需打开 Visual Studio 来执行此构建。
  • 直接从为每个项目设置的公共根文件夹运行应用程序。
  • 一个命令即可构建所有项目/解决方案。
  • 一次性创建的配置文件可以在团队中的所有机器和开发者之间使用。

成就

当前的快速构建已经实施,以解决开发者长期面临的一个痛点,这个痛点反过来也影响了客户,导致交付延迟。

这种通过一键式在本地进行构建的创新方法,在开发过程中节省了大量时间。此外,它还使整个过程变得可靠、稳健且不易出错。

由于上述优势,本文中介绍的方法已被推广到不同的团队,并且收到的反馈非常令人鼓舞。

致谢

我真诚地感谢 Creehan 及其公司,他们为这类构建提供了深刻的见解,在此基础上我对其进行了改进。我也感谢公司为我提供了分享想法所需的平台。

参考文献

  1. MSDN。
  2. 《Professional.Team Foundation Server》,Wrox 出版社。
  3. 《Pro Visual Studio Team System Application Lifecycle Management》,Apress 出版社。
  4. http://msbuildextensionpack.codeplex.com/
© . All rights reserved.