UML软件工程组织

集成与构建指南(二)
作者 胡协刚

6 集成构建基本流程

6.1 概述

在构建开始前,构架师应当确定项目初步的基本源码包组织结构,和包之间的依赖关系等,并定义项目统一的构建目录结构。构架师还应指导集成员制定集成构建计划,以确定集成的内容、构建周期和日程表。在项目构建阶段初期,构架师应密切参与或直接承担集成的工作,从而为项目源码结构确定演进的方向;其后还应给予足够的关注,并经常性地修订和维护源码目录结构,以便在整体上把握项目源码和最终交付工件。

随后的每个构建周期都包含:实施—〉单元测试—〉提交—〉集成—〉冒烟测试的基本过程。

6.2 集成过程说明

实施员在私有的开发工作站上,按照项目统一的源码结构组织其单元开发目录,完成源码和单元测试代码的编制,并在集成员的指导或帮助下编制私有构件的自动化构建脚本(为了编码和调试方便,实施员通常选择GUI集成环境进行编译,这种IDE内部格式的编译项目既不标准,也不适于集成构建使用,因此维护额外的构建脚本是必要的),在构建成功后向集成流(Stream)提交成果(delivery);在结束提交之前,实施员应尽可能在本机尝试集成构建,以验证构建脚本、单元测试代码与源码可用,之后完成提交。

集成员在集成服务器获取各实施员提交的构件源码,先根据需要调整各源码结构以解决构件间的编译冲突,再编制或修改集成构建脚本,加入集成构造目标(target),并增添对新增单元构建脚本的调用,以实施统一的批量编译、链接,最终生成集成构造;为了实现并验证集成目标,可能需要修改源码,增加或修改用于集成测试的代码,和进行集成调试等,这可由集成员或实施员来完成此项工作;集成员开始执行干净的集成构建(为了实现持续集成,可以配置操作系统任务,定时在夜间自动执行构建任务),生成可执行交付工件(也包括静态或动态链接库等);为了验证构建步骤成功,随后需要执行冒烟测试,其原则是尽可能利用已有的测试代码,通过在构建脚本中调用它们,实现自动化的冒烟测试(对于GUI目标系统而言,实现自动化较为困难,往往依靠手工完成冒烟测试)。

集成员或配置管理员为构建成功的一个构造建立基线;测试员将在测试工作站上对此基线进行集成测试;实施员则可能在各自的开发工作站上,重设开发基线于此基线上(rebase),以在新的基础上继续后续开发工作。

6.3 持续集成过程说明

集成员通过配置CruiseControl工具,可以将部分较为简单、不需要人工干预和希望经常重复执行的集成工作交给工具来自动完成。集成员可以配置多个持续集成项目,包括若干个多人同时在开发的子构件、最终发布的集成包等。

CruiseControl在活动时段,循环执行各构建周期,包含:引导初始化—〉检测源码变化—〉集成构建—〉单元测试—〉发布构建和测试结果等步骤。

每当实施员在私有开发工作站上,将源码检入(Checkin)、加入源码控制(Add to Source Control)、或者向集成流(Stream)提交成果(delivery)时,CruiseControl在随后的构建周期循环中,将通过检测源码变化步骤检测到这一变化,CruiseControl此时会等待预定的间隔,看看是否有新的源码变化出现,避免实施员批量检入或加入源码控制时遗漏后续变更;CruiseControl开始调用Ant封装(wrapper)配置文件执行构建,它首先更新目标源码目录下的所有内容(调用ClearCase ccupdate指令),以同步变化的源码,再进行编译、链接,完成预定的冒烟测试,并将结果记录到相应的日志中;CruiseControl在构建完成后,通过e-mail将成功或失败的结果通知提交源码变更的实施员、以及指定的其他人员,并生成构建报告网页,相关人员通过e-mail接受通知的同时,也可以登陆CruiseControl的发布网页来浏览构建报告详细信息。

7 构件的集成原则

7.1 生产者与消费者

不同构件的开发者之间通常处于一种生产者与消费者的关系,即生产者交付工件提供给消费者使用。生产者交付的工件类型有:原始(Original)工件(源码、构建脚本、配置文件等),中间(Derived)工件(Obj文件、lib库等),最终产品(Product)工件(可执行文件、Dll库等),其它临时(Temporal)工件(编译器产生的临时文件等)。消费者将使用这些工件。C++项目中这种消费关系包含:编译引用他方的公开(public)头文件、链接他方的静态库文件(或Obj文件)、以及生成的最终执行文件调用他方的动态链接库等。

除了原始(Original)工件,其它派生工件都可以通过构建过程来自动生成,为了简化生产者与消费者的依赖关系,保持派生工件对原始工件版本的一致性,并减轻配置管理的开销,生产者应尽量只向消费者提交原始工件,消费者则依靠构建脚本生成他们需要的派生工件(出于效率考量,在构建开销较大时,消费者也可以直接使用生产者构建出的最终产品工件,其后果是版本冲突的风险变大,配置管理的开销增加)。

为了满足只提交原始工件的需要,编制自动化程度很高和高质量的构建脚本变得格外重要,如果构建过程增加了消费者的负担,或者出现了构建失败的情况,消费者将更倾向于直接从生产者手中拿到构建后的产品工件。

理想情况下,生产者可能在消费者开始使用前就交付了其依赖的工件,但通常团队会采用并行开发模式,消费者必须在生产者完成交付前就开始工作,因此开发替代的桩(Stub)代码,甚至为了调试方便而专门开发摹仿(Mock)代码,成了家常便饭。根据实际需要,替代代码可以由生产者自身或消费者来开发。针对这种状况,在编写构建脚本时,应提供相应的编译、链接选项,使得消费者可以自由选择将交付代码或桩(Stub)代码编译、链接到他的构造工作版本中,同时集成员还可以利用这些选项来开展增量式的迭代集成。

7.2 源码目录组织原则

开发目录组织结构从来就是关系到项目健康成长的关键因素。开发(产品)目录提供了项目团队进行开发、管理等活动的统一共享场所,它需要满足不同涉众(角色),在不同的阶段,对不同类型工件进行访问的多种场景需求。因为项目的编码实施、集成等活动相互间的依赖关系远比其它文档编写类活动要复杂,协同整合更为困难,使得合理的源码组织结构变得极为重要,构架师和配置管理员必须投入更多精力关注开发(产品)目录中的源码组织部分。

源码目录组织的首要原则是目录结构的统一,项目中应当采用统一的分类方式、同一的风格、一致的命名格式来创建所有构件的源码目录。本项目中涉及到第三方库、项目构件、子系统、系统四类构件,其源码目录结构采用同一的基础目录结构,并根据各自需要做了相应的扩展。

开发(产品)目录的组织应当尽量在同一级的目录中采用同一种分类标准。目录分类标准有:按照工件类型(src/bin/lib/doc/mdl/script/web/xdoc),按照工件功能或用途(build/test/interface/example),按照活动组织(plan/requirement/design),按照归属关系(第三方库3rd Lib/基础构件Components/子系统Subsystem),等等。

如下图所示,本项目中,第一级目录按照归属关系来划分,源码构件的第二级目录按照构件设计包结构来划分,源码构件的第三级目录则按照工件类型并结合工件用途来划分。

开发(产品)目录应当面向团队协作,既要做到实施员在私有空间开发时相对独立,不受他人工作的影响,又能保证与他人的工作成果同步,并顺利地实现集成。这将依赖软件配置管理工具的支持,方能较好地达成上述目标。

开发(产品)目录本身可以看作是项目的一个工件,随着项目的进展也在不断地调整和演进,代码需要经历重构,目录结构也不例外。在项目初期就完全固定目录结构是不现实的。当前支持对目录进行版本控制,并能方便修改目录结构的配置管理工具不多,其中以ClearCase为主要代表,这也是本项目选用ClearCase的原因之一。

8 各类构件集成的实施方案

8.1 第三方开发包的实施方案

第三方开发包在使用时有如下特点:每个产品有各自不同的目录结构,组织的方式不统一,直接使用将增加引用和依赖关系的复杂性;产品目录全部展开后有时文件数量非常庞大,如果直接纳入配置管理的话,加入源码控制的开销很大,而当其版本升级时替换原有文件更是非常繁琐且容易出错,但是不控制的话又会造成第三方开发包版本冲突和安装路径不一致的问题;项目中对第三方开发包的引用,通常不直接使用其源码,而是链接其编译好的静态库。

针对上述特点,本项目对第三方开发包的源码结构组织如下图所示:

本方案中,纳入配置管理的通常只有第三方产品的发布包(zip压缩文件),和能够自动将第三方产品进行构建和安装的构建脚本。项目将在各基线中保持其不同版本的发布包,版本控制变得非常简单,而构建脚本的执行能确保正确的发布版本安装到目标开发空间,供项目其它构件引用。第三方产品虽然本身展开的目录各自不同,但是其安装后的目标目录结构却完全一样,其它构件只需要到include下找头文件,到lib下找需要链接的库文件,引用依赖关系变得简单明了。

构建脚本的执行步骤:

首先执行初始化(-Init),准备好编译工具配置;执行清除工作(Clean),得到干净的工作空间;完成构建准备(-Prep),创建一些临时目录(build_space)和目标目录(include);进行解包工作(Unpack),将第三方产品解压到build_space;进行自动编译(AutoBuild),在build_space目录之下生成目标库和可执行文件;开启安装过程(Install),将公开头文件(即Interface)自动拷贝到include下,将用户参考文档拷贝到doc下,将静态库和动态库拷贝到lib下,将可执行文件拷贝到bin下,最后再设置一个环境变量指向本开发包的当前根目录;执行测试准备(-TestPrep),从第三方发布包中组织相关的测试代码,拷贝到test下;进行测试构建(TestBuild),生成测试执行文件;执行测试(Test),验证第三方产品安装成功,使用它的构件可以正常编译,并且可以通过测试。

引用第三方发布包的示例:

执行log4cplus开发包的构建脚本后(保证安装过程Install已正确完成),将在D:\Development_Home\Building.Workspace\ env.properties文件中增添一条属性记录 getenv.LOG4CPLUS_ROOT=K\:\\PCHL_V1_Dev\\Libraries\\log4cplus;并添加一个环境变量(限于Windows NT平台)指向本开发包的当前根目录LOG4CPLUS_ROOT=K:\PCHL_V1_Dev\Libraries\log4cplus。

引用构件如果使用Ant脚本,则要在脚本中引用env.properties文件,然后于编译任务中,使用getenv.LOG4CPLUS_ROOT属性,分别在includepath段添加${getenv.LOG4CPLUS_ROOT}\include路径,和在libset段使用${getenv.LOG4CPLUS_ROOT}\lib目录来指引log4cplus.lib等静态链接库。

引用构件如果使用msvc6 IDE,则在项目文件中分别添加%LOG4CPLUS_ROOT%\include头文件查找路径和%LOG4CPLUS_ROOT%\lib库文件查找路径。

而在引用构件的源码中使用类似以下的格式来包含log4cplus的公开头文件:

#include <log4cplus/socketappender.h>

#include <log4cplus/helpers/socket.h>

8.2 项目构件的实施方案

项目本身构件与第三方开发包不同,可以直接使用统一的目录结构,源码必须纳入配置管理,并且要进行细粒度的版本控制。

针对上述特点,本项目对构件的源码结构组织如下图所示:

构建脚本的执行步骤:

首先执行初始化(-Init),准备好编译工具配置;执行清除工作(Clean),得到干净的工作空间;完成构建准备(-Prep),创建一些临时目录和目标目录(lib);进行自动编译(AutoBuild),在lib目录下生成目标库,在bin目录下生成可执行文件;开启安装过程(Install),设置一个环境变量指向本开发包的当前根目录;进行测试构建(TestBuild),生成测试执行文件;执行测试(Test),验证构件安装成功,使用它的构件可以正常编译,并且可以通过测试。

8.3 项目系统集成发布的实施方案

项目最终产品即目标系统由各个构件和引用的第三方开发包共同组装而成,可以认为其源码就是各构件的源码,必须纳入配置管理只剩下构建脚本和专门用于验收测试的代码。

针对上述特点,本项目对系统的源码结构组织如下图所示

构建脚本的执行步骤:

首先执行初始化(-Init),准备好编译工具配置;执行清除工作(Clean),得到干净的工作空间;完成构建准备(-Prep),创建一些临时目录和目标目录(lib);开启安装过程(Install),分别从其它构件和第三方开发包将要发布的公开头文件(即Interface)自动拷贝到include下,将要发布的用户参考文档拷贝到doc下,将要发布的静态库拷贝到lib下,将要发布的可执行文件和动态库拷贝到bin下,最后再设置一个环境变量指向当前系统运行映像根目录;执行测试准备(-TestPrep),从其它构件组织相关的测试代码,拷贝到test/example下;进行测试构建(TestBuild),生成测试执行文件;执行测试(Test),验证系统发布安装成功,使用它进行二次开发的构件可以正常编译,并且可以通过测试。

8.4 批量构建步骤

为了方便构建工作,通常通过批处理的方式,按依赖顺序执行第三方开发包、项目子构件、和系统集成发布的所有构建步骤。\PCHL_System\Integration\build目录下有一个批命令脚本all.bat,用于自动执行上述过程。

批处理的方式主要通过Ant提供的子Ant项目执行功能来实现,详见相关批处理XML文件。

第三方开发包Libraries批处理脚本放在\Libraries\libs_bundle\batch-build下,通过批处理最终将生成汇集所有第三方开发库的3d_party.lib(在批处理过程的最后调用\Libraries\libs_bundle\build下的构建脚本生成,并放置于\Libraries\libs_bundle\lib下)。

基础设施Infrastructures批处理过程与Libraries类似也生成infrastructures.lib(放置于\Infrastructures\infrs_bundle\lib下)。

系统集成发布在\PCHL_System\Integration\build目录下有一个批处理脚本batchbuild.xml,将自动调用所有构件的构建脚本,并在最后调用集成发布构建脚本,生成系统的发布映像。

 

版权所有:UML软件工程组织