使用 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

相关文章
相关标签/搜索