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

使用 ClearCase 眨眼机制和新的 Visual Studio 版本

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.64/5 (5投票s)

2010 年 10 月 21 日

CPOL

14分钟阅读

viewsIcon

25888

downloadIcon

120

从 vcproj 文件创建 makefile 并将其与 ClearCase 眨眼机制一起使用。

引言

在某些情况下,使用 ClearCase 可能很有用,而在其他情况下则可能令人沮丧。当 Microsoft 停止支持 Visual Studio 中的“导出到 makefile”功能时,我们的团队正处于这种令人沮丧的时刻。我们正在从事的项目是用 Visual Studio 6 编写的,并使用了基于 makefile 的 ClearCase 构建系统。多年来,我们一直继续使用 Visual Studio 6 来处理该项目。这样做的主要动机是不面对 makefile 问题并继续使用 ClearCase 眨眼机制。最近,我们终于决定赶上世界潮流,迁移到 Visual Studio 9。但是,如果不使用 ClearCase 眨眼,构建时间会从二十分钟变成六个小时,软件团队则会像合唱团一样练习“救命!”。我们必须找到解决这个问题的方法。最终,我们花费了近两周时间开发了脚本和 makefile,为新的 Visual Studio 提供了眨眼功能。本文分享了这一经验,并详细介绍了将旧的 Visual Studio 6 项目顺利过渡到更新的 Visual Studio 版本而又不放弃 makefile 和 ClearCase 眨眼功能的过程。

背景

在深入技术细节之前,请允许我讨论 ClearCase 构建系统。大规模项目的构建并非易事,可能需要大量的时间和资源。ClearCase 允许您通过所谓的“构建避免”系统来加速构建过程。每次构建步骤都会被 ClearCase 系统审核并记录到配置记录中。记录的数据由系统在每次后续构建时使用,以避免不必要的构建步骤。假设我们需要编译一个文件。除非该文件的物料清单自上次成功构建以来发生了变化,否则该文件将不会被编译。相反,ClearCase 会将生成的 obj 文件复制到您的 ClearCase 视图中。此机制称为“眨眼”,并且在 ClearCase 系统中的所有动态视图中都有效。唯一的限制是,眨眼机制仅在您的项目使用 makefile 构建时才有效。

从 Visual Studio 7 开始,Microsoft 移除了将项目导出到 makefile 的功能。要构建 Visual Studio 解决方案,必须使用 Microsoft 构建工具,例如 MSBbuild。问题在于,对于大型项目来说,这速度不够快。即使您花费大量金钱购买编译服务器,其速度也永远无法超越 ClearCase 眨眼机制。因此,如果构建时间很重要,ClearCase 眨眼是正确的选择。由于没有 makefile 就无法实现眨眼,因此别无选择,只能自己实现“导出到 makefile”的功能。

当然,ClearCase 眨眼也有其自身的缺点,因此您可能需要检查项目的替代方案。让我们简要了解一下这些替代方案。眨眼的主要问题是它仅在动态视图中可用。动态视图速度很慢,并且依赖于网络和 ClearCase 服务器的性能。因此,您可能会考虑使用快照视图。快照视图的问题在于其创建和更新时间。如果您有一个具有多个依赖项的大型项目,则创建快照视图可能根本不可行。此外,对于大型项目,构建时间也会相应增加。因此,要有效使用快照视图,您必须保持项目规模较小。尽管存在配置管理优势,但这种“小型项目”方法意味着高度模块化,从而使您的系统设计更好。唯一的问题是它需要一个构建系统来跟踪小型项目的版本并将它们整合到最终产品中。为旧项目投资这样一个系统绝对不划算。至于新项目,强烈建议您采用这种方法,并将您的系统设计为高度模块化。您还应该考虑聘请专业公关人员来说服您的管理层投资支持这种模块化的构建系统。这是因为这样的系统只有在几年后,并且只有当您的项目变成真正拥有多个版本和数十名开发人员的产品时,才能获得回报。换句话说,它将变成一个怪物,在每次功能设计时消耗您的脑细胞,并在每次合并时消耗您生命的数小时。

以上不仅适用于 ClearCase 环境,也适用于任何其他源代码控制系统。幸运的是,在 ClearCase 中,我们有眨眼技巧,它允许我们构建模块化差的项目。

为 Vcproj 文件构建 Makefiles

假设您正在处理一个庞大的旧项目(或者不是那么旧的项目),并且高度模块化方法对您来说不是一个选项。如前所述,您应该考虑自己实现“导出到 makefile”的功能。总的来说,创建这样一个 makefile 并不难,最终是编写一些脚本和 makefile。本文的其余部分旨在帮助您快速、高质量地完成这项任务。首先,让我们定义我们的目标

  1. 实现完全眨眼 - 意味着您的构建将使用 ClearCase 眨眼功能来处理项目中所有所谓的派生对象。
  2. 快速开发 - 您不想花太多时间编写脚本来完成此任务。
  3. 尽可能少的维护 - 您更不愿意追赶项目中的更改并根据更改调整脚本。

上述内容的一个重要结果是,我们不会编写一个可以将任何可能的 Visual Studio 项目转换为 makefile 的脚本,而是专注于特定项目。尽管如此,最终系统必须足够健壮,能够处理未来的更改。

为了加快流程,脚本将仅解析项目(vcproj)文件,而不会解析解决方案(sln)文件。解析解决方案文件是可能的,但可能需要一些复杂性,最好避免。起初,解析项目并在 makefile 中设置不同项目之间的依赖关系就足够了。更重要的是,如果您从 Visual Studio 6 升级项目,那么您可能已经使用了 makefile 来定义 Visual Studio 6 项目(dsp)文件之间的依赖关系。确实,Visual Studio 6 工作区(dsw)文件无法保存在 ClearCase 中,因为 Visual Studio 6 在其工作过程中会修改它们。因此,如果您已经拥有处理 Visual Studio 6 dsp 文件的 makefile,则迁移到 vcproj 文件将是 makefile 中的简单替换。这将保留项目依赖关系。

无论您决定处理项目(vcproj)文件还是解决方案(sln)文件,最终都会得到一个包含 vcproj 文件规则的 makefile。假设您将此文件命名为“makefile.mak”,并且它包含以下规则

文件 makefile.mak
Project_1.vcproj : Project_2.vcproj Project_3.vcproj

为简单起见,我们假设

  1. 所有项目文件都提供了完整路径。因此,上述规则包含完整路径(直到 ClearCase 视图的驱动器号)。
  2. 依赖树中的所有项目都使用相同的配置进行构建。项目的配置在参数 CONFIG 中指定,并且包含以下值之一:D - 表示 Debug,R - 表示 Release。
  3. 所有项目均在 Visual Studio 9 中创建。

当然,上述假设与大多数项目的情况相去甚远,但它们足以说明这种技术。

现在,让我们在 makefile.mak 中添加一个通用的 vcproj 文件规则,以便此规则适用于 Project_2 和 Project_3,然后按照上述规则的建议,适用于 Project_1 vcproj 文件

文件 makefile.mak
%.vcproj .ALWAYS : 
       %chdir $(.TARGET,D)

       # Run makefile generator
       python.exe -m GenerateMakefileFromVCPROJ $( .TARGET,B) \
                         $(CONFIG) $(.TARGET,B)_$(CONFIG).vcproj_mak

       
       # Call makefile that handles VisualStudio-9 projects build
       omake -f makefile_vs9.mak build_vs9_project_target \            
                         VCPROJ_MAK=$(.TARGET,B)_$(CONFIG).vcproj_mak

       %chdir $(MAKEDIR)

上述规则适用于所有 vcproj 文件,并执行两项操作。首先,它调用 Python 脚本来解析 vcproj 文件,并将所有相关定义放入扩展名为“vcproj_mak”的文件中。例如,对于 Project Project_1.vcproj,在 Debug 配置下,它将创建文件 Project_1_D.vcproj_mak。我们假设在进入规则脚本之前已设置了 Python 可执行文件的路径和 PYTHONPATH 环境变量。

脚本执行的第二项操作是使用 makefile_vs9.mak makefile 递归调用 omake。此 makefile 负责构建 Visual Studio 9 项目,并将最近创建的 vcproj_mak 文件作为参数传递给它。出于几个原因,将 Visual Studio 9 相关内容分离到单独的 makefile 并递归调用 omake 至关重要。

首先,最好不要将所有规则混在一个文件中,并在规则之间进行逻辑分离。如果您有 Visual Studio 6 的规则,最好将其放入 makefile_vs6.mak。当然,逻辑分离不需要 omake 递归调用,但它可以使 makefile 更清晰、更模块化。

第二个原因是技术性的。递归调用 omakeomake 为派生对象记录的物料清单有积极影响。假设您需要将 Python 可执行文件路径添加到 PATH 变量。那么 vcproj 规则可能如下所示

文件 makefile.mak
%.vcproj .ALWAYS : 
       %set OLD_PATH = $(PATH)
       %setenv PATH=$(PYTHON_PATH);$(PATH)
       %chdir $(.TARGET,D)

       # Run makefile generator
       python.exe -m GenerateMakefileFromVCPROJ $( .TARGET,B) \
                         $(CONFIG) $(.TARGET,B)_$(CONFIG).vcproj_mak

       
       # Call makefile that handles VisualStudio-9 projects build
       omake -f makefile_vs9.mak build_vs9_project_target \            
                         VCPROJ_MAK=$(.TARGET,B)_$(CONFIG).vcproj_mak

       %setenv PATH=$(OLD_PATH)
       %undef OLD_PATH %undef OLD_PATH 
       %chdir $(MAKEDIR)

以上是有效的,但有一个陷阱。ClearCase 会记录您在规则脚本中进行的所有操作。因此,您在脚本中引用的所有宏(R 值)都会被添加到派生对象的配置记录中。在上面的示例中,PATHOLD_PATH 宏的值将成为 $(.TARGET,B)_$(CONFIG).vcproj_mak 文件的配置记录的一部分。这显然是一个问题,因为 PATH 的值可能因计算机而异,omake 每次遇到不同的文件配置记录时都会重新构建 vcproj_mak 文件。

有两种方法可以解决这个问题。第一种是在规则脚本执行之前在 makefile 中的某个位置更新路径变量。例如,您可以在 makefile 的开头添加以下指令

%setenv PATH=$(PYTHON_PATH);$(PATH) 

另一种更优雅的处理这种情况的方法是使用递归调用 omake。这样的调用会启动一个新的 omake 实例,该实例会继承调用 omake 的环境,包括新定义的 PATH 变量值。新的 omake 对调用它的脚本一无所知,因此在调用脚本中访问的任何 R 值都不会被包含在新 omake 实例的配置记录中。

第三个使用递归调用 omake 的原因最为重要。Python 脚本刚刚解析了 vcproj 文件并创建了 vcproj_mak 文件。vcproj_mak 文件包含决定 makefile_vs9.mak 中规则行为的定义。因此,此时使用 makefile_vs9.mak 重新加载 omake 对于正确处理至关重要。

为了说明最后一点,让我们仔细看一下 vcproj_makmakefile_vs9.mak 文件。

文件 Project_1_D.vcproj_mak
VS9_INTERMEDIATE_DIR=Debug 
VS9_SOURCES=file1.cpp file2.cpp file3.cpp ... 
VS9_COMPILER_OPTIONS=-I. /D DEBUG /Z7 ... 
...
文件 makefile_vs9.mak
# Include makefile_vcproj.mak
%include $(VS9_MAKEFILE) 

# Create list of object files
VS9_OBJECTS=$(VS9_SOURCES,B,>.obj,<${VS9_INTERMEDIATE_DIR}/) 

# Compilation rules for object files 
%foreach VS9_SOURCE in $(VS9_SOURCES)  
${VS9_INTERMEDIATE_DIR}/$(VS9_SOURCE,B,>.obj) : $(VS9_SOURCE)
         # Compile 
         cl.exe $(VS9_COMPILER_OPTIONS) /Fo"$(.TARGET)" $(.SOURCE) 
%end 
...

如上面的代码所示,Project_1_D.vcproj_mak 文件不包含任何规则,只包含从相应 vcproj 文件中提取的定义。makefile 使用 Project_1_D.vcproj_mak 文件中的定义来创建对象文件列表和编译它们的规则。如果没有递归调用 omakeomake -f makefile_vs9.mak),将不可能生成这些规则。

现在我们准备完成 makefile_vcproj.mak。同样,为简单起见,我们假设 vcproj 包含静态库定义。上述规则处理库源代码的编译。归档和 build_vs9_project_target 规则将如下所示

文件 Project_1_D.vcproj_mak
...
VS9_TARGET= Project_1.lib 
VS9_ARCHIVER_OPTIONS=
VS9_POST_BUILD_COMMAND_1=echo "Build has been finished"
VS9_POST_BUILD_COMMANDS_COUNT = 1 
...
文件 makefile_vs9.mak
# Rule to build lib 
%.lib : $(VS9_OBJECTS) 
         # Archive 
         $(VS9_ARCHIVER) $(VS9_ARCHIVER_OPTIONS) $(VS9_OBJECTS) /out:"$(VS9_TARGET)"  

# Build target 
build_vs9_project_target .ALWAYS : $(VS9_TARGET)
         # Post build steps
         $(VS9_POST_BUILD_COMMAND_1)
         $(VS9_POST_BUILD_COMMAND_2)
         ...
         $(VS9_POST_BUILD_COMMAND_10)
         %if $(VS9_POST_BUILD_COMMANDS_COUNT) > 10
                 %error "Too much post build commands"
         %endif 
...

同样,Project_1_D.vcproj_mak 文件仅包含简单的定义,不包含任何规则。链接规则很简单,但“build_vs9_project_target”规则需要一些解释。此规则是 makefile 的主要入口点。其中的构建后命令始终执行(.ALWAYS 指令)。这是特意为之的。事实上,构建后命令可能包含一些 ClearCase 难以跟踪的晦涩操作。例如,构建后命令可能会将文件复制到本地磁盘以供以后创建安装程序。

我们基本上完成了 makefile 的工作。最后一个重要问题是解决编译器和链接器的“增量”功能。这些功能通过文件在编译器或链接器的连续调用之间传递数据。例如,如果您启用最小重建(/Gm)选项,编译器将在第一次编译期间创建 .ibd 文件,并在后续编译中使用它来确定源文件是否应重新编译。如果重新编译了源文件,其对象文件的配置记录将包含对 .ibd 文件的引用。显然,这将破坏眨眼过程,并导致其他 ClearCase 视图中的重新构建。因此,总的指导原则是,您应尽可能避免增量功能。不过,有一项功能是您无法完全放弃的;那就是调试信息(pdb)文件。这个比较棘手,只有一个部分解决方案。在编译期间,您可以使用 /Z7 标志将调试信息放入 obj 文件中,但对于链接器,没有这样的选项,使用链接器的 /DEBUG 选项意味着也使用 /INCREMENTAL 选项。因此,如果您打算在生成 EXE 或 DLL 的同时创建 PDB 文件,请准备在每次构建中链接它们。

Vcproj 文件解析器

现在让我们检查解析 vcproj 文件的脚本。vcproj 文件是 XML 文件,其结构是每个配置都有自己的 XML 子树,该子树又分为常规设置和工具设置。为了导航到正确的子树,脚本应该在输入时接收配置。将定义从 vcproj 提取到相应的 vcproj_mak 文件是一项简单的任务,需要基本的脚本编写技能。完整的 vcproj 解析脚本示例已附加。它用 Python 编写,可以作为您自己脚本的基础。以下是一些规则,可以帮助您加快脚本编码速度并减少未来的维护工作

规则 1:仅支持与您的项目相关的设置。

不要试图创建最通用的脚本来解析所有可能的设置。例如,如果您在项目中使用“最大化速度(/O2)”编译器优化,则无需支持任何其他设置。相反,如果您的某些项目不使用此设置,脚本将发现它并报告错误。然后,您可以考虑更改设置以符合您项目的其余部分。通常,这样做是正确的,因为这种设置差异很可能是由于对该问题缺乏关注造成的。

规则 2:避免为特定文件使用特殊设置。

项目文件列在“文件”XML 子树中。每个文件可能为每个可能的工具都有特定的设置。例如,您可能希望允许在某些文件中使用内在函数(/Oi)。这是一个重要的功能,但它会使脚本和 makefile 规则的创建更加复杂。因此,如果您有一堆需要内在函数的文件,请考虑将它们分离到静态库中,而不是支持每个文件的设置。

规则 3:在 vcproj 高级功能和脚本复杂性之间找到适当的平衡。

vcproj 文件具有一些复杂的特性,可能很难支持。尝试找出哪些特性是必不可少的,并编写您的解析器来支持它们。应删除或更改非必需特性以简化您的脚本。例如,“InheritedPropertySheets”属性允许从某些默认配置继承设置。支持此 vcproj 属性将需要递归解析 vsprops 文件。因此,您应仔细检查是否真的需要在您的项目中保留此功能。例如,如果您从 Visual Studio 6 升级项目,“InheritedPropertySheets”属性将指向“UpgradeFromVC60.vsprops”,该文件又将一个宏(_VC80_UPGRADE=0x0600)添加到您的编译器设置中。因此,在这种特定情况下,您可以绕过脚本中 vsprops 文件的解析,只需将 _VC80_UPGRADE 宏添加到编译器设置中。

摘要

本文展示了一种为 Visual Studio 项目实现完全眨眼的简单方法。此时,您应该能够编写自己的 makefile 和 vcproj 解析器。您需要为您的 makefile 添加 DLL、EXE 和其他目标的规则。vcproj 解析器可能也需要进行调整。希望本文介绍的技术能帮助您处理这些任务和其他挑战。

© . All rights reserved.