重新认识生成流程
终于到了不得不讨论这个话题的时候. 重写团队基础生成流程, 是团队基础最富于弹性和扩展能力的地方, 也是实践最多优劣各异的地方. 这是MSBuild引擎的优秀能力: 给MSBuild引擎提供任意一个格式正确的生成脚本, MSBuild引擎都能搞解析生成脚本并形成可以顺序(或并行)执行的执行顺序流. 所以我们现在回过头来看, Team Build是什么? 我们试着从本质上归结一下:
"在MSBuild引擎驱动下的, 以团队基础框架提供的包含一系列生成目标(Target)的默认生成脚本文件 - Microsoft.TeamFoundation.Build.targets - 为基础的, 可以被用户自定义生成脚本文件 - tfsbuild.proj - 所覆盖从而形成一条确定的可执行的生成流程."
既然这样, 我们就可以从战略上藐视Team Build流程了 : 只要我们理解了本系列第一篇文章所讲默认的生成流程, 理解某一目标定义在了什么地方(Where), 这一步在整个流程中的什么时间点上(When), 做了什么(What), 为什么这么做(Why), 那么如果我们觉得这个目标的相关4W中有一项或者几项让我们很不爽, 那么我们就完全可以改掉它! 不要担心你的做法是错误的, 只要修改是符合MSBuild生成脚本格式的, 是考虑了上下文的, 那么就尽管去做.
没错, 一个别扭的逻辑悖论让我们忍了很久了: "GET"的位置问题. 譬如上一篇我们提到了"Get"目标和我们自定义Task之间的时间逻辑问题: 我们要生成自己的版本号, 我们写了自己的Task来完成这个任务, 但是在重写版本号的时候, 我们的自定义任务居然还没到达本地位置!
如何调整流程
Target这个概念是MSBuild的核心概念, 不单单用于Team Build, 包括我们平常在VS IDE里面点一下F6或者F5, 都是用到了MSBuild的这个概念(不信您随便找个项目文件用记事本打开看看第一行). 如果您对这个概念还有点不是很清楚的感觉的话, 我建议您一定看一下<MSDN Target Reference>, 然后再回来看这一节.
我们的目标是调整微软提供的默认生成流程. 修改的需求, 基本上可以归类为:
-
添加一个新的生成目标到合适的地方,
-
遮蔽一个已有的生成目标,
-
更改几个已有生成目标的执行顺序,
-
更改已有生成目标的执行条件或内容
修改, 需要修改的切口. 团队基础生成流程提供给我们的切口有两个: 需要修改的Target, 和它的DependsOnTargets属性.如果要修改一个生成目标的执行条件/对象或者执行的内容, 我们可以修改对应的Target本身; 如果调整几个Target的执行顺序, 那么我们可以修改它们的DependsOnTargets属性. 不管是修改Target本身还是它的DependsOnTargets属性, 我们修改的场所都是tfsbuild.proj文件. 值得强调的一点, MSbuild 只认识Target的名字(name属性), 或者DependsOnTargets属性指向的那个属性组的名字(PropertyGroup Name). 所谓重写, 就是相同名字的Target或者属性在tfsbuild.proj和Microsoft.TeamFoundation.Build.targets文件里的不同实现. MSbuild依赖于这些来构建生成流程. 如果您只想修改一个Target的执行条件, 那么最好不要修改这个Target的名字, 否则您得负责把这个改过名字的Target重新加入到生成流程的合适位置, 因为MSbuild不会记得它就是原来那个未修改的Target.
下面我们分类根据Team Build写几个具体的例子来讲述怎么完成重写.
添加新的Target
向默认的生成流程中添加新的生成目标, 首先需要一个写好的Target, 可以写在TFSBuild.proj文件里, 也可以在TFSBuild.proj文件中使用import标记引入定义在别处的Target. 比如, 我们在使用AssemblyInfo Task的时候, 直接将一个现成的Target拷贝到TFSBuild.proj文件里:
<!-- The target that acutally does all the work. The inputs are the same as the CoreCompileDependsOn target
(with the addition of @(AssemblyInfoFiles) to ensure that we only ever update the AssemblyInfo files if a
compile is actually going to take place. The outputs are the AssemblyInfoFiles that were passed in for update. -->
<Target Name="UpdateAssemblyInfoFiles" Inputs="$(MSBuildAllProjects);
@(Compile);
@(ManifestResourceWithNoCulture);
$(ApplicationIcon);
$(AssemblyOriginatorKeyFile);
@(ManifestNonResxWithNoCultureOnDisk);
@(ReferencePath);
@(CompiledLicenseFile);
@(EmbeddedDocumentation);
@(CustomAdditionalCompileInputs);
@(AssemblyInfoFiles)" Outputs="@(AssemblyInfoFiles);@(IntermediateAssembly)">
<AssemblyInfo AssemblyInfoFiles="@(AssemblyInfoFiles)" AssemblyMajorVersion="$(AssemblyMajorVersion)" AssemblyMinorVersion="$(AssemblyMinorVersion)" AssemblyBuildNumber="$(AssemblyBuildNumber)" AssemblyRevision="$(AssemblyRevision)" AssemblyBuildNumberType="$(AssemblyBuildNumberType)" AssemblyBuildNumberFormat="$(AssemblyBuildNumberFormat)" AssemblyRevisionType="$(AssemblyRevisionType)" AssemblyRevisionFormat="$(AssemblyRevisionFormat)" AssemblyFileMajorVersion="$(AssemblyFileMajorVersion)" AssemblyFileMinorVersion="$(AssemblyFileMinorVersion)" AssemblyFileBuildNumber="$(AssemblyFileBuildNumber)" AssemblyFileRevision="$(AssemblyFileRevision)" AssemblyFileBuildNumberType="$(AssemblyFileBuildNumberType)" AssemblyFileBuildNumberFormat="$(AssemblyFileBuildNumberFormat)" AssemblyFileRevisionType="$(AssemblyFileRevisionType)" AssemblyFileRevisionFormat="$(AssemblyFileRevisionFormat)" ComVisible="$(AssemblyComVisible)" AssemblyGuid="$(AssemblyGuid)" AssemblyCompany="$(AssemblyCompany)" AssemblyConfiguration="$(AssemblyConfiguration)" AssemblyCopyright="$(AssemblyCopyright)" AssemblyCulture="$(AssemblyCulture)" AssemblyDescription="$(AssemblyDescription)" AssemblyProduct="$(AssemblyProduct)" AssemblyTitle="$(AssemblyTitle)" AssemblyIncludeSigningInformation="$(AssemblyIncludeSigningInformation)" AssemblyDelaySign="$(AssemblyDelaySign)" AssemblyKeyFile="$(AssemblyKeyFile)" AssemblyKeyName="$(AssemblyKeyName)">
<Output TaskParameter="MaxAssemblyVersion" PropertyName="MaxAssemblyVersion"/>
<Output TaskParameter="MaxAssemblyFileVersion" PropertyName="MaxAssemblyFileVersion"/>
</AssemblyInfo>
</Target>