了解生成过程
作者 :Jason Lee
本主题提供企业级生成和部署过程的演练。 本主题中所述的方法使用自定义Microsoft 生成引擎 (MSBuild) 项目文件来提供对过程的各个方面的精细控制。 在项目文件中,自定义 MSBuild 目标用于运行部署实用工具,例如 Internet Information Services (IIS) Web 部署工具 (MSDeploy.exe) 和数据库部署实用工具VSDBCMD.exe。
本主题是一系列教程的一部分,这些教程基于名为 Fabrikam, Inc 的虚构公司的企业部署要求。本教程系列使用示例解决方案( Contact Manager 解决方案)来表示具有实际复杂程度的 Web 应用程序,包括 ASP.NET MVC 3 应用程序、Windows Communication Foundation (WCF) 服务和数据库项目。
这些教程的核心部署方法基于了解项目文件中所述的拆分 项目文件方法,其中生成过程由两个项目文件控制,一个项目文件包含适用于每个目标环境的生成说明,另一个包含特定于环境的生成和部署设置。 在生成时,特定于环境的项目文件将合并到与环境无关的项目文件中,形成一组完整的生成指令。
生成和部署概述
在 Contact Manager 解决方案中,三个文件控制生成和部署过程:
- 通用项目文件 (Publish.proj) 。 它包含不会在目标环境之间更改的生成和部署说明。
- 特定于 环境的项目文件 (Env-Dev.proj) 。 这包含特定于特定目标环境的生成和部署设置。 例如,可以使用 Env-Dev.proj 文件为开发人员或测试环境提供设置,并创建名为 Env-Stage.proj 的替代文件来为过渡环境提供设置。
- 命令 文件 (Publish-Dev.cmd) 。 它包含一个MSBuild.exe命令,用于指定要执行哪些项目文件。 可以为每个目标环境创建命令文件,其中每个文件都包含一个指定不同环境特定项目文件的 MSBuild.exe 命令。 这样,开发人员只需运行相应的命令文件即可部署到不同的环境。
在示例解决方案中,可以在“发布解决方案”文件夹中找到这三个文件。
在更详细地查看这些文件之前,让我们看看使用此方法时整体生成过程的工作原理。 概括而言,生成和部署过程如下所示:
首先,两个项目文件(一个包含通用生成和部署说明,一个包含特定于环境的设置)合并到单个项目文件中。 然后,MSBuild 将按照项目文件中的说明进行操作。 它使用每个项目的项目文件生成解决方案中的每个项目。 然后,它会调用其他工具,例如 Web 部署 (MSDeploy.exe) 和 VSDBCMD 实用工具,以将 Web 内容和数据库部署到目标环境。
从头到尾,生成和部署过程执行以下任务:
它删除输出目录的内容,为全新生成做准备。
它会生成解决方案中的每个项目:
- 对于 Web 项目(在本例中为 ASP.NET MVC Web 应用程序和 WCF Web 服务),生成过程为每个项目创建 Web 部署包。
- 对于数据库项目,生成过程为每个项目创建部署清单 (.deploymanifest 文件) 。
它使用 VSDBCMD.exe 实用工具将项目文件(目标连接字符串和数据库名称)中的各种属性与 .deploymanifest 文件一起部署解决方案中的每个数据库项目。
它使用 MSDeploy.exe 实用工具在解决方案中部署每个 Web 项目,并使用项目文件中的各种属性来控制部署过程。
可以使用示例解决方案更详细地跟踪此过程。
注意
有关如何为自己的服务器环境自定义特定于环境的项目文件的指南,请参阅 为目标环境配置部署属性。
调用生成和部署过程
若要将 Contact Manager 解决方案部署到开发人员测试环境,开发人员将运行 Publish-Dev.cmd 命令文件。 这会调用 MSBuild.exe,将 Publish.proj 指定为要执行的项目文件,将 Env-Dev.proj 指定为参数值。
msbuild.exe Publish.proj /fl /p:TargetEnvPropsFile=EnvConfig\Env-Dev.proj
注意
/fl 开关 (/fileLogger 的缩写) 将生成输出记录到当前目录中名为 msbuild.log 的文件。 有关详细信息,请参阅 MSBuild 命令行参考。
此时,MSBuild 开始运行,加载 Publish.proj 文件,并开始处理其中的说明。 第一条指令指示 MSBuild 导入 TargetEnvPropsFile 参数指定的项目文件。
<Import Project="$(TargetEnvPropsFile)" />
TargetEnvPropsFile 参数指定 Env-Dev.proj 文件,因此 MSBuild 将 Env-Dev.proj 文件的内容合并到 Publish.proj 文件中。
MSBuild 在合并的项目文件中遇到的下一个元素是属性组。 属性按它们在文件中的出现顺序进行处理。 MSBuild 为每个属性创建键值对,前提是满足任何指定的条件。 文件中稍后定义的属性将覆盖与文件前面定义的同名的任何属性。 例如,请考虑 OutputRoot 属性。
<OutputRoot Condition=" '$(OutputRoot)'=='' ">..\Publish\Out\</OutputRoot>
<OutputRoot Condition=" '$(BuildingInTeamBuild)'=='true' ">$(OutDir)</OutputRoot>
当 MSBuild 处理第一个 OutputRoot 元素(未提供类似名称的参数)时,它会将 OutputRoot 属性的值设置为 。\Publish\Out。当它遇到第二个 OutputRoot 元素时,如果条件的计算结果为 true,它将用 OutDir 参数的值覆盖 OutputRoot 属性的值。
MSBuild 遇到的下一个元素是单个项组,其中包含名为 ProjectsToBuild 的项。
<ItemGroup>
<ProjectsToBuild Include="$(SourceRoot)ContactManager-WCF.sln"/>
</ItemGroup>
MSBuild 通过生成名为 ProjectsToBuild 的项列表来处理此指令。 在这种情况下,项列表包含单个值 - 解决方案文件的路径和文件名。
此时,其余元素是目标。 目标的处理方式与属性和项不同, 实质上,除非目标由用户显式指定或由项目文件中的另一个构造调用,否则不会处理目标。 回想一下,打开 的 Project 标记包含 DefaultTargets 属性。
<Project ToolsVersion="4.0"
DefaultTargets="FullPublish"
xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
如果调用MSBuild.exe时未指定目标,则指示 MSBuild 调用 FullPublish 目标。 FullPublish 目标不包含任何任务;相反,它只是指定依赖项列表。
<PropertyGroup>
<FullPublishDependsOn>
Clean;
BuildProjects;
GatherPackagesForPublishing;
PublishDbPackages;
PublishWebPackages;
</FullPublishDependsOn>
</PropertyGroup>
<Target Name="FullPublish" DependsOnTargets="$(FullPublishDependsOn)" />
此依赖项告知 MSBuild,若要执行 FullPublish 目标,它需要按提供的顺序调用此目标列表:
- 它必须调用 Clean 目标。
- 它必须调用 BuildProjects 目标。
- 它必须调用 GatherPackagesForPublishing 目标。
- 它必须调用 PublishDbPackages 目标。
- 它必须调用 PublishWebPackages 目标。
清理目标
清理目标基本上会删除输出目录及其所有内容,为全新生成做准备。
<Target Name="Clean" Condition=" '$(BuildingInTeamBuild)'!='true' ">
<Message Text="Cleaning up the output directory [$(OutputRoot)]"/>
<ItemGroup>
<_FilesToDelete Include="$(OutputRoot)**\*"/>
</ItemGroup>
<Delete Files="@(_FilesToDelete)"/>
<RemoveDir Directories="$(OutputRoot)"/>
</Target>
请注意,目标包含 ItemGroup 元素。 在 Target 元素中定义属性或项时,需要创建 动态 属性和项。 换句话说,在执行目标之前,不会处理属性或项。 在生成过程开始之前,输出目录可能不存在或不包含任何文件,因此无法将 _FilesToDelete 列表生成为静态项;你必须等到执行正在进行。 因此,将列表构建为目标中的动态项。
注意
在这种情况下,由于 “清理 ”目标是第一个要执行的,因此实际上不需要使用动态项组。 但是,最好在此方案中使用动态属性和项,因为你可能希望在某个时候以不同的顺序执行目标。
还应避免声明永远不会使用的项目。 如果你的项仅由特定目标使用,请考虑将它们放在目标内,以消除生成过程中的任何不必要的开销。
撇开动态项不谈, 清理 目标相当简单,并利用内置的 Message、 Delete 和 RemoveDir 任务来:
- 向记录器发送消息。
- 生成要删除的文件列表。
- 删除文件。
- 删除输出目录。
BuildProjects 目标
BuildProjects 目标基本上生成示例解决方案中的所有项目。
<Target Name="BuildProjects" Condition=" '$(BuildingInTeamBuild)'!='true' ">
<MSBuild Projects="@(ProjectsToBuild)"
Properties="OutDir=$(OutputRoot);
Configuration=$(Configuration);
DeployOnBuild=true;
DeployTarget=Package"
Targets="Build" />
</Target>
上一主题 “了解项目文件”中详细描述了此目标,以说明任务和目标如何引用属性和项。 此时,你主要对 MSBuild 任务感兴趣。 可以使用此任务生成多个项目。 该任务不会创建 MSBuild.exe 的新实例;它使用当前正在运行的实例来生成每个项目。 此示例中关注的要点是部署属性:
- DeployOnBuild 属性指示 MSBuild 在每个项目的生成完成后,在项目设置中运行任何部署说明。
- DeployTarget 属性标识要在生成项目后调用的目标。 在这种情况下, 包 目标会将项目输出生成到可部署的 Web 包中。
注意
包目标调用 Web 发布管道 (WPP) ,它提供 MSBuild 和 Web 部署之间的集成。 如果要查看 WPP 提供的内置目标,请查看 %PROGRAMFILES (x86) %\MSBuild\Microsoft\VisualStudio\v10.0\Web 文件夹中的 Microsoft.Web.Publishing.targets 文件。
GatherPackagesForPublishing 目标
如果你研究 GatherPackagesForPublishing 目标,你会注意到它实际上不包含任何任务。 相反,它包含一个定义三个动态项的项组。
<Target Name="GatherPackagesForPublishing">
<ItemGroup>
<PublishPackages
Include="$(_ContactManagerDest)ContactManager.Mvc.deploy.cmd">
<WebPackage>true</WebPackage>
<!-- More item metadata -->
</PublishPackages>
<PublishPackages
Include="$(_ContactManagerSvcDest)ContactManager.Service.deploy.cmd">
<WebPackage>true</WebPackage>
<!-- More item metadata -->
</PublishPackages>
<DbPublishPackages Include="$(_DbDeployManifestPath)">
<DbPackage>true</DbPackage>
<!-- More item metadata -->
</DbPublishPackages>
</ItemGroup>
</Target>
这些项是指在执行 BuildProjects 目标时创建的部署包。 无法在项目文件中静态定义这些项,因为在执行 BuildProjects 目标之前,项引用的文件不存在。 相反,必须在目标中动态定义项,直到执行 BuildProjects 目标之后才会调用该目标。
项不在此目标内使用-此目标只是生成与每个项值关联的项和元数据。 处理这些元素后, PublishPackages 项将包含两个值: ContactManager.Mvc.deploy.cmd 文件的路径和 ContactManager.Service.deploy.cmd 文件的路径。 Web 部署将这些文件创建为每个项目的 Web 包的一部分,这些是必须在目标服务器上调用才能部署包的文件。 如果打开其中一个文件,则基本上会看到具有各种特定于生成的参数值的 MSDeploy.exe 命令。
DbPublishPackages 项将包含单个值,即 ContactManager.Database.deploymanifest 文件的路径。
注意
生成数据库项目时会生成 .deploymanifest 文件,该文件使用与 MSBuild 项目文件相同的架构。 它包含部署数据库所需的所有信息,包括数据库架构 (.dbschema) 的位置以及任何预部署和部署后脚本的详细信息。 有关详细信息,请参阅 数据库生成和部署概述。
你将在 生成和打包 Web 应用程序项目 和部署数据库项目中详细了解如何创建和使用部署包和 数据库部署清单。
PublishDbPackages 目标
简言之, PublishDbPackages 目标调用 VSDBCMD 实用工具将 ContactManager 数据库部署到目标环境。 配置数据库部署涉及许多决策和细微差别,你将在为多个环境 部署数据库项目 和 自定义数据库部署中了解详细信息。 在本主题中,我们将重点介绍此目标的实际运行方式。
首先,请注意,开始标记包含 Outputs 属性。
<Target Name="PublishDbPackages" Outputs="%(DbPublishPackages.Identity)">
这是 目标批处理的示例。 在 MSBuild 项目文件中,批处理是循环访问集合的一种技术。 Outputs 属性的值“% (DbPublishPackages.Identity) ”是指 DbPublishPackages 项列表的 Identity 元数据属性。 此表示法 Outputs=% (ItemList.ItemMetadataName) 被转换为:
- 将 DbPublishPackages 中的项拆分成批包含相同 标识 元数据值的项。
- 每批执行一次目标。
注意
标识 是创建时分配给每个项的 内置元数据值 之一。 它引用 Item 元素中的 Include 属性的值,即项的路径和文件名。
在本例中,由于不应有多个具有相同路径和文件名的项,因此我们实质上使用的是一个批大小。 每个数据库包都会执行一次目标。
可以在 _Cmd 属性中看到类似的表示法,该表示法使用适当的开关生成 VSDBCMD 命令。
<_Cmd>"$(VsdbCmdExe)"
/a:Deploy
/cs:"%(DbPublishPackages.DatabaseConnectionString)"
/p:TargetDatabase=%(DbPublishPackages.TargetDatabase)
/manifest:"%(DbPublishPackages.FullPath)"
/script:"$(_CmDbScriptPath)"
$(_DbDeployOrScript)
</_Cmd>
在这种情况下, % (DbPublishPackages.DatabaseConnectionString) 、 % (DbPublishPackages.TargetDatabase) 和 % (DbPublishPackages.FullPath) 都引用 DbPublishPackages 项集合的元数据值。 _Cmd 属性由调用 命令的 Exec 任务使用。
<Exec Command="$(_Cmd)"/>
由于此表示法, Exec 任务将根据 DatabaseConnectionString、 TargetDatabase 和 FullPath 元数据值的唯一组合创建批处理,并且该任务将针对每批执行一次。 这是 任务批处理的示例。 但是,由于目标级别批处理已将项集合划分为单项批处理,因此对于目标的每次迭代, Exec 任务将运行一次,并且只运行一次。 换句话说,此任务为解决方案中的每个数据库包调用一次 VSDBCMD 实用工具。
注意
有关目标和任务批处理的详细信息,请参阅 MSBuild Batching、 目标批处理中的项元数据和 任务批处理中的项元数据。
PublishWebPackages 目标
此时,你已调用 BuildProjects 目标,该目标为示例解决方案中的每个项目生成 Web 部署包。 每个包附带一个 deploy.cmd 文件,其中包含将包部署到目标环境所需的MSDeploy.exe命令,以及一个指定目标环境必要详细信息 的SetParameters.xml 文件。 你还调用了 GatherPackagesForPublishing 目标,该目标生成包含你感兴趣的 deploy.cmd 文件的项集合。 从本质上讲, PublishWebPackages 目标执行以下函数:
- 它使用 XmlPoke 任务操作每个包的SetParameters.xml 文件,以包含目标环境的正确详细信息。
- 它使用适当的开关为每个包调用 deploy.cmd 文件。
与 PublishDbPackages 目标一样, PublishWebPackages 目标使用目标批处理来确保为每个 Web 包执行一次目标。
<Target Name="PublishWebPackages" Outputs="%(PublishPackages.Identity)">
在目标中, Exec 任务用于为每个 Web 包运行 deploy.cmd 文件。
<PropertyGroup>
<_Cmd>
%(PublishPackages.FullPath)
$(_WhatifSwitch)
/M:$(MSDeployComputerName)
%(PublishPackages.AdditionalMSDeployParameters)
</_Cmd>
</PropertyGroup>
<Exec Command="$(_Cmd)"/>
有关配置 Web 包部署的详细信息,请参阅 生成和打包 Web 应用程序项目。
结论
本主题演示了如何使用拆分项目文件来控制联系人管理器示例解决方案的生成和部署过程。 使用此方法,只需运行特定于环境的命令文件,即可在单个可重复步骤中运行复杂的企业级部署。
深入阅读
有关项目文件和 WPP 的更深入介绍,请参阅 In the Microsoft 生成引擎: Using MSBuild and Team Foundation Build by Sayed Ibrahim Hashimi and William Bartholomew, ISBN: 978-0-7356-4524-0。