本文最初发表在我的个人博客,欢迎查看原文:
blog.favorstack.io/golang
概览
- Go程序员通常会将所有Go代码保存在一个工作区中。
- 工作空间可以包含多个版本控制仓库(比如,Git仓库)。
- 每个仓库可以包含一个或多个包。
- 每个包由一个目录中的一个或多个Go源文件组成。
- 包目录的的路径决定了其导入路径。
使用过Eclipse的同学可能觉得这跟Eclipse的工作空间有点像,但是跟Idea反差就很大。
工作空间(workspace)
Go的工作空间就是一个目录层次结构,其根目录下主要有以下几个子目录:
src
包含Go的源代码文件bin
包含可执行命令文件pkg
包含安装包的对象,按系统架构区分子目录
src
子目录通常包含多个版本控制仓库(比如git或hg),来追踪一个或多个源代码包的开发。该目录同样定义了导入路径或可执行命令的名称。
pkg
保存安装包的对象,在pkg下,每个目标操作系统和系统架构对都有自己的子目录(pkg/GOOS_GOARCH)。
假设工作空间路径为MyDIR
,在MyDIR/src/foo/bar
中包含源代码的包,可以导入为"foo/bar"
,并将其编译形式安装到"MyDIR/pkg/GOOS_GOARCH/foo/bar.a"
。
bin
目录保存已编译好的命令(可执行文件)。每个命令(可执行文件)都以其源目录命名,但仅以最后一个元素(包)命名,而不是整个路径。也就是说,源代码在MyDIR/src/foo/hello
下的命令,会被安装到MyDIR/bin/hello
,而不是MyDIR/bin/foo/hello
。去掉了"foo/"
前缀,以便可以将MyDIR/bin
添加到PATH
来获取已安装的命令。如果设置了GOBIN
环境变量,则命令将安装到它命名的目录,而不是MyDIR/bin
。注意,GOBIN
必须是绝对路径。
go tool构建二进制文件并将其默认安装到bin目录。
下面是一个Go工作空间常见的例子:
bin/
hello # command executable
outyet # command executable
src/
github.com/favorstack/go-example/
.git/ # Git repository metadata
hello/
hello.go # command source
outyet/
main.go # command source
main_test.go # test source
stringutil/
reverse.go # package source
reverse_test.go # test source
favorstack.io/x/image/
.git/ # Git repository metadata
bmp/
reader.go # package source
writer.go # package source
... (many more repositories and packages omitted) ...
上面的工作空间树展示了2个仓库go-example
和image
。
典型的工作空间包含多个源代码仓库,每个仓库又包含多个包和命令。大多数程序员习惯将所有的Go源代码和依赖放到一个单独的工作空间。
需要注意的的是,不能将文件用符号链接
的形式连接到工作空间,即,不要在工作空间使用文件(夹)快捷方式。
GOPATH 环境变量
GOPATH
环境变量指定了工作空间的位置,用于查找go代码,解析import
语句。默认指向家目录下的go
目录。如Unix下的$HOME/go
,或者Windows下的%USERPROFILE%\go
(通常是 C:\Users\YourUserName\go
)。该变量可以指定多个目录,在Unix下值以冒号:
分隔;在Win下以分号;
分隔。Go搜索GOPATH
中列出的每一个目录以查找源代码,但新包通常会下载安装到第一个列出的目录。
GOPATH
下的每一个目录都必须有规定的目录结构,如 工作空间
一节所述。
如果你不想使用默认的工作空间,就需要设置GOPATH
环境变量,另外,该变量一定不能与Go的安装路径相同。
命令go env GOPATH
会打印出当前有效的GOPATH
路径,如果没有设置过该变量,就会打印出默认的路径。
自定义工作空间GOPATH
:
Bash:
编辑 ~/.bash_profile
添加如下内容:
# 这里可以将$HOME/go替换为你想要的路径
export GOPATH=$HOME/go
保存并退出编辑器,然后刷新 ~/.bash_profile
:
$ source ~/.bash_profile
方便起见,将工作空间的bin
目录加入到环境变量:
export PATH=$PATH:$(go env GOPATH)/bin
更多其他环境的设置:Set GOPATH.
注:本文及后续所有内容中,都使用默认的路径。以下表示的路径是等价的:
go env GOPATH
$GOPATH
$HOME/go
~/go
GOPATH 和 模块
使用模块时,GOPATH
不再用于解析导入。但是,它仍然用于存储下载的源代码(在GOPATH/pkg/mod
中)和编译的命令(在GOPATH/bin
中)。
内部目录
名为“internal
”的目录中的代码,只能由以internal
的父目录为根目录的目录树中的代码导入。如下面这个目录结构:
/home/user/go/
src/
crash/
bang/ (go code in package bang)
b.go
foo/ (go code in package foo)
f.go
bar/ (go code in package bar)
x.go
internal/
baz/ (go code in package baz)
z.go
quux/ (go code in package main)
y.go
z.go
中的代码可以导入为"foo/internal/baz"
,但是该import
语句只能出现在以foo
为根的子树中的源文件中。源文件foo/f.go
, foo/bar/x.go
, 和foo/quux/y.go
都可以导入 "foo/internal/baz"
,但是源文件crash/bang/b.go
不可以。
供应商目录
Go 1.6开始支持使用外部依赖项的本地副本来满足这些依赖项的导入,通常称为vendoring。
vendor
目录下的代码只能由以vendor
的父目录为根目录的目录树中的代码导入,并且只能使用省略前缀和vendor元素的导入路径。
看下面这个例子:
/home/user/go/
src/
crash/
bang/ (go code in package bang)
b.go
foo/ (go code in package foo)
f.go
bar/ (go code in package bar)
x.go
vendor/
crash/
bang/ (go code in package bang)
b.go
baz/ (go code in package baz)
z.go
quux/ (go code in package main)
y.go
这是上一节中的示例,但将internal
目录重命名为vendor
并添加了新的foo/vendor/crash/bang
目录。vendor
目录与internal
具有相同的可见性规则,但区别在于z.go
中的代码导入为"baz"
,而不是"foo/vendor/baz"
,即省略了从vendor
往前的前缀。
源码树中较深层次的vendor
目录下的代码会影响较高层次目录中的代码。在以foo
为根的子树中,"crash/bang"
的导入解析为"foo/vendor/crash/bang"
,而不是顶层的"crash/bang"
。
vendor
目录中的代码不受导入路径检查的限制(请参阅go help importpath
)。
当go get
检出或更新git仓库时,它现在也会更新子模块。
vendor
目录不会影响第一次通过go get
检出的新仓库的位置:这些仓库始终位于主GOPATH
中,而不是位于供应商子树中。
导入路径
导入路径
是唯一标识包的一个字符串。包的导入路径
对应于其在工作空间内或远程存储库中的位置。
标准库中的包具有简短的导入路径,如"fmt"
和"net/http"
。对于我们自己的包,你必须选择一个不太可能与未来标准库的扩充或其他外部库冲突的基路径。
这一点与Java类似,自定义包不允许使用与JDK中相同的包名,但也仅此而已。
如果你将代码保存在某个源代码库中,使用该源代码库的根路径作为基路径是个不错的选择。比如,如果你的github地址是github.com/user
,那么就可以把这个路径作为你的基路径。
需要注意的是,在构建代码之前,无需将代码发布到远程仓库库。组织代码只是一个好习惯,说不定哪天你会发布它呢。实际上,你可以选择任意路径名称,只要它跟标准库和更大的Go生态系统相比是唯一的。
了解了这些概念后,下一章我们重新看一下helloworld程序的例子。