解析包路径到模块路径的流程
通常在使用“go get”时可能是指定到一个包路径,而非模块路径,Go 是如何找到模块路径的呢?
go 命令会在主模块(当前模块)的 build list 中搜索有哪些模块路径匹配这个包路径的前缀。举个例子,如果导入的包路径是 example.com/a/b,发现 example.com/a 是一个模块路径,那么就会去检查 example.com/a 在 b 目录中是否包含这个包,在这个目录中要至少存在一个 go 源码文件才会被认为是一个有效的包。编译约束(Build Constraints)在这一过程中不会被应用。 如果确实在 build list 中找到了一个模块包含这个包,那么这个模块将被使用。如果没有发现模块能提供这个包或者发现两个及两个以上的模块提供了这个包,那么 go 命令会提示报错。但是你可以指定 -mod=mod 来使 go 命令尝试下载本地找不到的包,并且更新 go.mod 和 go.sum。go get 和 go mod tidy 这两个命令会自动的做这些工作。
当 go 命令试图下载一个新的代码包时,它回去检查 GOPROXY 环境变量,这是一个使用逗号分隔的 URL 列表,当然也支持像 direct 和 off 这样的关键字。代理 URL 代表 go 将使用 GOPROXY 协议拉取模块,direct 表示 go 需要和版本控制系统直接交互,off 不需要和外界做任何交互。另外,GOPRIVATE 和 GONOPROXY 环境变量也可以精细的控制 go 下载代码包的策略。
对于 GOPROXY 列表中的每一项, go 命令回去请求模块路径的每一个前缀。对于请求成功的模块,go 命令回去下载最新模块并且检查这个某块是否包含请求的包。如果多个模块包含了请求的包,拥有最长路径的将被选择。如果发现的模块中没有包含这个包,会报错。如果没有模块被发现,go 命令会尝试 GOPROXY 列表中的下一个配置项,如果最终都尝试过没有发现则会报错。举个例子,假设用户想要去获取 golang.org/x/net/html 这个包,之前配置的 GOPROXY 为 https://corp.example.com,https://goproxy.io。go 命令会遵循下面的请求顺序:郑州看精神科医院哪家好http://www.juenpt.com/
向 https://corp.example.com/ 发起请求 (并行):
Request for latest version of golang.org/x/net/html
Request for latest version of golang.org/x/net
Request for latest version of golang.org/x
Request for latest version of golang.org
如果 https://corp.example.com/ 上面都失败了返回 410 或者 404 状态码,向 https://proxy.golang.org/ 发起请求:
Request for latest version of golang.org/x/net/html
Request for latest version of golang.org/x/net
Request for latest version of golang.org/x
Request for latest version of golang.org
当一个需要的模块被发现后,go 命令会将这个依赖模块的路径和对应版本添加到主模块的 go.mod 文件中。这样就确保了以后在编译该模块时,同样的模块版本将被使用,保证了编译的可重复性。如果解析的代码包没有被主模块直接引用,在 go.mod 文件中添加的新依赖后会有 // indirect 注释。