本文章是由機器翻譯。

MSBuild

第 1 部 For 建立可靠的最佳作法組建

Sayed Ibrahim Hashimi

本文將告訴您:
  • 定義目標的相依性
  • 批次處理工作
  • 擴充的初始狀態的處理序
  • 組織目標
本文將使用下列技術:
MSBuild,Visual Studio

可從 MSDN 程式庫 的程式碼下載
瀏覽線上的程式碼

內容

定義目標的相依性
批次處理工作
定義動態項目和屬性
擴充的初始狀態的處理程序
組織目標
使用萬用字元

在本文的兩個部分,中的第一個我會描述幾個最佳的作法,開發人員使用 MSBuild,Visual Studio 來建置 Managed 的專案中使用 Microsoft 建置引擎時,應遵循。 在這第一個的部分,我將介紹一些基本作法 」 和 「 可以套用至最每個專案的技巧。 我也會討論主題,例如定義工作的相依性批次處理工作、 組織目標,和更多。 第二部我將討論一些相關組建組態,主要是因為其大小的需要 heaver 的自訂的作法。

如果您不熟悉使用 MSBuild,看我先前的文章中發行 MSDN Magazine : " 內部 MSBuild: 將應用程式的自訂工作您向編譯,Microsoft 建置引擎「 和 」 WiX 秘訣: 自動化版本,使用 MSBuild 和 Windows Installer 的 XML."

定義目標的相依性

當您在 MSBuild 中定義目標時,您可以使用 DependsOnTargets 屬性來定義目標的相依性。 MSBuild 使用這個屬性來決定的順序在其中執行目標。 例如,如果您所定義,名為目標部署 PrepareForBuild 和建置目標而定請您可以表示的相依性,如下所示:

<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 目標執行實際工作。 您可以藉由使用這個步驟,來擴充執行目標之前和之後 CoreDeploy 執行 DeployDependsOn 屬性。

此外,有是兩個其他空白目標此處: 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 會使用一個概念稱為工作批次處理,可讓每個唯一的批次或的值群組執行一次工作。 批次一定會建立根據項目中繼資料。 請看一下簡單的範例使用 Batching01.proj) 檔案, [圖 2 ] 所示的內容。

[圖 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 的項目類型。 每個值會有群組和識別碼的中繼資料值。 識別碼的中繼資料是唯一的每個項目的值,但群組的 A1 和 A2 只有兩個值。 發生在表單 %(ItemType.MetadataName) 的運算式時,會觸發批次處理。

在 TaskBatching01 目標,訊息工作會叫用的。 這個運算式,會導致 MSBuild,判斷從 SampleItem 項目群組中繼資料所定義的唯一批次。 因為我知道該群組會有兩個唯一的值,我知道兩個批次建立,從具有 A1 群組中繼資料和與 A2 與其他值。 另一個郵件工作,建立批次識別碼的中繼資料值為基礎。 執行此目標的結果如 [圖 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

在這個範例中,the 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 檔案,有某些工作可用,可以協助您。 一個這類的工作是從的 [壓縮] 工作, 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 項目來定義群組和存取的預設值。 SourceFiles 項目清單中的所有值沒有明確定義的群組值的都使用 「 通用值,並沒有存取值所都使用值的 「 公用]。

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)

在此圖中,您可以看到壓縮工作已呼叫四次,預期。 每個壓縮檔案的名稱會包含兩個批次的中繼資料值。

這裡描述的行為不限於單一項目清單或兩個的中繼資料值大小寫。 您可以自由使用多個的中繼資料值,必要時。 批次處理一個以上的項目清單上,行為是與哪些我所描述,將不討論以下不同的。

定義動態項目和屬性

我先前所討論項目和屬性,但我真的沒有定義它們。 MSBuild 中, 項目的排序所有的共用資料的特定項目型別,例如資源] 或 [編譯與中繼資料中,物件清單。 屬性會是關鍵值組。 動態的項目和屬性那些您會建立在目標,靜態項目和屬性的宣告外部目標。

在 MSBuild 2.0 中您必須使用工作 CreateItem CreateProperty,分別建立動態的項目和屬性。 MSBuild 3.5 中, 您可以利用相同的語法和您使用的靜態項目中建立它們。 慣用的方法是使用 ItemGroup 和 PropertyGroup 項目內為目標。

任何目標執行之前,永遠會評估靜態值。 這表示每個 ItemGroup 找不到目標之外的 PropertyGroup 必須展開和一個單一的建置步驟可以執行之前。 屬性評估快速,但是項目評估可能耗費時間的項目包含的值的數量而定。 因此,如果項目或屬性只能與一個目標,使用它應該宣告為在目標,而不是靜態的值。

屬性和項目的評估會發生在數個傳遞中。 靜態屬性和項目、 屬性的評估的頂端到底部 (從檔案在檔案的結尾開始),包括輸入任何匯入的專案以及項目會評估從頂端到底部,也包括輸入任何匯入的專案。 動態屬性和項目的評估,因為它們會執行。

您可以建立項目] 和 [屬性,慣例是以底線開頭的 「 內部的值名稱。 項目和其名稱執行不是以底線開頭的屬性表示的使用者它們可以覆寫。 當您建立這些屬性或項目時,請指定這種慣例,您應該先檢查是否在已定義與條件。 這在萬一有人已經定義相同的屬性或項目,應該覆寫您會很有用。

為了示範這,瞧瞧來自 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 方法。 您可以傳遞,建立項目時,包含屬性內包含的值,以移除值。 這個方法會顯示運算式。 其他如 [圖 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 的新功能。

擴充的初始狀態的處理程序

本節與相關建置 Visual Studio 中的 C# 或 Visual Basic.NET 專案時,使用 MSBuild。 用來建置這些專案類型的目標檔案中] 和 [MSBuild 本身內,不包含此處所述的行為。

當您修改建置處理序來建立新的檔案時,您負責擴充初始狀態的處理序來清除這些檔案。 MSBuild 檔案,自動,會負責清除,但是它無法將,執行自訂目標所產生的檔案。 MSBuild 會維護項目清單,名為 FileWrites 包含需要清除的檔案。 此清單會保存至 obj 資料夾,稱為快取 「 初始狀態區 」 檔案。 您可以將其他值放入 FileWrites 項目清單,以便清除專案時移除它們。

有兩個這樣,但是缺點。 要刪除的檔案必須存放在輸出路徑,並清除快取寫入至磁碟的發生中清除或 IncrementalClean 目標,根據所執行的組建的類型之前,您必須到該清單附加項目。 建置目標完成之前,會呼叫這些目標。

在本文所附範例程式碼,您可以找到 Windows Form 專案 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>

在 [這個目標,名為,檔案 settings.config 建立使用 WriteLinesToFile 工作。 在這種情況下的檔案包含只在文字 「 組態項目以下,」,但它可以包含在 appSettings 節點,以及其他內容的值]。 而且,FileWrites 清單會附加至中,,所以執行在清除時,將會移除 settings.config 檔案。

[圖 13 ] 中,您可以看到執行建置目標,然後清除目標 (排除某些不相關的行) 的結果。 您也可以看到執行初始狀態的目標時,該 settings.config 已成功刪除。 如果自訂已放置在 AfterBuild 目標,而不是 BeforeBuild 目標的檔案會不已移除的初始狀態。 若要完成這個工作,您必須擴充初始狀態的處理序。

[圖 13 清除目標結果

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)

有是兩個目標可在您的專案檔中覆寫,就呼叫初始狀態的目標時,會執行。 這些目標,是 BeforeClean] 和 [AfterClean。 如果您在專案檔中定義這些目標之一,目標會在適當時間呼叫。 這些目標,必須定義在匯入陳述式之後,Microsoft.CSharp.targets 或 Microsoft.VisualBasic.targets)。

另一種方法是使用目標的相依性。 您可以擴充 CleanDependsOn 屬性清除處理序中插入您自己的目標。 請看一下就會發生在匯入陳述式之後的 Microsoft.CSharp.targets 至顯示 [圖 14 ,WindowsFormsApplication2 專案自訂設定。

[圖 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 顯示呼叫初始狀態的目標的結果 (某些行已縮短以保留空間)。 如預期般,目標就會呼叫在適當的時間。

[圖 15 清除目標結果

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 所維護的項目中使用萬用字元,您應該明確包含每個檔案。

本文我將會採取一些金鑰的建議,您用來建立更好看建立您的產品處理程序。 與所有最佳一樣會有的情況,在其中這些規則可能無法套用時間一百百分比並需要一點彎曲。 最佳的方式,若要瞭解其中一個這些作法適用您是由使用它們,並在查看哪些工作] 及 [哪些失敗所為。 我會接受以獲得良好和不佳,您報導,這些最佳作法的資訊。 您也可以在我連絡 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) 的作者 ClickOnce (Apress,2006) 與 Deploying .NET Applications: Learning MSBuild 的並有寫入的數個出版物。 他搭配 Jacksonville,佛羅里達州的為顧問和培訓講師,專業技術,財務教育和集合的產業。 您也可以到達 Sayed 在他的部落格 sedodream.com.