你们好,我最近在想如何提交代码的时候自动的打包 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》