MSBuild基本概念(續)
在上一篇簡單的介紹了下MSBuild中的四個基本塊,每塊介紹比較單薄,在這里對在大多數的項目模版生成的*.*proj文件中比較常見一些用法和概念做些補充。主要有一下幾方面:
MSBuild特殊字符
一些字符在MSBuild中代表着特殊的上下文含義,如下:
針對MSBuild的特殊字符轉義需要用[%xx]這種方式,xx代表字符的ASCII十六進制值([%=%25][$=%24][@=%40]['=%27][;=%3B][?=%3F][*=%2A])。針對XML保留字符則使用<這種方式。 一般用到這些特殊字符的情況不多,見到時能知道是轉義就可以了。
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 }
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特性。如有錯誤之處,歡迎指正!