天天看點

MSBuild簡單介紹

背景

托部落格園的福,上周六,有家開發醫療行業系統的初創公司聯系我,說在部落格園上看到我關于WPF的幾篇文章,邀請我去他們那裡交流WPF相關的技術知識和心得體會。作為非大拿的我自然是受寵若驚,但對方好意相約,我便欣然前往。

諸事按過不表,在交流過程中,談到了單獨一個産品的版本控制的問題。

(以下該公司人員簡稱為對方)

對方:“我們用SVN,還不錯。隻是現在産品的版本越來越多。”

我:“怎麼說?”

對方:“我們開發平台有2.0、4.0,目前正在嘗試4.5,特性和文法還是有些許不一樣的。但是我們不可能為每個.net framework單獨維護一個解決方案。少數不同的地方,我們采用SVN分支的方式進行。”

我:“你們産品的市場版本應該也有若幹個吧……”

對方:“唔,客戶的需求大同小異,是以我們的版本是按子產品和功能的不同而區分的,比如标準版、決策分析版、旗艦版等等,但每個版本具體到某個子產品,基本上保持一緻,偶爾需要微調下。”

我:“微調也是通過SVN來管理?”

對方:“是的,是以現在分支越來越多,其實代碼都大同小異。”

我:“哦,我沒了解錯的話,很多情況下,當主幹代碼修改了,那所有的這些分支都需要進行相應的合并?”

對方:“是這樣沒錯,有時候想想,如果主幹修改的内容能自動無錯地合并到分支就好了。”

我:“SVN我隻會簡單使用,不過我認為你們的這種情況,可以使用另一種方式——vs原本就支援的……”

說到這裡,對方的眼睛突然亮了,我和他相視一笑,我知道他也想到了。這也暗合了我自己總結的一套哲理中的其中一條:汝握秘之鑰,just forget it。而這麼顯而易見的處理方法被整個開發團隊忽略,這是一個典型的“群體性失明”案例。

“MD,早該想到,條件編譯符号!”

沒錯,條件編譯符号結合#if、#else、#endif之類,恰當地使用它們,可以将SVN的各個分支重新整合在一起(當然并行開發過程仍然需要SVN進行管控)。前提是各分支不要有太大差異,有些分支項目結構都變了,那更沒必要整在一起了。

各位看官,是不是有點疑惑——不是說MSBuild嗎,怎麼扯到條件編譯符号上去了?

隔天晚上,我正在安裝前幾天解除安裝掉的快播軟體,準備用來觀看這兩天落下的新聞聯播時,手機響了,是上面那家公司的技術主管(以下仍稱對方)。在這個關鍵時刻打我電話,肯定是發生了緊急的事情,于是我淡定地按了接聽鍵。

對方:“XX,我們用WPF版本的項目做整合,發現有個問題。”

我:“不會吧,什麼問題?”

對方:“代碼檔案沒問題,通過條件編譯符号能生成特定版本的程式集,但是xaml檔案似乎并不支援#if、#elif這些指令。”

我一驚:“what?!”

對方:“某些情況,我們通過binding方式,就是說,在代碼檔案中根據編譯符号設定某一屬性的值,然後前台根據這個值來顯示特定版本的内容。但是另外有些情況不曉得怎麼處理。電話說不清楚,我發你QQ。”

挂了電話,我不情願地中止了快播安裝程序,打開QQ,一段代碼跳了出來。

1 <!--SmartGridView.Title綁定至背景代碼,可以在背景通過條件編譯符号設定合适的值-->
 2 <his:SmartGridView AutoGenerateColumns="True"                   
 3                    Title="{Binding Title}"
 4                    ItemsSource="{Binding GridDataItems}">
 5     <!--當為版本A時,需要下面這段代碼,其餘版本不需要-->
 6     <his:SmartGridView.RowDetailsTemplate>
 7         <DataTemplate>
 8             <his:MedicineStockDetailsTemplate />
 9         </DataTemplate>
10     </his:SmartGridView.RowDetailsTemplate>
11     <!--當為版本A時,需要上面這段代碼,其餘版本不需要-->
12 </his:SmartGridView>      

對方表示第6行到第10行是目前難點所在。我半晌之後回過神,确實,編譯指令+條件編譯符号對cs檔案有用,在xaml中能結合binding達到一部分效果,當遇到一段xaml要麼存在,要麼不存在的情況,這三者就無能為力了。怎麼辦?

我查閱頭腦中的知識庫,無果,上網搜尋也沒搜出個是以然來,難道又要怪微軟這個坑爹貨,不在xaml中加入預編譯指令這麼拉轟的功能?正當拙計之時,猛然發現有個單詞在谷歌的懷抱中向我抛着媚眼,定睛一看——“MSBuild”!

 基本概念

MSBuild基于項目檔案發揮作用。我們以Visual C# 項目檔案為例。當在Visual Studio中建立一個項目,VS自動給我們生成了一個.csproj項目檔案。一個典型的項目檔案格式如下:

1 <Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
 2   <PropertyGroup>
 3     <AssemblyName>MSBuildSample</AssemblyName>
 4     <OutputPath>Bin\</OutputPath>
 5   </PropertyGroup>
 6   <ItemGroup>
 7     <Compile Include="helloworld.cs" />
 8   </ItemGroup>
 9   <Target Name="Build" Inputs="@(Compile)" Outputs="$(OutputPath)$(AssemblyName).exe">
10     <MakeDir Directories="$(OutputPath)" Condition="!Exists('$(OutputPath)')" />
11     <Csc Sources="@(Compile)" OutputAssembly="$(OutputPath)$(AssemblyName).exe" />
12   </Target>
13   <Target Name="Clean" >
14     <Delete Files="$(OutputPath)$(AssemblyName).exe" />
15   </Target>
16   <Target Name="Rebuild" DependsOnTargets="Clean;Build" />
17 </Project>      
  • 其中PropertyGroup定義各種屬性鍵值對,類似于字元串變量;
  • ItemGroup定義各種項,項可以有中繼資料(MetaData),我們可以将之看作對象(class),一般用于定義檔案/檔案夾;
  • Target,目标,其中一般包含若幹Task,針對生成程式集這個過程來講,Target即為執行一系列任務完成的一個生成步驟,當預定的所有生成步驟都完成之後,程式集也就生成成功了。
  • 以$(PropertyName)方式擷取屬性值;
  • @(ItemType)文法引用項,直接列印為Include指定的字元串,要擷取項的中繼資料,使用%(ItemType.MetaDataName);
  • 可以将項清單轉換為新的項清單。文法:@(ItemType -> '%(MetadataName)')。當然右邊可以是任意你想要的格式,可以使用屬性、函數或自定義字元串等等;
  • Target中設定Inputs和Outputs屬性将改變該步驟為增量模式,即MSBuild會比對這兩者之間最晚的檔案修改時間以決定是否執行該步驟;
  • Target中的DependsOnTargets表示該Target依賴于其它Target,其它Target執行完畢之後該Target才能執行,且其它Target的執行順序為DependsOnTargets中定義的順序。
  • 另外,幾乎所有MSBuild元素都可以有Condition特性,隻有當Condition的計算結果為“true”時,才會定義或重新定義相關元素。

假設上述檔案儲存為Test.csproj,我們可以在Visual Studio 指令提示鍵入

msbuild Test.csproj /t:Rebuild      

/t表示要執行的Target,若去除/t:Rebuild,則會執行Build Target,因為Project節點有個DefaultTargets,指定了預設的Target。

以上為基本概念,詳情請參看:演練:使用 MSBuild

進階

  1. 有些系統預定義的Target.前邊說道vs會幫我們自動生成一個項目檔案,用記事本打開看,會發現Project的DefaultTargets="Build",但是檔案中并未定義名稱為"Build"的Target,這就是系統預定義的。另外末尾有兩個被注釋掉的Target,在Build Target執行前後要做些額外事情,我們就可以使用它們.
  2. 項(Item)也有很多預定義的中繼資料,比如Filename、Extension、FullPath,從字面就能了解它們的意思,具體請看MSBuild: By Example—Introducing Well-Known Metadata;
  3. 也有若幹保留屬性,參考MSBuild 保留屬性;
  4. 在 .NET Framework 4 版和 4.5 版中,可以使用屬性函數來計算 MSBuild 腳本。參考屬性函數;
  5. 動态建立項.有時候需要根據已有條件建立新項,比如在磁盤中動态生成了一些檔案,我們就可以為這些檔案建立對應的項,以便使之有機會參與Build的過程.注意項Include特性指向的檔案如果不存在,在生成過程中可能會抛出error導緻生成中止.3.5之前,可以使用CreateItem;3.5及以後版本對這方面做了改進,可以直接在Target中嵌入ItemGroup,不但能建立新Item,還能Remove或者Modify原有的Item.參考CreateItem vs ItemGroup;
  6. 前邊說過Target裡包含若幹Task,表示按順序執行的步驟,咱們也可以自定義Task.比如自定義了一個Task名稱為PreprocessXaml,它包含在BuildTasks.dll的程式集中,可以使用以下方式獲得該Task的引用.
    1 <PropertyGroup>
    2   <BuildTasksPath Condition="'$(BuildTasksPath)' == ''">..\BuildTasks\</BuildTasksPath>
    3   <BuildTasksLib>$(BuildTasksPath)BuildTasks.dll</BuildTasksLib>
    4 </PropertyGroup>
    5 
    6 <UsingTask AssemblyFile="$(BuildTasksLib)" TaskName="PreprocessXaml" />      
    然後可以這麼使用之.
    <Target Name="MyTarget">
      <PreprocessXaml SourceFile="%(PreprocessedXaml.FullPath)"
                                    DestinationFile="$(ProjectDir)%(PreprocessedXaml.OutputFile)">
      </PreprocessXaml>
    </Target>      
    更多參考Best Practices For Creating Reliable Builds, Part 2;
  7. 第1條說道我們可以在項目檔案末尾取消預設注釋掉的兩個Target,在其中插入我們想要在Build之前之後執行的邏輯.當然還有另一種方法,覆寫預定義的BuildDependsOn屬性.
    <PropertyGroup>
      <BuildDependsOn>
        MyBeforeTarget;
        $(BuildDependsOn);
        MyAfterTarget;
      </BuildDependsOn>
    </PropertyGroup>      
    還有其它預定義東東可以覆寫,更多詳情:如何:擴充 Visual Studio 生成過程;
  8. 除了在項目檔案中寫一份完整的MSBuild文檔,還可以将之分離成多個.targets格式檔案,targets文檔格式同.csproj/.vbproj檔案.then我們可以在項目檔案中使用<Import Project=".XXX.targets" />引入,targets檔案也能引入其它targets,MSBuild根據引入順序執行生成過程;
  9. 生成過程如果出錯,就會終止,而使用vs不能像普通項目一樣進行運作時調試.往往出錯都是出自自定義Task,Task有個Log對象,我們可以使用它在vs底部的錯誤清單中輸出錯誤\警告\普通消息.如base.Log.LogErrorFromException(exception);  

使用MSBuild給XAML增加條件編譯符号的支援

概念方面,算是基本把該寫到的點都寫到,哎,累了,特别是這種相對枯燥的概念學習、解釋、搬運,剛開始根本無從下手,各種繁雜困惑。接下去就是完成咱們最初的目的。大家先自己思考下怎麼個方案可行。這下篇再寫吧。剛說了,哥累了。

轉載請注明本文出處:http://www.cnblogs.com/newton/p/3156873.html 

繼續閱讀