持续集成之 Nuget 进阶
持续集成之 Nuget 进阶
Intro
之前介绍了一篇基于 Azure pipeline 的 nuget 包的持续集成配置,但是比较粗糙,这里介绍一下结合 Cake 实现更优雅的 nuget 包发布流程。
实现目标:
- 分支(除master/preview)有代码 push 或者 pr 时 自动 build
- preview 分支有代码 push 的时候将 build 并将发布 preview 版的 nuget 包
- master 分支有代码 push 的时候将 build 并将发布稳定版的 nuget 包
什么是Cake?为什么要使用 Cake?
Cake 是C# Make的缩写,是一个基于C# DSL的自动化构建系统。它可以用来编译代码,复制文件以及文件夹,运行单元测试,压缩文件以及构建Nuget包等等。
熟悉大名鼎鼎的Make的小伙伴,应该已经知道Cake大致是个什么样的工具了,Cake具有以下几个特点:
- 方便编写:使用基于C#的DSL,非常易于编写自动化的脚本。
- 跨平台: 基于Roslyn和Mono来编译我们写的自动化脚本,使得它可以运行在windows,linux,mac上。
- 可靠的:可以建立在自己的机器上,也可以建立在像AppVeyor,TeamCity,TFS,VSTS或Jenkins这样的CI系统上,都可以以相同的方式运行。
- 丰富的工具集:支持MSBuild,MSTest,xUnit,NUnit,Nuget,ILMerge,Wix和SignTool等等,以及支持丰富的插件(Cake Addins)。
- 开源:基于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
通过以上脚本可以本文开篇提到的目标:
- 分支(除master/preview)有代码 push 或者 pr 时 自动 build
- preview 分支有代码 push 的时候将 build 并将发布 preview 版的 nuget 包
- 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 进阶的更多相关文章
- Jenkins 从小白入门到企业实践打怪放弃之路系列笔记 【持续集成与交付快速入门必备】
Jenkins 从小白入门到企业实践打怪放弃之路系列笔记 [持续集成与交付快速入门必备]
- 如何持续集成/交付一个开源.NET函数库到Nuget.org
(此文章同时发表在本人微信公众号"dotNET每日精华文章",欢迎右边二维码来关注.) 题记:这是一个简单的入门向导,涉及到GitHub.AppVeyor和Nuget.org. 最 ...
- Selenium Web 自动化 - 项目持续集成(进阶)
Selenium Web 自动化 - 项目持续集成(进阶) 2017-03-09 目录 1 背景及目标2 环境配置 2.1 SVN的安装及使用 2.2 新建Jenkins任务3 过程分析 1 背景 ...
- .NET Core 从 Github到 Nuget 持续集成、部署
一.前言 Nuget 作为一个.NET研发人员,我想你都不会陌生,他为我们提供非常方便的程序包管理,不管是版本,还是包的依赖都能轻松应对,可以说是我们的好助手.而 Nuget 除了官方nuget.or ...
- 持续集成配置之Nuget
持续集成配置之Nuget Intro 本文是基于微软的 VSTS(Visual Studio Team Service) 做实现公众类库的自动打包及发布. 之前自己的项目有通过 Github 上的 T ...
- .NET持续集成与自动化部署之路第二篇——使用NuGet.Server搭建公司内部的Nuget(包)管理器
使用NuGet.Server搭建公司内部的Nuget(包)管理器 前言 Nuget是一个.NET平台下的开源的项目,它是Visual Studio的扩展.在使用Visual Studio开发基 ...
- 持续集成~Jenkins里的NuGet和MSBuild插件
Jenkins是一个持续集成的环境,它是java开发的,大叔认为它的工作流程是 从源代码拉一个项目下来到它本地(可以配置定时机制) 恢复相关程序包nuget 编译程序 发布程序 现在说一下在配置jen ...
- Linux进阶之Jenkins持续集成介绍及安装演示
一.Jenkins介绍 Jenkins是一个开源软件项目,是基于Java开发的一种持续集成工具,用于监控持续重复的工作,旨在提供一个开放易用的软件平台,使软件的持续集成变成可能. Jenkins功能包 ...
- 【进阶之路】持续集成、持续交付与持续部署(CI/CD)
由来 记得7月份刚刚换工作的时候,中午和老大一起去吃饭,回来的路上老大问我:"南橘,CI/CD有没有研究过?" 我隐隐约约在哪里听过这个名词,但是又想不起来,秉着实事求是的态度,我 ...
随机推荐
- Spring Boot实战笔记(一)-- Spring简介
一.Spring 概述 Spring框架是一个轻量级的企业级开发的一站式解决方案.所谓的解决方案就是可以基于Spring解决所有的Java EE开发的所有问题. Spring框架主要提供了Ioc(In ...
- 向Oracle数据库插入中文乱码解决方法
解决方法: 第一步:sqlplus下执行:select userenv('language') from dual;//查看oracle字符集 注:如果oracle字符集与后台代码设置的 ...
- 16个必须熟悉的linux服务器监控命令
本原创文章属于<Linux大棚>博客. 博客地址为http://roclinux.cn. 文章作者为roc. == 原文:16 Linux Server Monitoring Comman ...
- Oracle-02:SQL语言的分类或者说SQL语言的组成
------------吾亦无他,唯手熟尔,谦卑若愚,好学若饥------------- 小结一版: 01.DDL(Data Definition Language)数据定义语言. 用来创建数据库中 ...
- SSM-Spring-07:Spring基于注解的di注入
------------吾亦无他,唯手熟尔,谦卑若愚,好学若饥------------- 注解: 说起注解,哇哦,每个人都或多或少的用到过 像什么@Overried,@Test,@Param等等之前就 ...
- 理解矩阵与线性代数<转>
作者:张帅链接:https://www.zhihu.com/question/21082351/answer/34361293来源:知乎著作权归作者所有.商业转载请联系作者获得授权,非商业转载请注明出 ...
- Java 架构师眼中的 HTTP 协议
HTTP 协议的内容比较多,本文我们将分六部分来介绍. HTTP 协议的基本内容 什么是 HTTP 协议 首先我们来看协议是什么?协议是指计算机通信网络中两台计算机之间进行通信所必须共同遵守有规则的文 ...
- MySQL大量数据入库的性能比较
单位IM改版了用户聊天内容要存放在数据库. 一般JAVA Insert MySQL有如下几种方式1.自动提交Insert2.事务提交Insert3.批量提交4.使用Load File接口 模拟表结构如 ...
- jmeter添加断言
先创建一个线程组,再创建一个http请求. 为了方便观察,我们添加两个监听器,察看结果树和断言结果. 添加断言:响应断言,响应断言也是比较常用的一个断言 设置响应断言:正常情况下响应代码是200.选择 ...
- 如何设置PPT中的演讲者模式
①1.首先将投影设备或其它幻灯片输出设备连接到笔记本或 PC 上,在 Windows 7 中按Win 键+P 并选择扩展模式将当前笔记本或 PC 的显示器与投影显示输出设备设置为扩展模式. ②我们 ...