MSBuild

用于创建可靠的最佳实践生成,第 1 部分

Sayed Ibrahim Hashimi

本文讨论:
  • 定义目标依赖项
  • 批处理任务
  • 扩展清理过程
  • 组织目标
本文涉及以下技术:
MSBuild,Visual Studio

代码下载可从 MSDN 代码库
浏览代码联机

内容

定义目标依赖项
批处理任务
定义动态项和属性
扩展清理过程
组织目标
使用通配符

本文中的两个部分,第一个中我将介绍几个使用 MSBuild,Microsoft 生成引擎使用 Visual Studio 生成托管的项目时,开发人员应遵循的最佳实践。 在此第一的部分,我将介绍一些基本操作和技术,可以将应用于最每个项目。 我将还讨论等定义任务相关性主题批处理任务、 组织目标,和执行更多。 在部分 2,我将讨论与生成 heaver 自定义,需要很大程度上因其大小的配置的一些操作。

如果您不熟悉 MSBuild,一下我先前的文章中发布 MSDN 杂志 》 : " 内部 MSBuild: 编译的 Microsoft 生成引擎的应用程序您的方式自定义任务"和" WiX 技巧: 自动化使用 MSBuild 和 Windows Installer XML 版本."

定义目标依赖项

MSBuild 中定义一个目标时, 可以使用 DependsOnTargets 属性来定义目标依赖项。 MSBuild 使用此属性确定在其中执行目标的顺序。 是例如如果您定义名为的目标部署依赖目标 PrepareForBuild 和生成的如下所示 Express 的依赖项:

<Target Name="PrepareForBuild">
  <!-- target contents here -->
</Target>

<Target Name="Build">
  <!-- target contents here -->

</Target>

<Target Name="Deploy" DependsOnTargets="PrepareForBuild;Build">
  <!-- target contents here -->
</Target>

尽管上一列表定义所需依赖的项,不提供可以扩展部署目标的行为。 如果您可以指定部署目标依赖而不必修改本身部署目标的其他目标,它会更好。 可以通过在 DependsOnTargets 属性而不是使用硬编码值定义属性来实现此行为。

请看一下此处显示修改后的部署目标:

<PropertyGroup>
  <DeployDependsOn>
    $(DeployDependsOn);
    PrepareForBuild;
    Build
  </DeployDependsOn>
</PropertyGroup>
<Target Name="Deploy" DependsOnTargets="$(DeployDependsOn)">
  <!-- target contents here -->
</Target>

在此版本,部署目标依赖于 DeployDependsOn 属性中包含的目标。 (按约定,跟 DependsOn,目标名称命名为这些类型的属性但不需要)。 通过重写其使用者现在可以其他目标附加到 DeployDependsOn 属性。 但是,注意 DeployDependsOn 属性的声明中包含对自身的引用: $ (DeployDependsOn)。 包括此引用可以确保 DeployDependsOn 的所有现有值将不被覆盖。 相反,它们只被追加。

此方法最好,但它仅支持添加目标相关目标之前执行。 如果您希望能够指定应执行之前或之后目标的目标的使用者,需要声明在部署目标,如 图 1 所示。

图 1 部署目标声明

<PropertyGroup>
  <DeployDependsOn>
    $(DeployDependsOn);
    PrepareForBuild;
    Build;
    BeforeDeploy;
    CoreDeploy;
    AfterDeploy
  </DeployDependsOn>
</PropertyGroup>
<Target Name="Deploy" DependsOnTargets="$(DeployDependsOn)"/>
<Target Name="BeforeDeploy"/>
<Target Name="AfterDeploy"/>
<Target Name="CoreDeploy">
  <!-- target contents here -->
</Target>

在这种情况下部署目标是一个空的目标。 唯一它是指定这取决于的目标。 实际工时是在 CoreDeploy 目标执行的。 通过执行此步骤,您可以扩展使用 DeployDependsOn 属性,可以执行的目标之前和之后都 CoreDeploy 执行。

另外,还有一些两个其他空目标此处: BeforeDeploy 和 AfterDeploy。 它们被声明允许使用者快速扩展部署目标。 如果这些目标之一的使用者定义,它将调用在适当的时间。 重写 BeforeDeploy 类似的目标,并扩展该 DeployDependsOn 属性在区别是只有一个定义 BeforeDeploy 目标将存在。 这意味着如果多个声明目标则处理的最后一个将会生效的定义。

作为一种替代方法这,您可以将为根据需要许多目标依赖属性的名称。 可重用的生成文件后一个方法就是确保不会发生目标名称冲突的唯一方法。 作为扩展 DeployDependsOn 属性的示例,您可以声明 DeployDependsOn 目标再次,如下所示:

<PropertyGroup>
  CustomBeforeDeploy;
  $(DeployDependsOn);
  CustomAfterDeploy
</PropertyGroup>
<Target Name="CustomBeforeDeploy">
  <!-- your steps here -->
</Target>
<Target Name="CustomAfterDeploy">
  <!-- your steps here -->
</Target>

在此示例,CustomBeforeDeploy 和 CustomAfterDeploy 目标已添加到要执行的目标的列表。 Visual Basic.NET 和 C# 项目已发货的目标声明空的目标,如 BeforeBuild 以及 RebuildDependsOn 等的多个依赖属性。 可以找到有关在两个这些主题的更多信息, MSBuild 团队博客.

可从外部调用的每个目标应具有其 DependsOnTargets 属性中的目标的完整列表。 此步骤是重要,因为如果用户想执行特定任务时,假设 UnitTests,它们应该能够指定的目标中切换 msbuild.exe。 正确声明相关的目标,项目文件将支持此。

批处理任务

有关 MSBuild 的此常见问题之一是如何执行循环。 由于 MSBuild 使用声明性语言,不能指定显式的循环。 相反,必须描述希望 MSBuild 引擎执行,并让它处理的循环。

MSBuild 使用称为任务批处理的概念,该任务将执行一次按每个唯一的批处理或的值组。 批始终创建基于项目的元数据。 请看一下一个简单的示例使用 图 2 所示该 Batching01.proj 文件的内容。

图 2 A 简单批处理示例

<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003"
         ToolsVersion="3.5">

  <ItemGroup>
    <SampleItem Include="one">
      <Group>A1</Group>
      <Id>A363BE85-2CB1-4221-A9CB-2881B7699329</Id>
    </SampleItem>
    <SampleItem Include="two">
      <Group>A2</Group>
      <Id>48E171C8-2274-4567-84D5-D20C6B0CB363</Id>
    </SampleItem>
    <SampleItem Include="three">
      <Group>A2</Group>
      <Id>618E5BD8-650F-43c9-855E-259126284004</Id>
    </SampleItem>
    <SampleItem Include="four">
      <Group>A1</Group>
      <Id>65E8E8E7-5A3F-4e02-A1D9-34F797CB68D9</Id>
    </SampleItem>
    <SampleItem Include="five">
      <Group>A1</Group>
      <Id>43D0D1FE-304F-4aff-BE19-67AD2195872B</Id>
    </SampleItem>
  </ItemGroup>

  <Target Name="TaskBatching01">
    <!-- No batching here -->
    <Message Text="SampleItem: @(SampleItem)"/>
    <Message Text=" "/>
    <!-- Batches created based on Group Metadata -->
    <Message Text="SampleItem.Group: %(SampleItem.Group)"/>
    <Message Text=" "/>
    <!-- Batches created based on Id Metadata -->
    <Message Text="SampleItem.Id: %(SampleItem.Id)"/>
  </Target>

</Project>

此项目文件定义名为包括五个值的 SampleItem 的项目类型。 每个值具有为组和 ID 的元数据值。 元数据 ID 是唯一的每个项值,但组有 A 1 和 A 2 的只有两个值。 时遇到的窗体 %(ItemType.MetadataName) 表达式时,将触发批处理。

在 TaskBatching01 目标中用调用消息任务。 此表达式将导致 MSBuild 来确定由从 SampleItem 物料组元数据定义唯一批。 因为我知道该组有两个唯一的值,我知道的两个批创建,从组元数据和其他与使用 A 2 中具有 A 1 的值。 另一条消息任务,创建基于 ID 的元数据值的数目。 执行此目标的结果如 图 3 所示。

图 3 Batching01.proj 结果

C:\Samples\Batching>msbuild Batching01.proj /t:TaskBatching01 /nologo
Build started 10/20/2008 1:27:58 AM.
Project "C:\Samples\Batching\Batching01.proj" on node 0 (TaskBatching01  
  target(s)).
  SampleItem: one;two;three;four;five

  SampleItem.Group: A1
  SampleItem.Group: A2
  SampleItem.Id: A363BE85-2CB1-4221-A9CB-2881B7699329
  SampleItem.Id: 48E171C8-2274-4567-84D5-D20C6B0CB363
  SampleItem.Id: 618E5BD8-650F-43c9-855E-259126284004
  SampleItem.Id: 65E8E8E7-5A3F-4e02-A1D9-34F797CB68D9
  SampleItem.Id: 43D0D1FE-304F-4aff-BE19-67AD2195872B
Done Building Project "C:\Samples\Batching\Batching01.proj" 
  (TaskBatching01 target(s)).


Build succeeded.
    0 Warning(s)
    0 Error(s)

可以看到该语句 flattens SampleItem 值到一个字符串传递到消息任务导致的 @(SampleItem) 表达式的位置"一 ; 两个 ; 三个 ; 四个 ; 五个"。 对比这具有与该调用所示的行为。 在这种情况下已两次执行消息任务。 在该 SampleItem.id 实例的情况下消息任务已执行五次。

批处理的一个常见用途从到另一个位置是以递归方式复制文件。 在这种情况下必须使用 RecursiveDir 已知元数据以确定该文件夹驻留相对于父目录。 (MSBuild 已知的元数据的一组可在该" MSBuild 已知的项元数据"文章 MSDN 上)。 项目文件 Copy01.proj 中, 显示 图 4 将文件从 src 文件夹复制到目标文件夹。

图 4 Copy01.proj

<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003"
         ToolsVersion="3.5">

  <ItemGroup>
    <!-- Get all files under src\ except svn files -->
    <SrcFiles Include="src\**\*" Exclude="**\.svn\**\*" />
  </ItemGroup>

  <PropertyGroup>
    <Dest>dest\</Dest>
  </PropertyGroup>

  <Target Name="CopyToDest">
    <Copy SourceFiles="@(SrcFiles)"
          DestinationFolder ="$(Dest)%(SrcFiles.RecursiveDir)" />
  </Target>

  <Target Name="Clean">
    <ItemGroup>
      <_FilesToDelete Include="$(Dest)**\*"/>
    </ItemGroup>

    <Delete Files="@(_FilesToDelete)"/>
  </Target>
</Project>

在 CopyToDest 目标内复制文件一起批处理用于复制任务。 下面列举了 src 文件夹的内容:

src
¦   class1.cs
¦   class2.cs
¦   class3.cs
¦   class4.cs
¦
+---Admin
¦       admin_class1.cs
¦       admin_class2.cs
¦
+---Utilities
        util_class1.cs
        util_class2.cs

在本示例,在 DestinationFolder 被指定为 $(Dest)%(SrcFiles.RecursiveDir). 此语句将 SrcFiles 项目分为三个组的 RecursiveDir 根据值 RecursiveDir、 管理和实用程序的空值。 复制任务都调用三次,一次每个组。 期间的每次调用只当前组中的该文件为 SrcFiles 项传递给复制任务。

若要才能清楚地看到此,一下生成 图 5 所示的详细的日志。 从日志,您就可以确认复制任务执行三次,并且每次只文件匹配 RecursiveDir 的当前值已包括在 SrcFiles 项目中。

图 5 递归复制文件

Build started 10/26/2008 12:09:28 AM.
Project "C:\Samples\Batching\Copy01.proj" on node 0 (default targets).
Building with tools version "3.5".
Target "CopyToDest" in file "C:\Samples\Batching\Copy01.proj" from project "C:\Samples\Batching\Copy01.proj":
Using "Copy" task from assembly "Microsoft.Build.Tasks.v3.5, Version=3.5.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a".
Task "Copy"
  Copying file from "src\Admin\admin_class1.cs" to "dest\Admin\admin_
  class1.cs".
  Command:
  copy /y "src\Admin\admin_class1.cs" "dest\Admin\admin_class1.cs"
  Copying file from "src\Admin\admin_class2.cs" to "dest\Admin\admin_
  class2.cs".
  Command:
  copy /y "src\Admin\admin_class2.cs" "dest\Admin\admin_class2.cs"
Done executing task "Copy".
Task "Copy"
  Copying file from "src\class1.cs" to "dest\class1.cs".
  Command:
  copy /y "src\class1.cs" "dest\class1.cs"
  Copying file from "src\class2.cs" to "dest\class2.cs".
  Command:
  copy /y "src\class2.cs" "dest\class2.cs"
  Copying file from "src\class3.cs" to "dest\class3.cs".
  Command:
  copy /y "src\class3.cs" "dest\class3.cs"
  Copying file from "src\class4.cs" to "dest\class4.cs".
  Command:
  copy /y "src\class4.cs" "dest\class4.cs"
Done executing task "Copy".
Task "Copy"
  Copying file from "src\Utilities\util_class1.cs" to "dest\Utilities\
  util_class1.cs".
  Command:
  copy /y "src\Utilities\util_class1.cs" "dest\Utilities\util_class1.cs"
  Copying file from "src\Utilities\util_class2.cs" to "dest\Utilities\
  util_class2.cs".
  Command:
  copy /y "src\Utilities\util_class2.cs" "dest\Utilities\util_class2.cs"
Done executing task "Copy".
Done building target "CopyToDest" in project "Copy01.proj".
Done Building Project "C:\Samples\Batching\Copy01.proj" (default 
  targets).


Build succeeded.
    0 Warning(s)
    0 Error(s)

此以前的示例是一个常见用途的批处理。 另一个创建一个.zip 文件。 如果您需要创建一个.zip 文件在生成过程,没有提供可帮助您的某些任务。 一个任务是从 Zip 任务在 MSBuild 社区任务项目. 图 6 所示,Zip01.proj 文件中使用此任务。

图 6 Zip01.proj

<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003"
         ToolsVersion="3.5">

  <PropertyGroup>
    <SrcFolder>src\</SrcFolder>
  </PropertyGroup>

  <ItemGroup>
    <SourceFiles Include="$(SrcFolder)*" 
                 Exclude="$(SrcFolder).svn\**\*">
    </SourceFiles>
    <SourceFiles Include="$(SrcFolder)class1.cs;$(SrcFolder)class3.cs">
      <Access>public</Access>
    </SourceFiles>
    <SourceFiles Include="$(SrcFolder)class2.cs;$(SrcFolder)class4.cs">
      <Access>private</Access>
    </SourceFiles>
    <SourceFiles Include="$(SrcFolder)Utilities\*" 
                 Exclude="$(SrcFolder)Utilities\.svn\**\*">
      <Group>util</Group>
    </SourceFiles>
    <SourceFiles Include="$(SrcFolder)Admin\*" 
                 Exclude="$(SrcFolder)Admin\.svn\**\*">
      <Group>admin</Group>
      <Access>private</Access>
    </SourceFiles>
  </ItemGroup>

  <!--
  Define default values here.
  -->
  <ItemDefinitionGroup>
    <SourceFiles>
      <!-- Default Group value -->
      <Group>common</Group>
      <!-- Default Access value -->
      <Access>public</Access>
    </SourceFiles>
  </ItemDefinitionGroup>

  <!-- Import SDC tasks from known location in source control -->
  <Import 
    Project="..\Contrib\CommunityTasks\MSBuild.Community.Tasks.Targets"/>

  <Target Name="ZipFiles">
    <!-- Create an item with output in order to get the full path -->
    <PropertyGroup>
      <_ZipWorkingDir>output\</_ZipWorkingDir>
    </PropertyGroup>
    <MakeDir Directories="$(_ZipWorkingDir)"/>
    <!--
    Zip the files based on what zip file they should be placed in.
    Also this task should be passed a full path for the WorkingDirectory.
    -->
    <Zip Files="@(SourceFiles)"
         ZipFileName=
         "$(_ZipWorkingDir)%(SourceFiles.Group).%(SourceFiles.Access).
         zip"/>
  </Target>
</Project>

此项目文件中声明的项列表 SourceFiles,包含 src 文件夹的内容。 此项列表还包含某些其他元数据、 组和对访问该功能来确定 src 文件夹中的文件应放在哪个.zip 文件。 项目使用 ItemDefinitionGroup 元素可以定义为组和 Access 的默认值。 SourceFiles 项列表中没有显式定义一组值的所有值都使用"通用"值,并访问没有一个值的那些都使用值"公共"。

Zip 任务用于在 ZipFile 目标和值 $(_ZipWorkingDir)%(SourceFiles.group).内 %(SourceFiles.Access).zip is provided for the ZipFileName property. 在包含从相同的项列表的多个批处理表达式的情况下,批创建使用元数据的唯一组合。 此语句,批会创建从唯一值的组和访问。 因为有组的三个值和两个访问,将有最多六个批。 在这种情况下有 图 7 所示的组合所组成的只有四个批。

fig07.gif

其他可能的组合 (管理 / 公用和 Util / 私钥) 永远不会在项目中显示,因此不会使用它们。 现在,我们知道应创建四个.zip 文件。 如果执行命令 msbuild Zip01.proj /t:ZipFiles,可以看到结果如 图 8 所示。

图 8 创建基于元数据的 Zip 文件

C:\Samples\Batching>msbuild Zip01.proj /nologo
Build started 11/3/2008 11:40:40 PM.
Project "C:\Samples\Batching\Zip01.proj" on node 0 (default targets).
  Creating directory "output\".
  Creating zip file "output\common.public.zip".
    added "src/class1.cs".
    added "src/class3.cs".
  Created zip file "output\common.public.zip" successfully.
  Creating zip file "output\common.private.zip".
    added "src/class2.cs".
    added "src/class4.cs".
  Created zip file "output\common.private.zip" successfully.
  Creating zip file "output\util.public.zip".
    added "src/Utilities/util_class1.cs".
    added "src/Utilities/util_class2.cs".
  Created zip file "output\util.public.zip" successfully.
  Creating zip file "output\admin.private.zip".
    added "src/Admin/admin_class1.cs".
    added "src/Admin/admin_class2.cs".
  Created zip file "output\admin.private.zip" successfully.
Done Building Project "C:\Samples\Batching\Zip01.proj" (default targets).

Build succeeded.
    0 Warning(s)
    0 Error(s)

在该图,可以看到 zip 任务 IMAPITable 四次,预期。 每个 zip 文件名称中包含使用批处理的两个元数据值。

此处描述的行为并不限于两个元数据值或单个项目列表。 您可以自由使用如有必要的尽可能多的元数据值。 批处理在多个项列表的行为与不同内容我已经介绍并不将此处讨论。

定义动态项和属性

我以前讨论项和属性,但我确实没有定义它们。 MSBuild 中, 项的排序的对象与元数据,所有这些共享资源或编译一个特定项目类型的列表。 属性是键 / 值对。 动态项和属性的创建在目标,静态项和属性被声明外部目标。

MSBuild 2.0 中必须使用 CreateItem 和 CreateProperty 任务分别创建动态的项和属性。 MSBuild 3.5 中, 您可以通过使用相同的语法,当您使用的静态项创建它们。 首选的方法是使用 ItemGroup 和 PropertyGroup 元素,在一个目标。

任何目标执行之前,都始终会计算静态值。 这意味着每个 ItemGroup 和 PropertyGroup 找到外部目标必须扩展一个单一的生成步骤可执行前。 属性求值是快速,但项目评估很耗时根据的项目包含的值的数目。 由于这个原因如果项目或属性仅用于一个目标,它必须声明在该目标,而非静态值。

属性和 Item 评估发生多个周期。 静态属性和项,属性计算的上向下 (从文件末尾文件的开头),包括输入任何导入的项目和项目计算从上到下,还包括任何导入的项目中输入。 它们执行计算动态属性和项。

在创建项和属性时, 约定是开始使用下划线的名称为"internal"的值。 项目和属性名称不能启动使用下划线表示用户它们能够替代它们。 在创建这些属性或项目时,给出此约定,应首先检查以查看是否在已定义一个条件。 这非常有用,以防其他人已定义相同的属性或应重写您的项目。

为了说明这,一下来自 Microsoft.common.targets 以下声明:

<PropertyGroup>
  <AssemblyName 
    Condition=" '$(AssemblyName)'=='' ">$(MSBuildProjectName)</AssemblyName>
</PropertyGroup>

从此的代码中,我们可以看到 AssemblyName 属性将设置为 $(MSBuildProjectName) 仅当它已经不以前声明。

MSBuild 3.5 包含与动态项的新功能。 是例如 MSBuild 2.0 中,您不能修改的项列表的内容。 在一个项列表中包括项目后无法删除。 在解决此限制是创建新项目不包括您不想值。 MSBuild 3.5 现在支持通过删除属性的项目中的删除值。 图 9 中, 显示该项目文件 RemoveItems01.proj,演示此。

图 9 RemoveItems01.proj

<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003"
         ToolsVersion="3.5">

  <ItemGroup>
    <SrcFiles Include="Batching\src\class1.cs"/>
    <SrcFiles Include="Batching\src\class2.cs"/>
    <SrcFiles Include="Batching\src\class3.cs"/>
  </ItemGroup>

  <Target Name="Remove">
    <Message Text="SrcFiles: @(SrcFiles)"/>
    <ItemGroup>
      <!-- Remove using value provided inside -->
      <SrcFiles Remove="Batching\src\class2.cs"/>
      <!--
      Remove all items in SrcFiles that match the condition. 
      In this case, a single file.
      -->
      <SrcFiles Remove="@(SrcFiles)"
                Condition="'%(Filename)%(Extension)'=='class3.cs'" />
    </ItemGroup>
    <Message Text="SrcFiles: @(SrcFiles)"/>
  </Target>
</Project>

有两种方法在 ItemGroup 上使用属性。 可以通过创建项目时包含在 Include 属性的值来删除一个值。 此方法将显示该表达式。 图 9 中显示其他方法是置于该元素的条件时传递到删除属性的整个项目。

图 10 显示了执行删除目标的结果。 基于 图 10 所示的输出,您可以看到,class2.cs 和 class3.cs 文件已成功删除从 SrcFiles 物料。 可以同时添加和更新使用 ItemGroup 元素内部的一个的目标的元数据使用相同的语法处理创建动态的项目。 若要了解如何更新元数据值,请一下 UpdateMetadata01.proj 文件 图 11 所示。

图 10 从项目中删除值

C:\Samples>msbuild RemoveItems01.proj /t:Remove /nologo
Build started 10/26/2008 12:54:11 AM.
Project "C:\Samples\RemoveItems01.proj" on node 0 (Remove target(s)).
  SrcFiles: Batching\src\class1.cs;Batching\src\class2.cs;Batching\src\class3.cs
  SrcFiles: Batching\src\class1.cs
Done Building Project "C:\Samples\RemoveItems01.proj" (Remove target(s)).

Build succeeded.
    0 Warning(s)
    0 Error(s)

图 11 UpdateMetadata01.proj

<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003"
         ToolsVersion="3.5">
  <ItemGroup>
    <Reference Include="IronPython, Version= ...">
      <SpecificVersion>False</SpecificVersion>
      <HintPath>..\shared\IronPython-1.1\IronPython.dll</HintPath>
    </Reference>
    <Reference Include="log4net, Version= ...">
      <SpecificVersion>False</SpecificVersion>
      <HintPath>..\binaries\log4net.dll</HintPath>
    </Reference>
    <Reference Include="nunit.core, ...">
      <SpecificVersion>False</SpecificVersion>
      <HintPath>..\shared\nunit2.2\nunit.core.dll</HintPath>
    </Reference>
    <Reference Include="nunit.framework, Version= ...">
      <SpecificVersion>False</SpecificVersion>
      <HintPath>..\shared\nunit2.2\nunit.framework.dll</HintPath>
    </Reference>
    <Reference Include="nunit.util, ...">
      <SpecificVersion>False</SpecificVersion>
      <HintPath>..\shared\nunit2.2\nunit.util.dll</HintPath>
    </Reference>
  </ItemGroup>

  <Target Name="UpdateSpecificVersion">
    <Message Text="%(Reference.Identity) : %(Reference.
    SpecificVersion)"/>

    <Message Text=" "/><!-- blank line -->
    <Message Text="Update Reference.SpecificVersion to True"/>
    <Message Text=" "/><!-- blank line -->

    <!-- Update all Reference items to be SpecificVersion=True -->
    <ItemGroup>
      <Reference>
        <SpecificVersion>True</SpecificVersion>
      </Reference>
    </ItemGroup>
    <Message Text="%(Reference.Identity) : %(Reference.SpecificVersion)"/>
  </Target>
</Project>

此项目文件中声明名为引用的项目列表。 (在这些示例,强名称被截断为保留空间)。 此外,项目包含名为 UpdateSpecificVersion 的目标。 此目标使用 ItemGroup 元素更新 SpecificVersion 元数据为参考项列表。 此元数据值被打印之前和之后的更新。 图 12 显示结果。

图 12 更新现有的元数据

C:\Samples>msbuild UpdateMetadata01.proj /t:UpdateSpecificVersion /nologo
Build started 10/29/2008 12:07:39 AM.
Project "C:\Samples\UpdateMetadata01.proj" on node 0 
  (UpdateSpecificVersion target(s)).
  IronPython, Version= ... : False
  log4net, Version= ... : False
  nunit.core, ... : False
  nunit.framework, Version= ... : False
  nunit.util, ... : False

  Update Reference.SpecificVersion to True

  IronPython, Version= ... : True
  log4net, Version= ... : True
  nunit.core, ... : True
  nunit.framework, Version= ... : True
  nunit.util, ... : True
Done Building Project "C:\Samples\UpdateMetadata01.proj" 
  (UpdateSpecificVersion target(s)).

Build succeeded.
    0 Warning(s)
    0 Error(s)

您可以看到添加 AddMetadata01.proj 文件包含相应的示例代码中的元数据的一个示例。 我不会转到详细信息。 与修改项目列表本身,更新数据,并将元数据添加到项目中,在执行目标时可以是具有 MSBuild 2.0 没有等效项的新功能。

扩展清理过程

此部分与使用 MSBuild 会生成 Visual Studio 中的 C# 或 Visual Basic.NET 项目时。 此处描述的行为都包含内部目标文件用于生成这些类型的项目和不在 MSBuild 本身。

在修改生成过程来创建新文件,您负责扩展清理过程清理这些文件。 MSBuild 清理文件,它负责自动,但它不能,生成的自定义的目标的文件。 MSBuild 维护名为 FileWrites 包含需要清洗的文件的项目列表。 此列表被保存到一个文件被称为"清理缓存"obj 文件夹。 可以将其他值放到 FileWrites 项列表,以便在被删除项目清理时。

有两个缺点这样,但是。 要删除的文件必须位于该输出路径下,并您必须将项目追加到该列表,清除的缓存写入磁盘的中清理或会出现 IncrementalClean 目标根据执行的生成类型之前。 生成目标完成之前,将调用这些目标。

在示例代码本文随附,您将看到 Windows 窗体项目 WindowsFormsApplication1,在其中我已定义 BeforeBuild 目标,如下所示:

<Target Name="BeforeBuild">
  <ItemGroup>
    <CustomConfigFile Include="$(OutputPath)settings.config" />
  </ItemGroup>
  <!-- Generate config file here -->
  <WriteLinesToFile File="@(CustomConfigFile)"
                    Lines="config entries here"
                    Overwrite="true" />
  <!-- Append to FileWrites so the file will be removed on clean -->
  <ItemGroup>
    <FileWrites Include="@(CustomConfigFile)"/>
  </ItemGroup>
</Target>

此目标,一个名为的文件中通过 WriteLinesToFile 任务创建 settings.config。 在这种情况下该文件包含仅文本"配置项下面,",但是它可能包含 appSettings 节点,以及其他内容的值。 此外,FileWrites 列表被追加到,因此 settings.config 文件将被移除,执行清理时。

图 13 中,您可以查看执行生成目标和 Clean 目标 (排除不相关的某些行) 的结果。 还可以查看已执行 Clean 目标时,该 settings.config 已成功删除。 如果自定义放置在 AfterBuild 目标而不是 BeforeBuild 目标,该文件将不已被删除清理。 要完成此,必须扩展清理过程。

图 13 Clean 目标结果

C:\Samples\WindowsFormsApplication1>msbuild WindowsFormsApplication1.csproj /fl /t:Build;Clean /nologo
...
CoreClean:
  Deleting file "C:\Samples\WindowsFormsApplication1\bin\Debug\settings.config".
  Deleting file "C:\Samples\WindowsFormsApplication1\bin\Debug\WindowsFormsApplication1.exe.config".
  Deleting file "C:\Samples\WindowsFormsApplication1\bin\Debug\WindowsFormsApplication1.exe".
  Deleting file "C:\Samples\WindowsFormsApplication1\bin\Debug\WindowsFormsApplication1.pdb".
  Deleting file "C:\Samples\WindowsFormsApplication1\obj\Debug\WindowsFormsApplication1.Form1.resources".
  Deleting file "C:\Samples\WindowsFormsApplication1\obj\Debug\WindowsFormsApplication1.Properties.Resources.resources".
  Deleting file "C:\Samples\WindowsFormsApplication1\obj\Debug\WindowsFormsApplication1.csproj.GenerateResource.Cache".
  Deleting file "C:\Samples\WindowsFormsApplication1\obj\Debug\WindowsFormsApplication1.exe".
  Deleting file "C:\Samples\WindowsFormsApplication1\obj\Debug\WindowsFormsApplication1.pdb".
Done Building Project "C:\Samples\WindowsFormsApplication1\WindowsFormsApplication1.csproj" (Build;Clean target(s)).

Build succeeded.
    0 Warning(s)
    0 Error(s)

有两个目标,可以在项目文件中重写调用 Clean 目标时执行的。 这些目标是 BeforeClean 和 AfterClean。 如果您在项目文件中定义这些目标之一,则会在适当的时间调用目标。 这些目标必须在 Microsoft.CSharp.targets 或 Microsoft.VisualBasic.targets 定义导入语句之后。

另一种方法是使用目标依赖项。 您可以扩展清理过程中注入您自己的目标将 CleanDependsOn 属性。 请看一下自定义到 WindowsFormsApplication2 项目 图 14 中, 显示的 Microsoft.CSharp.targets 导入语句之后发生。

图 14 WindowFormsApplication2

<Target Name="AfterClean">
  <Message Text="AfterClean target executed"/>
</Target>
<!-- Inject a custom target into Clean by extending CleanDependsOn -->
<PropertyGroup>
  <CleanDependsOn>
    CustomBeforeClean;
    $(CleanDependsOn);
    CustomAfterClean
  </CleanDependsOn>
</PropertyGroup>
<Target Name="CustomBeforeClean">
  <Message Text="CustomBeforeClean target executed"/>
</Target>  
<Target Name="CustomAfterClean">
  <Message Text="CustomAfterClean target executed"/>
</Target>

在此列表已覆盖 AfterClean 目标同时将两个目标放 CleanDependsOn 属性中。 这些目标仅显示演示执行目标但一个实际的生成过程中它们未能清理资源消息。 图 15 显示了调用 Clean 目标的结果 (某些行被缩短到保留空间)。 按预期方式,目标被称为在适当的时间。

图 15 Clean 目标结果

C:\Samples\WindowsFormsApplication2>msbuild WindowsFormsApplication2.csproj /t:Clean /nologo
Build started 10/26/2008 1:17:40 PM.
Project "C:\Samples\WindowsFormsApplication2\WindowsFormsApplication2.csproj" on node 0 (Clean target(s)).
  CustomBeforeClean target executed
CoreClean:
  Deleting file "\bin\Debug\WindowsFormsApplication2.exe".
  Deleting file "\bin\Debug\WindowsFormsApplication2.pdb".
  Deleting file "\obj\Debug\WindowsFormsApplication2.Form1.resources".
  Deleting file "\obj\Debug\WindowsFormsApplication2.Properties.Resources.resources".
  Deleting file "\obj\Debug\WindowsFormsApplication2.csproj.GenerateResource.Cache".
  Deleting file "\obj\Debug\WindowsFormsApplication2.exe".
  Deleting file " \obj\Debug\WindowsFormsApplication2.pdb".
AfterClean:
  AfterClean target executed
CustomAfterClean:
  CustomAfterClean target executed
Done Building Project "C:\Samples\WindowsFormsApplication2\WindowsFormsApplication2.csproj" (Clean target(s)).

Build succeeded.
    0 Warning(s)
    0 Error(s)

组织目标

生成过程的增长需要组织驻留了不同的部分。 通常情况下,最好使用专用的职责的文件和其内容反映它们的用途。 定义为产品的生成过程时,请解决这种情况下,会有所帮助创建三个文件:

  • MyProduct.settings.targets
  • MyProduct.targets
  • MyProduct.csproj

使用 MyProduct.setting.targets 可以包含与生成和部署进程,以及任何其他公共设置期间使用的共享实用程序的各种属性。 是例如如果您使用 Nunit 进行测试您可能会将放置在该文件的设置是 Nunit 进行测试引用位置。 当属性该文件中声明的时它们应始终将声明,以便它们不会覆盖任何现有的值。 此文件包含默认设置。 (这是同样的情况将在"定义动态项和属性"的节中讨论)。

MyProduct.targets 包含声明所使用的自定义任务的 UsingTask 元素以及任何共享的目标。 此文件定义了如何获取产品生成的大部分目标以及它们的依赖项应包含此文件中。

MyProduct.csproj 定义获取构建。 此项目文件声明的所有项目和 MyProduct.targets 文件使用的目标中的属性。 这两个文件之间的合同由这些共享的项目和属性表示。 此文件还应导 MyProduct.settings.targets 入顶部和底部 MyProduct.targets 文件。

通过以便重写 MyProduct.settings.targets 中的任何值并准备 MyProduct.targets 属性和项。 如果您与此生成过程的 C# 项目,MyProduct.targets 相当于 Microsoft.CSharp.targets (和 Microsoft.common.targets),并其他两个文件构成实际的项目文件。

使用通配符

Visual Studio 项目文件并不使用通配符来填充 Visual Studio 进行的交互的项目。 例如,您可以定义编译项目包含将在如下所示发送给该的编译器的文件的列表:

<ItemGroup>
  <Compile Include="src\*"/>
</ItemGroup>

将该表达式将放所有文件在 src 文件夹到该编译项目和这是有效和将起作用时,它不是最佳的方法采用外部随意的项目。 Visual Studio 对项目文件所做的任何编辑将导致此项可扩展到分别列出每个文件。 此外,这种方法使得易于忘记了签入源代码管理的新的或已删除文件从本地计算机。 而不是 Visual Studio 所维护的项目使用通配符,应该显式包含每个文件。

本文中我将执行使用可以创建更好的几个关键建议查看生成产品的过程。 与所有的最佳操作会有的这些规则可能无法应用时间 100 个百分比和需要有点弯曲的情况。 若要了解哪些这些做法适用于您在最好方法是使用它们,并查看哪些工作和哪些失败。 我将 honored 听到您文章好和差,有关这些最佳做法。 您可以联系我在 sayed.hashimi@gmail.com.

我想感谢 Dan Moseley 从 MSBuild 团队和 Brian Kretzler 有关这篇文章其宝贵帮助。

由于早期的预览位,Visual Studio 2005 的发布, sayed Ibrahim Hashimi 一直从事 MSBuild。 他著 Inside the Microsoft Build Engine: Using MSBuild and Team Foundation Build (Microsoft Press,2009),也是 Deploying .NET Applications: Learning MSBuildClickOnce (Apress,2006) 并已写入对多个出版物。 他工作 Jacksonville,佛罗里达担任顾问和培训师,使用中,财务的专业技术培训和集合行业。 您可以访问 Sayed 在他的博客 sedodream.com.