Nuke 自動建置入門教學

我嘗試用一個問世未滿一年的 Nuke 建置工具來為我的 C# 專案提供自動建置與打包 NuGet 套件的腳本。初次使用的感覺還不錯。

摘要
  • 建置工具已經很多了,為什麼要嘗試這麼新的 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。程式碼如下:

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);
});
}
view raw NukeBuild.cs hosted with ❤ by GitHub

注意到裡面有好幾個地方出現類似表情符號的語法嗎?這個: => _ => _
第一次看到的感覺是不是有點那個…… >_<

如果看不慣 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!

Post Comments

技術提供:Blogger.