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 http://cakebuild.net/ 的缩写, 是一个基于 C# DSL 的自动化构建系统. 它可以用来编译代码, 复制文件以及文件夹, 运行单元测试, 压缩文件以及构建 Nuget 包等等.
熟悉大名鼎鼎的 https://en.wikipedia.org/wiki/Make_(software) 的小伙伴, 应该已经知道 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 http://cakebuild.net/addins/ ).
开源: 基于 MIT 开放源代码(Cake on GitHub https://github.com/cake-build/cake ), 并且是. NET 基金会支持的一个项目(Cake on dotnet foundation https://dotnetfoundation.org/cake ).
最初做自动化发布的时候自己尝试去写 powershell 和 bash shell 脚本, 但是写的多了一点会发现, 很多语法不太一致, 往往写一个功能要写一个 powershell 脚本 再写一个 bash shell 脚本, 徒然增加自己的工作量, 而且有时候会发生一些奇怪的问题, 在 Windows 上的路径和 Linux 的路径有时候会不同, 使用了 Cake, 我们就只需要专注于脚本要执行的过程, 不需要关注 powershell 和 bashshell 的不同, 不需要太多关注于 Windows 和 Linux 的差异.
Cake 文档
Cake API https://www.cakebuild.net/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/**
示例项目
这里以我的一个个人开源项目 https://github.com/WeihanLi/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 变量, 这里需要转换一下, 不然, 直接从环境变量读取是读取不到的, 详细参考:
通过以上脚本可以本文开篇提到的目标:
分支 (除 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://www.cnblogs.com/weihanli/p/advanced-nuget-ci.html