1. 程式人生 > >[獨孤九劍]持續整合實踐(二)– MSBuild語法入門

[獨孤九劍]持續整合實踐(二)– MSBuild語法入門

本系列文章包含:

1、開始                                                                                                                       

在這篇文章中,我們會從頭開始,一步步完成一個屬於我們自己的MSBuild指令碼。在它完成以後,我們只需要一個命令就可以刪除之前的構建產物,構建.NET應用,執行單元測試。後面我們還會配一個Jenkins Job,讓它從程式碼庫中更新程式碼,執行MSBuild指令碼。最後還會配另一個Jenkins Job,讓它監聽第一個Job的結果,當第一步成功以後,它會把相關的構建產物複製出來,放到web伺服器裡啟動執行。

我們用一個ASP.NET MVC 3應用做例子,在VS裡面建立ASP.NET MVC 3應用並選擇“application”模版就行。我們還要用一個單元測試專案來跑測試。程式碼可以在這裡下載。【由於我的機器環境無法跑通他給的例子,因此我簡單的建立了另一個webForm專案用於測試,如果你同樣無法跑起來HelloCI這個專案,並且懶癌嚴重,請點選這裡下載我的程式碼】

2、你好,MSBuild                                                                                                      
   

MSBuild是在.NET 2.0中引入的針對Visual Studio的構建系統。它可以執行構建指令碼,完成各種Task──最主要的是把.NET專案編譯成可執行檔案或者DLL。從技術角度來說,製作EXE或者DLL的重要工作是由編譯器(csc,vbc等等)完成的。MSBuild會從內部呼叫編譯器,並完成其他必要的工作(例如拷貝引用──CopyLocal,執行構建前後的準備及清理工作等)。

這些工作都是MSBuild執行指令碼中的Task完成的。MSBuild指令碼就是XML檔案,根元素是Project,使用MSBuild自己的名稱空間。

MSBuild檔案都要有Target。Target由Task組成,MSBuild執行這些Task,完成一個完整的目標。Target中可以不包含Task,但是所有的Target都要有名字。

下面來一起建立一個“Hello World”的MSBuild指令碼,先保證配置正確。我建議用VS來寫,因為它可以提供IntelliSense支援,不過用文字編輯器也無所謂,因為只是寫個XML檔案,IntelliSense的用處也不是很大。先建立一個XML檔案,命名為“basics.msbuild”,這個副檔名只是個約定而已,好讓我們容易認出這是個MSBuild指令碼,你倒不用非寫這樣的副檔名。給檔案新增一個Project元素作為根元素,把 http://schemas.microsoft.com/developer/msbuild/2003設定成名稱空間,如下所示

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

下一步,給Project元素新增一個Target元素,起名叫“EchoGreeting”

<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
    <Target Name="EchoGreeting" />
</Project>

這就行了。我們已經有了一個可以執行的MSBuild指令碼。它雖然還啥事都沒幹,但我們可以用它來驗證當前環境是不是可以執行MSBuild指令碼。

在執行指令碼的時候,我們要用到.NET框架安裝路徑下的MSBuild可執行檔案。開啟命令列,執行“MSBuild /nologo /version”命令,看看.NET框架安裝路徑是不是放到了PATH環境變數裡面。如果一切正確,你應該能看到螢幕上打印出MSBuild的當前版本。倘若沒有的話,或者把.NET框架安裝路徑放到PATH裡面去,或者直接用Visual Studio Command Prompt,它已經把該配的都配好了。【我的Path裡沒有,所以要配置PATH環境變數,機器是Win7 x64的,VS2013,MSBuild.exe檔案的Bin目錄位置在C:\Program Files (x86)\MSBuild\12.0\Bin】

進入存放剛才那個指令碼的目錄後,以檔名當作引數呼叫MSBuild,就可以執行指令碼了。在我的機器上可以看到下面的執行結果:

C:\>msbuild basics.msbuild

Microsoft (R) Build Engine Version 4.0.30319.1
[Microsoft .NET Framework, Version 4.0.30319.269]
Copyright (C) Microsoft Corporation 2007. All rights reserved.
Build started 8/2/2012 5:59:45 AM.

Build succeeded.

0 Warning(s)
0 Error(s)

Time Elapsed 00:00:00.03

執行完指令碼以後,MSBuild會首先顯示一個啟動介面和版權資訊(用 /nologo 開關可以隱藏掉它們)。接下來會顯示一個啟動時間,然後便是真正的構建過程。因為咱們的指令碼啥都沒幹,所以構建就直接成功了。總計用時也會顯示在介面上。下面咱們來給EchoGreeting Target新增一個Task,讓指令碼真的乾點事。【以下內容在練習是一定要注意拼寫錯誤,不要問我為什麼。。。】

<Target Name="EchoGreeting">
    <Exec Command="echo Hello from MSBuild" />
</Target>

現在EchoGreeting Target有了一個Exec Task,它會執行Command屬性中定義的任何命令【Command裡的命令應該都是批處理命令】。再執行一次指令碼,你應該能看到更多資訊了。在大多數時候,MSBuild的輸出資訊都很長,你可以用 /verbosity 開關來只顯示必要資訊【使用MSBuild /help可查詢所有命令引數】。不過無論怎樣,MSBuild都會把我們的文字顯示到螢幕上。下面再新增一個Target。

<Target Name="EchoDate">
    <Exec Command="echo %25date%25" />
</Target>

這個Target會輸出當前日期。它的命令要做的事情就是“echo %25date%25”,但是“%”字元在MSBuild中有特殊含義,所以這個命令需要被轉義。當遇到轉義字元的時候,“%”後面的十進位制字元會被轉成對應的ASCII碼。MSBuild只會執行Project元素中的第一個Target。要執行其他Target的時候,需要把/target開關(可簡寫為 /t)加上Target名稱傳給MSBuild。你也可以指定MSBuild執行多個Target,只要用分號分割Target名字就可以。

C:\>msbuild basics.msbuild /nologo /verbosity:minimal /t:EchoGreeting;EchoDate
Hello from MSBuild
Thu 08/02/2012

3、更實用的構建指令碼                                                                                                         

演示就先到這裡。下面來用MSBuild來構建一個真實專案。首先把示例程式碼下載下來,或是自己建立一個ASP.NET應用。給它新增一個MSBuild指令碼,以solution或project名字給指令碼命名,副檔名用“.msbuild”。照先前一樣指定MSBuild名稱空間。

開始寫指令碼之前,先把指令碼要乾的事情列出來:

1. 建立BuildArtifacts目錄

2. 構建solution,把構建產物(DLL,EXE,靜態內容等等)放到BuildArtifacts目錄下。

3. 執行單元測試。

因為示例應用叫做HelloCI,於是這個指令碼也就命名為HelloCI.msbuild。先新增名稱空間,然後就可以新增第一個Target了,我管它叫做Init。

<Target Name="Init">
    <MakeDir Directories="BuildArtifacts" />
</Target>

這個Target會呼叫MakeDir Task建立一個新的目錄,名叫BuildArtifacts,跟指令碼在同一目錄下。執行指令碼,你會發現該目錄被成功建立。如果再次執行,MSBuild就會跳過這個Task,因為同名目錄已經存在了。

接下來寫一個Clean Target,它負責刪除BuildArtifacts目錄和裡面的檔案。

<Target Name="Clean">
    <RemoveDir Directories="BuildArtifacts" />
</Target>

理解了Init之後,這段指令碼就應該很好懂了。試著執行一下,BuildArtifacts目錄應該就被刪掉了。下面再來把程式碼中的重複幹掉。在Init和Clean兩個Target裡面,我們都把BuildArtifacts的目錄名硬編碼到程式碼裡面了,如果未來要修改這個名字的話,就得同時改兩個地方。這裡可以利用Item或Property避免這種問題。

Item和Property只有些許差別。Property由簡單的鍵值對構成,在指令碼執行的時候還可以用 /property 賦值。Item更強大一些,它可以用來儲存更復雜的資料。我們這裡不用任何複雜資料,但需要用Items獲取額外的元資訊,例如檔案全路徑。

接下來修改一下指令碼,用一個Item存放路徑名,然後修改Init和Clean,讓它們引用這個Item。

<ItemGroup>
    <BuildArtifactsDir Include="BuildArtifacts\" />
</ItemGroup>

<Target Name="Init">
    <MakeDir Directories="@(BuildArtifactsDir)" />
</Target>
<Target Name="Clean">
    <RemoveDir Directories="@(BuildArtifactsDir)" />
</Target>

Item是在ItemGroup裡面定義的。在一個Project中可以有多個ItemGroup元素,用來把有關係的Item分組。這個功能在Item較多的時候特別有用。我們在ItemGroup裡定義了BuildArtifactsDir元素,並用Include屬性指定BuildArtifacts目錄。記得BuildArtifacts目錄後面要有個斜槓。最後,我們用了@(ItemName)語法在Target裡面引用這個目錄。現在如果要修改目錄名的話,只需要改BuildArtifactsDir的Include屬性就好了。

接下來還有個問題要處理。在BuildArtifacts目錄已經存在的情況下,Init是什麼事都不幹的。也是就說,在呼叫Init的時候磁碟上的已有檔案還會被保留下來。這一點著實不妥,如果能每次呼叫Init的時候,都把目錄和目錄裡面的所有檔案都一起刪掉再重新建立,就能保證後續環節都在乾淨的環境下執行了。我們固然可以在每次呼叫Init的時候先手工調一下Clean,但給Init Target加一個DependsOnTargets屬性會更簡單,這個屬性會告訴MSBuild,每次執行Init的時候都先執行Clean。

<Target Name="Init" DependsOnTargets="Clean">
    <MakeDir Directories="@(BuildArtifactsDir)" />
</Target>

現在MSBuild會幫我們在調Init之前先調Clean了。跟DependsOnTargets這個屬性所暗示的一樣,一個Target可以依賴於多個Target,之間用分號分割就行

接下來我們要編譯應用程式,把編譯後的結果放到BuildArtifacts目錄下。先寫一個Compile Target,讓它依賴於Init。這個Target會呼叫另一個MSBuild例項來編譯應用。我們把BuildArtifacts目錄傳進去,作為編譯結果的輸出目錄。

<ItemGroup>
    <BuildArtifactsDir Include="BuildArtifacts\" />
    <SolutionFile Include="HelloCI.sln" />
</ItemGroup>

<PropertyGroup>
    <Configuration Condition=" '$(Configuration)' == '' ">Release</Configuration>
    <BuildPlatform Condition=" '$(BuildPlatform)' == '' ">Any CPU</BuildPlatform>
</PropertyGroup>

<Target Name="Compile" DependsOnTargets="Init">
    <MSBuild Projects="@(SolutionFile)" Targets="Rebuild" Properties="OutDir=%(BuildArtifactsDir.FullPath);Configuration=$(Configuration);Platform=$(BuildPlatform)" />
</Target>

上面的指令碼做了幾件事情。

首先,ItemGroup添加了另一個Item,叫做SolutionFile,它指向solution檔案。在構建指令碼中用Item或Property代替硬編碼,這算的是一個優秀實踐吧。

其次,我們建立了一個PropertyGroup,裡面包含兩個Property:Configuration和BuildPlatform。它們的值分別是“Release”和“Any CPU”。當然,Property也可以在執行時通過/property(簡寫為/p)賦值。我們還用了Condition屬性,它在這裡的含義是,只有當這兩個屬性沒有值的情況下,才用我們定義的資料給它們賦值。這段程式碼實際上就是給它們一個預設值。

接下來就是Compile Target了,它依賴於Init,裡面內嵌了一個MSBuild Task。它在執行的時候會呼叫另外一個MSBuild例項。在指令碼中定義了這個被內嵌的MSBuild Task要操作的專案。在這裡,我們既可以傳入另外一個MSBuild指令碼,也可以傳入.csproj檔案(它本身也是個MSBuild指令碼)。但我們選擇了傳入HelloCI應用的solution檔案。Solution檔案不是MSBuild指令碼,但是MSBuild可以解析它。指令碼中還指定了內嵌的MSBuild Task要執行的Target名稱:“Rebuild”,這個Target已經被匯入到solution的.csproj檔案中了。最後,我們給內嵌的Task傳入了三個Property。

OutDir 編譯結果的輸出目錄
Configuration 構建(除錯、釋出等)時要使用的配置
Platform 編譯所用的平臺(x86、x64等)

給上面這三個Property賦值用的就是先前定義的Item和Property。OutDir Property用的是BuildArtifacts目錄的全路徑。這裡用了%(Item.MetaData) 語法。這個語法應該看起來很眼熟吧?就跟訪問C#物件屬性的語法一樣。MSBuild創建出來的任何Item,都提供了某些元資料以供訪問,例如FullPath和ModifiedTime。但這些元資料有時候也沒啥大用,因為Item不一定是檔案。

Configuration和Platform用到了先前定義好的Property,語法格式是$(PropertyName)。在這裡可以看到系統保留的一些屬性名,使用者不能更改。定義Property的時候請不要用它們。

這裡還有些東西值得提一下。用了Property以後,我們可以在不更改構建指令碼的情況下使用不同的Configuration或者BuildPlatform,只要在執行的時候用 /property 傳值進去就行。所以“msbuild HelloCI.msbuild /t:Compile /p:Configuration:Debug”這個命令會用Debug配置構建專案,而“msbuild HelloCI.msbuild /t:Compile /p:Configuration:Test;BuildPlatform:x86”會在x86平臺下使用Test配置。

現在執行Compile,就可以編譯solution下的兩個專案,把編譯結果放到BuildArtifacts目錄下。在完成構建指令碼之前,只剩下最後一個Target了:

<ItemGroup>
    <BuildArtifacts Include="BuildArtifacts\" />
    <SolutionFile Include="HelloCI.sln" />
    <NUnitConsole Include="C:\Program Files (x86)\NUnit 2.6\bin\nunit-console.exe" />
    <UnitTestsDLL Include="BuildArtifacts\HelloCI.Web.UnitTests.dll" />
    <TestResultsPath Include="BuildArtifacts\TestResults.xml" />
</ItemGroup>
<Target Name="RunUnitTests" DependsOnTargets="Compile">
    <Exec Command='"@(NUnitConsole)" @(UnitTestsDLL) /[email protected](TestResultsPath)' />
</Target>

ItemGroup裡現在又多了三個Item:

NUnitConsole指向NUnit控制檯執行器(console runner);

UnitTestDLL指向單元測試專案生成的DLL檔案;

TestResultsPath是要傳給NUnit的,這樣測試結果就會放到BuildArtifacts目錄下。

RunUnitTests Target用到了Exec Task。如果有一個測試執行失敗,NUnit控制檯執行器會返回一個非0的結果。這個返回值會告訴MSBuild有個地方出錯了,於是整個構建的狀態就是失敗【這裡提一下,單元測試之類的第三方類庫要保證一直在專案中,否則在上傳至版本管理伺服器上時,可能會被忽略導致後面的執行測試期間出現問題】

現在這個指令碼比較完善了,用一個命令就可以刪除舊的構建產物、編譯、執行單元測試:

C:\HelloCI\> msbuild HelloCI.msbuild /t:RunUnitTests

我們還可以給指令碼設一個預設Target,就省得某次都要指定了。在Project元素上加一個DefaultTargets屬性,讓RunUnitTests成為預設Target。

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

你還可以建立自己的Task。這裡有個例子,AsyncExec【我是沒開啟,可能被Q了】,它允許人們以非同步的方式執行命令。比如有個Target用來啟動Web伺服器,要是用Exec命令的話,整個構建都會停住,直到伺服器關閉。用AsyncExec這個命令可以讓構建繼續執行,不用等待命令執行結束。

本文的完整指令碼可以在這裡下載【或者下載我的】。

在接下來的文章中,我會講述如何配置Jenkins。我們不再需要手動執行命令來構建整個專案,Jenkins會檢測程式碼庫,一旦有更新就會自動觸發構建。

參考:

相關推薦

[]持續整合實踐MSBuild語法入門

本系列文章包含: 1、開始                                                                                                                        在這篇文章中,我們會從頭開始,一步步完

Linux運維•持續整合自動化

一、安裝Maven 1.1 安裝開始 1.2 解壓 Maven 包 tar xf apache-maven-3.5.4-bin.tar.gz 1.3 移動 解壓後的maven檔案 到 /usr/local/ mv apache-maven-3.5.4 /usr/

Web前端持續整合方案

三、利用Grunt實現專案自動化打包     Grunt是一款基於node的javascript工作管理員工具。我們的專案使用Grunt實現專案自動化打包,以及後續的持續整合。Grunt如何使用,本文不詳細介紹(其實是不會-_-!),詳見《【grunt整合版】30分鐘學會使

--PHP視頻學習--錯誤處理

給他 log 關閉 生產 post 社會工程學 如何 txt文件 bsp (1)錯誤處理概念 在生產環境(即公網)給他人訪問的網站、手機網站、手機接口等。如果錯誤顯示出來就容易暴露 1.服務器文件路徑和文件存儲規範 2.有些人喜歡用個人名命名,通過社會工程學可以反向推理出密

--PHP視頻學習--文件系統

獲取文件 pen 讀取 warning 寫入 字符型 級別 fread 資源 【一】文件系統概述 文件的創建,刪除,編輯,復制,粘貼(移動),即文件的增刪改查 【二】讀取文件 打開讀取:readfile(string),傳入文件路徑即可打開讀取(如讀取成功,則返回字節數

--PHP視頻學習--cURL

開啟 blog print 關閉 pre time .cn http協議 exe 【一】概論 日常開發裏,cURL使用最多的協議就是HTTP協議的GET、POST請求,其他協議和請求方式用的較少。 【二】開啟 開發前檢驗是否開啟了cURL模塊,開啟方法為php.int中

金庸武功之“”--redis一主三從配置

刪掉 tab 保留 得到 第一步 127.0.0.1 tcp http bsp redis-server說明服務器A:192.168.1.131:8000(主)服務器B:192.168.1.135:8000服務器C:192.168.1.231:8000服務器D:192.16

運維的

架構 時間 資源 核心技術 技術 不遠 能夠 規劃 大於 心法:遇強則強,遇弱則弱;如果你覺得自己Low,那就是你的對手Low(對手包括你自己)! 第一式、監控報警報表 沒有監控就是不知己,不知己沒戰必殆。 沒有報警就是不能兼聽,不兼聽就不明。 沒有報表,反正Lead

與乾坤大挪移—uikiller外掛系統

上篇《雷神之錘》介紹了uikiller的基本用法,有人說長按功能可以取名為蓄力攻擊、重擊,我覺得還真是可以的,但就是感覺招數名字不夠大氣。在這裡還要給大家道個歉,上篇中我說了這一樣句話: uikiller只有一行需要要被主動呼叫的函式:uikiller.bindCompo

軟體架構質量屬性之《

   什麼是架構?架構是一個系統的基本組織結構,涵蓋所包含的元件、元件之間的關係、元件與環境的關係、以及指導架構設計和演進的原則等內容。經常有人問架構重點需要關注多少屬性,我們粗略的可以概括一下九點:可修改性、可測試性、可擴充套件性、效能、可用性、安全性、可部署性、共享性。       一、可修改性

mllib實踐之LinearRegression實踐DataFrame方式,普通標籤格式轉DataFrame整合網際網路上多個例項

package mllib; import org.apache.spark.{ SparkConf, SparkContext } import org.apache.spark.ml.linalg.Vectors import org.apache.spark.mllib.regress

Jenkins+RF持續整合測試 環境搭建

通常在自動化測試中,我們需要對自動化測試用例定時構建,並生成報告並通過郵件發給指定的人。最佳工具選擇莫過於Jenkins了。通過Jenkins整合robot framework外掛,我們能非常方便的定時從git/svn上拉取最新的自動化測試用例,然後執行用例並把最終結果以測試報告的形式發給指定的人群。 下面

Docker+Jenkins持續整合環境1使用Docker搭建Jenkins+Docker持續整合環境

來源:https://www.cnblogs.com/xiaoqi/p/docker-jenkins-cicd.html 本文介紹如何通過Jenkins的docker映象從零開始構建一個基於docker映象的持續整合環境,包含自動化構建、釋出到倉庫\並部署上線。 0. 前置條件 伺服器安

Web前端持續整合方案

一、引言     前端專案從開發到部署上線,中間通常要做一些額外的處理。比如程式碼壓縮、合併、css預編譯、巨集替換等。對於一個成熟的專案,這些通常都是通過指令碼自動完成的。本文結合專案實踐,介紹grunt在web前端持續整合中的應用。 二、專案工程化     1、開發一

MSBuild和Jenkins搭建持續整合環境1

你或其他人剛剛寫完了一段程式碼,提交到專案的版本倉庫裡面。但等一下,如果新提交的程式碼把構建搞壞了怎麼辦?萬一出現編譯錯誤,或者有的測試失敗了,或者程式碼不符合質量標準所要求的底限,你該怎麼辦? 最不靠譜的解決方案就是寄希望於所有人都是精英,他們根本不會犯這些

Elasticsearch實踐在Springboot微服務中整合搜尋服務

關於如何用Docker搭建Elasticsearch叢集環境可以參考前一篇:Elasticsearch實踐(一)用Docker搭建Elasticsearch叢集。本文主要介紹,如果在Springboot體系中整合Elasticsearch服務。本文基於:Elas

使用TeamCity對專案進行可持續整合管理

一、可持續整合管理 持續整合,CI:即Continuous integration。 可持續整合的概念是基於團隊(小組)協作開發而提出來的,為了提高團隊開發效率與降低整合風險(早發現,早解決。晚發現,解決更麻煩<1>),各種可持續整合的管理平臺應運而生,這

搭建面向NET Framework的CI/CD持續整合環境

# 前言 網上大多數都是針對主流的Spring Cloud、NET Core的CI/CD方案。但是目前國內絕大部分的公司因為一些歷史原因無法簡單的把專案從NET Framework切換升級到NET Core,又急切的需要引入CI/CD流程來提高開發體驗和效率。因此,本系列部落格針對這一問題,旨在解決NET

Mongodb基礎實踐

數據庫查詢 表達式 where 技術 文章 在前面的文章裏面主要介紹了MongoDB的文檔,集合,數據庫等操作和對文檔的增、刪、改相關知識,接下來會總結一點有關查詢的相關知識。 在MySQL中,我們知道數據查詢是優化的主要內容,讀寫分離等技術都是可以用來處理數據庫查詢優化的,足以見數

MVC項目實踐——需求分析

用例 分析 strong span 詳細 現在 同時 喜歡 發揮 需求: 作為一名觀眾,我希望知道詳細的比分變化和得分信息,以便於了解比賽走向和隊員的精彩得分。 用例故事: 裏約奧運女排決賽進行中... Ht7:現在比分多少了? LP:2:1,中國隊領先。 Ht7:那小比