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

dotnet semantic versioning in a continuous deployment environment

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

看了很多意见,有些自相矛盾,这是我清楚的。 语义版本应该是这样的 M.m.P.B-abc123 其中

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

我有以下 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 用于 master 分支(稳定或生产就绪)和 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 中检索 <VersionPrefix> 内容。

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

谢谢

The problem with my approach is that I have VS solutions with multiple projects, each project will be a nuget package and each one has its own version. Sometimes I modify one project but not the other, therefore in theory I shouldn't need to produce a new artifact of the project that I didn't touch nor a new version, of course.

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

来自 visual studio 团队服务的人对此有话要说:

Immutability and unique version numbers In NuGet, a particular package is identified by its name and version number. Once you publish a package at a particular version, you can never change its contents. But when you’re producing a CI package, you can’t know whether it will be version 1.2.3 or just a step along the way towards 1.2.3. You don’t want to burn the 1.2.3 version number on a package that still needs a few bug fixes.

SemVer to the rescue! In addition to Major.Minor.Patch, SemVer provides for a prerelease label. Prerelease labels are a “-” followed by whatever letters and numbers you want. Version 1.0.0-alpha, 1.0.0-beta, and 1.0.0-foo12345 are all prerelease versions of 1.0.0. Even better, SemVer specifies that when you sort by version number, those prerelease versions fit exactly where you’d expect: 0.99.999 < 1.0.0-alpha < 1.0.0 < 1.0.1-beta.

Xavier’s blog post describes “hijacking” the prerelease tag to use for CI. We don’t think it’s hijacking, though. This is exactly what we do on Visual Studio Team Services to create our CI packages. We’ll overcome the paradox by picking a version number, then producing prereleases of that version. Does it matter that we leave a prerelease tag in the version number? For most use cases, not really. If you’re pinning to a specific version number of a dependent package, there will be no impact, and if you use floating version ranges with project.json, it will mean a small change to the version range you specify. Source: https://devblogs.microsoft.com/devops/versioning-nuget-packages-cd-1/

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

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

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

背景介绍:我在一家小公司工作,我们开始使用 NPM 包进入 NodeJS 世界。一段时间后,我们也开始使用 .NET Core 进行构建,并且我们从 JavaScript 世界中改编了一些东西。我们经常使用的一个工具是 Semantic Release。它通过解析提交消息来自动进行版本控制。所以你(和项目开发的每个人)使用一种特殊的格式来提交带有前缀类型,如 fix、feat、chore 等等,语义发布解析自上次发布以来的每条提交消息并确定下一个版本号。

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

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

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

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

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

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

好处是:

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

你所要做的就是配置一次并强制执行一种特殊的提交消息样式(哪个不是那么重要,你可以相应地配置 SR)。