文档章节

持续集成之 Nuget 进阶

o
 osc_gu9d45li
发布于 2019/04/08 09:36
字数 1274
阅读 10
收藏 0

行业解决方案、产品招募中!想赚钱就来传!>>>

持续集成之 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 有 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

o
粉丝 0
博文 500
码字总数 0
作品 0
私信 提问
加载中
请先登录后再评论。
数据中心生命周期管理--Foreman

Foreman是一个集成的数据中心生命周期管理工具,提供了服务开通,配置管理以及报告 功能,和Puppet Dahboard一样,Foreman也是一个Ruby on Rails程序.Foreman和 Dashboard不同的地方是在于,Fore...

匿名
2012/10/24
1.5W
0
基于 Debian 的 Linux 系统--Raspbian

针对 Raspberry Pi 专门优化、基于 Debian 的 Raspbian OS。它面向 Raspberry Pi 硬件(armhf 处理器架构)而做了优化。 这款 OS 对浮点运算有更好的支持,能为用户带来更快的上网浏览体验。...

匿名
2012/11/06
3.2W
4
LightWeb--LightWeb

使用较少的外部框架, 搭建轻型Web架构. 已经或将包含: 轻型依赖注入的实现 Front Controllerf模式实现Http Request的处理,完全摆脱Web Form和ASP.Net Repository实现持久层。 持续完善中, 希...

予沁安
2012/11/21
1.4K
0
基于Web的开源测试服务器--CDash

CDash 是一个基于Web的开源测试服务器. CDash 收集并分析来自于全世界各个地方的终端提交的测试过程. 开发者可以借助CDash系统持续的提高其软件质量. CDash 是一个集成了 CMake, CTest, and ...

匿名
2013/05/23
891
0
phalapi-进阶篇8(PhalApi能带来什么和进阶篇总结)

phalapi-进阶篇8(PhalApi能带来什么和进阶篇总结) 先在这里感谢phalapi框架创始人@dogstar,为我们提供了这样一个优秀的开源框架. 到今天位置PhalApi已经开源一周年了,他从一个不起眼的小框架...

喵了_个咪
2015/12/19
621
6

没有更多内容

加载失败,请刷新页面

加载更多

如何在SQL Server中将多行文本合并为单个文本字符串?

问题: Consider a database table holding names, with three rows: 考虑一个包含名称的数据库表,该表具有三行: PeterPaulMary Is there an easy way to turn this into a single str......

富含淀粉
33分钟前
9
0
在JavaScript中生成特定范围内的随机整数? - Generating random whole numbers in JavaScript in a specific range?

问题: 如何可以生成两个指定的变量之间的随机整数在JavaScript中,例如x = 4和y = 8将输出任何的4, 5, 6, 7, 8 ? 解决方案: 参考一: https://stackoom.com/question/6PRz/在JavaScript中...

fyin1314
今天
8
0
Vim清除最后一个搜索突出显示 - Vim clear last search highlighting

问题: Want to improve this post? 想要改善这篇文章吗? Provide detailed answers to this question, including citations and an explanation of why your answer is correct. 提供此问题......

技术盛宴
今天
23
0
马化腾每天刷 Leetcode?代码你打算写到几岁?

本文作者:o****0 前几天,一张未证真伪的截图流传,图中显示马化腾几乎每天都会在 Leetcode 上提交代码。 截图还贴出一个 Leetcode 账户地址。该地址的头像已从马化腾的照片换成腾讯 logo,...

百度开发者中心
前天
13
0
滴滴 3000+ Kylin Cube 背后的实践经验揭秘

本次分享主要有三个部分:Kylin 在滴滴的整体应用、架构的实践经验、滴滴全局字典最新版本的实现以及 Kylin 最新实时 OLAP 探索经验分享。 Kylin 在滴滴的应用&架构 Kylin 在滴滴的三类应用场...

浪尖聊大数据
昨天
9
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部