跟第一集相同,本文內容僅適用於 .NET Core 專案。
內容綱要:
工具使用:FlubuCore v5.0.3
(根目錄)
+ build
build.csproj
BuildScript.cs
+ source
- BuildAll.sln
+ MyApp
+ ClassLibrary1
+ output
此目錄配置方式意味著當你在使用命令列指令來建置專案時,現行工作目錄會是 (根目錄)。
另一種常見的安排是把建置腳本也放在 source 目錄底下:
(根目錄)
+ source
- BuildAll.sln
+ build
build.csproj
BuildScript.cs
+ MyApp
+ ClassLibrary1
+ output
這種目錄配置方式則意味著當你在使用命令列指令來建置專案時,現行工作目錄通常是(但不必然是)source。
這兩種目錄結構的安排方式,對你在撰寫建置腳本時多少會有一些影響,例如 solution 檔案的相對路徑名稱、建置輸出結果(即 output 目錄)的相對路徑等等。當然,你在寫建置腳本時,也可以使用完整的路徑名稱,但這不是好主意,因為它有個缺點:一旦把建置腳本放到其他電腦上執行,可能就因為檔案路徑不同而建置失敗。(稍後會進一步說明如何在建置腳本中避免使用絕對路徑)
目錄結構的重點是 build 和 source 這兩個子目錄;前者用來存放建置腳本,腳本的專案名稱也命名為 build.csproj,而用來寫腳本內容的 C# 檔案則是 BuildScript.cs。這兩個檔案名稱都有其內定的慣例命名,在上一篇文章已經整理了預設名稱列表,就不重複列出。只要知道,按慣例來命名腳本檔案就能讓 FlubuCore 找到它們,可減少一些麻煩。同樣的,它們彼此的相對位置以及它們與「根目錄」的相對位置也是挺重要的,因為你得在建置腳本中指定建置結果的輸出路徑,也就是前面目錄結構範例中的 output 資料夾。
如果你需要在 Visual Studio 中除錯你的建置腳本,那麼最簡單的方式就是採用 Console Application 了。相較於 Class Library,採用 Console App 類型的建置腳本只是在專案中多了一個 Program.cs,如下所示:
其中的
如果想要在除錯建置腳本時,也能夠追進(trace into)FlubuCore 的原始碼,這會稍微 tricky 一點。還好我們通常不需要除錯 FlubuCore 原始碼,所以相關的專案配置以及可能碰到的問題與解法,這裡就不特別說明。
乍聽之下,使用相對路徑應該很簡單,不會是令人頭疼的問題,然而事情也不是那麼單純。底下進一步說明原因。
在建立 Console Application 類型的建置腳本專案時,基本上有兩種運行建置腳本的方式:
然而,這兩種執行建置腳本的方式有個根本上的差異:執行時的工作目錄不一樣。
當你在 Visual Stduio 中按 F5 或 Ctrl+F5 來執行建置腳本專案時,其執行時的工作目錄是建置腳本專案的輸出目錄,例如 bin/debug/netcoreapp3.1/。另一方面,當你在命令列視窗透過
既然兩種運行建置腳本的工作目錄不一樣,那麼我們在建置腳本當中使用的相對路徑名稱,到底是相對於哪一個工作目錄呢?這裡就會有問題了。
你可以在命令列視窗使用
根據我的經驗,在命令列視窗底下使用 flubu 命令來執行建置腳本時的所在目錄,就是存放 .flubu 檔案的最佳位置(通常會是整個 repository 的根目錄,或者根目錄之下的 source 目錄)。按此作法,將能夠避免許多檔案路徑找不到的問題。
如果你實際用過
這裡有個關鍵:Flubu 會從建置腳本執行時的工作目錄開始尋找 .flubu 檔案,如果找不到,就會往上層目錄尋找,直到磁碟機的根目錄或者找到 .flubu 檔案為止;而 .flubu 檔案所在的資料夾,就會被 Flubu 設定為「運行建置腳本的工作目錄」。你在建置腳本中撰寫的相對路徑名,就是相對於這個工作目錄。
以上是本文最重要的部分。接著介紹一些常見的工作與 API 用法範例。
執行此工作的命令列指令為:
範例:
第 19 行程式碼改寫了第 7 行所設定的 "Release" 組態。
另外,你也可以透過命令列的指令來蓋過建置腳本中的參數,例如:
上列指令,Flubu 在實際執行 dotnet 建置工作時,會轉換成類似底下的指令:
也就是說,只要你知道某項工作在實際執行時的外部程式有哪些命令列參數,就可以在使用 flubu 命令列工具時一併傳入。
執行此工作的命令列指令為:
注意第 10 行的
當 targetC 執行時,實際執行的工作依序為 targetB, targetA, targetC。
內容綱要:
- 工作目錄結構與檔案名稱
- 使用 Console App 為建置腳本
- 工作目錄與相對路徑
- 編譯方案(專案)
- 改寫預設參數
- 清理輸出目錄
- 目標工作的相依關係
- 工作的事件
- 結語
工具使用:FlubuCore v5.0.3
工作目錄結構與檔案名稱
在上一篇文章裡提過,我偏好的目錄結構類似這樣:(根目錄)
+ build
build.csproj
BuildScript.cs
+ source
- BuildAll.sln
+ MyApp
+ ClassLibrary1
+ output
此目錄配置方式意味著當你在使用命令列指令來建置專案時,現行工作目錄會是 (根目錄)。
另一種常見的安排是把建置腳本也放在 source 目錄底下:
(根目錄)
+ source
- BuildAll.sln
+ build
build.csproj
BuildScript.cs
+ MyApp
+ ClassLibrary1
+ output
這種目錄配置方式則意味著當你在使用命令列指令來建置專案時,現行工作目錄通常是(但不必然是)source。
這兩種目錄結構的安排方式,對你在撰寫建置腳本時多少會有一些影響,例如 solution 檔案的相對路徑名稱、建置輸出結果(即 output 目錄)的相對路徑等等。當然,你在寫建置腳本時,也可以使用完整的路徑名稱,但這不是好主意,因為它有個缺點:一旦把建置腳本放到其他電腦上執行,可能就因為檔案路徑不同而建置失敗。(稍後會進一步說明如何在建置腳本中避免使用絕對路徑)
目錄結構的重點是 build 和 source 這兩個子目錄;前者用來存放建置腳本,腳本的專案名稱也命名為 build.csproj,而用來寫腳本內容的 C# 檔案則是 BuildScript.cs。這兩個檔案名稱都有其內定的慣例命名,在上一篇文章已經整理了預設名稱列表,就不重複列出。只要知道,按慣例來命名腳本檔案就能讓 FlubuCore 找到它們,可減少一些麻煩。同樣的,它們彼此的相對位置以及它們與「根目錄」的相對位置也是挺重要的,因為你得在建置腳本中指定建置結果的輸出路徑,也就是前面目錄結構範例中的 output 資料夾。
用來存放建置結果的 output 資料夾無需事先建立,也無須保存檔案的版本,所以通常會加入 .gitignore 檔案,以免存入 Git 儲存庫。
使用 Console App 為建置腳本
FlubuCore 的建置腳本可以是 Class Library 或 Console Application 類型的專案,前者編譯結果為 .dll,後者為 .exe。無論哪一種應用程式類型,在命令列視窗裡面下的指令都一樣是flubu [target]
。如果你需要在 Visual Studio 中除錯你的建置腳本,那麼最簡單的方式就是採用 Console Application 了。相較於 Class Library,採用 Console App 類型的建置腳本只是在專案中多了一個 Program.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.Collections.Generic; | |
using System.IO; | |
using FlubuCore.Scripting; | |
namespace build | |
{ | |
class Program | |
{ | |
static void Main(string[] args) | |
{ | |
var engine = new FlubuEngine(); | |
engine.RunScript<BuildScript>(args); | |
// 當你需要除錯特定 target,也可以這樣寫: | |
// engine.RunScript<BuildScript>(new string[] { "compile" }); | |
} | |
} | |
} |
其中的
BuildScript
類別就是你的建置腳本。如果你對這個部分覺得陌生,請先閱讀我的上一篇文章:〈FlubuCore 入門:建置 .NET Core 專案〉。如果想要在除錯建置腳本時,也能夠追進(trace into)FlubuCore 的原始碼,這會稍微 tricky 一點。還好我們通常不需要除錯 FlubuCore 原始碼,所以相關的專案配置以及可能碰到的問題與解法,這裡就不特別說明。
👉 如需除錯 FlubuCore 程式碼,可參考官方文件 Test and Debugging。我的作法比較像土法煉鋼,將來若有適當時機,再寫專文介紹。
工作目錄與相對路徑
前面提過,在編寫建置腳本時,檔案路徑名稱最好不要使用完整路徑,否則當你把建置腳本放在其他機器上面執行時,很可能會因為路徑找不到而發生錯誤。乍聽之下,使用相對路徑應該很簡單,不會是令人頭疼的問題,然而事情也不是那麼單純。底下進一步說明原因。
在建立 Console Application 類型的建置腳本專案時,基本上有兩種運行建置腳本的方式:
- 在命令列視窗中,以
flubu
指令來執行建置腳本。 - 在 Visual Studio 環境中,以 F5 或 Ctrl+F5 來執行建置腳本專案(以便除錯建置腳本)。
然而,這兩種執行建置腳本的方式有個根本上的差異:執行時的工作目錄不一樣。
當你在 Visual Stduio 中按 F5 或 Ctrl+F5 來執行建置腳本專案時,其執行時的工作目錄是建置腳本專案的輸出目錄,例如 bin/debug/netcoreapp3.1/。另一方面,當你在命令列視窗透過
flubu
指令來執行建置腳本時,其工作目錄則是下指令當時的所在目錄,而且通常是 solution 所在的目錄,或整個專案的最上層目錄。既然兩種運行建置腳本的工作目錄不一樣,那麼我們在建置腳本當中使用的相對路徑名稱,到底是相對於哪一個工作目錄呢?這裡就會有問題了。
解決方法:使用 .flubu 檔案
要達到前面說的目標(兩種運行建置腳本的結果必須一致),同時又要避免在建置腳本中使用完整路徑,目前看來最簡單、也可能是唯一的解決方法,就是使用 .flubu 檔案。你可以在命令列視窗使用
flubu setup
指令來產生 .flubu 檔案,具體作法可參考上一篇文章的說明。這裡的重點是 .flubu 檔案該放在哪個資料夾。根據我的經驗,在命令列視窗底下使用 flubu 命令來執行建置腳本時的所在目錄,就是存放 .flubu 檔案的最佳位置(通常會是整個 repository 的根目錄,或者根目錄之下的 source 目錄)。按此作法,將能夠避免許多檔案路徑找不到的問題。
如果你實際用過
flubu setup
指令來產生 .flubu 檔案,你會知道 .flubu 檔案裡面保存的是建置腳本檔案的相對路徑名。再細想一下,你可能會問:這樣一來,當你在 Visual Studio 中按 F5 來執行建置腳本時,其輸出目錄(也是執行時的工作目錄)是 bin/Debug 底下的資料夾,那麼建置腳本如何找到需要建置的 .sln 和其他相關檔案呢?這裡有個關鍵:Flubu 會從建置腳本執行時的工作目錄開始尋找 .flubu 檔案,如果找不到,就會往上層目錄尋找,直到磁碟機的根目錄或者找到 .flubu 檔案為止;而 .flubu 檔案所在的資料夾,就會被 Flubu 設定為「運行建置腳本的工作目錄」。你在建置腳本中撰寫的相對路徑名,就是相對於這個工作目錄。
💬 萬一 Flubu 找不到 .flubu 檔案呢?那麼它就會以「執行建置腳本時的所在目錄」作為工作目錄。
以上是本文最重要的部分。接著介紹一些常見的工作與 API 用法範例。
編譯方案(專案)
建置腳本的各項工作,或者說目標(target),都是寫在ConfigureTargets
方法裡面。底下範例的功能是用來編譯一個方案或專案:
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
// 使用外部檔案來控制產品版本,檔案應放你執行建置腳本的工作目錄下。 | |
[FetchBuildVersionFromFile(ProjectVersionFileName = "ProductVersion.txt")] | |
protected override void ConfigureTargets(ITaskContext session) | |
{ | |
var compile = session.CreateTarget("compile") | |
.SetDescription("Compile the solution.") | |
.AddCoreTask( | |
x => x.Build() | |
.Version(ProductVersion.Version.ToString()) | |
); | |
} |
flubu compile
改寫預設參數
所有執行外部程序的工作,都可以加入其他參數,也可以改寫既有的參數。範例:
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
public class BuildScript : DefaultBuildScript | |
{ | |
[SolutionFileName] | |
public string SolutionFileName => "source/MyApp.sln"; | |
[BuildConfiguration] | |
public string BuildConfiguration { get; set; } = "Release"; // Debug or Release | |
protected override void ConfigureBuildProperties(IBuildPropertiesContext context) | |
{ | |
} | |
protected override void ConfigureTargets(ITaskContext session) | |
{ | |
var compile = session.CreateTarget("compile") | |
.SetDescription("Compile the solution.") | |
.AddCoreTask( | |
x => x.Build() | |
.Configuration("debug") // 改寫預設的 "Release" 組態 | |
); | |
} | |
} |
第 19 行程式碼改寫了第 7 行所設定的 "Release" 組態。
另外,你也可以透過命令列的指令來蓋過建置腳本中的參數,例如:
flubu compile --configuration=Debug
上列指令,Flubu 在實際執行 dotnet 建置工作時,會轉換成類似底下的指令:
flubu dotnet build MyApp.sln --configuration Debug
也就是說,只要你知道某項工作在實際執行時的外部程式有哪些命令列參數,就可以在使用 flubu 命令列工具時一併傳入。
相關文件:Override existing options or add additional options to tasks through console
清理輸出目錄
底下範例 target 的作用是清除輸出目錄:
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
protected override void ConfigureTargets(ITaskContext session) | |
{ | |
var cleanTarget = session.CreateTarget("clean") | |
.SetDescription("Cleaning solution output.") | |
.AddCoreTask( | |
x => x.Clean() | |
.CleanOutputDir() | |
); | |
} |
flubu clean
目標工作的相依關係
建置腳本通常包含多項工作,而有些工作可能彼此有執行上的先後順序關係。以下結合剛才兩個範例來示範多個相依 target 之間的執行順序:
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
protected override void ConfigureTargets(ITaskContext session) | |
{ | |
var clean = session.CreateTarget("clean") | |
.SetDescription("Cleaning solution output.") | |
.AddCoreTask(x => x.Clean() | |
.CleanOutputDir()); | |
var compile = session.CreateTarget("compile") | |
.SetDescription("Compile the solution.") | |
.DependsOn(clean) // compile 工作依賴 clean | |
.AddCoreTask(x => x.Build() | |
.Version(ProductVersion.Version.ToString())); | |
} |
DependsOn
方法,這行的作用是告訴 FluboCore:要執行 compile
工作之前,必須先執行 clean
。DependsOn
方法接受一個參數陣列,這表示你可以傳入多個相依工作。例如:
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
var targetA = context.CreateTarget("TargetA"); | |
var targetB = context.CreateTarget("TargetB"); | |
var targetC = context.CreateTarget("TargetC").DependsOn(targetB, targetA); | |
// 當 targetC 執行時,實際執行的工作依序為 targetB, targetA, targetC |
工作的事件
你可以針對每一項工作出錯或結束的時候寫一些額外的程式碼。出錯的時候是呼叫工作的OnError
方法,工作結束時呼叫 Finally
方法。Finally
方法的運作方式如同 C# 的finally
關鍵字:無論執行過程是否發生錯誤,最終都會呼叫 Finally
方法。參考以下範例:
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
protected override void ConfigureTargets(ITaskContext session) | |
{ | |
var compile = session.CreateTarget("compile") | |
.SetDescription("Compile the solution.") | |
.AddCoreTask(x => x.Build() | |
.OnError((context, ex) => | |
{ | |
context.LogError($"糟糕, 編譯失敗了: {ex}", Color.Red); | |
}) | |
.Finally(context => | |
{ | |
Console.WriteLine("編譯工作結束"); | |
}) | |
); | |
} |
結語
這是我的第二篇介紹 FlubuCore 的文章,其中最重要的部分,我認為是 FlubuCore 針對「如何決定工作目錄」所提出的解決辦法,也就是 .flubu 檔案以及自動搜尋 .flubu 檔案的機制。解決了這個問題,我們便能夠在建置腳本中使用相對路徑名,而無須擔心在哪個資料夾啟動建置腳本。
FlubuCore 還有許多功能是我在最近兩篇文章裡面沒還提到的,例如:以互動模式執行、為工作撰寫自訂方法、撰寫自訂工作(task plugin)等等。如果需要更多資訊,可以參考 Flubu 官方文件。
如果你也在使用 FlubuCore,記得到它的 GitHub 頁面上點個星星喔。
Happy building!
FlubuCore 還有許多功能是我在最近兩篇文章裡面沒還提到的,例如:以互動模式執行、為工作撰寫自訂方法、撰寫自訂工作(task plugin)等等。如果需要更多資訊,可以參考 Flubu 官方文件。
如果你也在使用 FlubuCore,記得到它的 GitHub 頁面上點個星星喔。
Happy building!