書接上一回(https://www.cnblogs.com/h82258652/p/4898983.html)?[手動狗頭]
前段時間折騰了一下,總算是把我自己的圖片緩存控件(https://github.com/h82258652/HN.Controls.ImageEx)發布到了 nuget 上,目前已經進入一個比較穩定的版本了,基本沒有很嚴重的 bug 了。其實核心代碼早就寫完了,后期主要都在折騰持續集成以及自動構建(包含制作 nuget 包)。持續集成使用了 appveyor,園子里也有不少相關的資料,這里我就不說了。
在制作 nuget 包的過程中,我折騰了很久,最開始打算直接用 appveyor 的自動打包功能,但是在打包 UWP 的包時,打包出來的版本號一直都是 1.0.0,而 WPF 的就沒這問題(這個已確認是 appveyor 的 bug,然而好久都還沒有修復(lll¬ω¬))。另外包里的 dll 的版本號也不好統一控制,我發一次版本需要發 3 個 nuget 包,每個 nuget 包都有一個 dll,手動折騰版本號那不切實際的。
在經過一番調研之后,我終於發現了一個能滿足我需求的工具,Cake,也叫 C# Make,是一個專門用來進行 .net 項目構建的工具。官方網站在此:https://cakebuild.net/
接下來就按着教程開始吧。
這個是我項目的根目錄。接下來我們在此目錄運行 Powershell(可以通過左上角的文件 –> 打開 Windows Powershell 來打開)。然后輸入以下命令。
Invoke-WebRequest https://cakebuild.net/download/bootstrapper/windows -OutFile build.ps1
然后敲下回車,稍微等待之后,我們的目錄下就會出現一個叫 build.ps1 的文件。
接下來我們在該目錄創建一個叫 build.cake 的空白文件,這個文件主要就是執行構建的邏輯。
然后在宇宙最強的 IDE,Visual Studio 中進行編寫腳本吧,在這里,建議各位看官先安裝一個插件:
安裝之后,Visual Studio 就具備對 .cake 腳本的語法高亮能力(可惜還是沒法有語法提示功能/(ㄒoㄒ)/~~)。
使用 Visual Studio 打開 build.cake 之后,先編寫個 Hello world 熱熱身:
var target = Argument("target", "Default"); Task("Default") .Does(() => { Information("Hello World!"); }); RunTarget(target);
然后保存並運行剛才的 build.ps1。(Powershell 里輸入 .\build.ps1)
如果運氣好的話,那么你應該和我一樣碰到了這樣的情況:
這是由於執行 Powershell 腳本是一直比較危險的操作,所以需要更改權限。
輸入如下的腳本:
或者可以參考微軟的官方文檔來修改權限:https://docs.microsoft.com/zh-cn/powershell/module/microsoft.powershell.core/about/about_execution_policies?view=powershell-6
接着再次執行腳本,應該就可以看見如下信息。
我們的 Hello world 終於跑起來了,熱身完畢,接下來開始編寫構建腳本。
Cake 腳本是由一個個 Task 串聯起來的,我們先定義一些變量和一個叫 Build 的 Task,用於執行構建。
var target = Argument("target", "Default"); var configuration = Argument("configuration", "Release"); var verbosity = Argument("verbosity", Verbosity.Minimal); var solution = "./HN.Controls.ImageEx.sln"; Task("Build") .Does(() => { if(IsRunningOnWindows()) { // Use MSBuild MSBuild(solution, configurator => configurator.SetConfiguration(configuration) .SetVerbosity(verbosity)); } else { // Use XBuild XBuild(solution, configurator => configurator.SetConfiguration(configuration) .SetVerbosity(verbosity)); } }); Task("Default") .IsDependentOn("Build"); RunTarget(target);
這里定義了一些變量,configuration 定義為 Release,在 Build Task 里用到,設置為使用 Release 模式。verbosity 表示編譯時的信息輸入,這里設置為 Minimal,以免輸出過多的信息。solution 表示解決方案的路徑。然后運行:
這樣一執行就把這個解決方案構建了一遍。
然后我們開始編寫打包的 Task,命名為 Package。修改腳本如下:
var target = Argument("target", "Default"); var configuration = Argument("configuration", "Release"); var verbosity = Argument("verbosity", Verbosity.Minimal); var version = Argument("version", "1.0.0"); var solution = "./HN.Controls.ImageEx.sln"; Task("Build") .Does(() => { if(IsRunningOnWindows()) { // Use MSBuild MSBuild(solution, configurator => configurator.SetConfiguration(configuration) .SetVerbosity(verbosity)); } else { // Use XBuild XBuild(solution, configurator => configurator.SetConfiguration(configuration) .SetVerbosity(verbosity)); } }); Task("Package") .IsDependentOn("Build") .Does(() => { var nuGetPackSettings = new NuGetPackSettings { Version = version }; var nuspecFiles = GetFiles("./src/*/*.nuspec"); NuGetPack(nuspecFiles, nuGetPackSettings); }); Task("Default") .IsDependentOn("Package"); RunTarget(target);
在這里我加了一個 version 的變量,在下面打包 nuget 包的時候會用到,統一每個 nuget 包的版本號。GetFiles("./src/*/*.nuspec") 這個則獲取了源文件夾下面的項目下的 nuspec 文件(我這里一個 nuspec 對應一個 csproj,就放到同一個文件夾下了),這個文件的作用是用於描述 nuget 包如何進行打包,具體可以參考本文開頭指向的上一篇文章。
執行之后,我們會得到些 nuget 包(當然對於看官你們的項目需要有 nuspec 才行啦):
編譯、打包都說完了。最后就是版本號的問題。nuget 包的版本在上面已經解決了,現在就是 dll 的版本號比較棘手。在我這三個項目中,WPF 和 UWP 都是傳統的項目,都是有一個 AssemblyInfo.cs 的文件,然后里面通過 AssemblyVersionAttribute 來設置 dll 的版本號的。但是我這個 Core 的項目是新類型的項目,並沒有 AssemblyInfo.cs 文件,版本號是在 csproj 里設置的。這難道沒辦法了么,最后通過萬能的 Google 和 StackOverflow,我還是找到了辦法。編輯 csproj 文件,並添加下面一節。
這樣,這個項目的版本號等信息就不會從 csproj 里面讀取。我們可以添加自己的 AssemblyInfo.cs 文件進行版本號管理。
現在情況就是每個項目都有自己的 AssemblyInfo.cs 了,如何統一使用一個 AssemblyInfo.cs 文件來管理這幾個項目呢?還記得 Visual Studio 有一個添加引用文件的功能么?
這樣幾個項目都通過這種方式引用同一個 AssemblyInfo.cs 文件就行了。
最后的問題就是如何將 AssemblyInfo.cs 里的版本號跟 build.cake 腳本里的版本號保持一致。在查閱 Cake 的官方文檔后,我發現有一個能生成 AssemblyInfo 的功能。修改我們的 build.cake 腳本:
var target = Argument("target", "Default"); var configuration = Argument("configuration", "Release"); var verbosity = Argument("verbosity", Verbosity.Minimal); var version = Argument("version", "1.0.0"); var solution = "./HN.Controls.ImageEx.sln"; Task("Version") .Does(() => { var file = "./src/SolutionInfo.cs"; CreateAssemblyInfo(file, new AssemblyInfoSettings { Version = version, FileVersion = version, InformationalVersion = version }); }); Task("Build") .IsDependentOn("Version") .Does(() => { if(IsRunningOnWindows()) { // Use MSBuild MSBuild(solution, configurator => configurator.SetConfiguration(configuration) .SetVerbosity(verbosity)); } else { // Use XBuild XBuild(solution, configurator => configurator.SetConfiguration(configuration) .SetVerbosity(verbosity)); } }); Task("Package") .IsDependentOn("Build") .Does(() => { var nuGetPackSettings = new NuGetPackSettings { Version = version }; var nuspecFiles = GetFiles("./src/*/*.nuspec"); NuGetPack(nuspecFiles, nuGetPackSettings); }); Task("Default") .IsDependentOn("Package"); RunTarget(target);
在 Version Task 中,會生成一個 SolutionInfo.cs 的文件,也就相當於前面的 AssemblyInfo.cs。那現在所有的項目都引用這個文件來進行統一的版本管理就行了。
這樣就已經實現了統一版本、構建、打包的一件腳本化了。后續還可以添加上構建完之后執行單元測試以及打包后自動發布到 nuget 的功能,但我自己比較習慣打包完之后本地也測試一下(畢竟 nuget 發了就不能刪),所以就暫時不折騰這功能了。
博主圖片緩存控件項目的 build.cake 腳本可以參考這里:https://github.com/h82258652/HN.Controls.ImageEx/blob/master/build.cake,雖然配置好了 xunit,但是沒有寫單元測試就是了,ε=ε=ε=┏(゜ロ゜;)┛
這篇博文主要記錄操作步驟,方便以后自己(我還有個微博的庫的坑想要填……)。但也希望看完這篇博文的各位,制作一個 nuget 包並不是一件難事,馬上行動把珍藏都弄一份 nuget 包,讓各位 .net 開發者也機會用上吧。