> 文档中心 > Golang学习日志 ━━ 理解依赖包的管理(mod/非mod)和加载方式(项目路径、相对路径、绝对路径)及实战运用

Golang学习日志 ━━ 理解依赖包的管理(mod/非mod)和加载方式(项目路径、相对路径、绝对路径)及实战运用


go有很多种方法调用依赖包,mod又加入了对包的版本管理。方式太多不免有令人迷惑和混乱的地方,希望本文能帮助大家了解目前使用规则。

本文所用案例的环境设定:

  1. GO111MOUDLEauto
  2. GOPATHd:/Projects
  3. GOPATH目录binpkgsrc
  4. src中以不同目录区分项目
  5. ppp为非GOPATH设定的项目存放目录;
  6. 初始的案例目录结构如下:
+D:  +/ppp  +/Projects    +/bin    +/pkg      +/mod    +/src      +/p1 +/package    +/cls1    +/cls2 main.go      -/p2
  1. 涉及引用的代码大部分在%GOPATH%/src/p1/main.go | d:/Projects/src/p1/main.go 入口文件,即名为p1的项目;
  2. 本文所提到的“公共包”指的是类似github.com/c1类型的第三方依赖包;而“私有包”则是指cls1类型的自定义依赖包;

一、mod / 非mod 管理方式

go提供了两种项目依赖包的管理方式,一种是mod方式,一种是非mod方式。

1. mod方式

main.go 代码

import("github.com/c1""github.com/c2""bbq.org/o1")

终端执行

> go mod init

此刻系统自动生成go.mod,用于标记所有依赖包。

如果提示依赖包不存在,那可能因为依赖包下载失败了,此刻就需要设定合适的代理,再用go buildgo getgo mod vendorgo mod tidy四种命令获取依赖包。

例:

> go get github.com/c1> go get github.com/c2> go get bbq.org/o1

go会将这些包下载到%GOPATH%/pkg/mod(本例即d:/Projects/pkg/mod,如果是go mod vendor命令则会下载至p1/vendor目录中,后面会提到)目录中,并将这些包标记在go.mod内,同时生成用于记录包版本的go.sum文件。

go.mod内容如下:

module p1go 1.17require (github.com/c1 v0.0.0-20190825152654-46b345b51c96 // indirectgithub.com/c2 v0.3.1 // indirectbbq.org/o1 v1.24.0 // indirect)

下载后go会根据版本号自动区分目录

目录结构

+D:  +/Projects    +/bin    +/pkg      +/mod +/github.com   +/c1@v0.0.0-20190825152654-46b345b51c96   +/c2@v0.3.1 +/bbq.org   +/o1@v1.24.0    +/src      +/p1 +/package    +/cls1    +/cls2 main.go go.mod go.sum      -/p2

2.非mod方式

加载方式有多种,本例采用了下一节提到的"项目路径"方式。

main.go 代码

import("github.com/c1""github.com/c2""bbq.org/o1")

github.com/c1github.com/c2bbq.org/o1三个目录移到%GOPATH%/srcd:/Projects/src/)下即可。
注意这种非mod方式的目录是不能带版本号的,所以一般不适用于公共依赖包

目录结构

+D:  +/Projects    +/bin    +/pkg    +/src      +/github.com +/c1 +/c2      +/bbq.org +/o1      +/p1 +/package   +/cls1   +/cls2 main.go     -/p2

相当于src目录下有四个项目,分别是github.combbq.orgp1p2,此时用“项目路径”的方式来调用包就比较好理解了吧。


二、包的加载方式

1.项目路径

以项目名为开头直接引用,我称为项目路径,此处为项目p1(可以回看上面“非mod方式”所举的例子再理解一下什么是项目~~)。

main.go 代码

import("p1/package/cls1""p1/package/cls2")

如果cls1下某文件引用了cls2包,那么也直接用项目路径来表示,不需要考虑层级(../../)关系。

  1. 依托环境变量GOPATH,其src下的项目可直接使用本载入方式;
  2. 项目根目录中若有go.mod文件,则意味着该项目使用了mod方式来管理包,那么只能使用本载入方式。

2. 相对路径

在go没有mod的早期版本,本方式较为常见。

main.go 代码

import("./package/cls1""./package/cls2")

本文最初目录结构直接go build的话,系统会警告目录结构不合理

unexpected directory layout:import path: _/D_/Projects/src/p1/packageroot: D:\Projects\srcdir: D:\Projects\src\p1\pkgexpand root: D:\Projectsexpand dir: D:\Projects\src\p1\pkgseparator: \

意思是你引用的包怎么能和项目都在src中,不合适,赶紧换~~~

解决办法很简单,把项目p1移出%GOPATH%/src目录就好了,让GOPATH里只放系统级或公共级的内容。

目录结构

+D:  +Projects    +bin    +pkg    +src  +ppp +p1      +package +cls1 +cls2      main.go   -p2

go build 报错 unexpected directory layout 的解决

3. 绝对路径

从没用过,为了写本文试了一下,结果为失败,无论项目在src目录内还是ppp目录内,都提示不存在包

main.go 代码

import("d:/Projects/src/p1/package/cls1""d:/Projects/src/p1/package/cls2")

系统提示路径无效

main.go:3:2: invalid import path: "d:/Projects/src/p1/package/cls1"main.go:4:2: invalid import path: "d:/Projects/src/p1/package/cls2"

三、实战运用

结合上面的测试,实践中可以用三种方法。

1. 相对路径+项目路径

注意:
一旦开头用了./,那么就意味着整个项目采用了相对路径,此时无法使用mod方式。
也可以说一旦用了mod方式,则无法采用相对路径方法来获取包。

A)公共和私有稍作区隔

main.go 代码

import("github.com/c1""github.com/c2""bbq.org/o1""./package/cls1""./package/cls2")

采用相对路径的项目,其目录不能放在%GOPATH%/src目录下,请移到别处,src目录主要用来放依赖包,否则会提示unexpected directory layout错误。

即:
项目目录p1中的包使用相对路径调用;
%GOPATH%/src中的包使用项目路径调用。

目录结构

+D:  +/Projects    +/bin    +/pkg    +/src      +/github.com +/c1 +/c2      +/bbq.org +/o1  +/ppp +/p1      +/package +/cls1 +/cls2      main.go   -/p2

B)公共和私有都属一个项目

main.go 代码

import("./package/github.com/c1""./package/github.com/c2""./package/bbq.org/o1""./package/cls1""./package/cls2")

用这个方法,那就和GOPATH完全没关系了,如果要让兄弟帮忙,把整个项目包给他就完事了,轻松。

目录结构

+D:  +/Projects    +/bin    +/pkg    +/src  +/ppp +/p1      +/package +/cls1 +/cls2 +/github.com   +/c1   +/c2 +/bbq.org   +/o1      main.go   -/p2

C)必须注意准确的层级关系

注意:
用了相对路径法后,层级关系就必须非常准确。

比如p1下多一个pck目录及show.go文件

目录结构

+D:  +/Projects    +/bin    +/pkg    +/src  +/ppp +/p1      +/pck show.go      +/package +/cls1      main.go   -/p2

如果在pck/show.go内调用package/cls1,就需要这么写了

show.go 代码

import("../package/cls1")

2. 非mod + 项目路径

参考本文的“非mod方式

A)公共和私有稍作区隔

main.go 代码

import("github.com/c1""github.com/c2""bbq.org/o1""p1/package/cls1""p1/package/cls2")

将公共包和项目同级,都放在%GOPATH%/src目录中,公共包相当于项目,go会自动去src下找项目调用。
总之无论嵌套多少层,一律从项目名开始写包路径

目录结构

+D:  +/Projects    +/bin    +/pkg    +/src      +/github.com +/c1 +/c2      +/bbq.org +/o1      +/p1 +/package   +/cls1   +/cls2 main.go     -/p2

B)公共和私有都属一个项目

main.go 代码

import("p1/package/github.com/c1""p1/package/github.com/c2""p1/package/bbq.org/o1""p1/package/cls1""p1/package/cls2")

意思是一样的:一切从项目名开始,本例把包都放在目录package里了。

知道了原理,怎么玩就是你自己的事情了。

目录结构

+D:  +/Projects    +/bin    +/pkg    +/src      +/p1 +/package   +/cls1   +/cls2   +/github.com     +/c1     +/c2   +/bbq.org     +/o1 main.go     -/p2

3. mod + 项目路径

mod初始化的方法请参考本文的“mod方式”一节。

注意:

  1. 项目根目录下有go.mod文件就意味着本项目采用了【mod+项目路径】;
  2. 下载不到包或者go.mod为空,则配置好 goproxy 后再次使用go build或用 go get 方式获取依赖包;
  3. mod有很多管理方法,比如replace [原地址] => [新地址],用此基本可以解决所有路径问题,但不在本文谈论内容里。

A)公共和私有稍作区隔

main.go 代码

import("github.com/c1""github.com/c2""bbq.org/o1""p1/package/cls1""p1/package/cls2")

并非只有当项目处于%GOPATH%/src下才能使用“项目路径”法。
随便搞个文件夹,只要go.mod所在目录名就是项目名~~
比如这里的D:/ppp/p1p1就是项目名,因为go.modp1文件夹内。

目录结构

+D:  +/Projects    +/bin    +/pkg      +/mod +/github.com   +/c1@v0.0.0-20190825152654-46b345b51c96   +/c2@v0.3.1 +/bbq.org   +/o1@v1.24.0    +/src  +/ppp +/p1      +/package +/cls1 +/cls2      main.go      go.mod      go.sum   -/p2

B)公共和私有都属一个项目

代码部分并未改变,但是目录结构变了。

main.go 代码

import("github.com/c1""github.com/c2""bbq.org/o1""p1/package/cls1""p1/package/cls2")

终端执行

通过go mod vendor命令将依赖包下载到p1/vendor目录中。
(上一个例子中的依赖包在%GOPATH%/pkg/mod,即D:/Project/pkg/mod目录)

> go mod vendor

目录结构

这时系统会在项目内生成一个vendor目录,并且将所有包都放入这个目录中。

+D:  +/Projects    +/bin    +/pkg    +/src  +/ppp +/p1      +/package +/cls1 +/cls2      +/vendor+/github.com   +/c1@v0.0.0-20190825152654-46b345b51c96   +/c2@v0.3.1 +/bbq.org   +/o1@v1.24.0      main.go      go.mod      go.sum   -/p2

C)系统自动调用和下载

  • 在mod方式下,系统会尝试到%GOPATH%/pkg/mod目录和D:/ppp/p1/vendor目录读取依赖包。
  • 如果两个目录都没有,则会将包下载到%GOPATH%/pkg/mod目录中,毕竟GOPATH放在那里不能当摆设啊~~
  • 如果两个目录都有,则会优先加载。。。。(估计是p1/vendor,但没测试过,有知道的朋友可以留言)

四、总结

1. 可以使用项目路径的两种情况:

  • 项目在%GOPATH%/src目录内;
  • 项目目录内存在go.mod,此时的项目在任何目录内都可以使用项目路径;

2. 使用相对路径只有一种情况:

项目目录不在%GOPATH%/src目录内且项目目录内没有go.mod

3. mod管理包的路径方式只有一种:

一旦使用mod管理包,那么只能使用项目路径,且相对路径无效,不太清楚为什么,期未来吧。

4. 公共包默认下载位置

  • 非mod方式的公共包都在%GOPATH%/src目录;
  • 一般mod方式公共包都在%GOPATH%/pkg/mod
  • mod vendor方式的公共包都在项目/vendor目录;

5. 建议和推荐

  • 无论用什么方式,建议项目目录不要放在GOPATH,只把公共包或系统级的内容放到GOPATH里去。
  • 相对而言,mod方式具有版本管理的优势,并且使用go mod tidy|vendor命令就可以完成所有依赖包的下载,所以优先推荐。

文中涉及的命令解释、配置安装等内容请参考下面的文章:

《Golang学习日志 ━━ 下载及安装》
《Golang学习日志 ━━ LiteIDE的主要配置》
《Golang学习日志 ━━ VSCode安装Go插件(代理的使用)及初用mod》
《go modules:使用 mod 管理项目依赖包,通过vendor实现一键分发编译包》
《Go:go mod vendor 使用》
《go package、import、go.mod 理解 以及 私有包引入》
《go-module的使用》
《go安装依赖包(go get, go module)》
《Go语言go mod包依赖管理工具使用详解》
《go 1.11 go mod replace 的使用方法》

如果有写的不对的地方,欢迎指正,谢谢。