谈谈go.sum

众所周知,Go 在做依赖管理时会创建两个文件,go.mod 和 go.sum。相比于 go.mod,关于 go.sum 的资料明显少得多。自然,go.mod 的重要性不言而喻,这个文件几乎提供了依赖版本的全部信息。而 go.sum 看上去就是 go module 构建出来的天书,而不是什么人类可读的数据。但实际上,日常开发中我们仍然不得不跟 go.sum 打交道(通常是解决这个文件带来的合并冲突,抑或试图手工调整里面的内容)。如果不了解 go.sum,只凭经验随便涂改,不一定能够改对。因此,为了更好地掌握 Go 的依赖管理,完全有必要了解 go.sum 的来龙去脉。鉴于涉及 go.sum 的资料是如此地稀少(即使 Go 官方文档中,对于 go.sum 的描述也是支离破碎的),我花了些时间整理了相关的资料,希望读者可以从中受益。go.sum 的每一行都是一个条目,大致是这样的格式:1<module> <version>/go.mod <hash>或者12<module> <version> <hash><module> <version>/go.mod <hash>其中module是依赖的路径,version是依赖的版本号。hash是以h1:开头的字符串,表示生成checksum的算法是第一版的hash算法(sha256)。有些项目实际上并没有 go.mod 这个文件,所以 Go 文档里提到这个 /go.mod 的 checksum,用了 "possibly synthesized" (也许是合成的)的说法。估计对于没有 go.mod 的项目,Go 会尝试生成一个可能的 go.mod,并取它的 checksum。如果只有对于 go.mod 的 checksum,那么可能是因为对应的依赖没有单独下载。比如用 vendor 管理起来的依赖,便只有 go.mod 的 checksum。由于 go 的依赖管理背负着沉重的历史包袱,确定 version 的规则较为复杂。整个过程就像一个调查问卷,需要回答一个接一个的问题:一、项目是否打tag?如果项目没有打 tag,会生成一个版本号,格式如下:v0.0.0-commit日期-commitID比如 github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=。引用一个项目的特定分支,比如 develop branch,也会生成类似的版本号:v当前版本+1-commit日期-commitID比如 github.com/DATA-DOG/go-sqlmock v1.3.4-0.20191205000432-012d92843b00 h1:Cnt/xQ9MO4BiAjZrVpl0BiqqtTJjXUkWhIqwuOCVtWo=。二、项目有没有用 go module?如果项目有用到 go module,那么就是正常地用 tag 来作为版本号。比如 github.com/DATA-DOG/go-sqlmock v1.3.3 h1:CWUqKXe0s8A2z6qCgkP4Kru7wC11YoAnoupUKFDnH08=。如果项目打了 tag,但是没有用到 go module,为了跟用了 go module 的项目相区别,需要加个 +incompatible 的标志。比如 github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=三、项目用的 go module 版本是不是 v2+?关于 go module v2+ 的特性,可以参考 Go 的官方文档:https://blog.golang.org/v2-go...。简单而言,就是通过让依赖路径带版本号后缀来区分同一个项目里不同版本的依赖,类似于 gopkg.in/xxx.v2 的效果。对于使用了 v2+ go module 的项目,项目路径会有个版本号的后缀。比如 github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM=之所以 Go 会在依赖管理时引入 go.sum 这样的角色,是为了实现下面的目标:(1)提供分布式环境下的包管理依赖内容校验不像其他包管理机制,Go 采用分布式的方式来管理包。这意味着缺乏一个可供信赖的中心来校验每个包的一致性。在主流的包管理机制中,通常存在一个中央仓库来保证每个发布的版本的内容不会被篡改。比如在 pypi 里面,即使发布过的版本存在严重的bug,发布者也不能重新发布一个同样版本,只能发布一个新版本。(但是却可以删掉已发布的版本抑或删掉整个项目,参考当年 npm 的 leftpad 事件,所以主流的包管理机制并非严格意义上的 Append Only。不过这并不影响我的论证)而 Go 并没有一个中央仓库。发布者在 GitHub 上给自己的项目打上 0.1 的 tag 之后,依旧可以删掉这个 tag ,提交不同的内容后再重新打个 0.1 的 tag。哪怕发布者都是老实人,发布平台也可能作恶。所以只能在每个项目里存储自己依赖到的所有组件的 checksum,才能保证每个依赖不会被篡改。(2)作为 transparent log 来加强安全性go.sum 还有一个很特别的地方,就是它不仅仅记录了当前依赖的checksum,还保留了历史上每次依赖的 checksum。这种做法效法了 transparent log 的概念。transparent log 旨在维护一个 Append Only 的日志记录,提高篡改者的作案成本,同时方便审查哪些记录是篡改进来的。根据 Proposal: Secure the Public Go Module Ecosystem 的说法,go.sum 之所以要用 transparent log 的形式记录历史上的每个checksum,是为了便于 sum db 的工作。不得不说的是,go.sum 也带来一些麻烦:(1)容易产生合并冲突这恐怕是 go.sum 最为人诟病的地方了。由于许多项目都没有通过打tag的方式来管理发布,每个commit都相当于新发布一个版本,这导致拉取它们的代码时会偶尔往 go.sum 文件里插入一条新记录。go.sum会记录间接依赖的特性,更是让这种情况雪上加霜。这一类的项目带来的影响可不小 —— 我粗略地统计下 go.sum 里这类记录的行数,大概占了总数的 40%。比如 golang.org/x/sys 在某个项目的 go.sum 里就有多达 37 个不同的版本。如果只是莫名其妙的行数多,那最多不过是让人皱皱眉。在多人协作且用到几个经常升版本号的内部公共库的场景下,go.sum 会让人头疼。想象这种情况:公共库原来有版本甲。开发者A的分支a依赖了公共库版本乙,开发者B的分支b依赖了公共库版本丙。他们分别给 go.sum 添加记录如下:12common/lib 甲 h1:xxxcommon/lib 乙 h1:yyy12common/lib 甲 h1:xxxcommon/lib 丙 h1:zzz之后公共库发布了版本丁,包含了版本乙和版本丙的功能。然后合并分支a和分支b到主干,这时候就会有合并冲突。现在有两个选择:把两个中间版本都纳入到 go.sum 进来既不选乙,也不选丙,直接采用版本丁无论采用哪种方法,都需要手动介入。这无疑带来了不必要的工作量。(2) 对于胡乱操作的第三方库,缺乏约束能力go.sum 的本意在于提供防篡改的保障,如果拉第三方库的时候发现其实际内容和记录的校验值不同,就让构建过程报错退出。然而它能做的也就只限于此。go.sum 的检测功能,给库的使用者带来的负担更甚于库的开发者。在有中央仓库保障的其他包管理器里,人们可以在源头上限制那些捣蛋鬼,不让他们随意变更已经发布出去的版本。但是 go.sum 带来的约束纯粹是道德上的。如果一个库乱改已经发布的版本,会让依赖这个库的项目构建失败。对此库的使用者除了咒骂几句,在 issue 或别的地方痛斥作者,然后更新go.sum文件,似乎也没别的解决办法。犯错的本来是库的作者,麻烦的却是库的用户。这种设计可算不上高明。一个可能的解决办法是由官方把知名的库的各个版本镜像起来。虽然知名的库通常不会犯乱改已发布版本的错误,但是如果发生了(或者出于某种不可抗力发生了),至少有个镜像可用。然而这又回到单一中央仓库的路子上去。(3) 实际情况下,手动编辑go.sum不可避免。比如前面举的,编辑go.sum文件解决合并冲突的情况。我也见过有些项目只在go.sum里保留依赖的最新版本的checksum。如果 go.sum 不是完全由工具管理的,又怎么能保证它一定是 Append Only 呢?如果 go.sum 不是 Append Only 的,又怎么能把它当作 transparent log 使用呢?

(0)

相关推荐

  • 第一个Go语言类库:启用、创建并发布第一个模块

    这是<Go语言简易入门>系列内容第6篇,所有内容列表见:https://yishulun.com/books/go-easy/目录.html.所有源码及资料在"程序员LIYI&q ...

  • 学到了:goroutine 可能使程序变慢

    如何使用 goroutine 才能使你的 CPU 满负载运行呢 下面,我们将会展示一个关于 for 循环的代码,将输入分成几个序列添加到 Goroutines 里面!我敢打赌你之前可能有过几次这种情况 ...

  • 发现 go version 的一个另类用法:你肯定想不到

    大家好,我是站长 polarisxu. 对于 go version,大家应该不陌生.在很多入门教程,安装 Go 后,一般会建议执行 go version 看看是否安装成功:亦或遇到问题,别人会问你 G ...

  • 石桥码农:如何在本地基于 nideshop 架设一个微信小程序商城?

    目录 一.首先架构服务器端 二.创建数据库 三.尝试运行后端代码 四.配置数据库连接,与初始化数据 五.小程序这一端,下载.导入.运行 六.问题 架设完成后,产品相貌大概长这个样子: 是仿网易严选的小 ...

  • go modules:使用 mod 管理项目依赖包,通过vendor实现一键分发编译包

    在go语言1.11版本之前,没有modules机制,所有软件包都在安装在$GOPATH/src目录下.不同项目如果引用了同一个软件包的不同版本,就会造成编译麻烦.修改$GOPATH变量是当时一种比较简 ...

  • [Go]Golang 1.16 中 Modules的主要变化更新

    本文转载,目的方便查阅 01介绍 Golang 1.16 已经正式发布了,其中 Modules 有一些变化: 默认开启 Modules. 不自动修改 go.mod 和 go.sum. 通过指定 @ve ...

  • go mod 错误“malformed module path”新发现

    当项目出现"malformed module path ......: missing dot in first path element"错误时,网上说的解决办法有两种: 修改g ...

  • 周末复盘:谈谈牛股的启动时机

    周四晚上直播时第一次吹票,观点鲜明的阐述了华自科技必涨的原因,也确实有几个朋友隔夜挂单买入成交,而我自己周五9:15:24的委托单直到收盘前面还有5000多手未成交.有需要的朋友可以去微博看一下当天的 ...

  • 谈谈最近对做项目的思考,扶摇生财思维

    一 做项目在前期,一定要按照别人探好的路规规正正的走. 多做多问,任何细节都要得到答案,直到打通流程,获取收益回馈. 收益稳步上升,然后再进行放大操作. 放大无非就是两点,批量操作和培训抽成. 前段时 ...

  • 谈谈一般人想不到的口腔学冷门小知识

    ​​第一恒磨牙很重要,而且是非口腔医学专业人士想象不到的重要,没错是想象不到的. 先说第一恒磨牙是什么? 第一恒磨牙,俗称六龄齿,也就是六岁左右所萌出的牙齿,是口腔内萌出的第一颗恒牙. 在这里需说明一 ...

  • 谈谈腰椎射频治疗腰椎间盘突出

    中国疼痛找桑迪 文章仅供参考,不作为临床依据,如有不当之处,请联系我们删除!

  • 谈谈在耶路撒冷发现的灯

    灯是古代人民日常生活中不可缺少的一种物品,一般被称作为"油灯".在古代地中海时期,油灯可以分为七个类别,其中一个是可以转动的灯,特点就是简单,没有任何装饰,没有手柄,这种造型早在3 ...

  • 谈谈应用文的结构项问题

    谈谈应用文的结构项问题 邓旺林 任何一种应用文体都有其一定的结构项.应用文的结构项,指的是构成一种应用文体的各个部分和项目,是一种应用文体形式方面的基本构件.例如现代普通书信一般由称呼.正文.祝颂致敬 ...

  • 后江改深田、金鸡亭保送一中、梧桐多校划片,这三件事一起谈谈

    2021年5月9日 星期日 周末好,昨天收到提问最多的三个问题,惠民大叔把各个观点分享一下给你. 01 后江小学改为深田小学 昨天大家都看到了教育局官方发布的关于深田小学的批复文件了,批复说:经研究, ...

  • 谈谈课堂纪律

    信息技术是孩子们比较喜爱的学科.每周一节课的课时量更显得它的弥足珍贵.因此每次来孩子们来上课,微机室的地板都像轻微地震了一样.随后便是像花果山群猴初入水帘洞时抢盆儿抢碗般地一阵骚动,每次都需好好整顿一 ...

  • 治水需行气,气行水不聚——谈谈木香流气饮治疗水肿的体会

    "治水需行气,气行水不聚"是赵进喜教授治水五法之一,我学习之后,应用于临床,提高了临床疗效.最近用木香流气饮治疗一位水肿患者,效果尚可,体会到了行气.调气之法治疗水肿的妙处. 患者 ...