你們好,我最近在想如何提交代碼的時候自動的打包 NuGet 而後發佈到 AzureDevOps
中的 Artifacts
,在這個過程當中踩了不少坑,也走了不少彎路,因此此次篇文章就是將我探索的結果和我遇到的一些問題整理分享給你們。html
個人上一篇關於 CI/CD
的文章《使用 Gitlab CI/CD 實現自動化發佈站點到 IIS》 中是使用腳本的形式實現的,後來有園友在下面評論說可使用 Cake(C# Make) 這個工具來實現其中的功能,因此本次就不用了腳本了。有時間會使用 Cake 對它進行改造。git
總體思路:github
首先介紹下 Cake
、AzureDevops Pipelines/Artifacts
怎麼使用shell
接着配置 AzureDevops Pipelinesnpm
建立 AzureDevops Artifacts (NuGet 服務端)windows
AzureDevops 配置 PAT (Personal Access Tokens) 和 Pipelines 所需的 Variables(變量)ide
Cake 增長打包、推送 NuGet 包代碼。工具
最後查看運行結果post
使用到的工具及版本:單元測試
dotnet core 2.2
cake 0.33.0
PowerShell、NuGet、CredentialProvider
AzureDevops Pipelines 和 AzureDevops Artifacts
Cake 的全稱是 C# Make
,它是一個跨平臺的自動化構建系統,基於 C# DSL,因此能夠用咱們熟悉的 C# 語言來替換掉咱們以前使用腳本的構建方式。使用它咱們能很是方便的編譯代碼,複製文件和文件夾,固然還能夠運行單元測試以以確保咱們的代碼沒有問題,咱們本次的 NuGet 發佈到 Artifacts 它佔很重要的地位。
CredentialProvider 是憑據提供程序,當咱們進行 NuGet Push 時須要進行身份驗證,只須要將它放在 NuGet 程序的下便可。
本次案例我已經發布到 GitHub 上了:https://github.com/WuMortal/CakePushNuGet.Example
安裝:這裏我使用 dotnet core 進行演示,cake 還支持 .NET Framework、Mono。首先咱們須要安裝 cake,藉助 dotnet tool 這個命令。
dotnet tool install --global cake.tool --version 0.33.0
安裝成功會出現以下提示:
cake 的使用方式很是簡單,並且仍是 C# 語法相信應該是很容易就能理解的。
這裏首先定義了一個 target 變量,它裏面保存的就是咱們將要執行的 Task(任務)的名稱。接着能夠看到在在代碼塊中定義了許多的 Task,這裏就是具體須要執行的 「任務」,第一個任務是還原項目的依賴,其實核心代碼就一行 DotNetCoreRestore(solution);
,第二個任務是生成項目,須要說明的是第三個任務實際上是將前面兩個任務整合到一塊兒。你也能夠在中第二個任務 .IsDependentOn ("Restore")
調用第一個任務,固然 var target = Argument ("target", "Demo");
就須要改成 var target = Argument ("target", "Build");
了,這個看我的喜愛了。
var rootPath = "../"; //根目錄 var srcPath = rootPath + "src/"; var solution = srcPath + "Wigor.CakePushNuGet.Example.sln"; //解決方案文件 //須要執行的目標任務 var target = Argument ("target", "Demo"); Task ("Restore") .Description ("還原項目依賴") .Does (() => { //Restore Information ("開始執行還原項目依賴任務"); DotNetCoreRestore (solution); }); Task ("Build") .Description ("編譯項目") .Does (() => { Information ("開始執行編譯生成項目任務"); //Build DotNetCoreBuild (solution, new DotNetCoreBuildSettings { NoRestore = true, //不執行還原,上一步已經還原過了 Configuration = "Release" }); }); // 執行的任務 Task ("Demo") .IsDependentOn ("Restore") //1. 執行上面的 Restore 任務 .IsDependentOn ("Build") //2. 須要執行 上面的 Build 任務 .Does (() => { Information ("全部任務完成"); }); //運行目標任務 Demo RunTarget (target);
cake 編寫好後咱們就能夠嘗試運行它,這裏個人 cake 路徑是 build/build.cake 你們能夠根據具體狀況更改 ,命令以下:
dotnet cake build/build.cake -verbosity=diagnostic
能夠看到這裏的 cake 已經運行成功了,它會將咱們每一個任務運行的結果和信息顯示在控制檯上。
到這裏相信你們對 cake 是幹什麼了有點了解吧,有關它跟多的使用方法能夠訪問官網:https://cakebuild.net/
首先你須要一個 Microsoft 帳號或者 GitHub 帳號,登陸地址爲:https://dev.azure.com,登陸以後你須要建立一個項目,這裏我已經建立好一個項目了,首先咱們點擊 Pipelines 選擇 Builds,以後會出現以下界面,點擊 New Pipeline。而後跟着我下面圖片的步驟一步一步來就行。
若是你的倉儲就在 AzureDevops上那麼直接選 Azure Repos Git 就行。
這裏你的帳號是 GitHub 受權登陸的話會先跳轉到受權界面可能會跳轉屢次,贊成便可。
刪除我選中的代碼,由於我不打算用 AzureDevops Pipelines 的腳原本執行本次操做,它作的只是提供咱們 cake 運行的環境。
更換爲以下腳本,PowerShell.exe -file ./cake.ps1
是指使用 PowerShell 運行咱們的 cake.ps1 文件,關於 cake.ps1 文件後面會介紹,這裏咱們先這樣寫,接着點擊 Save and run
。
trigger: - master pool: vmImage: 'windows-latest' steps: - script: PowerShell.exe -file ./cake.ps1 displayName: 'Push NuGet Package'
能夠看到問們管道的運行出現了錯誤,那是由於咱們上面在運行了 cake.ps1 這個腳本,可是咱們如今尚未建立這個腳本。
回到咱們的項目中,將 AzureDevops Pipelines 建立的 azure-pipelines.yml
文件 pull 到咱們本地。
接着咱們編寫咱們下面缺乏的 cake.ps1 文件,它作的事情就是將咱們以前手動在 cmd 中運行的命令放入了一個 PowerShell 腳本文件中,Linux 平臺的話就編寫一個 shell 腳本。
# Install cake.tool dotnet tool install --global cake.tool --version 0.33.0 # 輸出將要執行的命命令 Write-Host "dotnet cake build\build.cake -verbosity=diagnostic" -ForegroundColor GREEN dotnet cake build\build.cake -verbosity=diagnostic
嘗試項目根目錄下運行這個腳本,在 cmd 中執行 powershell .\cake.ps1
,下面報了一個錯。
咱們只須要以管理員身份運行 PowerShell 而後執行 set-ExecutionPolicy RemoteSigned
便可
而後再次運行 powershell .\cake.ps1
或者命令,能夠看到正確的輸出了
OK,此次咱們推送(git push)下代碼,在到 AzureDevops Pipelines 看看咱們執行結果。
點進去能夠看到整個執行的過程,若是報錯了也能夠從這裏看到出錯的信息
若是是 powershell 報錯 AzureDevops Pipelines 是不會顯示執行失敗的,若是沒獲得你想要的結果你就須要點開認真的分析你的腳本了。
前面已經講過了若是使用 cake 和 在 AzureDevops Pipelines 下執行 cake。下面咱們須要建立一個 NuGet Repository,這裏我使用 AzureDevops 提供的 Artifacts。
這裏面會用的就是 package source URL
和下面命令中的 -ApiKey 中的 AzureDevOps
,還有這裏咱們須要將 NuGet + Credentials Provider
下載到咱們的本地,若是你的運行環境是 Linux 或其餘能夠在 microsoft/artifacts-credprovider
的 GitHub 上獲取對應平臺的這兩個包, 點擊查看 GitHub 地址。
上面說過了咱們推送 NuGet 包到 Artifacts 時候是須要爲兩個參數提供指的的 -UserName 和 -Password,這裏的 UserName 咱們能夠隨意填,可是 Password 填的的是咱們建立的 PAT。
這是選擇咱們 PAT 所擁有的權限,須要點擊 Show all scopes
找到 Packaging
勾選 Red,wirte,& manage
。
咱們能夠看到咱們的 PAT ,須要注意的是這個 token 只會出現一次,你須要將它保存好,若是忘記了,那麼能夠點擊 Regenerate
從新獲取 token。
在 上一篇文章 中我說過了爲何須要變量,這裏就不重複了,有興趣的能夠看看。下面開始添加咱們須要的變量。
咱們須要添加的變量有四個,分別是 NUGET_REPOSITORY_API_URL
、NUGET_REPOSITORY_API_KEY
、USERNAME
、PASSWORD
。
NUGET_REPOSITORY_API_URL:就是咱們在建立 AzureDevops Artifacts 後出現的 package source URL
。
NUGET_REPOSITORY_API_KEY:就是那個 -ApiKey 參數的值 AzureDevOps
。
USERNAME:這個上面說過了能夠隨便填。
PASSWORD:這個就是以前建立的 PAT。
點擊保存(Save & queue)或者 Ctrl + s 保存。
這裏爲已經封裝過了的工具類包含了打包和推送方法,地址:NuGet.Tool.cake
using System; using System.Collections.Generic; using System.Linq; using Cake.Common.Tools.DotNetCore; using Cake.Common.Tools.DotNetCore.Pack; using Cake.Common.Tools.NuGet; using Cake.Common.Tools.NuGet.List; using Cake.Core; using NuGet.Packaging; public class NuGetTool { public ICakeContext CakeContext { get; } public string RepositoryApiUrl { get; } public string RepositoryApiKey { get; } public string UserName { get; set; } public string Password { get; set; } private NuGetListSettings ListSettings => new NuGetListSettings { AllVersions = true, Source = new string[] { this.RepositoryApiUrl } }; private DotNetCorePackSettings BuildPackSettings (string packOutputDirectory) => new DotNetCorePackSettings { Configuration = "Release", OutputDirectory = packOutputDirectory, IncludeSource = true, IncludeSymbols = true, NoBuild = false }; private NuGetTool (ICakeContext cakeContext) { CakeContext = cakeContext; RepositoryApiUrl = cakeContext.Environment.GetEnvironmentVariable ("NUGET_REPOSITORY_API_URL"); RepositoryApiKey = cakeContext.Environment.GetEnvironmentVariable ("NUGET_REPOSITORY_API_KEY"); UserName = cakeContext.Environment.GetEnvironmentVariable ("USERNAME"); Password = cakeContext.Environment.GetEnvironmentVariable ("PASSWORD"); CakeContext.Information ($"獲取所需參數成功:{RepositoryApiUrl}"); } public static NuGetTool FromCakeContext (ICakeContext cakeContext) { return new NuGetTool (cakeContext); } public void Pack (List<string> projectFilePaths, string packOutputDirectory) { projectFilePaths.ForEach (_ => CakeContext.DotNetCorePack (_, BuildPackSettings (packOutputDirectory))); } public void Push (List<string> packageFilePaths) { foreach (var packageFilePath in packageFilePaths) { CakeContext.NuGetAddSource ( "wigor", this.RepositoryApiUrl, new NuGetSourcesSettings { UserName = this.UserName, Password = this.Password }); CakeContext.NuGetPush (packageFilePath, new NuGetPushSettings { Source = "wigor", ApiKey = this.RepositoryApiKey }); } } }
在項目的 build/ 下建立 nuget.tool.cake
文件(build/nuget.tool.cake) 拷貝上面的代碼。
這裏參考了最開始提到的園友的項目,很是感謝它的貢獻,GitHub 地址以下:cake.example
在建立 AzureDevops Artifacts
的時候那不是提供了 NuGet + Credentials Provider
的下載地址嘛,如今把它解壓到咱們項目的 build\tool\
下。再次說明這裏我是 Windows 環境,若是你的運行環境是 Linux 或其餘能夠在 microsoft/artifacts-credprovider
的 GitHub 上獲取對應平臺的這兩個包, 點擊查看 GitHub 地址。
修改 cake.ps1,只是增長了 NuGet.exe 的環境變量,由於不加到時候 cake 會找不到 NuGet.exe,或許還有其餘辦法這裏就先這麼幹,若是各位還有更方便的方法能夠在下面留言,感謝!
# 執行的文件 [string]$SCRIPT = 'build/build.cake' [string]$CAKE_VERSION = '0.33.0' # 配置 NuGet 環境變量 $NUGET_EXE = "build/tool/NuGet.exe" $NUGET_DIRECTORY = Get-ChildItem -Path $NUGET_EXE $NUGET_DIRECTORY_NAME=$NUGET_DIRECTORY.DirectoryName $ENV:Path += ";$NUGET_DIRECTORY_NAME" # Install cake.tool dotnet tool install --global cake.tool --version $CAKE_VERSION # 參數:顯須要執行cake 執行信息 [string]$CAKE_ARGS = "-verbosity=diagnostic" # 輸出將要執行的命命令 Write-Host "dotnet cake $SCRIPT $CAKE_ARGS $ARGS" -ForegroundColor GREEN dotnet cake $SCRIPT $CAKE_ARGS $ARGS
修改 build.cake 文件,看着是多了不少東西其實就多了兩個 Task (任務) 分別是: pack(打包)
和 push(推送包)
,這裏須要你們須要修改的就是 solution
和 project
兩個變量,將其修改成本身的解決方案名稱和須要打包的項目名稱。
#reference "NuGet.Packaging" #load nuget.tool.cake var target = Argument ("target", "PushPack"); var rootPath = "../"; var srcPath = rootPath + "src/"; var solution = srcPath + "Wigor.CakePushNuGet.Example.sln"; var project = GetFiles (srcPath + "Wigor.CakePushNuGet.HelloWorld/*.csproj"); var nugetPakcageDirectory = $"{srcPath}nugetPackage/"; var nugetTool = NuGetTool.FromCakeContext (Context); Task ("Restore") .Description ("還原項目依賴") .Does (() => { //Restore Information ("開始執行還原項目依賴任務"); DotNetCoreRestore (solution); }); Task ("Build") .Description ("編譯項目") .Does (() => { Information ("開始執行編譯生成項目任務"); //Build DotNetCoreBuild (solution, new DotNetCoreBuildSettings { NoRestore = true, Configuration = "Release" }); }); Task ("UnitTest") .Description ("單元測試") .Does (() => { Information ("開始執行單元測試任務"); DotNetCoreTest(solution); }); Task ("Pack") .Description ("Nuget 打包") .Does (() => { Information ("開始執行打包任務"); // 確保目錄存在 EnsureDirectoryExists (nugetPakcageDirectory); var packageFilePaths = project.Select (_ => _.FullPath).ToList (); nugetTool.Pack (packageFilePaths, nugetPakcageDirectory); }); Task ("Push") .Description ("Nuget 發佈") .Does (() => { Information ("開始執行 Nuget 包發佈任務"); var packageFilePaths = GetFiles ($"{nugetPakcageDirectory}*.symbols.nupkg").Select (_ => _.FullPath).ToList (); nugetTool.Push(packageFilePaths); }); Task ("PushPack") .Description ("發佈 Nuget 包") .IsDependentOn ("Restore") .IsDependentOn ("Build") .IsDependentOn ("Pack") .IsDependentOn ("Push") .Does (() => { Information ("全部任務完成"); }); RunTarget (target);
最後咱們推送修改後的代碼,查看執行結果看看 NuGet 包是否發佈到 AzureDevops Artifacts
上。
至此已經實現了 使用 Cake 推送 NuGet 包到 AzureDevops 的 Artifacts 上,你若是不熟悉 AzureDevops Pipelines
你也能夠用其餘的 CI/CD 工具來執行。
在整個嘗試過程當中確定會出現一些問題,不要着急認真分析,看看 AzureDevops Pipelines
上給出的提示,也能夠如今本機跑一下看看是否正常。出現問題第一步查看錯誤信息,看看有沒有錯誤信息(基本都有),而後根據錯誤信息去分析是咱們的那個地方出錯了,順序是 cake.ps1 --> build.cake --> nuget.tool.cake,而後是所需的 PAT 的權限是否勾選,AzureDevops Pipelines
變量是否配置而且是 URL、Key 什麼的都是正確,再而後就是 百度、Google。最後你能夠在評論區留言(分享你碰到的問題以及解決方法)。
在這裏感謝各位的貢獻!
《Pushing Packages From Azure Pipelines To Azure Artifacts Using Cake》