前言
大家好,我最近在想如何提交代碼的時候自動的打包 NuGet 然后發布到 AzureDevOps
中的 Artifacts
,在這個過程中踩了很多坑,也走了很多彎路,所以這次篇文章就是將我探索的結果和我遇到的一些問題整理分享給大家。
我的上一篇關於 CI/CD
的文章《使用 Gitlab CI/CD 實現自動化發布站點到 IIS》 中是使用腳本的形式實現的,后來有園友在下面評論說可以使用 Cake(C# Make) 這個工具來實現其中的功能,所以本次就不用了腳本了。有時間會使用 Cake 對它進行改造。
整體思路:
-
首先介紹下
Cake
、AzureDevops Pipelines/Artifacts
怎么使用 -
接着配置 AzureDevops Pipelines
-
創建 AzureDevops Artifacts (NuGet 服務端)
-
AzureDevops 配置 PAT (Personal Access Tokens) 和 Pipelines 所需的 Variables(變量)
-
Cake 增加打包、推送 NuGet 包代碼。
-
最后查看運行結果
使用到的工具及版本:
dotnet core 2.2
cake 0.33.0
PowerShell、NuGet、CredentialProvider
AzureDevops Pipelines 和 AzureDevops Artifacts
介紹
-
Cake 的全稱是
C# Make
,它是一個跨平台的自動化構建系統,基於 C# DSL,所以可以用我們熟悉的 C# 語言來替換掉我們之前使用腳本的構建方式。使用它我們能非常方便的編譯代碼,復制文件和文件夾,當然還可以運行單元測試以以確保我們的代碼沒有問題,我們本次的 NuGet 發布到 Artifacts 它占很重要的地位。 -
AzureDevops 的前身是 VSTS,它提供了 Repos、Pipelines、Boards、Test Plans、Artifacts:
- Repos 提供 Git 存儲庫,用於代碼的源代碼控制,你可以直接引入你在 GitHub 上的倉儲。
- Pipelines 提供構建和發布服務,以支持應用程序的持續集成和交付(CI/CD)
- Boards 提供了一套 Agile 工具,支持使用看板和 Scrum 方法規划和跟蹤工作,代碼缺陷管理等等,類似的工具有騰訊的 TAPD、阿里的 雲效、華為雲的 DevCloud 等等。
- Test Plans 提供了多種測試應用程序的工具,包括手動/探索性測試和持續測試
- Artifacts 允許團隊從公共和私人來源共享 Maven,npm 和 NuGet 包。
協作工具,包括可自定義的團隊儀表板和可配置的小部件,以共享信息,進度和趨勢; 用於共享信息的內置 wiki; 可配置的通知等。
-
CredentialProvider 是憑據提供程序,當我們進行 NuGet Push 時需要進行身份驗證,只需要將它放在 NuGet 程序的下即可。
Cake 安裝和使用
本次案例我已經發布到 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/
AzureDevops Pipelines 使用
首先你需要一個 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 是不會顯示執行失敗的,如果沒得到你想要的結果你就需要點開認真的分析你的腳本了。
AzureDevops Artifacts 使用
前面已經講過了如果使用 cake 和 在 AzureDevops Pipelines 下執行 cake。下面我們需要創建一個 NuGet Repository,這里我使用 AzureDevops 提供的 Artifacts。
這里面會用的就是 package source URL
和下面命令中的 -ApiKey 中的 AzureDevOps
,還有這里我們需要將 NuGet + Credentials Provider
下載到我們的本地,如果你的運行環境是 Linux 或其他可以在 microsoft/artifacts-credprovider
的 GitHub 上獲取對應平台的這兩個包, 點擊查看 GitHub 地址。
創建 PAT (Personal Access Tokens)
上面說過了我們推送 NuGet 包到 Artifacts 時候是需要為兩個參數提供指的的 -UserName 和 -Password,這里的 UserName 我們可以隨意填,但是 Password 填的的是我們創建的 PAT。
這是選擇我們 PAT 所擁有的權限,需要點擊 Show all scopes
找到 Packaging
勾選 Red,wirte,& manage
。
我們可以看到我們的 PAT ,需要注意的是這個 token 只會出現一次,你需要將它保存好,如果忘記了,那么可以點擊 Regenerate
重新獲取 token。
AzureDevops Pipelines 添加變量
在 上一篇文章 中我說過了為什么需要變量,這里就不重復了,有興趣的可以看看。下面開始添加我們需要的變量。
我們需要添加的變量有四個,分別是 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 和 NuGet.exe、Credentials Provider
這里為已經封裝過了的工具類包含了打包和推送方法,地址: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 和 build.cake 文件
修改 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》