SpringBoot运行单元时测试报错:java.lang.IllegalStateException Unable to find a @SpringBootConfiguration, you need to use @ContextConfiguration or…

错误描述

SpringBoot运行单元测试时报错:java.lang.IllegalStateException: Unable to find a @SpringBootConfiguration, you need to use @ContextConfiguration or @SpringBootTest(classes=…) with your test

我的软件环境

JDK 11.0

SpringBoot 2.7

Maven 3.8.7

出错原因

src/test/java目录下的包结构和src/main/java目录下的包结构应该一样,否则就会报上述错误。

我的src/main/java目录下的包是com.comp.example,但是src/test/java目录下的包是com.sample.example,两者不一样,因此报上述错误。

解决方法

把src/test/java目录下的包结构和src/main/java目录下的包结构改成一样即可。

参考

https://stackoverflow.com/questions/47487609/unable-to-find-a-springbootconfiguration-you-need-to-use-contextconfiguration

JDBC连接MySQL数据库警告:Establishing SSL connection without server’s identity verification is not recommend

JDBC连接MySQL出现如下警告:

Establishing SSL connection without server’s identity verification is not recommended. According to MySQL 5.5.45+, 5.6.26+ and 5.7.6+ requirements SSL connection must be established by default if explicit option isn’t set. For compliance with existing applications not using SSL the verifyServerCertificate property is set to ‘false’. You need either to explicitly disable SSL by setting useSSL=false, or set useSSL=true and provide truststore for server certificate verification.

原因是高版本MySQL需要指明是否进行SSL连接。

解决方法是在JDBC连接MySQL的URL字符串中加入useSSL=true或者useSSL=false即可,例如:

jdbc:mysql://127.0.0.1:3306/framework?serverTimezone=UTC&characterEncoding=utf8&useSSL=false

如果你的连接确实没有暴露给网络(仅限本地主机),或者你在没有真实数据的非生产环境中工作,那么可以肯定的是:通过包含选项useSSL=false来禁用SSL没有坏处。

如果要使用useSSL=true,需要以下一组选项才能使SSL使用证书、主机验证并禁用弱协议选项:

  • useSSL=true
  • sslMode=VERIFY_IDENTITY
  • trustCertificateKeyStoreUrl=file:path_to_keystore
  • trustCertificateKeyStorePassword=password
  • enabledTLSProtocols=TLSv1.2

因此,作为一个示例,让我们假设你希望使用SSL将其中一种Atlassian产品(Jira、Confluence、Bamboo等)连接到MySQL服务器,你需要执行以下主要步骤:

  • 首先,确保你有一个为MySQL服务器主机生成的有效的SSL/TLS证书,并且CA证书安装在客户端主机上(如果你使用自签名,那么你可能需要手动执行此操作,但对于流行的公共CA,它已经在那里了)。
  • 接下来,确保java密钥库包含所有CA证书。在Debian/Ubuntu上,这是通过运行以下命令来实现的:
update-ca-certificates -f
chmod 644 /etc/ssl/certs/java/cacerts
  • 最后,更新Atlassian产品使用的连接字符串以包含所有必需的选项,这在Debian/Ubuntu上类似于:
jdbc:mysql://mysql_server/confluence?useSSL=true&sslMode=VERIFY_IDENTITY&trustCertificateKeyStoreUrl=file%3A%2Fetc%2Fssl%2Fcerts%2Fjava%2Fcacerts&trustCertificateKeyStorePassword=changeit&enabledTLSProtocols=TLSv1.2&useUnicode=true&characterEncoding=utf8

更多相关信息请参见MySQL官方文档https://dev.mysql.com/doc/connector-j/8.0/en/connector-j-reference-using-ssl.html

参考

https://beansandanicechianti.blogspot.com/2019/11/mysql-ssl-configuration.html

CentOS7 yum安装MySql报错The GPG keys listed for the MySQL 8.0 Community Server repository are already installed but they are not correct for this package.的解决方法

在CentOS7中使用yum安装MySql8的时候,遇到这个问题。

原因是,MySQL GPG密钥已过期,无法从官方存储库安装或升级MySQL包。其他详细信息也可以在MySQL网站上找到:https://bugs.mysql.com/bug.php?id=106188

解决方法是,在运行安装程序之前导入密钥:

rpm --import https://repo.mysql.com/RPM-GPG-KEY-mysql-2022

对于Ubuntu系统:

wget -q -O - https://repo.mysql.com/RPM-GPG-KEY-mysql-2022 | apt-key add -

参考

https://support.cpanel.net/hc/en-us/articles/4419382481815

https://forums.cpanel.net/threads/mysql-upgrade-process-failed-the-gpg-keys-listed-for-the-mysql-8-0-community-server-repository-are-already-installed-but-they-are-not-correct-for.697213/

编译原理名词解释

元字符

关于元字符,类似元数据(meta data)的概念,元数据是用来描述数据的数据,例如一篇文章的关键字、一首歌的长度等。元字符是描述其他字符的字符,主要用在正则表达式中,例如在正则表达式中’.’号就是其中一个元字符,’.’号代表任意字符,一个’.’号匹配任意一个字符。在不同的正则表达式实现方式中,使用的元字符可能会有所不同。

C和C++有符号数溢出是一种未定义行为

本文翻译自《Signed Overflow》。

C和C++语言标准规定,有符号数的溢出是未定义的行为。在C99标准的第6.5节。在C++98标准中,它位于第5节[expr]的第5段。这意味着正确的C/C++程序在计算表达式时绝不能生成有符号数溢出。这也意味着编译器可能会假设程序永远不会产生有符号数溢出。

gcc在做优化时很早就利用了这一事实。例如,考虑这个函数:

int f(int x) { return 0x7ffffff0 < x && x + 32 < 0x7fffffff; }

如果有符号算术只是在溢出时回绕,则这等价于0x7ffffff0 < x,因为将32加到这样的值将产生负数。然而,即使是2001年发布的gcc 2.95.3,也会将此函数优化为只返回0的代码。这是有效的,因为编译器可能会假设在正确的程序中不会发生有符号数溢出,并且x + 32得到一个负数的情况只能从有符号数溢出发生。

最近gcc已经开始使用有符号数溢出这一未定义行为来实现更好的边界测试。特别是,考虑这样的代码:

int f()
{
int i;
int j = 0;
for (i = 1; i > 0; i += i)
++j;
return j;
}

这段代码假设如果它不断地将一个正数加倍,它最终会得到一个负数。也就是说,它期望有符号数溢出以某种方式运行。当前主流的gcc会认为在正确的程序中不会发生有符号数的溢出,并将这段代码编译成无限循环。

一些gcc用户对这种优化感到惊讶,并且在2007年初左右提出了强烈抗议。有一段时间有人建议gcc编译应该始终使用-fwrapv选项-fwrapv告诉编译器将有符号数的溢出视为回绕。这是Java定义有符号数溢出的方式。该选项是在2003年为gcc 3.3引入的。

-fwrapv的缺点是它会抑制优化。大多数程序不会产生有符号数溢出,而且正如我们所见,正确的程序都不会产生。当编译器可以假定不会发生有符号数溢出时,它可以生成更好的代码。这尤其出现在循环中。当循环使用有符号整数索引时,编译器可以做得更好,因为它不必考虑索引溢出的可能性。

考虑这个简单的例子:

int f(int i) { int j, k = 0; for (j = i; j < i + 10; ++j) ++k; return k; }

此函数返回什么?当编译器可以假设有符号数溢出是未定义行为时,此函数将编译为只返回10的代码。使用-fwrapv选项,代码就不那么简单了,因为i可能恰好有一个像0x7ffffff8这样的值,它会在循环期间溢出。虽然这是一个简单的例子,但很明显,像这样的循环总是在实际代码中发生。

然而,gcc确实需要回应社区用户的担忧。我在gcc 4.2中引入了两个新选项。第一个是-fno-strict-overflow,告诉编译器,它可能不会假设有符号数溢出是未定义行为。第二个是-Wstrict-overflow,告诉编译器在使用有符号数溢出这个未定义行为来实现优化的情况下发出警告。使用这些选项,可以检测程序中发生有符号数溢出的情况,并且可以禁用依赖有符号数溢出进行的优化,直到程序修复。-Wstrict-overflow甚至发现了一个很小的情况,即gcc本身依赖于有符号数溢出这个未定义行为,在处理常数0x80000000的除法时。

这自然会引出一个问题:-fwrapv-fno-strict-overflow之间有什么区别?在不使用普通二进制补码算法的处理器上有一个明显的区别:-fwrap需要二进制补码溢出,而-fno-strict-overflow则不需要。然而,目前还没有这种处理器被普遍使用。在实践中,我认为这两个选项生成的代码的行为始终相同。然而,它们对优化器的影响不同。例如,此代码:

int f(int x) { return (x + 0x7fffffff) * 2; }

使用-fwrapv-fno-strict-overflow编译出不同汇编代码。之所以出现这种差异,是因为-fwrapv精确地指定了溢出的行为方式,而-fno-strict-overflow只是说编译器不应该优化溢出。对于当前的编译器,使用-fwrapv(和-O2 -fomit-frame-pointer),我可以看到:

movl $1, %eax
subl 4(%esp), %eax
addl %eax, %eax
negl %eax
ret

而使用-fno-strict-overflow选项我看到:

movl 4(%esp), %eax
leal -2(%eax,%eax), %eax
ret

运算结果一样,但是使用了不同的算法。

建议:迁移到Go 2

建议:迁移到Go 2

本文翻译自《Proposal: Go 2 transition》。

作者:Ian Lance Taylor

最后更新时间:2018年10月15日

摘要

本文是关于如何在尽可能少地破坏的情况下,从Go 1迁移到Go 2进行不兼容更改的建议。

背景

目前Go语言和标准库都遵守Go 1兼容性保证。该文档的目标是承诺Go的新版本不会破坏现有的程序。

Go 2进展的目标之一是对会破坏兼容性保证的语言和标准库进行更改。由于Go是在分布式开源环境中使用的,因此我们不能依赖卖旗日(flag day)(译者注:指一种既不向前兼容也不向后兼容的软件更改,其制作成本高且撤消成本高)。我们必须允许使用不同版本的Go编写的不同包可以互相操作。

每种语言都会经历版本转换。作为背景,这里有一些关于其他语言所做的事情的记录。你可以随意跳过本节的其余部分。

C

C语言版本由ISO标准化过程驱动。C语言开发非常注重向后兼容性。在第一个ISO标准C90之后,每个后续标准都保持严格的向后兼容性。在引入新关键字的地方,它们被引入到C90保留的命名空间中(一个下划线后跟一个大写的ASCII字母),并且可以通过特定头文件中的#define宏访问它们,这些头文件以前并不存在(例如_Complex<complex.h>中定义为复数,而_Bool<stdbool.h>中定义为bool)。C90中定义的基本语言语义都没有改变。

此外,大多数C编译器都提供选项来精确定义代码应该针对哪个版本的C标准进行编译(例如,-std=c90)。大多数标准库实现都支持在包含头文件之前使用#define定义的特性宏,用来准确指定应该提供哪个版本的库(例如,_ISOC99_SOURCE)。虽然这些特性以前存在过Bug,但它们相当可靠并且被广泛使用。

这些选项的一个关键特性是,能让使用不同的语言版本和库版本编写的代码,通常都可以链接在一起并按照预期工作。

第一个标准C90确实对以前的C语言实现进行了重大更改,可以非正式地认为以前的C语言就是K&R C。引入了新关键字,例如volatile(实际上这可能是C90中唯一的新关键字)。 整数表达式中整数提升的精确实现从无符号保留(unsigned-preserving)更改为值保留(value-preserving)。幸运的是,很容易检测到由于使用了新关键字而编译错误的代码,并且很容易调整该代码。整数提升的变化实际上让新手用户没有那么难理解,有经验的用户大多使用显式转换,来确保在具有不同整数大小的系统之间的可移植性,因此虽然没有自动检测该问题,但在实践中并没有多少代码被破坏。

也有一些恼人的变化。C90引入了三字母词,它改变了一些字符串常量的行为。编译器适应了诸如-no-trigraphs-Wtrigraphs之类的选项。

更严重的是,C90引入了未定义行为(undefined behavior)的概念,并声明调用未定义行为的程序可能会采取任何操作。在K&R C中,被C90描述为未定义行为的情况大多被视为C90中所谓的依赖具体实现的行为(implementation-defined behavior):程序将采取一些不可移植但可以预测行为的操作。编译器编写者吸收了未定义行为的概念,并开始编写假定该行为不会发生的编译优化。这造成了令不熟悉C标准的人感到惊讶的影响。我不会在这里详细介绍,但其中一个示例(来自我的博客)是有符号数的栈溢出

当然C仍然是内核开发的首选语言和计算行业的胶水语言。尽管它已被更加新的语言部分取代了,但这并不是新版本的C做出的任何选择之过。

我在这里看到的教训是:

  • 向后兼容性很重要。
  • 小部分破坏兼容性是可以的,只要人们可以通过编译器选项或编译器错误发现这些破坏。
  • 可以选择特定语言/库版本的编译器选项很有用,前提是使用不同选项编译的代码可以链接在一起。
  • 没有限制的未定义行为会让用户感到困惑。

C++

C++语言的版本现在也由ISO标准化过程驱动。与C一样,C++也非常关注向后兼容性。历史上,C++在添加新关键字方面更加自由(C++ 11中有10个新关键字)。这很正常,因为较新的关键字往往相对较长(constexprnullptrstatic_assert),使得使用新关键字作为标识符的代码很容易找到编译错误。

C++使用与C中相同的选项来指定语言和库的标准版本。在未定义的行为方面,C++遇到与C相同的问题。

C++中一个突破性变化的例子是在for循环的初始化语句中声明的变量范围的变化。在C++的预标准版本中,该变量的范围扩展到for循环所在的封闭块的末尾,就好像它是在for循环之前声明的一样。在第一个C++标准C++ 98的开发过程中,对其进行了修改,使其范围仅限于for循环本身。编译器通过引入诸如-ffor-scope之类的选项进行了调整,以便用户可以控制变量的预期范围(在一段时间内,当既不使用-ffor-scope也不使用-fno-for-scope进行编译时,GCC编译器使用了旧的范围,但警告任何依赖这一行为的代码)。

尽管向后兼容性相对较强,但用新版本的C++(如C++ 11)编写代码往往与用旧版本的C++编写代码有着非常不同的感觉。这是因为样式已更改为使用新的语言和库功能。原始指针不太常用,使用范围循环而不是迭代器,诸如右值引用和移动语义等新概念被广泛使用,等等。熟悉C++旧版本的人很难理解用新版本编写的代码。

C++当然是一种非常流行的语言,正在进行的语言修改过程并没有损害它的流行性。

除了C的教训,我还想补充一点:

  • 在保持向后兼容的同时,新版本可能会有非常不同的感觉。

Java

与我讨论的其他语言相比,我对Java的了解较少,因此这里可能存在更多错误,当然也存在更多偏见。

Java在字节码级别很大程度上向后兼容,这意味着Java N+1版本的库可以调用由Java版本N(以及N-1、N-2等)编写和编译的代码。Java源代码也大多是向后兼容的,尽管它们会不时添加新的关键字。

Java文档非常详细地介绍了从一个版本迁移到另一个版本时可能出现的兼容性问题。

Java标准库非常庞大,每个新版本都会添加新包。包也会不时被弃用。使用已弃用的包将在编译时引发一个警告(警告可能会被关闭),并且在几次发布后,已弃用的包将被删除(至少在理论上是这样)。

Java似乎没有太多的向后兼容性问题。问题集中在JVM上:较旧的JVM通常不会运行较新版本的库,因此你必须确保你的JVM至少与你要使用的最新库所需的一样新。

Java按理说具有某种前向兼容性问题,因为JVM字节码提供了比CPU更高级别的接口,这使得引入不能使用现有字节码直接表示的新特性变得更加困难。

这种前向兼容性问题是Java泛型使用类型擦除的部分原因。更改现有字节码的定义会破坏已经编译成字节码的现有程序。扩展字节码以支持泛型类型需要定义大量额外的字节码。

从某种程度上说,这种前向兼容性问题对于Go来说并不存在。由于Go编译为机器代码,并通过生成额外的机器代码来实现所有必需的运行时检查,因此不存在类似的前向兼容性问题。

但总的来说:

  • 请注意兼容性问题可能会限制编程语言未来的更改。

Python

Python 3.0(也称为Python 3000)于2006年开始开发,最初于2008年发布。2018年过渡仍未完成。有些人继续使用Python 2.7(2010年发布)。这不是Go 2想要效仿的路径。

这种缓慢过渡的主要原因似乎是缺乏向后兼容性。Python 3.0故意与早期版本的Python不兼容。值得注意的是,print从语句更改为函数,字符串更改为使用Unicode。Python通常与C代码结合使用,后者的变化意味着任何将字符串从Python传递到C的代码都需要调整C代码。

因为Python是一种解释型语言,并且因为没有向后兼容性,所以不可能在同一个程序中混合使用Python 2和Python 3代码。这意味着对于使用一系列库的典型的程序,每个库都必须先转换为Python 3,然后才能转换程序。由于程序处于各种转换状态,库必须同时支持Python 2和3。

Python支持from __future__ import FEATURE形式的语句。像这样的语句以某种方式改变了Python对文件其余部分的解释。例如,from __future__ import print_functionprint从语句(如在 Python 2中)更改为函数(如在Python 3中)。这是一种渐进地把代码更新到新的语言版本的方式,并使在不同的语言版本之间共享相同代码变得更加容易。

因此我们知道:

  • 向后兼容性是必不可少的。
  • 与其他语言的接口的兼容性很重要。
  • 升级到新语言版本也受到你使用的代码库所支持的版本的限制。

Perl

Perl 6的开发过程始于2000年。Perl 6规范的第一个稳定版本于2015年发布。这不是Go 2想要效仿的路径。

这条道路如此缓慢有很多原因。Perl 6有意不向后兼容:它旨在修复语言中的缺陷。Perl 6旨在通过规范来表示,而不是像以前版本的Perl那样,通过实现来表示。Perl 6从一组更改建议开始,随着时间的推移会不断发展,再继续发展得更多。

Perl支持use feature,类似于Python的from __future__ import。它更改了文件其余部分的解释方式,以使用新语言的指定功能。

  • 不要成为Perl 6。
  • 设定并遵守最后期限。
  • 不要一下子改变一切。

建议

语言更改

迂腐地说,我们必须使用一种方式来谈论特定的语言版本。Go语言的每个更改首先出现在Go的发行版中。我们将使用Go版本号来定义语言版本。这是唯一合理的选择,但它可能会造成混淆,因为标准库的更改也与Go版本号相关联。在考虑兼容性时,有必要在概念上将Go语言版本与标准库版本分开。

作为特定更改的一个示例,类型别名(type aliase)首先在Go语言1.9版本中可用。类型别名是向后兼容语言更改的一个示例。所有用Go语言1.0到1.8版本编写的代码在Go语言1.9版本继续与以前相同的方式工作。但使用类型别名的代码需要使用Go语言1.9或更高版本才能编译运行。

增加语言特性

类型别名是增加语言特性的一个示例。使用类型别名语法type A = B的代码无法在1.9版本之前的Go中编译。

类型别名和自Go 1.0以来的其他向后兼容的更改向我们表明,对于语言特性的增加,包没有必要显式声明它们所需的最低语言版本。一些包使用类型别名这一新特性。当用Go 1.8工具编译这样的包时,编译失败。包作者可以简单地说:升级到Go 1.9,或者降级到包的早期版本。Go工具不需要知道这个要求;因为无法使用旧版本的工具进行编译暗示了这一点。

程序员当然需要了解语言特性的增加,但工具不需要。Go 1.8工具和Go 1.9工具都不需要明确知道Go 1.9中增加了类型别名,除了Go 1.9编译器可以编译类型别名而Go 1.8编译器不能,这个有限的意义之外。指定最低语言版本以获得更好的不支持语言特性的错误消息的可能性,将在下文讨论。

移除语言特性

我们还必须考虑从语言中删除特性的语言更改。例如,issue 3939建议我们删除string(i)转换整数值i。如果我们在Go版本1.20中进行此更改,那么使用此语法的包将在Go 1.20中停止编译。(如果你更愿意将向后不兼容的更改限制在新的主版本中,那么在此讨论中将1.20替换为2.0;问题仍然存在。)

在这种情况下,使用旧语法的包没有简单的修改方法。虽然我们可以提供将1.20之前的代码转换为可工作的1.20代码的工具,但我们不能强制包作者运行这些工具。一些软件包可能没有维护但仍然有用。一些组织可能希望升级到1.20而不必重新验证他们所依赖的软件包的版本。一些软件包作者可能希望使用1.20,他们的软件包现在已经损坏,但没有时间修复他们的软件包。

这些场景表明我们需要一种机制,来指定可以用来构建软件包的Go语言的最高版本。

重要的是,指定Go语言的最高版本不应被视为要使用的Go工具的最高版本。随Go 1.20版本发布的Go编译器必须能够构建Go 1.19版本编写的包。这可以通过模仿C编译器支持的-std选项向编译器(以及,如果需要,汇编器和链接器)添加一个选项来完成。当编译器看到选项时,可能是-lang=go1.19,它将使用Go 1.19语法来编译代码。

这需要编译器以某种方式支持所有以前的版本。如果证明支持旧语法很麻烦,则可以通过将代码从旧版本转换到当前版本来实现-lang选项。这将使对旧版本的支持不在编译器的合理范围内,并且转换器对于想要更新其代码的人可能很有用。支持旧的语言版本不太可能会成为一个重大问题。

当然,即使包是用语言版本1.19的语法构建的,它在其他方面也必须是1.20版本的包:它必须与1.20版本的代码链接,能够调用和被1.20版本的代码调用,等等。

go工具需要知道最大语言版本,以便它知道如何调用编译器。我们继续考虑模块,此信息的逻辑位置位于go.mod文件。模块M的go.mod文件可以为其定义的包指定最大语言版本。当M作为其他模块的依赖项被下载时,它的最大语言版本将会被遵循。

最高语言版本不是最低语言版本。如果一个模块需要1.19版本的语言特性,但可以用1.20构建,我们可以说最大语言版本是1.20。如果我们使用Go 1.19版本来构建,我们低于最大版本值,但使用1.19语言版本来构建不是不可以。可以忽略大于当前工具支持的最大语言版本。如果我们稍后使用Go 1.21版本来构建该模块,我们可以使用-lang=go1.20选项使用最高1.20版本的语言特性。

这意味着这些工具可以自动设置最大语言版本。当我们使用Go 1.30发布模块时,我们可以将模块标记为具有最大语言版本1.30。该模块的所有用户都会看到这个最高版本并做正确的事情。

这意味着我们将不得不无限期地支持该语言的旧版本。如果我们在1.25版本之后删除了一个语言特性,如果使用-lang=go1.25选项(或-lang=go1.24或任何其他包含支持功能)。当然,如果没有使用-lang选项,或者选项是-lang=go1.26或更高版本,则该功能将不可用。由于我们不希望大规模删除现有语言功能,因此这应该是一个可管理的负担。

我相信这种方法足以实现移除语言特性

最小语言版本

为了获得更好的错误消息,允许模块文件指定最低语言版本可能很有用。但这不是必需的:如果一个模块使用了语言版本1.N中引入的特性,那么用1.N-1版本构建它将编译失败。这可能令人困惑,但在实践中,问题很可能是显而易见的。

也就是说,如果模块可以指定最低语言版本,那么在使用1.N-1构建时,go工具可以立即生成一条清晰的错误消息。

最低语言版本可能由编译器或其他工具设置。编译每个文件时,查看它使用的特性,并使用这些特性确定最低语言版本。不需要很精确。

这只是建议,不是要求。随着Go语言的变化,它可能会提供更好的用户体验。

语言重定义

Go语言也能够以不添加或删除的方式进行更改,即对特定语言结构的工作方式进行更改。例如,在Go 1.1中,64位主机上int类型的大小从32位更改为64位。这一变化相对无害,因为该语言本就没有指定int的确切大小。然而,一些Go 1.0程序使用Go 1.1进行编译后,可能会停止工作。

重新定义语言是这样一种情况,即我们的代码在版本1.N和版本1.M中都能成功编译,其中M>N,但两个版本中代码的含义不同。例如,issue 20733提出范围循环(range loop)中的变量应在每次迭代中重新定义。尽管在实践中,这种变化似乎更可能是一种修复程序而不是破坏程序,但从理论上来说,这种变化可能会破坏某些程序。

请注意,新关键字通常不会导致语言重新定义,但我们必须小心确保在引入新关键字之前确实如此。例如,如果我们按照错误处理草案设计中的建议引入关键字 check,并且我们允许像check(f())这样的代码,如果check被定义为同一个包中的函数名,这可能看起来是一个语言重新定义。但是在引入check关键字之后,任何定义这样一个函数名的尝试都将失败。因此,使用check的代码无论在何种意义上都不可能同时使用1.N和1.M版本进行编译。新关键字可以作为移除语言特性(check作为非关键字使用时)或添加语言特性(check是关键字时)来处理。

为了让Go生态系统在向Go 2的过渡中幸存下来,我们必须尽量减少此类语言重新定义。如前所述,成功的语言通常基本上没有超出特定程度的重新定义。

当然,语言重新定义的复杂性在于我们不再能依赖编译器来检测问题。查看重新定义的语言结构时,编译器无法知道其含义。在存在重新定义的语言结构时,我们也无法确定最大语言版本。因为我们不知道该语言结构是打算用旧含义还是新含义进行编译。

唯一的可行性可能是让程序员设置软件包的语言版本。在这种情况下,它可能是最低或最高语言版本,视情况而定。它必须以不会被任何工具自动更新的方式设置。当然,设置这样的版本很容易出错。随着时间的推移,最大的语言版本会导致令人惊讶的结果,因为人们试图使用新的语言功能,但都失败了。

我认为唯一可行的安全方法是不允许重新定义语言。

我们被当前的语义所困扰。这并不意味着我们无法改进它们。例如,对于issue 20733,即range问题,我们可以更改range循环,以便禁止获取range参数的地址,或从函数字面值中引用它。这不是一个语言重新定义;这将是一个语言特性移除。这种方法可能会消除Bug,而不会意外破坏代码。

构建标签(Build tags)

构建标签是一种现有机制,程序可以使用它来根据要发布的程序版本选择要编译的文件。

构建标签可用于给出发布的程序的版本号,它们看起来就像Go语言的版本号,但是,学究式地说,它们是不同的。在上面的讨论中,我们讨论了使用Go版本 1.N的编译器来编译Go语言版本为1.N-1的代码。使用构建标签是不可能做到的。

构建标签可设置用于编译特定文件的最大版本号或最小版本号,或同时设置两者。这是一种方便的方式来利用只有在特定版本之后才可用的语言更改;也就是说,这可用于在编译文件时设置最低语言版本号。

如上所述,对于语言更改最有用的是可以根据这种更改设置最大语言版本。构建标签并没有以有用的方式提供这一点。如果使用构建标签将当前版本设置为最大版本,则你的包将不会再生成以后的版本。只有将最高语言版本号设置为当前版本之前的版本号时,才能设置最高语言版本,并且还需要一个用于发布后续版本的当前包的副本。也就是说,如果你使用1.N进行构建,那么使用!1.N+1构建标签是没有帮助的。你可以使用!1.M构建标签,其中M<N,并且在几乎所有情况下,你都还需要一个单独的文件,其构建标记为!1.M+1

构建标签可用于处理语言重新定义:如果语言版本1.N有语言重新定义,程序员可以使用!1.N构建标签,使用旧语义和使用一个1.N构建标签的不同文件。然而,这些重复实现需要大量的工作,一般来说很难知道何时需要,而且很容易出错。构建标签的可用性不足以克服先前关于不允许任何语言重新定义的讨论。

导入“go2”

可以为Go添加一种机制,类似于Python的from __future_import和Perl的use feature。例如,我们可以使用一个特殊的导入路径,import "go2/type-aliases"。这将把所需的语言功能放在使用它们的文件中,而不是隐藏在go.mod文件中。

这将提供一种方法来描述文件所需的语言特性添加集合。它更复杂,因为它不依赖于语言版本,而是将语言分解为单独的功能。没有明显的办法消除这些特殊import,因此它们会随着时间的推移而积累。Python和Perl通过故意进行向后不兼容的更改来避免累积的问题。在转到Python3或Perl6之后,可以丢弃累积的特性。由于Go试图避免向后不兼容,因此没有明确的方法可以消除这些导入。

此机制无法处理移除语言特性的情况。我们可以引入删除导入,例如import "go2/no-int-to-string",但不清楚为什么会有人使用它。实际上,根本没有办法删除语言特性,即使是那些容易混淆和出错的特性。

这种方法似乎不适合Go。

标准库改变

迁移到Go 2的好处之一是有机会发布一些兼容Go 1的标准库包。另一个好处是有机会将许多(也许是大部分)软件包移出六个月的发布周期。如果模块实验成功,甚至有可能尽早开始做这件事,使一些包的发布周期更快。

我建议继续保持六个月的发布周期,但将其视为编译器/运行时的发布周期。我们希望Go发行版开箱即用,因此发行版将继续包含与今天大致相同的软件包集合的当前版本。然而,其中许多包实际上有它们自己的发布周期。使用给定Go版本的人将能够明确选择使用标准库包的新版本。事实上,在某些情况下,他们可能会使用旧版本的标准库包。

不同的发布周期需要包维护者投入更多的资源。只有当我们有足够的人手来管理它,有足够的测试资源来测试它时,我们才能做到这一点。

我们还可以继续对所有内容使用六个月的发布周期,但将可分离的包单独提供兼容的、不同的版本。

核心标准库

尽管如此,标准库的某些部分仍必须被视为核心库。这些库与编译器和其他工具紧密相关,必须严格遵循发布周期。不得使用这些库的旧版本或新版本。

理想情况下,这些库将保留在当前版本1上。如果似乎有必要将其中任何一个更改为版本2,则必须根据具体情况进行讨论。目前我看不出有什么理由这样做。

核心库的暂定清单是:

  • os/signal
  • plugin
  • reflect
  • runtime
  • runtime/cgo
  • runtime/debug
  • runtime/msan
  • runtime/pprof
  • runtime/race
  • runtime/tsan
  • sync
  • sync/atomic
  • testing
  • time
  • unsafe

我可能乐观地从这个列表中省略了net、os和syscall包。我们将看看我们能管理什么。

伴生标准库

伴生标准库是那些包含在Go发行版中但独立维护的包。当前标准库中的大部分内容其实都是这种包。这些软件包将遵循与今天相同的规则,并可选择在适当的情况下迁移到v2。可以使用go get升级或降级这些标准库包。特别是,这些包可以独立于每六个月的核心库发布周期,修复Bug发布自己的次版本。

go工具必须能够区分核心库和伴生库。我不知道这将如何运作,但它似乎是可行的。

将标准库包移动到v2时,必须规划好同时使用包的v1和v2版本的程序。这些程序必须按预期运行,如果不可能,就必须干净利落地迅速运行失败。在某些情况下,这将涉及修改v1版本以使用也由v2版本使用的核心库。

标准库包必须能够使用Go语言的旧版本进行编译,至少是我们目前支持的前两个发布周期的Go语言版本。

从标准库中移除包

标准库包支持的go get能力将允许我们从发布中删除包。这些包将继续存在和维护,人们能够在需要时检索它们。但是,默认情况下它们不会随Go版本一起发布。

这将包括像以下这样的包:

  • index/suffixarray
  • log/syslog
  • net/http/cgi
  • net/http/fcgi

以及其他似乎没有被广泛使用的包。

我们应该在适当的时候为旧包制定弃用政策,将这些包设置为不再维护。弃用政策也适用于移至v2版本的v1版本的软件包。

或者这可能被证明是有问题的,我们不应该弃用任何现有的包,也不应该将它们从标准的Go发行版本中删除。

Go 2

如果上述过程按计划进行,那么在某种重要意义上永远不会有Go 2。或者,换句话说,我们将慢慢过渡到新的语言和库特性。我们可以在过渡期间的任何时候决定我们现在是Go 2,这可能是一个很好的营销方式。或者我们可以跳过它(从来没有C 2.0,为什么要有Go 2.0?)。

C、C++和Java等流行语言从来没有v2版本。实际上,它们始终处于版本1.N,尽管它们使用不同的名称来称呼该状态。我认为我们应该效仿它们。事实上,从不兼容的新版本语言或核心库的意义上说,完全意义上的Go 2对我们的用户来说不是一个很好的选择。不夸张地说,一个真正的Go 2版本可能是有害的。

开发和发布模块

本文翻译自《Developing and publishing modules》。

目录

开发和发布模块的工作流程

设计和开发

分布式发布

发现包

版本控制

你可以将相关的包收集到模块中,然后发布模块供其他开发人员使用。本主题概述开发和发布模块。

要支持开发、发布和使用模块,请使用:

  • 开发和发布模块,并随时间使用新版本对其进行修改的工作流。参见开发和发布模块的工作流
  • 帮助模块用户理解并以稳定的方式升级到新版本的设计实践。参见设计和开发
  • 用于发布模块和检索其代码的分布式去中心化系统。你可以让其他开发人员从自己的存储库中使用你的模块,并使用版本号发布。请参见分布式发布
  • 一个软件包搜索引擎和文档浏览器(pkg.go.dev),开发人员可以在其中找到你的模块。请参阅发现包
  • 模块版本编号规约,用于向使用你的模块的开发人员传达稳定性和向后兼容性的信息。请参见版本控制
  • 让其他开发人员更容易管理依赖关系的Go工具,包括获取模块的源代码、升级等。请参阅管理依赖项

另请参阅

开发和发布模块的工作流程

当你想为其他人发布你的模块时,你可以采用一些约定来让这些模块更容易使用。

模块发布和版本控制工作流程中更详细地描述了以下高级步骤。

1 设计并编写模块将包含的软件包。

2 根据规约将代码提交到你的存储库,以确保其他人可以通过Go工具使用该代码。

3 发布模块以使开发人员能够发现它。

4 随着时间的推移,使用版本编号规约来修订模块,可以从版本编号中看出每个版本的稳定性和向后兼容性。

设计和开发

如果你的模块中的函数和包形成一个连贯的整体,那么开发人员将更容易找到和使用你的模块。当你设计模块的公共API时,请尽量使其功能目标明确且各不相关(译者注:高内聚,低耦合)。

此外,在设计和开发模块时考虑向后兼容性,有助于用户升级,同时最大限度地减少对用户代码的干扰。你可以在代码中使用某些技术来避免发布破坏向后兼容性的版本。有关这些技术的更多信息,请参阅Go博客上的保持模块兼容

在发布模块之前,可以使用replace指令在本地文件系统上引用它。这使得在模块仍在开发中时,编写调用其内函数的客户端代码更加容易。有关详细信息,请参阅模块发布和版本控制工作流程中的“针对未发布的模块进行编程”。

分布式发布

在Go中,你通过在存储库中为模块的代码打标签来发布模块,以便其他开发人员使用。你不需要将模块推送到一个集中式服务器,因为Go工具可以直接从存储库(使用模块路径,该路径是省略了网络协议的URL)或代理服务器下载模块。

在代码中导入包后,开发人员使用Go工具(包括go get命令)下载模块的源代码进行编译。为了支持此模式,你应该遵循惯例和最佳实践,使Go工具(代表另一个开发人员)可以从存储库中检索模块的源代码。例如,Go工具使用你模块的模块路径,以及用于标记模块以供发布的模块版本号,为其用户定位和下载你的模块。

有关源代码和发布约定以及最佳实践的更多信息,请参阅管理模块的源代码

有关发布模块的分步说明,请参阅发布一个模块

发现包

在你发布了模块并有人使用Go工具获取了它之后,它将在pkg.go.dev这个Go包发现站点上可见。在那里,开发人员可以搜索该站点找到它并阅读它的文档。

为了开始使用模块,开发人员从模块中导入包,然后运行go get命令下载其源代码进行编译。

有关开发人员如何查找和使用模块的更多信息,请参阅管理依赖项

版本控制

随着时间的推移,当你修改和改进模块时,你会分配版本号(基于语义版本控制模型),以表示每个版本的稳定性和向后兼容性。这有助于使用你的模块的开发人员确定模块何时稳定,以及升级模块是否在代码行为上有重大变化。你可以通过在存储库中打标签模块的源代码,来指派模块的版本号。

有关开发模块主版本更新的详细信息,请参阅开发一个主版本更新

有关如何为Go模块使用语义版本号模型的更多信息,请参阅模块版本编号

go.mod文件简明参考手册

本文翻译自《go.mod file reference》。

目录

例子

模块

语法

例子

笔记

go

语法

例子

笔记

需求(require)

语法

例子

笔记

替换(replace)

语法

例子

笔记

排除(exclude)

语法

例子

笔记

回收(retract)

语法

例子

笔记

每个Go模块都由一个go.mod文件定义,该文件描述模块的属性,包括它对其他模块和Go版本的依赖关系。

这些属性包括:

  • 当前模块的模块路径。这应该是Go工具可以从中下载模块代码的位置,例如模块代码的存储库的位置。当与模块的版本号结合使用时,它用作唯一标识符。它也是模块中所有包的包路径的前缀。有关Go如何定位模块的更多信息,请参阅Go模块参考手册
  • 当前模块所需的最低Go版本
  • 当前模块所需的其他模块的最低版本的一个列表。
  • 可选地,用另一个模块版本或本地目录替换所需模块,或排除所需模块的特定版本。

当你运行go mod init命令时,Go会生成一个go.mod文件。以下示例创建一个go.mod文件,将模块的模块路径设置为example/mymodule

$ go mod init example/mymodule

使用go命令来管理依赖项。这些命令确保go.mod文件中描述的需求保持一致,并且go.mod文件的内容有效。这些命令包括go getgo mod tidy以及go mod edit命令。

有关go命令的参考,请参阅Command go。你可以通过键入go help command-name从命令行获得帮助,就像go help mod tidy一样。

另请参阅

  • 当你使用Go工具来管理依赖项时,Go工具会对你的go.mod文件进行更改。有关更多信息,请参阅管理依赖项
  • 有关go.mod文件的更多详细信息和约束,请参阅Go模块参考手册

例子

go.mod文件包含指令,如下所示。这些将在本主题的其他地方进行描述。

module example.com/mymodule

go 1.14

require (
    example.com/othermodule v1.2.3
    example.com/thismodule v1.2.3
    example.com/thatmodule v1.2.3
)

replace example.com/thatmodule => ../thatmodule
exclude example.com/thismodule v1.3.0

模块

声明模块的模块路径,这是模块的唯一标识符(与模块版本号组合使用时)。模块路径成为模块包含的所有包的导入前缀。

有关更多信息,请参阅Go模块参考手册中的module指令

语法

module module-path

其中module-path代表模块的模块路径,通常是Go工具可以从中下载模块代码的存储库的位置。对于模块v2及更高版本,模块路径必须以主版本号结尾,例如/v2

例子

以下示例中的example.com代表可以从中下载模块代码的存储库的域名。

v0或v1模块的模块声明:

module example.com/mymodule

v2模块的模块路径:

module example.com/mymodule/v2

笔记

模块路径必须能唯一标识你的模块。对于大多数模块,其路径是一个URL,go命令可以在其中找到模块的代码(或重定向到模块的代码)。对于永远不会直接被下载的模块,模块路径可以是由你决定的某个名称,确保唯一性即可。前缀example/保留用于示例。

有关详细信息,请参阅管理依赖项

实际上,模块路径通常是模块源代码的存储库的域名和模块代码在存储库中的路径。go命令在下载模块版本或解决依赖关系时,就依赖于此形式。

即使你一开始并不打算让你的模块可供其他人使用,按照此形式确定其存储库路径也是一种最佳实践,这将帮助你在以后发布模块时避免重命名该模块。

如果一开始你不知道模块的最终存储库位置,请暂时使用安全的替代品,例如你拥有的域名或你能控制的名称(例如你的公司名称),然后紧跟着模块的名称或源代码的目录名。有关更多信息,请参阅管理依赖项

例如,如果你在stringtools目录中进行开发,你的临时模块路径可以是<company-name>/stringtools,如下例所示,其中company-name是你公司的名称:

go mod init <company-name>/stringtools

go

表示模块是在go指令指定的Go版本下编写的。

有关更多信息,请参阅Go模块参考手册中的go指令

语法

go minimum-go-version

其中minimum-go-version代表编译此模块中的包所需的最低的Go版本。

例子

模块必须在Go 1.14或更高版本上运行:

go 1.14

笔记

go指令最初旨在支持对 Go 语言的向后不兼容更改(请参阅Go 2转换)。自从引入模块以来没有不兼容的语言更改,但go指令仍然影响新语言功能的使用:

  • 对于模块中的包,编译器拒绝使用在go指令指定的版本之后引入的语言功能。例如,如果一个模块有指令go 1.12,它的包可能不会使用像1_000_000这样的数值字面量,因为这是在Go 1.13中引入的语言新特性。
  • 如果用较旧的Go版本构建模块的其中一个包,并遇到编译错误,则该错误会指出该模块是为较新的Go版本编写的。例如,假设一个模块有go 1.13指令,并且一个包使用了数值字面量1_000_000。如果该包使用Go 1.12来构建,编译器会注意到代码是为Go 1.13编写的。

此外,go命令根据go指令指定的版本号更改其行为。这具有以下效果:

  • go 1.14或更高版本中,可以启用自动vendor(译者注:vendor模式的模块管理属于Go语言在1.11版本之前的其中一种模块管理方式,Go 1.11引入的基于go.mod文件的模块管理方式才是最先进的模块管理方式)。如果文件vendor/modules.txt存在并且与go.mod一致,则无需显式使用-mod=vendor标志。
  • go 1.16或更高版本中,all包模式仅匹配主模块(main模块)中的包和测试导入的包。这是自引入模块以来由go mod vendor保留的同一组包。在较低Go版本中,all还包括主模块中导入的包及其测试文件等等。
  • go 1.17或更高版本中:
    • go.mod文件为每个模块包含一个明确的require指令,该指令提供由主模块中的包或测试文件导入的任何包。(在go 1.16及更低版本中,仅当最小版本选择会选择不同版本时才会包含间接依赖。)此额外信息支持模块图化简模块懒加载
    • 因为可能比以前的go版本有更多// indirect,间接依赖被记录在go.mod文件中的一个单独的块中。
    • go mod vendor会忽略vendor目录中依赖项的go.mod和go.sum文件。(这允许在vendor的子目录中调用go命令以识别正确的主模块。)
    • go mod vendor将每个依赖项的go.mod文件中的Go版本记录在vendor/modules.txt文件中。

go.mod文件最多可以包含一个go指令。大多数命令都会在当前Go版本中添加一个go指令(如果还不存在的话)。

需求(require)

将模块声明为当前模块的依赖项,指定所需模块的最低版本。

有关更多信息,请参阅Go模块参考手册中的require指令

语法

require module-path module-version

module-path代表模块的模块路径,通常是模块源代码的存储库的域名再加上模块名称。对于模块版本v2及更高版本,此值必须以主版本号结尾,例如/v2

module-version代表模块的版本号。这可以是发行版的版本号,例如v1.23,也可以是Go生成的伪版本号,例如v0.0.0-20200921210052-fa0125251cc4。

例子

需求已发布的版本v1.2.3:

require example.com/othermodule v1.2.3

需求在其存储库中尚未加标签的版本,使用Go工具生成伪版本号:

require example.com/othermodule v0.0.0-20200921210052-fa0125251cc4

笔记

当你运行go命令(例如go get)时,go命令会插入包含导入包的每个模块的指令。当模块在其存储库中尚未加标签时,Go会分配一个伪版本号,它会在你运行命令时自动生成。

通过使用replace指令,你可以让Go从存储库以外的位置需求模块。

有关版本号的更多信息,请参阅模块版本编号

有关管理依赖关系的详细信息,请参阅以下内容:

添加一个依赖项

获取特定依赖项的某个版本

发现可用更新

升级或降级一个依赖项

同步你的代码的依赖项

替换(replace)

用另一个模块版本或本地目录替换特定模块版本(或所有版本)的内容。Go工具将在解析依赖关系时使用替换路径。

有关更多信息,请参阅Go模块参考手册中的replace指令

语法

replace module-path [module-version] => replacement-path [replacement-version]

module-path代表要替换的模块路径。

module-version,可选,代表要替换的特定版本号。如果省略此版本号,则模块的所有版本都将被替换为箭头右侧的内容。

replacement-path代表Go应查找的所需模块的路径。这可以是模块路径,也可以是本地文件系统上的模块源代码所在目录的路径。如果这是模块路径,则必须指定replacement-version。如果这是本地路径,则不能使用replacement-version

replacement-version,可选,代表用来替换的模块的版本号。只有当replacement-path 是模块路径(而不是本地目录路径)时,才能指定replacement-version

例子

  • 被替换为模块存储库的分叉(fork)版本

在以下示例中,example.com/othermodule的任何版本都将被替换为其分支版本。

require example.com/othermodule v1.2.3
replace example.com/othermodule => example.com/myfork/othermodule v1.2.3-fixed

用另一个模块路径替换一个模块时,不要更改被替换模块里的包的导入语句。

有关使用模块代码的分叉版本的更多信息,请参阅从自己的存储库分叉版本请求外部模块代码

  • 被替换为模块的其他版本

以下示例指定使用v1.2.3版本,而不是该模块的任何其他版本。

require example.com/othermodule v1.2.2
replace example.com/othermodule => example.com/othermodule v1.2.3

以下示例将模块v1.2.5版本替换为同一模块的v1.2.3版本。

replace example.com/othermodule v1.2.5 => example.com/othermodule v1.2.3
  • 被替换为本地代码

以下示例指定使用本地目录里的代码替换模块的所有版本。

require example.com/othermodule v1.2.3
replace example.com/othermodule => ../othermodule

以下示例指定本地目录里的代码仅替换v1.2.5版本。

require example.com/othermodule v1.2.5
replace example.com/othermodule v1.2.5 => ../othermodule

有关使用模块代码的本地副本的更多信息,请参阅需求本地目录中的模块代码

笔记

如果要使用其他路径来查找模块的源,请使用replace指令将模块路径值临时替换为其他值。这具有将Go对模块的搜索重定向到替换的位置的效果。不需要更改包导入路径即可使用替换路径。

可以使用excludereplace指令来控制构建时依赖关系的解析。这些指令在依赖当前模块的模块中会被忽略。

replace指令在以下情况下很有用:

  • 你正在开发一个新模块,其代码尚未在存储库中。你希望使用客户端对本地版本进行测试。
  • 你发现了某个依赖项的一个问题,你克隆该依赖项的存储库到本地,修复这个问题,并使用该本地存储库测试修复是否成功。

请注意,单独的replace指令不会将模块添加到模块图中。require指令还需要引用在主(main)模块的go.mod文件或依赖项的go.mod文件中被替换的模块版本。如果没有要替换的特定版本,可以使用假版本,如下例所示。请注意,这将破坏依赖于该模块的模块,因为replace指令仅应用于主模块。

require example.com/mod v0.0.0-replace
replace example.com/mod v0.0.0-replace => ./mod

有关替换所需的模块(包括使用Go工具进行更改)的更多信息,请参阅:

有关版本号的更多信息,请参阅模块版本编号

排除(exclude)

指定要从当前模块的依赖关系图中排除的模块或模块版本。

有关详细信息,请参阅Go模块参考手册中的exclude指令

语法

exclude module-path module-version

module-path代表要排除的模块的模块路径。

module-version代表特定的版本号。

例子

排除example.com/their模块版本v1.3.0:

exclude example.com/theirmodule v1.3.0

笔记

使用exclude指令排除间接需求但由于某种原因无法加载的模块的特定版本。例如,你可以使用它排除校验和(checksum)无效的模块版本。

在构建当前模块(主模块)时,使用excludereplace指令来控制对构建时依赖关系的解析。这些指令在依赖于当前模块的模块中被忽略。

你可以使用go mod edit命令来排除模块,如下例所示。

go mod edit -exclude=example.com/[email protected]

有关版本号的更多信息,请参阅模块版本编号

撤回(retract)

指示由go.mod定义的模块的版本或某个范围的版本不应该被依赖。当版本未成熟就发布或发布版本后发现严重问题时,retract指令非常有用。

有关更多信息,请参阅Go模块参考手册中的retract指令

retract version // rationale
retract [version-low,version-high] // rationale

version代表要撤回的单个版本。

version-low代表要撤回的版本范围的下限。

version-high代表要撤回的版本范围的上限。version-lowversion-high都包含在范围内。

rationale是解释撤回原因的可选注释。可能显示在发给用户的消息中。

例子

撤回单个版本:

retract v1.1.0 // 意外发布。

撤回一个范围的版本:

retract [v1.0.0,v1.0.5] // 在某些平台会破坏构建。

笔记

使用retract指令指示某个模块的早期版本不应被使用。用户无法使用go getgo mod tidy或其他命令自动升级到被撤回的版本。用户使用go list -m -u命令将在可更新到的版本列表中看不到被撤回的版本。

撤回的版本应该保持可用,以便已经依赖它们的用户能够构建他们的包。即使从源存储库中删除了撤回的版本,它也可能在proxy.golang.org等镜像上仍然可用。依赖撤回的版本的用户在相关模块上运行go getgo list -m -u时可能会收到通知。

go命令通过读取模块最新版本的go.mod文件中的retract指令来发现被撤回的版本。最新版本按优先顺序为:

1 其最高版本号(如果有的话)

2 其最高预发布版本号(如果有的话)

3 存储库默认分支顶端的伪版本号。

新增一个撤回版本时,几乎总是需要加标签一个新的更高的版本,以便go命令在模块的最新版本中看到retract指令。

你可以发布一个唯一目的是发出撤回信息的新版本。在这种情况下,该新版本也可以自行撤回。

例如,如果意外加标签v1.0.0,可以使用以下指令加标签v1.0.1:

retract v1.0.0 // 意外发布。
retract v1.0.1 // 只包含撤回指令。

不幸的是,一旦发布了版本,就无法更改。如果稍后为其他commit加标签v1.0.0,go命令可能会在go.sum或校验和数据库中检测到不匹配的值。

模块的撤回版本通常不会出现在go list -m -versions的输出中,但你可以使用-reacted选项来显示它们。有关更多信息,请参阅Go模块参考手册中的go list -m