使用 Cake 推送 NuGet 包到 AzureDevops 的 Artifacts 上

前言

你們好,我最近在想如何提交代碼的時候自動的打包 NuGet 而後發佈到 AzureDevOps 中的 Artifacts,在這個過程當中踩了不少坑,也走了不少彎路,因此此次篇文章就是將我探索的結果和我遇到的一些問題整理分享給你們。html

個人上一篇關於 CI/CD 的文章《使用 Gitlab CI/CD 實現自動化發佈站點到 IIS》 中是使用腳本的形式實現的,後來有園友在下面評論說可使用 Cake(C# Make) 這個工具來實現其中的功能,因此本次就不用了腳本了。有時間會使用 Cake 對它進行改造。git

總體思路:github

  1. 首先介紹下 CakeAzureDevops Pipelines/Artifacts 怎麼使用shell

  2. 接着配置 AzureDevops Pipelinesnpm

  3. 建立 AzureDevops Artifacts (NuGet 服務端)windows

  4. AzureDevops 配置 PAT (Personal Access Tokens) 和 Pipelines 所需的 Variables(變量)ide

  5. Cake 增長打包、推送 NuGet 包代碼。工具

  6. 最後查看運行結果post

使用到的工具及版本:單元測試

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 安裝

cake 的使用方式很是簡單,並且仍是 C# 語法相信應該是很容易就能理解的。

這裏首先定義了一個 target 變量,它裏面保存的就是咱們將要執行的 Task(任務)的名稱。接着能夠看到在在代碼塊中定義了許多的 Task,這裏就是具體須要執行的 「任務」,第一個任務是還原項目的依賴,其實核心代碼就一行 DotNetCoreRestore(solution);,第二個任務是生成項目,須要說明的是第三個任務實際上是將前面兩個任務整合到一塊兒。你也能夠在中第二個任務 .IsDependentOn ("Restore") 調用第一個任務,固然 var target = Argument ("target", "Demo"); 就須要改成 var target = Argument ("target", "Build"); 了,這個看我的喜愛了。

cake 安裝

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 已經運行成功了,它會將咱們每一個任務運行的結果和信息顯示在控制檯上。

cake 運行

到這裏相信你們對 cake 是幹什麼了有點了解吧,有關它跟多的使用方法能夠訪問官網:https://cakebuild.net/

AzureDevops Pipelines 使用

首先你須要一個 Microsoft 帳號或者 GitHub 帳號,登陸地址爲:https://dev.azure.com,登陸以後你須要建立一個項目,這裏我已經建立好一個項目了,首先咱們點擊 Pipelines 選擇 Builds,以後會出現以下界面,點擊 New Pipeline。而後跟着我下面圖片的步驟一步一步來就行。

AzureDevops Pipelines 使用

若是你的倉儲就在 AzureDevops上那麼直接選 Azure Repos Git 就行。

AzureDevops Pipelines 使用

這裏你的帳號是 GitHub 受權登陸的話會先跳轉到受權界面可能會跳轉屢次,贊成便可。

AzureDevops Pipelines 使用
AzureDevops Pipelines 使用

刪除我選中的代碼,由於我不打算用 AzureDevops Pipelines 的腳原本執行本次操做,它作的只是提供咱們 cake 運行的環境。

AzureDevops Pipelines 使用

更換爲以下腳本,PowerShell.exe -file ./cake.ps1 是指使用 PowerShell 運行咱們的 cake.ps1 文件,關於 cake.ps1 文件後面會介紹,這裏咱們先這樣寫,接着點擊 Save and run

AzureDevops Pipelines 使用

trigger:
- master

pool:
  vmImage: 'windows-latest'

steps:
- script: PowerShell.exe -file ./cake.ps1
  displayName: 'Push NuGet Package'

能夠看到問們管道的運行出現了錯誤,那是由於咱們上面在運行了 cake.ps1 這個腳本,可是咱們如今尚未建立這個腳本。

AzureDevops Pipelines 使用

回到咱們的項目中,將 AzureDevops Pipelines 建立的 azure-pipelines.yml 文件 pull 到咱們本地。

AzureDevops Pipelines 使用

接着咱們編寫咱們下面缺乏的 cake.ps1 文件,它作的事情就是將咱們以前手動在 cmd 中運行的命令放入了一個 PowerShell 腳本文件中,Linux 平臺的話就編寫一個 shell 腳本。

AzureDevops Pipelines 使用

# 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,下面報了一個錯。
AzureDevops Pipelines 使用

咱們只須要以管理員身份運行 PowerShell 而後執行 set-ExecutionPolicy RemoteSigned 便可

AzureDevops Pipelines 使用

AzureDevops Pipelines 使用

而後再次運行 powershell .\cake.ps1 或者命令,能夠看到正確的輸出了

AzureDevops Pipelines 使用

OK,此次咱們推送(git push)下代碼,在到 AzureDevops Pipelines 看看咱們執行結果。
AzureDevops Pipelines 使用

點進去能夠看到整個執行的過程,若是報錯了也能夠從這裏看到出錯的信息

AzureDevops Pipelines 使用

若是是 powershell 報錯 AzureDevops Pipelines 是不會顯示執行失敗的,若是沒獲得你想要的結果你就須要點開認真的分析你的腳本了。

AzureDevops Artifacts 使用

前面已經講過了若是使用 cake 和 在 AzureDevops Pipelines 下執行 cake。下面咱們須要建立一個 NuGet Repository,這裏我使用 AzureDevops 提供的 Artifacts。

AzureDevops Artifacts 使用
AzureDevops Artifacts 使用
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。

APersonal Access Tokens 使用
Personal Access Tokens 使用

這是選擇咱們 PAT 所擁有的權限,須要點擊 Show all scopes 找到 Packaging 勾選 Red,wirte,& manage

Personal Access Tokens 使用

咱們能夠看到咱們的 PAT ,須要注意的是這個 token 只會出現一次,你須要將它保存好,若是忘記了,那麼能夠點擊 Regenerate 從新獲取 token。

Personal Access Tokens 使用

AzureDevops Pipelines 添加變量

上一篇文章 中我說過了爲何須要變量,這裏就不重複了,有興趣的能夠看看。下面開始添加咱們須要的變量。

添加變量 使用
A添加變量 使用

咱們須要添加的變量有四個,分別是 NUGET_REPOSITORY_API_URLNUGET_REPOSITORY_API_KEYUSERNAMEPASSWORD

  • 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 地址

NuGet.exe、Credentials Provider

修改 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(推送包),這裏須要你們須要修改的就是 solutionproject 兩個變量,將其修改成本身的解決方案名稱和須要打包的項目名稱。

#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。最後你能夠在評論區留言(分享你碰到的問題以及解決方法)。

相關文獻

在這裏感謝各位的貢獻!

《[Cake] 0. C#Make自動化構建-簡介》

《2. dotnet 全局工具 cake》

《基於cake-build的dotnet自動化發佈》

《Pushing Packages From Azure Pipelines To Azure Artifacts Using Cake》

AzureDevops Pipelines 變量相關文檔

《認識一下 Azure DevOps》
參考項目:CakePushNuGet.Example

參考項目:cake.example

相關文章
相關標籤/搜索