Modules简介

2019 年 11 月 14 日

Modules简介

最近项目需要开始正式的接触 golang 的项目,开源项目中使用了最新的以来管理 go mod,就跟进了解了相关的一些内容,经过几年的尝试和改进, 现在的版本也是作者希望能够持续十年的设计,也期望能够学习到作者们的思想,提升自己对版本依赖的认识。


1 背景介绍


历史介绍


1.Makefile, goinstall 和 go get


最早是通过 Makefile 的形式进行编译,连接程序。后来的 goinstall 能够在无额外配置的情况拉取到相应的代码,此时只能使用在标准库中。go get 解决了开发者之间的代码共享问题,能够通过 git 等进行代码共享。


2.Versioning 和 API Stability


通过 import 的路经中添加相应的版本信息达到了引用不同版的效果。


3.Vendoring 和 Reproducible Builds


由于 go get 没有版本相关的信息,所以不能提供一个可以可稳定重复的编译过程。vendor 通过拷贝相应的代码到 vendor 目录下,依赖项不会变更除非手动修改其中的依赖项。


4.An Official Package Management Experiment


dep 是官方的依赖管理实验项目,主要是用来探索最佳的实践,是找到最终的解决的重要的一步。


解决的问题


官方的定义


moduleis a collection of related Go packages that are versioned together as a single unit.Modules record precise dependency requirements and create reproducible builds.Most often, a version control repository contains exactly one moduledefinedin the repository root.(Multiple modules are supported in a single repository, but typically that would result in more workon an on-going basis than a single module per repository).Summarizing the relationship between repositories, modules, and packages:A repository contains one or more Go modules.Eachmodule contains one or more Go packages.Eachpackage consists of one or more Go source files in a single directory.Modules must be semantically versioned according to semver, usually in theform v(major).(minor).(patch), such as v0.1.0, v1.2.3, or v1.5.0-rc.1.The leading v is required. IfusingGit, tag released commits with their versions.Publicandprivatemodule repositories and proxies are becoming available (see FAQ below).
复制代码


module 是一组相关的 package 的集合,作为一个整体进行版本控制。module 记录精确的依赖需求并创建可重复的构建。


一个 repo 可以包含一个或者多个 module,一个 module 可以包含一个或者多个 package,一个 package 是一个路经下包含一个或者多个文件。


module 必须是经过语义版本 semver 的。


版本控制


如何引入一个向下不兼容的版本?


import 的兼容规则如下


If an old package and a new package have the same import path,

the new package must be backwards compatible with the old package.


如果使用相同的 import 路经,则新版本必须是兼容就版本的。go 是通过不兼容的版本使用不同的 import 路经来区分的,


例如 import path 和 import path/v2 的方式


2.同一个主版本采用最低版本选择策略进行版本选择?


最小版本选择,即为选择所有的版本中(不同的主版本是不同的 package)选择版本最大的一个版本,也是可用的最小版本。


2 设计目标


鼓励用户打标签,使得更加的可读,并且能够帮助使用者明确哪些是已经发布的,哪些是正在开发中的不依赖具体的版本控制既可以在一个 repo 中使用一个 module,也可以一个 repo 下使用多个 module,并且各自进行版本控制


能够添加自己的代理


删除之前的 vendor 目录


3 基本操作


创建 Module


1.创建⼀个⽬录,新建 hello.go⽂档


package hellofunc Hello() string{return"Hello, world."}
复制代码


1.添加相应的测试⽂档


package helloimport"testing"func TestHello(t *testing.T) {    want := "Hello, world."if got := Hello(); got != want {        t.Errorf("Hello() = %q, want %q", got, want)}}
复制代码


执行测试,确认程序正常运行


注意需要关闭掉相应的 module 强制设置,确保 GO111MODULE 不为 on,否则会得到


go: cannot find main module; see ‘go help modules’


➜  hello go testgo testPASSok      _/home/zhangchao11/work/golang/hello    0.003s➜  hello
复制代码


初始化为 module


go mod init example.com/hello
复制代码


查看相应的 go.mod


module example.com/hello
go 1.13
复制代码


执行测试


$ go testgo: finding rsc.io/quote v1.5.2go: downloading rsc.io/quote v1.5.2go: extracting rsc.io/quote v1.5.2go: finding rsc.io/sampler v1.3.0go: finding golang.org/x/text v0.0.0-20170915032832-14c0d48ead0cgo: downloading rsc.io/sampler v1.3.0go: extracting rsc.io/sampler v1.3.0go: downloading golang.org/x/text v0.0.0-20170915032832-14c0d48ead0cgo: extracting golang.org/x/text v0.0.0-20170915032832-14c0d48ead0cPASSok      example.com/hello    0.023s$
复制代码


说明: go 命令能够自动从 go.mod 中导入指定的依赖项。如果在 go.mod 中没有找到特定的依赖项的情况


则会从对应的 package 中自动的查找相应的 module,并且把结果写入到 go.mod 中。导入的 module 的版本默认选择最新的版本(latest),go.mod 中默认自动添加的只有直接依赖的 module,并不包含间接依赖的 module。


查看新的 go.mod


module example.com/hello
go 1.13
require rsc.io/quote v1.5.2
复制代码


执行 test


➜  hello go testgo testPASSok      example.com/hello   0.006s➜  hello
复制代码


通过上述结果可以看出,第二次执行 go test 的时候,由于 go.mod 已经是最新状态,并且需要的 module 已经缓存在本地了,就不再执行上述的获取,展开的过程了。


查看 module 的所有依赖


➜  hello go list -m allgo list -m allexample.com/hellogolang.org/x/text v0.0.0-20170915032832-14c0d48ead0crsc.io/quote v1.5.2rsc.io/sampler v1.3.0➜  hello
复制代码


列出了 module 的所以依赖项,第一行列出的是当前 module(也称为主 module),依赖的 module 则按照 module 的路径进行排序列在下面的内容.


查看生成的 go.sum


➜ hello cat go.sumcat go.sumgolang.org/x/text v0.0.0-20170915032832-14c0d48ead0c h1:qgOY6WgZOaTkIIMiVjBQcw93ERBE4m30iBm00nkL0i8=golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=rsc.io/quote v1.5.2 h1:w5fcysjrx7yqtD/aO+QwRjYZOKnaM9Uh2b40tElTs3Y=rsc.io/quote v1.5.2/go.mod h1:LzX7hefJvL54yjefDEDHNONDjII0t9xZLPXsUe+TKr0=rsc.io/sampler v1.3.0 h1:7uVkIFmeBqHfdjD+gZwtXXI+RODJ2Wc4O7MPEh/QiW4=rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=➜ hello
复制代码


go.sum 中保存的是模块对应的哈希值,该值可以保证后续下载的版本和第一次下载的版本没有被非预期的修改。


升级依赖


升级 golang.org/x/text


➜  hello go get golang.org/x/textgo get golang.org/x/text
复制代码


重新检查依赖列表


➜ hello go list -m allgo list -m allexample.com/hellogolang.org/x/text v0.3.2golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6ersc.io/quote v1.5.2rsc.io/sampler v1.3.0➜ hello cat go.modcat go.modmodule example.com/hellogo 1.13require (golang.org/x/text v0.3.2// indirectrsc.io/quote v1.5.2)➜ hello
复制代码


从上面的结果中可以看出,golang.org/x/text 从之前的版本已经升级到了 v0.3.2 的版本,更新之后的版本信息已经添加到了 go.mod,后面添加了 indirect 表明该依赖项不是主 module 直接依赖的。


查询可用的所有依赖


➜  hello go list -m -versions rsc.io/samplergo list -m -versions rsc.io/samplerrsc.io/sampler v1.0.0 v1.2.0 v1.2.1 v1.3.0 v1.3.1 v1.99.99➜  hello
复制代码


更新到指定版本


➜  hello go get rsc.io/sampler@v1.3.1go get rsc.io/sampler@v1.3.1go: finding rsc.io/sampler v1.3.1go: downloading rsc.io/sampler v1.3.1go: extracting rsc.io/sampler v1.3.1➜  hello
复制代码


添加一个新的主版本


1.添加 quoteV3 版本


package helloimport("rsc.io/quote"quoteV3 "rsc.io/quote/v3")func Hello() string{return quote.Hello()}func Proverb() string{return quoteV3.Concurrency()}1. 添加对应的测试代码func TestProverb(t *testing.T) {want := "Concurrency is not parallelism."if got := Proverb(); got != want {t.Errorf("Proverb() = %q, want %q", got, want)}}
复制代码


1.执⾏测试


➜ hello go testgo testgo: finding rsc.io/quote/v3 v3.1.0go: downloading rsc.io/quote/v3 v3.1.0go: extracting rsc.io/quote/v3 v3.1.0PASSok example.com/hello 0.006s➜ hello
复制代码


1.获取最新的依赖关系


➜ hello go list -m allgo list -m allexample.com/hellogolang.org/x/text v0.3.2golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6ersc.io/quote v1.5.2rsc.io/quote/v3 v3.1.0rsc.io/sampler v1.3.1➜ hello
复制代码


不同的主版本采用不同的导入路径,通过这种方式来提供不兼容的版本升级。v0,v1 是直接使用 module 的路径就可以了,v2 及以上的版本必须要添加相应的版本信息 path/v2 等方式。同一个主版本的不同子版本必须要保证向下的兼容性,也就是同一个主版本只会选择一个版本。这种方式保证了我们在使用新版本的特性的时候,又可以暂时不迁移依赖旧版本的代码。


删除不再使用的依赖


查看现有的依赖关系


➜ hello go list -m allgo list -m allexample.com/hellogolang.org/x/text v0.3.2golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6ersc.io/quote v1.5.2rsc.io/quote/v3 v3.1.0rsc.io/sampler v1.3.1➜ hello cat go.modcat go.modmodule example.com/hellogo 1.13require (golang.org/x/text v0.3.2// indirectrsc.io/quote v1.5.2rsc.io/quote/v3 v3.1.0rsc.io/sampler v1.3.1// indirect)➜ hello
复制代码


从上面的依赖列表中还是依然又 v1.5.2,go build 或者 go test 这样的命令,能够添加没有的以来项目但是不能够安全的删除掉相应的依赖。安全的删除一个依赖需要检查 module 里面的所有 package 是否依赖该 module,而 build,test 命令都没有加载相应的信息,所以不能够安全的删除相应的 module。


go mod tidy


➜ hello go mod tidygo mod tidy➜ hello go list -m allgo list -m allexample.com/hellogolang.org/x/text v0.3.2golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6ersc.io/quote/v3 v3.1.0rsc.io/sampler v1.3.1➜ hello cat go.modcat go.modmodule example.com/hellogo 1.13require (golang.org/x/text v0.3.2// indirectrsc.io/quote/v3 v3.1.0rsc.io/sampler v1.3.1// indirect)➜ hello
复制代码


通过这个命令能够删除掉不再需要的依赖module


问题


go list -m all?


按照什么排序策略进行排序


提交代码的时候一定要提交 go.mod 和 go.sum 不然会出现 checksum 的时候出错的情况


3 使用中遇到的问题


如何进行依赖库的代码开发?


可以用 replace 的方式,replace 可以替换成本地的路经,来达到使用本地代码的作用。


replace github.com/brocaar/lorawan v0.0.0-20190814113539-8eb2a8d6da09=> path/lorawan
复制代码


如何使用自由仓库进行依赖库的开发?


设置 git 的地址转化就可以达到预期的目的,是否可以通过 proxy 的形式来做到类似的效果,还没有具体研究,后面会去研究以下 proxy 相关的内容。


git config --global url."${UrlPath}lorawan.git".insteadOf "https://github.com/brocaar/lorawan"
复制代码


go.mod 和 go.sum 的提交?


提交相应的变更的时候,务必记得提交相应的 go.sum,不然会出现后续的 sum 检查不通过。


本文转载自公众号 360 云计算(ID:hulktalk)。


原文链接:


https://mp.weixin.qq.com/s/c4I1o_OMxNUwBeQQH_piOA


2019 年 11 月 14 日 15:16175

评论

发布
暂无评论
发现更多内容

APP 莫名崩溃,开始以为是 Header 中 name 大小写的锅,最后发现原来是容器的错!

程序员小航

Java bug Header携带签名 工作笔记 问题排查

LAXCUS大数据集群操作系统:一个分布式分时共享E级系统软件(四)

陈泽云

人工智能 大数据 数据结构 操作系统 数据存储

手把手带你玩转 openEuler | openEuler 的使用

openEuler

操作系统 openEuler

10个自动化测试框架,测试工程师用起来

华为云开发者社区

软件 测试 质量

Java Reference核心原理分析

AI乔治

Java 架构 JVM 性能调优

帆软授权失效处理

Flychen

最新版MySQL在MacOS上的安装与使用

王磊

MySQL

速度(Velocity)不背这个锅

BY林子

敏捷开发 估算与计划

十八、深入Python函数

刘润森

Python

关注你自己,如同篮球巨星一样,让身体最佳化,持续投入最爱的事情。

叶小鍵

健康 科普 王立铭 肥胖

黄金圈法则:成功者必备的深度思考方法

陆陆通通

黄金圈法则 厉害 牛逼

数字货币永续合约平台搭建方案,一键跟单系统开发

WX13823153201

云原生在京东丨基于 Tekton 打造下一代云原生 CI 平台

京东智联云开发者

ci 云原生 Tekton

忘记MySQL密码怎么办?一招教你搞定!

王磊

MySQL

在算力“沃土”上,种植互联网下一个奇迹十年

脑极体

PLSQL 过程语言-结构化查询语言

Flychen

微服务架构:基于微服务和Docker容器技术的PaaS云平台架构设计(微服务架构实施原理)

AI乔治

Java 架构 微服务 ,docker

spring-boot-route(二十)Spring Task实现简单定时任务

Java旅途

Java Spring Boot Spring Task

MySQL-技术专题-联合索引最左前缀匹配原则

李浩宇/Alex

金九银十期间成功斩获58万架构师Offer!六面字节跳动面经和面试题分享

Java架构追梦

Java 学习 架构 面试 JVM

目标2025:通信产业在能源变局中拥抱智能未来

脑极体

Servlet-技术专题-Servlet3异步原理与实践

李浩宇/Alex

java安全编码指南之:ThreadPool的使用

程序那些事

java安全编码 java编码指南 java安全编码指南 java代码规范

计算机网络基础知识总结

cxuan

计算机网络 计算机

sync-player:使用websocket实现异地同步播放视频

GoEasy消息推送

websocket 数据同步 实时通信

深度详解企业CRM系统,体验软件快速开发平台

Marilyn

敏捷开发 快速开发 CRM

架构师第一期作业(第5周)

Cheer

作业

iOS底层原理之—dyld与objc的关联

iOSer

ios开发 iOS Developer dyld objc

go-zero 如何应对海量定时/延迟任务?

Kevin Wan

golang 定时任务 时间轮 microservice 延迟任务

MySQL-技术专题-聚集索引和慢查询

李浩宇/Alex

据说99.99%的人都会答错的类加载的问题

AI乔治

Java 架构 JVM 类加载 性能调优

Modules简介-InfoQ