之前使用 go build, go install, go run 都需要显式的设置环境变量 GOPATH。代码的组织结构也需要按照 GOPATH 的约定来。否则在编译时,会报找不到
1 2 3 4 |
go run hello.go hello.go:4:8: cannot find package "rsc.io/quote" in any of: /usr/local/go/src/rsc.io/quote (from $GOROOT) /home/bruceding/go/src/rsc.io/quote (from $GOPATH) |
如果引入了第三方包,发现需要从 GOROOT, GOPATH 中找。
在之前的开发中,在编译过程中,会显式的设置 GOPATH,而且需要 go get 包时,也需要显式的设置下。如果存在多个项目的话,第三方包也要放到项目下的,导致项目的代码比较多。
Modules 特性从 golang 1.11 版本开始引入,成为了官方依赖管理的方案。Modules 使我们脱离了 GOPATH 的限制,在 GOPATH 之外可以编译代码,逐渐淡化 GOPATH 的使用。
当前来说,GOPATH 还是必要的,使用 go mod 下载的包还需要放在 \$GOPATH/pkg/mod 下面。包括二进制文件还是下载到 \$GOPATH/bin 下。
让我们通过示例说明 go mod 的使用。
简单的项目
设置的 $GOPATH=/home/bruceding/projects/go, 而代码目录为 /home/bruceding/hello, 项目代码是在 $GOPATH 之外的。
在目录 hello 下创建 hello.go 文件
1 2 3 4 5 6 7 8 9 10 |
➜ hello cat hello.go package main import "fmt" import "rsc.io/quote" func main() { fmt.Println("hello, world") fmt.Println(quote.Go()) } |
当我们直接 build 时, go build hello.go, 报错
1 2 3 4 |
➜ hello go build hello.go hello.go:4:8: cannot find package "rsc.io/quote" in any of: /usr/local/go/src/rsc.io/quote (from $GOROOT) /home/bruceding/projects/go/src/rsc.io/quote (from $GOPATH) |
说明还是之前的方式依据 $GOPATH 编译。
使用 go mod 的方式,执行 go mod init github.com/bruceding/hello
1 2 3 4 5 6 7 8 9 10 |
➜ hello go mod init github.com/bruceding/hello go: creating new go.mod: module github.com/bruceding/hello ➜ hello ll total 8.0K -rw------- 1 bruceding bruceding 42 Apr 11 09:03 go.mod -rw-rw-r-- 1 bruceding bruceding 120 Apr 11 08:39 hello.go ➜ hello cat go.mod module github.com/bruceding/hello go 1.12 |
可以看到了 go.mod 文件,我们定义了一个 module ,名称为 github.com/bruceding/hello。
我们运行下编译
1 2 3 4 5 6 7 8 9 10 |
➜ hello go build hello.go go: finding rsc.io/quote v1.5.2 go: finding rsc.io/sampler v1.3.0 go: finding golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c go: downloading rsc.io/quote v1.5.2 go: extracting rsc.io/quote v1.5.2 go: downloading rsc.io/sampler v1.3.0 go: extracting rsc.io/sampler v1.3.0 go: downloading golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c go: extracting golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c |
可以看到编译成了,而且自动下载了第三方包,而且第三方包的依赖也下载下来了。在当前文件生成了 hello 执行文件。
复杂些的例子
在项目下创建 util.go 文件
1 2 3 4 5 6 7 8 9 |
cat util.go package main import ( "fmt" ) func Hello() { fmt.Println("create a util func") } |
Hello.go 中直接使用 调用 Hello 函数。直接编译运行
1 2 3 4 5 |
➜ hello go build ➜ hello ./hello hello, world Don't communicate by sharing memory, share memory by communicating. create a util func |
但是我们运行 go build hello.go , 发现
1 2 3 |
➜ hello go build hello.go # command-line-arguments ./hello.go:9:2: undefined: Hello |
报错了。这个时候,需要把 hello.go 用到的所有文件一同编译, go build hello.go util.go 才可以。
假设目录下有两个包含 main 函数的文件
1 2 3 4 5 6 |
➜ hello cp hello.go hello1.go ➜ hello go build # github.com/bruceding/hello ./hello1.go:6:6: main redeclared in this block previous declaration at ./hello.go:6:6 ➜ hello go build hello1.go util.go |
这个时候,不能直接用 go build 编译了,需要指定 main 函数的文件及其依赖。
多本地包的应用
新建 utils 目录,把 util.go 移动进去,然后需要改成 package utils。然后修改 hello.go
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
➜ hello cat hello.go package main import ( "fmt" "rsc.io/quote" "github.com/bruceding/hello/utils" // 这里的不同 ) func main() { fmt.Println("hello, world") fmt.Println(quote.Go()) utils.Hello() } |
我们看到,在 import 的时候,utils 和之前的 $GOPATH 是不同的,是根目录下 go.mod 定义的 module + 相应的包目录名称。
依赖包放到项目本地
有时候我们会把第三方包放到本地目录,有时会在第三方包中插入调试代码,或者有修改的需求。
通过 go mod vendor, 会把依赖包放到本地。本地会自动创建 vendor 目录。编译时增加 mod 参数即可, go build -mod vendor。
多项目应用
有时候,同一个 repo 下有个项目,项目有各自的 local package, 也会公用 local package。代码结构组织如下
1 2 3 4 5 6 7 8 9 10 11 |
/repo |---- project1 |----------- main.go |----------- package1 |----------- package2 |---- project2 |----------- main.go |----------- package1 |----------- package2 |---- package1 |---- package2 |
我们测试的目录树如下
1 2 3 4 5 6 7 8 9 10 11 12 |
➜ hello tree . |-- go.mod |-- go.sum |-- project1 | |-- go.mod | |-- go.sum | |-- hello.go | `-- utils | `-- util.go `-- utils `-- util.go |
其中有两个 utils 包,一个是主 module 的, 一个是子 module的。在 project1 目录中,执行 go mod init 初始化
1 2 3 |
➜ hello cd project1 ➜ project1 go mod init github.com/bruceding/hello/project1 go: creating new go.mod: module github.com/bruceding/hello/project1 |
然后修改 hello.go 文件,让其对两个 utils 都 import。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
➜ project1 cat hello.go package main import ( "fmt" "rsc.io/quote" gutils "github.com/bruceding/hello/utils" lutils "github.com/bruceding/hello/project1/utils" ) func main() { fmt.Println("hello, world") fmt.Println(quote.Go()) gutils.Hello() lutils.Hello() } |
当我们编译时,反而会报错了
1 2 |
➜ project1 go build build github.com/bruceding/hello/project1: cannot load github.com/bruceding/hello/utils: cannot find module providing package github.com/bruceding/hello/utils |
可以把 module 想象成一个上下文,在子 module 中,是找不到主 module 的。这时,要显式的设置路径,让子 module 能找到主 module。
1 2 3 4 5 6 7 |
➜ project1 go mod edit -replace github.com/bruceding/hello=../ ➜ project1 go build ➜ project1 ./project1 hello, world Don't communicate by sharing memory, share memory by communicating. global create a util func local project create a util func |
这样就可以成功了。