dotnet 根据基线包版本实现库版本兼容
本文来告诉大家如何根据 基线包版本 的功能来实现自动在构建过程中,告诉开发者,当前版本是否存在不兼容旧版本的变更。其不兼容变更包括二进制中断变更和 API 不兼容变更和源代码中断变更。可以让库开发者花更少的精力在测试兼容性上
今天看到了队长推送的 .NET 6新特性试用 Nuget包验证 博客,才回忆起此功能。这个功能是给库和框架开发者使用的,用于处理多版本兼容性问题
背景
只有对一个库或框架准备对外发布且长期维护,以及期望给其他开发者使用时,才需要考虑库或框架的兼容性问题。越是开发底层的库,兼容性问题就越加重要。此重要性,只有自己参与开发,踩够坑之后,才能有所体会
换句话说,判断一位开发者是不是库或框架的老司机开发者,可以通过他的兼容性处理上来看出。哈哈,需要说明的是,不是所有老司机开发者都是库或框架开发方向的,这是判断有经验的开发者的充分不必要条件
开始之前,先聊聊什么是兼容性问题。兼容性可以分为以下不兼容变更:
- 源代码中断变更和 API 不兼容变更:简单说 API 不兼容变更,就是更改了开放出去的 API 签名。对于使用了此库或框架的开发者来说,如果更新到新的版本,为了适配变更,就 必须 更改源代码
- 二进制中断变更:尽管是不用更改源代码就能适配新版本,但是如果没有重新构建,提示替换 DLL 文件,那将会在运行程序时挂掉。例如给某个公开的函数加上了一个默认参数,尽管默认参数的添加,在源代码上是可以不做任何变更就可以用上新的版本,然而如果没有重新构建,只是将新版本的 DLL 或 EXE 替换过去,在运行的时候将提示找不到方法
- 行为中断变更:某个行为被更改,执行逻辑和之前不兼容。例如原本一个方法能好好工作,现在调用了,进程就退出了等等
此外,还有更换了底层运行时框架的变更等,但这些就不在本文讨论范围了
更多请参阅官方文档的详细描述: 重大更改和 .NET 库 Microsoft Docs
对于使用库或框架的开发者来说,一方面又期望用上新版本的强大功能,另一方面又怕有不兼容的变更,需要花费大量的精力在更新上面。如果库或框架的开发者,可以保持好兼容性,那么升级版本是一个很轻松的事情
对于咱 dotnet 系的大部分库或框架开发者来说,在开发过程中,考虑兼容性是一个必备的选项。那如果真的需要变更 API 了呢?问题也不大,别忘了咱还有版本号规则
版本号规则
基本所有 dotnet 系上,正经的库和框架都会遵循约定的版本号规则,从而让开发者在使用任何库的时候,通过版本号都能明确其中的含义,决定自己是否应该升级到最新版本
无异议的版本规则是,版本号由四个部分组成,分为 主版本号.次版本号.构建号.修订号 四个部分。其中的 构建号 和 修订号 都可忽略不写。各个部分的含义如下
- 主版本号: major version , 此版本如有变更,如从 1 升级到 2 的版本,代表着有重大更改。如存在不兼容的 API 或源代码更改,或者机制性,或者行为上的变更。大部分情况下,有主版本的变更就意味着需要在升级完成进行适配的工作
- 次版本号: minor version,此版本如有变更,代表着有新增的 API 定义或者是较大的但是兼容的修订,如修大 Bug 等,大部分情况下是不需要进行任何的适配工作
- 构建号: build number,此版本如有变更,代表着有小的更改,如修 Bug 等,不改变对外公开的约定的行为。升级新版本不需要进行任何的适配工作
- 修订号: revision,此版本大部分情况是给构建工具链编写的,开发者人类是很少需要变更此。升级到此新版本,无须进行任何适配
此外,有一些库毕竟激进,需要发布预览版本等,可以考虑采用语义版本号的方法,请看 语义版本号(Semantic Versioning) - walterlv - 博客园
通过如上的说明,可以了解到,如果不想刷主版本号,那就要求库或框架保持兼容旧版本。兼容旧版本需要在开发时,投入精力了解是否存在不兼容的更改,然而纯依靠手动去阅读代码了解是否存在不兼容的变更,当然是不靠谱的。本文将告诉大家如何使用 EnablePackageValidation 和 PackageValidationBaselineVersion 功能,自动让构建工具告诉开发者当前的更改是否存在不兼容的更改,从而更好保持库或框架的兼容
使用方法
一如既往的简单,只需要在项目文件上,添加如下代码即可
<EnablePackageValidation>true</EnablePackageValidation>
<PackageValidationBaselineVersion>基于的版本号</PackageValidationBaselineVersion>
例如当前是 2.0.0 的版本,期望进行对 1.0.0 包版本的兼容性测试,可以将 PackageValidationBaselineVersion 的值更改为 1.0.0 版本,如下面代码
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<PackageVersion>2.0.0</PackageVersion>
<EnablePackageValidation>true</EnablePackageValidation>
<PackageValidationBaselineVersion>1.0.0</PackageValidationBaselineVersion>
</PropertyGroup>
如此,在存在中断性(也就是不兼容,需要代码适配)变更时,在会在构建时给出提示,同时让构建不通过
例子
如何更好的使用此功能,还请让我用一个例子来告诉大家。此例子完全从 官方文档 抄的
在第一个版本时,作为 1.0.2 的版本的 NuGet 包,已对外发布。在进行 1.1.0 版本开发时,期望能做到完全的兼容第一个版本。利用 PackageValidationBaselineVersion 的功能,在 csproj 项目文件上,加上如下代码
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<PackageVersion>1.1.0</PackageVersion>
<EnablePackageValidation>true</EnablePackageValidation>
<PackageValidationBaselineVersion>1.0.2</PackageValidationBaselineVersion>
</PropertyGroup>
</Project>
通过在 PackageValidationBaselineVersion 执行定了基线包版本为 1.0.2 即可采用此指定的版本进行基线包版本对比。例如几周后,你的任务是为库添加对连接超时的支持,代码的 Connect 方法目前如下所示:
public static HttpClient Connect(string url)
{
// ...
}
由于连接超时是一个高级配置设置,因此你认为可以添加一个可选参数,更改如下:
public static HttpClient Connect(string url, TimeSpan timeout = default)
{
// ...
}
更改之后,构建过程可以正常,但是在打包的时候,将会收到如下提示,打包失败
D:\demo>dotnet pack
Microsoft (R) Build Engine version 17.0.0-preview-21460-01+8f208e609 for .NET
Copyright (C) Microsoft Corporation. All rights reserved.
Determining projects to restore...
All projects are up-to-date for restore.
You are using a preview version of .NET. See: https://aka.ms/dotnet-core-preview
PackageValidationThrough -> D:\demo\bin\Debug\net6.0\PackageValidationThrough.dll
Successfully created package 'D:\demo\bin\Debug\PackageValidationThrough.2.0.0.nupkg'.
C:\Program Files\dotnet\sdk\6.0.100-rc.1.21463.6\Sdks\Microsoft.NET.Sdk\targets\Microsoft.NET.Compatibility.Common.targets(32,5): error CP0002: Member 'A.B.Connect(string)' exists on [Baseline] lib/net6.0/PackageValidationThrough.dll but not on lib/net6.0/PackageValidationThrough.dll [D:\demo\PackageValidationThrough.csproj]
或者中文版本的提示如下
用于 .NET 的 Microsoft (R) 生成引擎版本 17.0.0-preview-21501-01+bbcce1dff
版权所有(C) Microsoft Corporation。保留所有权利。
正在确定要还原的项目…
所有项目均是最新的,无法还原。
你正在使用 .NET 的预览版。请查看 https://aka.ms/dotnet-core-preview
NallcearreyiHernareferkear -> C:\lindexi\NallcearreyiHernareferkear\NallcearreyiHernareferkear\bin\Debug\net6.0\NallcearreyiHernareferkear.dll
已成功创建包“C:\lindexi\NallcearreyiHernareferkear\NallcearreyiHernareferkear\bin\Debug\NallcearreyiHernareferkear.2.0.0.nupkg”。
C:\Program Files\dotnet\sdk\6.0.100-rc.2.21505.57\Sdks\Microsoft.NET.Sdk\targets\Microsoft.NET.Compatibility.Common.targets(32,5): error CP0002: Member 'NallcearreyiHernareferkear.Foo.Connect(string)' exists on [Baseline] lib/net6.0/NallcearreyiHernareferkear.dll but not on lib/net6.0/NallcearreyiHernareferkear.dll [C:\lindexi\NallcearreyiHernareferkear\NallcearreyiHernareferkear\NallcearreyiHernareferkear.csproj]
如此通过打包失败,提示的 CP0002 失败,可以了解到,自己没有做到让当前版本对写入到 PackageValidationBaselineVersion 的兼容。此时要做的事情,要么是废弃掉对 PackageValidationBaselineVersion 的兼容,也就是删除此属性,同时升级主版本号,告诉其他开发者,当前版本存在不兼容。要么是更改 API 定义,更改到兼容
例如以上的代码,虽然加上了一个默认参数,可以实现到源代码兼容。但是大家都知道,这是二进制不兼容的,如果直接替换 DLL 文件,而不经过编译,将会在运行的过程中,因为找不到对应的方法而失败
什么情况下会遇到没有重新构建,只是替换 DLL 文件而已?在于是其他底层库的依赖引用,例如再有另一个库 C 也引用了此,而库 C 打出的 NuGet 包被最终项目所引用。当最终项目升级版本时,由于 Connect 方法被更改,从而让库 C 里面的对应逻辑找不到方法,而在运行时失败
因此为了做到这部分的兼容,可以考虑作为重载的方法更改,更改如下
public static HttpClient Connect(string url)
{
return Connect(url, Timeout.InfiniteTimeSpan);
}
public static HttpClient Connect(string url, TimeSpan timeout)
{
// ...
}
这样进行重新打包,即可看到打包成功,兼容 PackageValidationBaselineVersion 的 1.0.2 版本
原理
此功能是依托于 NuGet 包发布而拿到指定版本号规则的,和 使用基于 Roslyn 的 Microsoft.CodeAnalysis.PublicApiAnalyzers 来追踪项目的 API 改动,帮助保持库的 API 兼容性 - walterlv 的方法是完全不相同的
本文介绍的方法,是在 PackageValidationBaselineVersion 里面,声明的包版本,在构建过程中,通过 NuGet 去拉取对应的版本,接着通过 DLL 导出类型的对比,从而了解是否存在不兼容的变更
也就是说在 PackageValidationBaselineVersion 里面写入的版本号,要求是可以在 NuGet 源里面(无论是 nuget.org 源,还是你的私有的源,还是你的本机文件夹都可以)拉到对应的版本。由此版本里面的 DLL 执行具体的对比逻辑。这也就要求了此功能只能用在简单的 NuGet 上,对于很多上了黑科技的 NuGet 包是无法执行的。例如使用 SourceYard 打包的源代码包
本文介绍的方法,对比使用基于 Roslyn 的 Microsoft.CodeAnalysis.PublicApiAnalyzers 来追踪项目的 API 改动,帮助保持库的 API 兼容性 的方法来说,优势在于不需要带上 PublicAPI.Unshipped.txt 和 PublicAPI.Shipped.txt 文件,此两个文件夹特别好在团队开发时进行冲突,而且需要进行手动管理。但是缺点在于本文介绍的方法功能单一,也依赖 NuGet 包版本
代码
可以通过如下方式获取本文的源代码,先创建一个空文件夹,接着使用命令行 cd 命令进入此空文件夹,在命令行里面输入以下代码,即可获取到本文的代码
git init
git remote add origin https://gitee.com/lindexi/lindexi_gd.git
git pull origin 95692dcaabfb0d143dffa8e31c0a1ad00e7c2e74
以上使用的是 gitee 的源,如果 gitee 不能访问,请替换为 github 的源
git remote remove origin
git remote add origin https://github.com/lindexi/lindexi_gd.git
获取代码之后,进入 NallcearreyiHernareferkear 文件夹
更多阅读
dotnet 使用 Obsolete 特性标记成员过时保持库和框架的兼容性
语义版本号(Semantic Versioning) - walterlv - 博客园
使用基于 Roslyn 的 Microsoft.CodeAnalysis.PublicApiAnalyzers 来追踪项目的 API 改动,帮助保持库的 API 兼容性 - walterlv
Assembly versioning Microsoft Docs
dotnet 根据基线包版本实现库版本兼容的更多相关文章
- 【Android 应用开发】 Android 各种版本简介 ( Support 支持库版本 | Android Studio 版本 | Gradle 版本 | jcenter 库版本 )
初学者遇到 Android Studio, 导入工程后, 会出现各种奇葩错误, 如果管理好各个插件, gradle, SDK, SDK Tools, 各种官方依赖库 的版本, 会将错误大大的减少; 这 ...
- 升级python的sqlite库版本
今天了解了一下用python获取chrome cookie信息,在研究的过程中,发现打开数据库失败,后来调查了一下发现是由于sqlite3库太老的缘故,起码需要3.8以上,然后看了一下python 2 ...
- 解决 Android 中出现依赖多个版本支持库的问题
在 app 的 build.gradle 中引入依赖时发现如下错误: All com.android.support libraries must use the exact same version ...
- git 本地库推送远程库 版本冲突的解决方法
参考: http://blog.csdn.net/shiren1118/article/details/7761203 github上的版本和本地版本冲突的解决方法 $ git push XXX ma ...
- Nuget包里的依赖包更新到最新版本会不会随主包回滚到旧包的研究
A包中有几个依赖包:A-1包,版本:>=1.0:但是我项目上已经引用了A-1包的2.0版本,那么我添加A包的时候,不会将A-1包2.0版本改成1.0版本,会直接用2.0版本的.
- VC运行库版本不同导致链接.LIB静态库时发生重复定义问题的一个案例分析和总结
转帖:http://blog.csdn.net/whygosofar/article/details/2821875 MSDN中对于在不同的配置下Link的LIB作了说明: C Runtime Lib ...
- 程序包需要 NuGet 客户端版本“2.12”或更高版本,但当前的 NuGet 版本为“2.8.50313.46”
由于安装install-package newtonsoft.json 会出现需要 NuGet 客户端版本“2.12”或更高版本来安装,于是换成旧版的newtonsoft.json PM> ...
- VS2015调用低版本lib库出现“无法解析的外部符号 __snprintf ”问题的解决
VS2015在调用低版本lib库出现有时会出现“无法解析的外部符号 __snprintf ”的问题,解决方法是加入lib库“legacy_stdio_definitions.lib”到工程.
- 解决Linux动态库版本兼容问题
说道“动态库版本兼容”,很多人头脑中首先蹦出的就是“Dll Hell”.啊,这曾经让人头疼的难题.时至今日,这个难题已经很好地解决了. 在进一步讨论之前来思考一个问题:Linux下为什么没有让人头痛的 ...
- VS2017 Thrift编译出的Release版本的库调用报错LNK2001
在使用thrift的过程中, 当我使用完thrift debug版本编译出来的库调试完成后, 改成release版本的时候, 就出现了如下错误, 莫名其妙啊, 同一套代码, 那只能是编译库的时候设置和 ...
随机推荐
- 三维模型3DTile格式轻量化的数据压缩与性能平衡关系分析
三维模型3DTile格式轻量化的数据压缩与性能平衡关系分析 对于三维模型的3DTile格式轻量化处理,数据压缩和性能之间的平衡关系是一个重要的考虑因素.以下是这两者关系的详细分析: 1.数据压缩与加载 ...
- .NET Emit 入门教程:第一部分:Emit 介绍
前言: Emit 是开发者在掌握反射的使用后,进阶需要的知识,它能显著的改善因反射带来的性能影响. 目前能搜到的 Emit 的相关文章,都是一篇系列,通常推荐对照着反绎后的 IL 编写 Emit 代码 ...
- 说说你对keep-alive的理解是什么?
这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助 一.Keep-alive 是什么 keep-alive是vue中的内置组件,能在组件切换过程中将状态保留在内存中,防止重复渲染DOM ke ...
- 《.NET内存管理宝典 》(Pro .NET Memory Management) 阅读指南 - 第3章
本章勘误: 暂无,等待细心的你告诉我哦. 本章注解: 暂无 本章释疑: 暂无,等待你的提问 致谢: MVP 林德熙 MVP 吕毅 sPhinX 相关链接 试读记录
- objective-c之Class底层结构探索
isa 走位图 在讲 OC->Class 底层类结构之前,先看下下面这张图: 通过isa走位图 得出的结论是: 1,类,父类,元类都包含了 isa, superclass 2,对象isa指向类对 ...
- 浅谈 KingbaseES 和 SQLServer 中的 instead of 触发器
本文基于Kingbase和SqlServer的INSTEAD OF 触发器主要功能特点进行对比浅析,同时针对SqlServer 的INSTEAD OF 触发器提出了多种kingbase环境的等价代码方 ...
- Scala 不可变数组Array
1 package chapter07 2 3 object Test01_ImmutableArray { 4 def main(args: Array[String]): Unit = { 5 / ...
- redis的三种模式
一.主从模式 1.主从模式是最为简单的redis集群模式 2.主要工作模式是主从复制.主数据库可以执行读写功能,而从数据库只能执行读功能.主数据库数据发生变化,会自动同步到从数据库. 3.主数据库为m ...
- 如何实现OpenHarmony的OTA升级
OTA简介 随着设备系统日新月异,用户如何及时获取系统的更新,体验新版本带来的新的体验,以及提升系统的稳定性和安全性成为了每个厂商都面临的严峻问题.OTA(Over the Air)提供对设备远程升级 ...
- 初学STM32 SDIO(一)
1. SDIO协议简介 SDIO全称是安全数字输入/输出接口 ,控制器对SD卡进行读写通信操作一般有两种通信接口可选,一种是SPI接口,另外一种就是SDIO接口. 多媒体卡(MMC).SD卡. S ...