MSBuild入門(續)


MSBuild基本概念(續)

在上一篇簡單的介紹了下MSBuild中的四個基本塊,每塊介紹比較單薄,在這里對在大多數的項目模版生成的*.*proj文件中比較常見一些用法和概念做些補充。主要有一下幾方面:

MSBuild特殊字符:MSBuild保留的一些字符,以及XML中的特殊字符處理。
MSBuild條件: Condition特性,作用類似於C#的if。
MSBuild屬性: 使用環境變量、保留屬性、全局屬性。
MSBuild項: 元數據、項轉換。
MSBuild任務: ITask接口、UsingTask[自定義任務]、ContinueOnError。
MSBuild目標: 初始目標、默認目標、目標依賴項。
Import元素: 導入項目文件到當前項目文件。

MSBuild特殊字符

一些字符在MSBuild中代表着特殊的上下文含義,如下:

MSBuild的特殊字符[%引用元數據]、[$引用屬性]、[@引用項]、['條件或其他表達式]、[;列表分隔符]、[?文件名通配符]、[*文件名通配符];
XML的保留字符:<、>、&、"、'

針對MSBuild的特殊字符轉義需要用[%xx]這種方式,xx代表字符的ASCII十六進制值([%=%25][$=%24][@=%40]['=%27][;=%3B][?=%3F][*=%2A])。針對XML保留字符則使用&lt這種方式。 一般用到這些特殊字符的情況不多,見到時能知道是轉義就可以了。

MSBuild條件

條件在*.*proj項目文件中非常常見,用Condition特性來表示一個布爾表達式,類似於if條件,幾乎所有的元素都可以具有Conditon特性。一個簡單的例子如下:

 1 <?xml version="1.0" encoding="utf-8"?>
 2 <!--condition.xml文件-->
 3 <Project DefaultTargets="show" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
 4   <PropertyGroup>
 5     <!--Condition在屬性、項、任務、目標生都有使用-->
 6     <!--如果Configuration為空(''),則其值為Debug-->
 7     <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
 8     <Platform Condition=" '$(Platform)' == '' ">x86</Platform>
 9   </PropertyGroup>
10   <ItemGroup>
11     <!--如果csfile1.cs文件存在就包含在CSFile項中-->
12     <CSFile Include="csfile1.cs" Condition="Exists('csfile1.cs')"></CSFile>
13   </ItemGroup>
14   <Target Name="show">
15     <!--輸出Debug|x86-->
16     <Message Text="$(Configuration)|$(Platform)"/>
17     <!--輸出空,因為csfile1.cs並不存在-->
18     <Message Text="@(CSFile)"/>
19   </Target>
20 </Project>

還有一些常用的表達式如!=、!、And、Or等。

MSBuild屬性

上篇介紹到可以用$可以引用自定義的屬性,除此之外亦可以引用系統的環境變量,如$(Path),以及 MSBuild保留屬性(MSDN)

屬性除了可以在項目文件中聲明是賦值外,在MSBuild命令行也允許設置屬性的值(語法:/p:propertyName=value)。稱作全局屬性,這類屬性會重寫在項目文件中設置的屬性值,保留屬性除外的任何屬性都可被這種方式覆蓋其原值。 以上面示例為基礎:[MSBuild condition.xml /p:Platform=x64],則最終輸出結果就為Debug|x64了。

屬性還有一種叫做任務發出屬性,在上篇用到了,由Output元素的PropertyName特性指定了屬性名,這類屬性不像一般的聲明式屬性那樣賦值,而是動態得到的值。是在項目文件中很常見的用法。

MSBuild項

項大都是用來引入文件用的,而文件會有一些附加信息,比如版本,語言等,而這些附加信息在項目文件中是以項的子元素的出現的,稱為項的元數據。元數據是鍵/值的形式存儲的,聲明方式和屬性相同。

1   <ItemGroup>
2     <!--如果csfile1.cs文件存在就包含在CSFile項中-->
3     <CSFile Include="csfile1.cs">
4       <!--聲明元數據,必須為項的一級子元素-->
5       <!--引用方式:%(CSFile.Culture)-->
6       <Culture>zh-cn</Culture>
7     </CSFile>
8   </ItemGroup>

除了自定義的一些元數據外,系統還提供一些隱式存在的元數據,即不用聲明即可使用,具體可參見MSBuild常見的已知元數據。引用這類元數據的語法和自定義的完全相同。

項轉換允許把一個項的列表與另一個列表一一變換。比如下面的例子:

 1 <?>
 5     <CSFile Include="1.cs;2.cs"/>
 6     <!--%(Filename)為項的元數據,由系統提供-->
 7     <VBFile Include="@(CSFile->'%(Filename).vb')"/>
 8   </ItemGroup>
 9   <Target Name="show">
10     <!--輸出1.cs;2.cs-->
11     <Message Text="@(CSFile)"/>
12     <!--輸出1.vb;2.vb-->
13     <Message Text="@(VBFile)"/>
14   </Target>
15 </Project>

MSBuild任務

從上篇中我們對任務的認識是它是一個原子操作,用來執行某一項邏輯處理,但是xml格式的項目文件是沒有這個處理能力的,所以這些任務都是映射到.NET類庫中的一些類,由這些類來處理操作中的邏輯。具體來說都是實現ITask接口的類,ITask接口位於Microsoft.Build.Framework命名空間。當然我們也可以實現自己的任務類,直接實現ITask接口或者繼承自Task(此抽象類實現了ITask接口的部分功能,可簡化自定義任務類的編寫,留出一個Execute抽象方法供子類重寫自己的任務邏輯)。然后通過UsingTask元素映射到出一個任務元素。我就繼承Task寫一個簡單的示例:

 1 //AddTwoNumberTask.cs,需編譯為dll
 2 using System;
 3 using Microsoft.Build.Utilities;
 4 using Microsoft.Build.Framework;
 5  
 6 /// <summary>
 7 /// 繼承Task,任務邏輯是處理加法
 8 /// </summary>
 9 public class AddTwoNumberTask : Task
10 {
11     /// <summary>
12     /// 定義一個加數,
13     /// 如果一個輸入屬性被要求必須輸入,則用[Required]特性標識該屬性
14     /// </summary>
15     public String Number1 { get; set; }
16     /// <summary>
17     /// 定義另一個加數
18     /// </summary>
19     public String Number2 { get; set; }
20     public override bool Execute()
21     {
22         this.Sum = (Int32.Parse(this.Number1) + Int32.Parse(this.Number2)).ToString();
23         return true;
24  
25     }
26     /// <summary>
27     /// 定義一個輸出參數,使用Output特性修飾該屬性
28     /// </summary>
29     [Output]
30     public String Sum { get; set; }
31 }
 然后編寫如下項目文件,放於AddTwoNumberTask.cs同目錄下:
 1 <!--buildAddTaskDll.csproj-->
 2 <?xml version="1.0" encoding="utf-8"?>
 3 <!--從AddTwoNumberTask.cs源文件到編譯成dll-->
 4 <Project DefaultTargets="buildAddTaskDll"  ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
 5 <PropertyGroup>
 6     <OutputType>Library</OutputType>
 7 </PropertyGroup>
 8 <ItemGroup>
 9     <Reference Include="Microsoft.Build.Framework" />
10     <Reference Include="Microsoft.Build.Utilities.v4.0" />
11     <Reference Include="System" />
12 </ItemGroup>
13 <ItemGroup>
14     <Compile Include="AddTwoNumberTask.cs" />
15 </ItemGroup>
16 <Target Name="buildAddTaskDll">
17     <!--@(Reference->'$(MSBuildBinPath)\%(Identity).dll')表示項轉換-->
18     <Csc Sources="@(Compile)"
19     References="@(Reference->'$(MSBuildBinPath)\%(Identity).dll')"
20         TargetType="$(OutputType)">
21     </Csc>
22 </Target>
23 </Project>

用MSBuild編譯buildAddTaskDll.csproj項目文件。得到AddTwoNumberTask.dll程序集。再編寫一個項目文件usingtask如下:

<?xml version="1.0" encoding="utf-8"?>
<!--使用自定義的任務做加法-->
<Project DefaultTargets="show"  ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <UsingTask TaskName="AddTwoNumberTask" AssemblyFile = "AddTwoNumberTask.dll"/>
  <Target Name="show">
    <AddTwoNumberTask Number1="1" Number2="2" >
      <Output TaskParameter="Sum" PropertyName="SumValue"/>
    </AddTwoNumberTask>
    <!--輸出3-->
    <Message Text="$(SumValue)"/>
  </Target>
</Project>

如果仔細看AddTwoNumberTask.cs文件就會發現

//如果Number1或者2不是數字,則此任務就會拋異常了 
this.Sum = (Int32.Parse(this.Number1) + Int32.Parse(this.Number2)).ToString();

那么如果<AddTwoNumberTask Number1="1" Number2="2" >在這里加一個ContinueOnError=“true”,則表示會忽略掉邏輯處理中的錯誤,繼續運行,否則會終止執行后續任務。如果任務有輸出參數的話,Output元素總是作為任務的子元素出現,作為一個中間橋梁把任務的輸出傳輸到屬性或者項中。

MSBuild目標

Project根元素代表者一個項目文件,上面的例子我都會寫一個DefaultTargets特性來指定該項目文件要執行的默認目標是哪一個。其實此特性是可選的,也是可以用分號分割寫多個的,執行順序依據書寫順序來判定,也可通過MSBuild命令行參數來傳遞:

msbuild /target:Build1;Build2

除此之外,Project元素還有一個可選特性InitialTargets,也支持多個目標。如果這兩個特性都沒有,則MSBuild先執行它遇到的第一個Target。Target有一個DependsOnTargets特性表示當前目標依賴另一個目標,效果就是DependsOnTargets特性指定的目標先於當前目標執行。這繞來繞去好多先后順序關系,寫一個示例看看吧。

 1 <?xml version="1.0" encoding="utf-8"?>
 2 <!--目標執行順序-->
 3 <!--如果InitialTargets特性存在,則首先執行此目標列表-->
 4 <!--如果DefaultTargets特性存在,則繼續執行此目標列表-->
 5 <Project InitialTargets="B1;B2" DefaultTargets="B3;B4"  
 6          ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
 7   <!--如果發現Target具有DependsOnTargets特性-->
 8   <!--則先執行DependsOnTargets指定的目標-->
 9   <!--MSBuild4新加入了RunBeforeTargets和RunAfterTargets特性-->
10   <!--其作用和DependsOnTargets類似,一前一后,不做演示了-->
11   <Target Name="B1" DependsOnTargets="B5">
12     <Message Text="B1"/>
13   </Target>
14   <Target Name="B2">
15     <Message Text="B2"/>
16   </Target>
17   <Target Name="B3">
18     <Message Text="B3"/>
19   </Target>
20   <Target Name="B4">
21     <Message Text="B4"/>
22   </Target>
23   <Target Name="B5">
24     <Message Text="B5"/>
25   </Target>
26   <!--結果為:B5 B1 B2 B3 B4-->
27 </Project> 

Import元素

項目模版產生的*.*proj項目文件大量的使用這個元素,用來導入可重用的項目文件,其中最常見的一個應該是這個吧,如果你用C#開發的話。

<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />

MSBuildToolsPath或者是MSBuildBinPath,Project特性指定要導入的項目文件。Import元素像是一個占位元素,MSBuild在執行到此時會用*.targets替換掉此元素,就像本來就聲明在這里一樣,所以和*.targets文件有關的所有保留屬性會被重置。 Import元素對導入文件的擴展名無要求,文件是正確的項目文件就行,但一般約定為*.targets。

總結和備注

了解了以上知識點后,閱讀一般的項目模版生成的項目文件(*.*proj)應該是可以的了,下篇文章先認識幾個重要的*.targets,為剖析項目文件做准備。

備注:針對項目文件中所指的“特性”是表示一個xml元素的“屬性”。由於屬性在MSBuild中有特殊含義,則MSDN文檔一律把項目文件中的xml屬性稱作是特性,比如Message任務的Text特性。如有錯誤之處,歡迎指正!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM