持续集成之 Nuget 进阶

Intro

之前介绍了一篇基于 Azure pipeline 的 nuget 包的持续集成配置,但是比较粗糙,这里介绍一下结合 Cake 实现更优雅的 nuget 包发布流程。

实现目标:

  1. 分支(除master/preview)有代码 push 或者 pr 时 自动 build

  2. preview 分支有代码 push 的时候将 build 并将发布 preview 版的 nuget 包

  3. master 分支有代码 push 的时候将 build 并将发布稳定版的 nuget 包

什么是Cake?为什么要使用 Cake?

Cake 是C# Make的缩写,是一个基于C# DSL的自动化构建系统。它可以用来编译代码,复制文件以及文件夹,运行单元测试,压缩文件以及构建Nuget包等等。

熟悉大名鼎鼎的Make的小伙伴,应该已经知道Cake大致是个什么样的工具了,Cake具有以下几个特点:

  1. 方便编写:使用基于C#的DSL,非常易于编写自动化的脚本。

  2. 跨平台: 基于Roslyn和Mono来编译我们写的自动化脚本,使得它可以运行在windows,linux,mac上。

  3. 可靠的:可以建立在自己的机器上,也可以建立在像AppVeyor,TeamCity,TFS,VSTS或Jenkins这样的CI系统上,都可以以相同的方式运行。

  4. 丰富的工具集:支持MSBuild,MSTest,xUnit,NUnit,Nuget,ILMerge,Wix和SignTool等等,以及支持丰富的插件(Cake Addins)。

  5. 开源:基于MIT开放源代码(Cake on Github),并且是.NET 基金会支持的一个项目(Cake on dotnet foundation)。

最初做自动化发布的时候自己尝试去写 powershell 和 bash shell 脚本,但是写的多了一点会发现,很多语法不太一致,往往写一个功能要写一个 powershell 脚本 再写一个 bash shell 脚本,徒然增加自己的工作量,而且有时候会发生一些奇怪的问题,在Windows上的路径和Linux的路径有时候会不同,使用了 Cake,我们就只需要专注于脚本要执行的过程,不需要关注 powershell 和 bashshell 的不同,不需要太多关注于 windows 和 linux 的差异。

  • Cake 文档

  • Cake api

使用 Cake

Cake 有 Visual Studio Code 插件,可以基于 VSCode 来编辑 cake 脚本

Cake 脚本示例

cake 主要文件:

  • build.ps1/build.sh 启动脚本,build.ps1 为 Windows 系统上要执行的 powershell 脚本,build.sh 为 *nix 上要执行的 shell 脚本

  • build.cake 实际执行的脚本,定义各种 build 需要的 task

  • tools/packages.config 启动脚本需要的 nuget 包

添加 cake 支持之后,你可能需要修改 .gitignore,官方推荐的 gitignore 是这样的

tools/**
!tools/package.config

实际使用下来,即使没有 package.config 也是可以正常工作的,可以简化为一条

tools/**

示例项目

这里以我的一个个人开源项目 WeihanLi.Redis 为例

cake 脚本

///
// ARGUMENTS
///
var target = Argument("target", "Default");
var configuration = Argument("configuration", "Release");
var solutionPath = "./WeihanLi.Redis.sln";
var srcProjects  = GetFiles("./src/**/*.csproj");
var testProjects  = GetFiles("./test/**/*.csproj");
var artifacts = "./artifacts/packages";
var isWindowsAgent = (EnvironmentVariable("Agent_OS") ?? "Windows_NT") == "Windows_NT";
var branchName = EnvironmentVariable("BUILD_SOURCEBRANCHNAME") ?? "local";
///
// SETUP / TEARDOWN
///
Setup(ctx =>
{   // Executed BEFORE the first task.  Information("Running tasks...");  PrintBuildInfo();
});
Teardown(ctx =>
{   // Executed AFTER the last task.    Information("Finished running tasks.");
});
///
// TASKS
///
Task("clean") .Description("Clean") .Does(() => {   var deleteSetting = new DeleteDirectorySettings()  {   Force = true,  Recursive = true   };  if (DirectoryExists(artifacts)) {   DeleteDirectory(artifacts, deleteSetting);  }   });
Task("restore")   .Description("Restore")   .Does(() =>     {   foreach(var project in srcProjects) {   DotNetCoreRestore(project.FullPath);    }   });
Task("build")     .Description("Build") .IsDependentOn("clean")   .IsDependentOn("restore") .Does(() => {   var buildSetting = new DotNetCoreBuildSettings{    NoRestore = true,  Configuration = configuration  };  foreach(var project in srcProjects) {   DotNetCoreBuild(project.FullPath, buildSetting);    }   });
Task("test")      .Description("Test")  .IsDependentOn("build")   .Does(() => {   var testSettings = new DotNetCoreTestSettings{ NoRestore = true,  Configuration = configuration  };  foreach(var project in testProjects)    {   DotNetCoreTest(project.FullPath, testSettings); }   });
Task("pack")  .Description("Pack package")  .IsDependentOn("test")    .Does(() => {   var settings = new DotNetCorePackSettings  {   Configuration = configuration, OutputDirectory = artifacts,   VersionSuffix = "",  NoRestore = true,  NoBuild = true };  if(branchName != "master"){  settings.VersionSuffix = $"preview-{DateTime.UtcNow:yyyyMMdd-HHmmss}";   }   foreach (var project in srcProjects)    {   DotNetCorePack(project.FullPath, settings); }   PublishArtifacts(); });
bool PublishArtifacts()
{   if(!isWindowsAgent) {   return false;   }   if(branchName == "master" || branchName == "preview")   {   var pushSetting =new DotNetCoreNuGetPushSettings   {   Source = EnvironmentVariable("Nuget__SourceUrl") ?? "https://api.nuget.org/v3/index.json", ApiKey = EnvironmentVariable("Nuget__ApiKey")    };  var packages = GetFiles($"{artifacts}/*.nupkg"); foreach(var package in packages)    {   DotNetCoreNuGetPush(package.FullPath, pushSetting); }   return true;    }   return false;
}
void PrintBuildInfo(){  Information($@"branch:{branchName}, agentOs={EnvironmentVariable("Agent_OS")}  BuildID:{EnvironmentVariable("BUILD_BUILDID")},BuildNumber:{EnvironmentVariable("BUILD_BUILDNUMBER")},BuildReason:{EnvironmentVariable("BUILD_REASON")}   ");
}
Task("Default")   .IsDependentOn("pack");
RunTarget(target);

我这里使用 Azure pipeline 来实现持续集成,上面的里面有一些Azure pipeline 的变量,实际执行 build.ps1 脚本

Azure pipeline config

trigger:
- '*'
pool:   vmImage: 'vs2017-win2016'
steps:
- script: dotnet --info displayName: 'dotnet info'
- powershell: ./build.ps1   displayName: 'Powershell Script'  env:    Nuget__ApiKey: $(nugetApiKey)   Nuget__SourceUrl: $(nugetSourceUrl)

nugetApiKey 是比较敏感的信息,在 Azure Pipeline 里的 Variables 的 Secret 变量,这里需要转换一下,不然,直接从环境变量读取是读取不到的,详细参考:https://docs.microsoft.com/en-us/azure/devops/pipelines/process/variables?view=azure-devops&tabs=yaml%2Cbatch&viewFallbackFrom=vsts#secret-variables

通过以上脚本可以本文开篇提到的目标:

  1. 分支(除master/preview)有代码 push 或者 pr 时 自动 build

  2. preview 分支有代码 push 的时候将 build 并将发布 preview 版的 nuget 包

  3. master 分支有代码 push 的时候将 build 并将发布稳定版的 nuget 包

preview 和 master 分支可以设置 branch policy,设置只能由 pull request 合并,不能直接 push 代码,如果必须要先发布 preview 再发布稳定版 nuget 包,可以添加自定以限制,限制 master 分支的代码只能从 preview 分支通过 pr 合并

Reference

  • https://www.cnblogs.com/linianhui/p/cake-overview.html

  • https://www.cakebuild.net/

  • https://github.com/cake-build/cake

  • https://github.com/WeihanLi/WeihanLi.Redis

  • https://docs.microsoft.com/en-us/azure/devops/pipelines/process/variables?view=azure-devops&tabs=yaml%2Cbatch&viewFallbackFrom=vsts#secret-variables

持续集成之 Nuget 进阶相关推荐

  1. 持续集成配置之Nuget

    Intro 本文是基于微软的 VSTS(Visual Studio Team Service) 做实现公众类库的自动打包及发布. 之前自己的项目有通过 Github 上的 Travis 和 Appve ...

  2. .NET Core 从 Github到 Nuget 持续集成、部署

    一.前言 Nuget 作为一个.NET研发人员,我想你都不会陌生,他为我们提供非常方便的程序包管理,不管是版本,还是包的依赖都能轻松应对,可以说是我们的好助手.而 Nuget 除了官方nuget.or ...

  3. 不可错过的「持续集成」进阶指南

    随着软件部署的越来越成熟,敏捷.DevOps.CI/CD.Docker等词语慢慢出现在工程师的视野中.对于持续集成,业界也没有一个通用的模式,每个团队可能习惯的方式和关注点都不一样.持续集成最关键的在 ...

  4. nuget 构建自己的包_适用于企业的NuGet:持续集成自动构建系统中的NuGet

    nuget 构建自己的包 I had the pleasure of speaking at TechEd 2011 North America last week in Atlanta. You c ...

  5. 如何使用Jenkins持续集成C#网站项目

    2019独角兽企业重金招聘Python工程师标准>>> 上两节分别讲了如何从vss迁移C#网站项目到gitlab和如何使用nuget管理C#网站项目,其实都是为了最后一节的内容做铺垫 ...

  6. Jenkins持续集成学习-Windows环境进行.Net开发4

    目录 Jenkins持续集成学习-Windows环境进行.Net开发4 目录 前言 目标 Github持续集成 提交代码到Github 从Github更新代码 git上显示构建状态 自动触发构建 Gi ...

  7. 持续集成工具 Jetbrains TeamCity 简介

    目录 安装 Windows下安装 Docker下安装 使用TeamCity 初始化 配置数据库 新建项目 设置构建步骤 构建项目 测试项目 自动构建 邮件通知 大名鼎鼎的Intellij IDEA大家 ...

  8. 以持续集成工具实现DevOps之禅

    作为DevOps流程中的一个重要组成部分,持续集成(CI)的目标是对开发团队的代码进行集成,包括代码的构建.单元测试与集成测试的执行,以及生成执行结果的报表等等.CI使开发团队无需将时间浪费在处理代码 ...

  9. fir.im 持续集成技术实践

    互联网时代,人人都在追求产品的快速响应.快速迭代和快速验证.不论是创业团队还是大中型企业,都在探索属于自己的敏捷开发.持续交付之道.fir.im 团队也在全面实施敏捷,并推出新持续集成服务 - flo ...

最新文章

  1. C#单例模式的懒汉与饿汉
  2. html用bmob做留言,bmob js-sdk 在vue中的使用教程
  3. MFC 缩放和显示IplImage
  4. lua 给userdata设置元表_lua学习之复习汇总篇
  5. git 查看远程仓库地址
  6. upload file to server
  7. nodejs实践录:开篇
  8. ajax 实时进度_【乐建工程宝】如何把控施工项目进度
  9. CSS大美集(关于细节)
  10. Swift - 动画效果的实现方法总结(附样例)
  11. 软件测试如何制作简历?
  12. Promise、then()、catch()详解
  13. 操作系统磁盘调度算法相关习题
  14. TMO (time-triggered message-triggered object)
  15. 如何在网页里插入3D模型并进行互动展示?
  16. python变量的声明和赋值
  17. VM安装的虚拟机如何在局域网内互相访问
  18. 如何判断两个QQ好友之间是否为好友关系
  19. 替代Xshell的良心国产SSH工具软件
  20. 夺命雷公狗—玩转SEO---44---外链群发原理

热门文章

  1. 多年经验的程序员迷失了自己,该怎么办?
  2. c语言 程序延时 校准,c语言实现系统时间校正工具代码分享
  3. vcenter 6.7 (vcsa)部署指南
  4. 算法:前K个最大的元素
  5. 219. 单页应用 会话管理(session、cookie、jwt)
  6. Linux文件系统之df
  7. Android帧缓冲区(Frame Buffer)硬件抽象层(HAL)模块Gralloc的实现原理分析(2)...
  8. 基本线程同步(三)在同步的类里安排独立属性
  9. [Todo] 乐观悲观锁,自旋互斥锁等等
  10. SQL 结合CASE WHEN 实现二维统计