我嘗試用一個問世未滿一年的 Nuke 建置工具來為我的 C# 專案提供自動建置與打包 NuGet 套件的腳本。初次使用的感覺還不錯。
摘要
一來,在一個開發團隊中,並非每一位成員都有機會去建立或修改建置腳本。二來,就算能夠接觸到建置腳本的原始碼,可能也不敢動手修改(萬一改壞了,某人可能會一直收到建置失敗的通知),因為其中往往涉及多種工具和語言的搭配運用,例如 PowerShell、C#、F# 等等,如果建置腳本是用自己不熟悉的語言寫成,欲窺其堂奧又更加困難。第三,建置腳本若採用動態編譯的語言(scripting language),通常不方便除錯,例如只能從 log 檔案裡面的訊息來研判建置失敗的原因,而無法單步追蹤、設定中斷點、觀察變數值。
因此,若能夠以平日使用的開發工具和程式語言來編寫建置腳本,應該是最理想的吧。如果你也這麼認為,不妨繼續看下去。
Nuke 大約在 2017 年秋季誕生,目前還不滿一歲。這麼新的工具,要用在實際的軟體開發專案上,是有風險的。畢竟,如果一兩年後它就不再出新版本,先前付出的學習成本可能就此泡湯。就建置工具而言,我個人是覺得不至於造成太大的麻煩,所以只要有發現新工具,總是願意多看一眼。
然後,我在網路上某個討論串裡面看到 Nuke。稍微看了一下官方文件,蠻喜歡它的設計理念,所以打算試試看。就我目前的粗淺理解,它的設計理念是:直接用我們已經熟悉的 C# 來寫建置腳本。
的確,Cake 和 Nuke 都可以用 C# 來編寫建置腳本。然而,我覺得 Nuke 把這件事變得更容易了。
進一步說,Nuke 的設計理念是:讓建置工具本身就是一個 Visual Studio 專案,而建置腳本就寫在這個專案的 C# 類別裡。
這個用來建置專案的專案,就叫做……建置專案(build project)。它是個 Console 類型的 .NET 應用程式,而我們就是透過編寫這個專案裡面的 C# 程式碼(Build.cs)來建立建置腳本。
沒錯,編寫建置腳本 = 用 C# 寫 Console 應用程式!
也就是說,每當要建置應用程式專案時,只要執行這個 Console 應用程式就行了。
由於建置專案的腳本就是我們已經很熟悉的 Console 應用程式,所以不論是編寫還是除錯建置腳本,都可以直接利用強大的 Visual Studio 整合開發環境的既有功能(設定中斷點、單步追蹤、觀察變數值等等)。換言之,即便是初學者也很容易上手。
了解 Nuke 的基本概念之後,接著就以一個簡單的例子來看看實際上怎麼使用。
步驟一:產生建置專案和預設腳本
要從無到有編寫一份建置腳本,對任何一個新手來說都不容易。Nuke 提供了產生預設腳本的工具,無論新手老手,應該都會覺得很方便。
首先,開啟命令列視窗,將現行目錄切換到應用程式專案的根目錄。我是拿目前正在整理的一個迷你開源專案來實驗。這個開源專案是 NChinese。
根據官方文件的說明,只要在應用程式專案的根目錄下執行底下兩個 PowerShell 命令,就會產生建置專案以及相關的檔案:
執行 setup1.ps1 時,它會要你回答幾個問題。如下圖,我是用 Cmder 來執行上述命令。
從上圖可以看到,setup.ps1 一開始會自動尋找並選擇一個 solution 檔案來做為建置標的。接著會要求你回答一些問題。
大部分的問題都可以直接按 Enter 鍵來選擇預設值,只有第一個問題比較值得留意。它問的是:Build project target platform。意思是:現在要產生的 Nuke 建置專案,你想使用哪一種目標平台?
此問題的選項有二:一個是 .NET Framework,另一個是 .NET Core。
我的建議是,應用程式專案的目標平台是什麼,建置專案就選擇那個目標平台。這不是官方建議,只是為了減少初次上手時可能遭遇的問題。稍後我會進一步解釋。
此外,若目標平台選擇 .NET Framework,接著會再問你 .csproj 的格式要用新格式(SDK-based format)還是舊格式(Legacy format)。當然是選擇新格式囉。如下圖:
執行完 setup.ps1 之後,Nuke 會在當前目錄下產生一些檔案,同時也會修改上一步驟中指定的 .sln 檔案:把新產生的、擔任建置工作的 .csproj 專案加入 .sln 方案。預設情況下,那個擔任建置工作的專案會被命名為「.build.csproj」(沒寫錯,檔名是小數點開頭)。
用 Visual Studio 開啟 solution 檔案,在 Solution Explorer 裡面可以看到這個名為 .build.csproj 的專案。如下圖:
這裡要注意的是,新產生的 .build.csproj 應單獨編譯,而不應該參與整個 solution 的編譯程序。也就是說,雖然 .build.csproj 有加入應用程式的 solution,可是在建置整個方案時,要排除 .build.csproj 這個專案。原因不難理解:我們會用 .build.csproj 所產生的執行檔來建置整個 solution。參考下圖:
然後,最好再用 git diff 或類似工具來查看所有的變動,以了解剛才執行的 setup.ps1 產生及改變了哪些檔案。下圖是用 TortoiseGit 執行差異比對功能的畫面:
此步驟一,每個專案通常只需要執行一次。如果因為某些原因需要重新做一遍也可以,只要將此步驟所產生的檔案刪除,再執行一次上述命令即可。
步驟二:執行建置
每當需要建置應用程式時,只要執行 .build.csproj 的執行檔就行了。如果 .build.csproj 的目標平台是 .NET Core(見上一步驟的說明),其預設的組件名稱會是 .build.dll。執行過程如下圖:
由於建置專案的執行檔藏在比較深層的子目錄,所以每次要執行時並不太方便。建置專案的執行檔案,我想最主要的用處還是能夠在 Visual Studio IDE 裡面直接執行和單步除錯。
每次需要執行建置時,有兩個方法比較方便。一個是執行專案根目錄下的 build.ps1:
另一個方法是執行專案根目錄下的 build.cmd。這個方法比更簡便。比如說,如果我要建置的 target 是 Pack,就只要輸入以下命令就行了:
相較於直接執行建置專案的執行檔,build.ps1 和 build.cmd 的好處是具備啟動程序(bootstrap),能夠自動下載建置時需要的相關工具,例如 .NET SDK、Nuget 等等。
步驟三:反覆修改和測試建置腳本
完成上面兩個步驟之後,我們便能確認這個很陽春的預設建置腳本已經能夠順利執行。接著就可以按個人需求來反覆修改和測試建置腳本。
前面提過,建置腳本其實就是個 C# 類別,預設的檔案名稱是 Build.cs。程式碼如下:
注意到裡面有好幾個地方出現類似表情符號的語法嗎?這個: => _ => _
第一次看到的感覺是不是有點那個…… >_<
如果看不慣 lambda 語法,當然也可以另外寫委派方法。
入門教學就寫到這裡。底下附上我剛開始胡亂嘗試時碰到的問題,萬一你也碰到同樣問題,可以參考看看。
我第一次使用 Nuke 腳本來執行建置時,結果失敗。錯誤訊息如下圖:
此錯誤訊息的原文是:
解法
原本我以為是因為應用程式專案的目標平台與建置專案(.build.csproj)的目標平台不同所致——我的應用程式專案的目標平台是 .NET Framework 4.x,而 .build.csproj 的目標平台是 .NET Core 2.0。
上網爬文後發現,我猜錯了。
造成建置失敗的原因是應用程式的 solution 裡面有一個 C# 專案是 Windows Forms 應用程式。如果把這個專案從 solution 的建置組態中排除,Nuke build 就能順利執行。
就我找到的資料來看,只要 solution 裡面有 Windows Forms 和 WPF 類型的專案,在產生 Nuke 建置腳本時(參見前面的步驟一)就應該選擇 .NET Framework,而不應選擇 .NET Core。否則執行建置時就會出現上述錯誤。
簡單起見,初次上手時,應用程式專案採用哪個目標平台,Nuke 建置專案就用哪個建置平台,應該就不會碰到這個問題了。
初次體驗 Nuke,覺得她真的很容易上手。然而,也許是因為這工具才出現不久,使用的人不多,反饋少,所以碰到問題時不容易從 Google 找到答案。還好,Nuke 作者很熱心回答我的每個問題。
另一方面,官方文件的數量雖然已經不少,但我覺得還不夠。若有更多 how-to 教學文件,應該會有更多人願意嘗試。
如果您也有興趣試試 Nuke,歡迎在底下留言討論,或者分享您的 auto build 經驗。
下集預告:撰寫 Nuke 腳本來打包 Nuget 套件(包含控制版本編號)。
Happy building!
摘要
- 建置工具已經很多了,為什麼要嘗試這麼新的 Nuke?
- Nuke 基本概念
- Nuke 入門逐步教學
前言
對有些人來說,應用程式的建置腳本似乎總是罩著一層面紗,只能遠觀,不易親近。一來,在一個開發團隊中,並非每一位成員都有機會去建立或修改建置腳本。二來,就算能夠接觸到建置腳本的原始碼,可能也不敢動手修改(萬一改壞了,某人可能會一直收到建置失敗的通知),因為其中往往涉及多種工具和語言的搭配運用,例如 PowerShell、C#、F# 等等,如果建置腳本是用自己不熟悉的語言寫成,欲窺其堂奧又更加困難。第三,建置腳本若採用動態編譯的語言(scripting language),通常不方便除錯,例如只能從 log 檔案裡面的訊息來研判建置失敗的原因,而無法單步追蹤、設定中斷點、觀察變數值。
因此,若能夠以平日使用的開發工具和程式語言來編寫建置腳本,應該是最理想的吧。如果你也這麼認為,不妨繼續看下去。
為什麼想嘗試 Nuke?
Nuke 大約在 2017 年秋季誕生,目前還不滿一歲。這麼新的工具,要用在實際的軟體開發專案上,是有風險的。畢竟,如果一兩年後它就不再出新版本,先前付出的學習成本可能就此泡湯。就建置工具而言,我個人是覺得不至於造成太大的麻煩,所以只要有發現新工具,總是願意多看一眼。
然後,我在網路上某個討論串裡面看到 Nuke。稍微看了一下官方文件,蠻喜歡它的設計理念,所以打算試試看。就我目前的粗淺理解,它的設計理念是:直接用我們已經熟悉的 C# 來寫建置腳本。
Nuke 基礎觀念
等等,不是已經有 Cake 了嗎?而且 Cake 已經蠻成熟了,也頗受歡迎。的確,Cake 和 Nuke 都可以用 C# 來編寫建置腳本。然而,我覺得 Nuke 把這件事變得更容易了。
進一步說,Nuke 的設計理念是:讓建置工具本身就是一個 Visual Studio 專案,而建置腳本就寫在這個專案的 C# 類別裡。
這個用來建置專案的專案,就叫做……建置專案(build project)。它是個 Console 類型的 .NET 應用程式,而我們就是透過編寫這個專案裡面的 C# 程式碼(Build.cs)來建立建置腳本。
沒錯,編寫建置腳本 = 用 C# 寫 Console 應用程式!
也就是說,每當要建置應用程式專案時,只要執行這個 Console 應用程式就行了。
由於建置專案的腳本就是我們已經很熟悉的 Console 應用程式,所以不論是編寫還是除錯建置腳本,都可以直接利用強大的 Visual Studio 整合開發環境的既有功能(設定中斷點、單步追蹤、觀察變數值等等)。換言之,即便是初學者也很容易上手。
了解 Nuke 的基本概念之後,接著就以一個簡單的例子來看看實際上怎麼使用。
入門逐步教學
作業環境:Windows 10,Visual Studio 2017 v15.6。步驟一:產生建置專案和預設腳本
要從無到有編寫一份建置腳本,對任何一個新手來說都不容易。Nuke 提供了產生預設腳本的工具,無論新手老手,應該都會覺得很方便。
首先,開啟命令列視窗,將現行目錄切換到應用程式專案的根目錄。我是拿目前正在整理的一個迷你開源專案來實驗。這個開源專案是 NChinese。
根據官方文件的說明,只要在應用程式專案的根目錄下執行底下兩個 PowerShell 命令,就會產生建置專案以及相關的檔案:
powershell -Command iwr https://nuke.build/powershell -OutFile setup.ps1 powershell -ExecutionPolicy ByPass -File ./setup.ps1
執行 setup1.ps1 時,它會要你回答幾個問題。如下圖,我是用 Cmder 來執行上述命令。
從上圖可以看到,setup.ps1 一開始會自動尋找並選擇一個 solution 檔案來做為建置標的。接著會要求你回答一些問題。
大部分的問題都可以直接按 Enter 鍵來選擇預設值,只有第一個問題比較值得留意。它問的是:Build project target platform。意思是:現在要產生的 Nuke 建置專案,你想使用哪一種目標平台?
此問題的選項有二:一個是 .NET Framework,另一個是 .NET Core。
我的建議是,應用程式專案的目標平台是什麼,建置專案就選擇那個目標平台。這不是官方建議,只是為了減少初次上手時可能遭遇的問題。稍後我會進一步解釋。
此外,若目標平台選擇 .NET Framework,接著會再問你 .csproj 的格式要用新格式(SDK-based format)還是舊格式(Legacy format)。當然是選擇新格式囉。如下圖:
執行完 setup.ps1 之後,Nuke 會在當前目錄下產生一些檔案,同時也會修改上一步驟中指定的 .sln 檔案:把新產生的、擔任建置工作的 .csproj 專案加入 .sln 方案。預設情況下,那個擔任建置工作的專案會被命名為「.build.csproj」(沒寫錯,檔名是小數點開頭)。
用 Visual Studio 開啟 solution 檔案,在 Solution Explorer 裡面可以看到這個名為 .build.csproj 的專案。如下圖:
這裡要注意的是,新產生的 .build.csproj 應單獨編譯,而不應該參與整個 solution 的編譯程序。也就是說,雖然 .build.csproj 有加入應用程式的 solution,可是在建置整個方案時,要排除 .build.csproj 這個專案。原因不難理解:我們會用 .build.csproj 所產生的執行檔來建置整個 solution。參考下圖:
然後,最好再用 git diff 或類似工具來查看所有的變動,以了解剛才執行的 setup.ps1 產生及改變了哪些檔案。下圖是用 TortoiseGit 執行差異比對功能的畫面:
此步驟一,每個專案通常只需要執行一次。如果因為某些原因需要重新做一遍也可以,只要將此步驟所產生的檔案刪除,再執行一次上述命令即可。
步驟二:執行建置
每當需要建置應用程式時,只要執行 .build.csproj 的執行檔就行了。如果 .build.csproj 的目標平台是 .NET Core(見上一步驟的說明),其預設的組件名稱會是 .build.dll。執行過程如下圖:
由於建置專案的執行檔藏在比較深層的子目錄,所以每次要執行時並不太方便。建置專案的執行檔案,我想最主要的用處還是能夠在 Visual Studio IDE 裡面直接執行和單步除錯。
每次需要執行建置時,有兩個方法比較方便。一個是執行專案根目錄下的 build.ps1:
powershell .\build.ps1
另一個方法是執行專案根目錄下的 build.cmd。這個方法比更簡便。比如說,如果我要建置的 target 是 Pack,就只要輸入以下命令就行了:
build Pack
相較於直接執行建置專案的執行檔,build.ps1 和 build.cmd 的好處是具備啟動程序(bootstrap),能夠自動下載建置時需要的相關工具,例如 .NET SDK、Nuget 等等。
步驟三:反覆修改和測試建置腳本
完成上面兩個步驟之後,我們便能確認這個很陽春的預設建置腳本已經能夠順利執行。接著就可以按個人需求來反覆修改和測試建置腳本。
前面提過,建置腳本其實就是個 C# 類別,預設的檔案名稱是 Build.cs。程式碼如下:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Linq; | |
using Nuke.Common; | |
using Nuke.Common.Git; | |
using Nuke.Common.Tools.GitVersion; | |
using Nuke.Core; | |
using static Nuke.Common.Tools.DotNet.DotNetTasks; | |
using static Nuke.Core.IO.FileSystemTasks; | |
using static Nuke.Core.IO.PathConstruction; | |
using static Nuke.Core.EnvironmentInfo; | |
class Build : NukeBuild | |
{ | |
// Console application entry. Also defines the default target. | |
public static int Main () => Execute<Build>(x => x.Compile); | |
// Auto-injection fields: | |
// [GitVersion] readonly GitVersion GitVersion; | |
// Semantic versioning. Must have 'GitVersion.CommandLine' referenced. | |
// [GitRepository] readonly GitRepository GitRepository; | |
// Parses origin, branch name and head from git config. | |
// [Parameter] readonly string MyGetApiKey; | |
// Returns command-line arguments and environment variables. | |
Target Clean => _ => _ | |
.OnlyWhen(() => false) // Disabled for safety. | |
.Executes(() => | |
{ | |
DeleteDirectories(GlobDirectories(SourceDirectory, "**/bin", "**/obj")); | |
EnsureCleanDirectory(OutputDirectory); | |
}); | |
Target Restore => _ => _ | |
.DependsOn(Clean) | |
.Executes(() => | |
{ | |
DotNetRestore(s => DefaultDotNetRestore); | |
}); | |
Target Compile => _ => _ | |
.DependsOn(Restore) | |
.Executes(() => | |
{ | |
DotNetBuild(s => DefaultDotNetBuild); | |
}); | |
} |
注意到裡面有好幾個地方出現類似表情符號的語法嗎?這個: => _ => _
第一次看到的感覺是不是有點那個…… >_<
如果看不慣 lambda 語法,當然也可以另外寫委派方法。
入門教學就寫到這裡。底下附上我剛開始胡亂嘗試時碰到的問題,萬一你也碰到同樣問題,可以參考看看。
問題排除
我第一次使用 Nuke 腳本來執行建置時,結果失敗。錯誤訊息如下圖:
此錯誤訊息的原文是:
error MSB4216: Could not run the "GenerateResource" task because MSBuild could not create or connect to a task host with runtime "CLR4" and architecture "x86".
解法
原本我以為是因為應用程式專案的目標平台與建置專案(.build.csproj)的目標平台不同所致——我的應用程式專案的目標平台是 .NET Framework 4.x,而 .build.csproj 的目標平台是 .NET Core 2.0。
上網爬文後發現,我猜錯了。
造成建置失敗的原因是應用程式的 solution 裡面有一個 C# 專案是 Windows Forms 應用程式。如果把這個專案從 solution 的建置組態中排除,Nuke build 就能順利執行。
就我找到的資料來看,只要 solution 裡面有 Windows Forms 和 WPF 類型的專案,在產生 Nuke 建置腳本時(參見前面的步驟一)就應該選擇 .NET Framework,而不應選擇 .NET Core。否則執行建置時就會出現上述錯誤。
簡單起見,初次上手時,應用程式專案採用哪個目標平台,Nuke 建置專案就用哪個建置平台,應該就不會碰到這個問題了。
結語
初次體驗 Nuke,覺得她真的很容易上手。然而,也許是因為這工具才出現不久,使用的人不多,反饋少,所以碰到問題時不容易從 Google 找到答案。還好,Nuke 作者很熱心回答我的每個問題。
另一方面,官方文件的數量雖然已經不少,但我覺得還不夠。若有更多 how-to 教學文件,應該會有更多人願意嘗試。
如果您也有興趣試試 Nuke,歡迎在底下留言討論,或者分享您的 auto build 經驗。
參考資料
下集預告:撰寫 Nuke 腳本來打包 Nuget 套件(包含控制版本編號)。
Happy building!