持续部署环境中的 dotnet 语义版本控制

Posted

技术标签:

【中文标题】持续部署环境中的 dotnet 语义版本控制【英文标题】:dotnet semantic versioning in a continuous deployment environment 【发布时间】:2019-08-30 22:27:03 【问题描述】:

我正在使用 GitLab 为我的 dotnet 核心应用程序和 netstandard 2.0 包配置语义版本控制。

看了很多意见,其中一些是矛盾的,这是我很清楚的。 语义版本应该类似于 M.m.P.B-abc123在哪里

M 是大版本 m 是次要版本 P是补丁版本 B 是构建版本(可选) -abc123 是后缀(可选),以防我使用预发布版本。必须以字母开头

因此以下软件包版本将是有效的:

1.0.0 1.0.1.20190301123 1.0.1.20190301123-beta 1.0.1-rc1

我有以下 gitlab 脚本用于我的版本控制

#Stages
stages:
  - ci
  - pack

#Global variables
variables:
  GITLAB_RUNNER_DOTNET_CORE: mcr.microsoft.com/dotnet/core/sdk:2.2
  NUGET_REPOSITORY: $NEXUS_NUGET_REPOSITORY
  NUGET_API_KEY: $NEXUS_API_KEY
  NUGET_FOLDER_NAME: nupkgs

#Docker image
image: $GITLAB_RUNNER_DOTNET_CORE

#Jobs
ci:
  stage: ci
  script:
    - dotnet restore --no-cache --force
    - dotnet build --configuration Release
    - dotnet vstest *Tests/bin/Release/**/*Tests.dll

pack-beta-nuget:
  stage: pack
  script:
    - export VERSION_SUFFIX=beta$CI_PIPELINE_ID
    - dotnet pack *.sln --configuration Release --output $NUGET_FOLDER_NAME --version-suffix $VERSION_SUFFIX --include-symbols
    - dotnet nuget push **/*.nupkg --api-key $NUGET_API_KEY --source $NUGET_REPOSITORY
  except:
    - master

pack-nuget:
  stage: pack
  script:
    - dotnet restore
    - dotnet pack *.sln --configuration Release --output $NUGET_FOLDER_NAME
    - dotnet nuget push **/*.nupkg --api-key $NUGET_API_KEY --source $NUGET_REPOSITORY
  only:
    - master

这会生成包,例如: 1.0.0 用于主分支(稳定或生产就绪),1.0.0-beta1234567 用于任何其他分支。

我的方法的问题是我有多个项目的 VS 解决方案,每个项目都是一个 nuget 包,每个项目都有自己的版本。有时我会修改一个项目,但不会修改另一个项目,因此理论上我当然不需要生成我没有接触过的项目的新工件,也不需要生成新版本。

现在我的 nuget 存储库阻止覆盖包,所以如果有一个 XXX.YYY 1.0.0 并且我生成另一个 XXX.YYY 1.0.0 并将其推送到存储库,它将抛出错误并且管道将失败。

我认为每次运行 CI/CD 管道时生成一个新包可能不是一个坏主意,所以我考虑引入内部版本号并使用类似 XXX.YYY 1.0.0.12345 的内容,即使我没有不要碰那里的任何东西,下次会产生一个新的包XXX.YYY 1.0.0.123499

这在持续部署场景中是正确的方法吗?或者如果我的 nuget 存储库中已经有一个具有相同版本的工件,我应该寻找一种方法让我的脚本更智能而不是生成新的工件?

假设始终可以使用内部版本号,我如何确保仅从管道中检索内部版本号,但 M.m.P 版本号仍保留在我的 csproj 中,如下所示?

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>netstandard2.0</TargetFramework>
    <Description>Whatever</Description>
    <VersionPrefix>1.0.1</VersionPrefix>
  </PropertyGroup>
</Project>

我需要类似的东西: dotnet pack *.sln --configuration Release -p:PackageVersion=$FIXED_VERSION.$CI_PIPELINE_ID --output nupkg

但我不知道如何通过 CLI 从 csproj 检索 &lt;VersionPrefix&gt; 内容。

假设我的方法有效,有什么建议、好的阅读或解决方案吗?

谢谢

【问题讨论】:

【参考方案1】:

我的方法的问题是我有多个项目的 VS 解决方案,每个项目都是一个 nuget 包,每个项目都有自己的版本。有时我会修改一个项目,但不会修改另一个项目,因此理论上我当然不需要为该项目制作一个我没有接触过的新工件,也不需要制作新版本。

由于持续集成管道无法确定您的代码应该是新的次要版本还是主要版本,您将始终必须确定您的包应该获得哪个语义版本。这使整个过程变得容易得多。

Visual Studio 团队服务的人对此有这样的看法:

不变性和唯一的版本号 在 NuGet 中,特定包由其名称和版本号标识。一旦你发布了一个特定版本的包,你就永远不能改变它的内容。但是当你制作一个 CI 包时,你不知道它是 1.2.3 版还是只是向 1.2.3 迈进了一步。您不想在仍然需要修复一些错误的包上刻录 1.2.3 版本号。

SemVer 来救援!除了 Major.Minor.Patch 之外,SemVer 还提供了一个预发布标签。售前赛标签是一个“-”,后面跟着你想要的任何字母和数字。版本 1.0.0-alpha、1.0.0-beta 和 1.0.0-foo12345 都是 1.0.0 的预发布版本。更好的是,SemVer 指定当您按版本号排序时,这些预发布版本完全符合您的预期:0.99.999

Xavier 的博客文章描述了“劫持”预发布标签以用于 CI。不过,我们认为这不是劫持。这正是我们在 Visual Studio Team Services 上为创建 CI 包所做的工作。我们将通过选择一个版本号来克服这个悖论,然后制作该版本的预发布版本。我们在版本号中留下预发布标签是否重要?对于大多数用例,并非如此。如果您固定到依赖包的特定版本号,则不会产生影响,如果您在 project.json 中使用浮动版本范围,则意味着对您指定的版本范围进行小幅更改。 来源:https://devblogs.microsoft.com/devops/versioning-nuget-packages-cd-1/

如前所述,您仍然需要自己为新软件包选择一个版本。对于最终包(在您的情况下为 master 分支)实际发布之前的过程,您可以使用预发布标签将内部版本号包含为预发布标签。

如果不做任何聪明的事情,这将为每个运行的管道发布一个新包。视觉工作室团队服务的人,我不认为这是一件坏事,但这将取决于每个人的个人品味

【讨论】:

【参考方案2】:

我知道有点晚了,但也许以后其他人也会问这个问题。

背景:我在一家小公司工作,我们从 NodeJS 世界开始使用 NPM 包。过了一段时间,我们也开始使用 .NET Core 进行构建,并且我们从 javascript 世界中改编了一些东西。我们大量使用的工具是Semantic Release。它通过解析提交消息来自动化版本控制。因此,您(以及项目中开发的每个人)使用特殊格式进行提交,前缀类型为 fix、feat、chore 等,Semantic Release 会解析自上次发布以来的每条提交消息并确定下一个版本号。

由于在 JS 世界中您只需更新 package.json 文件,它有点开箱即用,但对于 .NET,还有一些工作要做。 我们使用Directory.Build.props 文件来设置版本,就像使用 package.json 一样(但它也可以在 csproj 文件中使用)。

现在语义发布可以与插件一起使用,我们编写的one plugin 可以使用版本号更新文件。所以我们用它来更新 Directory.Build.Props 中的版本号。但是,您也可以直接将版本号提供给 dotnet nuget cli,而不是文件中的版本,但我们发现将它放在文件中更方便。

以下是我们用于(内部)NuGet 包的流程:

配置 Semantic Release 以更新 Directory.Build.props 文件中的版本号(在创建包之前) 创建 nuget 包(我们有另一个插件,但您可以在 SR 的准备阶段使用 exec plugin 在 SR 的发布阶段发布 nuget 包

然后,CI 脚本将在具有 NPM 和 .NET SDK 可用的环境中调用 npx semantic-release(为此我们使用容器映像)。

特别是对于 OP 的情况,它会稍微复杂一些,因为多个项目存在于一个存储库中,并且 SR 可以在单个存储库中工作,因为它使用 git 标签来确定最后一个版本。所以我建议将其拆分为多个存储库。

好处是:

每当有新功能/错误修复时自动发布 遵循语义版本控制的版本号自动化(并且是无主见的) 在每个版本中自动生成更新日志 根据 Git 平台,您还可以创建版本(有适用于 GitHub、GitLab 等的插件) 您可以配置 SR 以在某些分支中创建预发布,例如您可以拥有 alpha 或 beta 或开发分支 您还可以将提交哈希添加到 NuGet 包中,这样使用它的每个人都可以确切地知道其中捆绑了什么 语义发布使用更新后的 package.json 创建提交(您可以将其配置为还包含更新后的 Directory.Build.props,我建议您这样做)

您所要做的就是配置一次并强制执行一种特殊的 Commit Message 样式(哪个不太重要,您可以相应地配置 SR)。

【讨论】:

以上是关于持续部署环境中的 dotnet 语义版本控制的主要内容,如果未能解决你的问题,请参考以下文章

dotnet 核心包版本控制

dotnet 设置语义版本

GitLab+Jenkins结合构建持续集成(CI)环境

Jenkins持续部署-自动生成版本号

如何根据语义版本控制自动增加 Web 应用程序版本?

WPF 项目中的语义版本控制