每天go一点点,只记录学习中, 自己用到和迷惑的东西, 记录精华部分, 不求大而全.
GOPATH
翻译自go help gopath.
GOPATH被用来解析import语句.
GOPATH环境变量用来表示去哪里查找Go代码. 在Unix系统中, 这个环境变量是一个用:分割的字符串.
如果该环境变量没有设置, 则GOPATH默认是$HOME/go.
GOPATH目录结构
GOPATH/下的每个目录, 都有一个自己的结构规范:
-
src目录存放源代码.src/后面的path确定了①package的import path或者②package编译后可执行文件的名字(取自import path的最后一个目录名). -
pkg目录存放package的编译结果. 每个操作系统都有自己的pkg子目录pkg/GOOS_GOARCH.举个栗子: 如果
DIR是GOPATH环境变量的成员之一, 那么存放在DIR/src/foo/bar/下的package的导入路径就是foo/bar. 并且编译结果被放入(installed)DIR/pkg/GOOS_GOARCH/foo/bar.a. -
bin目录存放编译好的命令commands. 每个命令都是根据他的源文件包的import path的最后一个元素进行命名的.举个例子: 命令(
package main的编译结果)的源码存放在DIR/src/foo/quux/目录下(假设DIR已经在GOPATH环境变量里面了), 则他的import path是foo/quux, 那么这个命令会被安装到DIR/bin/quux, 而不是DIR/bin/foo/quux.当然, 如果
GOBIN环境变量被设置了, 那么所有的commands都会被安装到这个文件夹下.GOBIN必须是一个绝对路径.
下面是一个GOPATH目录布局的例子:
GOPATH=/home/user/go # GOPATH环境变量
/home/user/go/
src/
foo/
bar/ (package bar 源码包)
x.go
quux/ (package main 源码包)
y.go
bin/
quux (package main 安装后的命令/可执行文件)
pkg/
linux_amd64/
foo/
bar.a (package bar 编译后的库文件)
go命令会搜索所有的GOPATH环境变量中的所有目录去查找源文件, 但是新下载的package都会被放到GOPATH的第一个目录中.
GOPATH and Modules
如果你使用了modules(推荐使用), 就不再用GOPATH 来解析import path了. 此时它起的作用就是用来存储:
- 下载的
package源码都会被存放到GOPATH/pkg/mod/目录下. - 编译之后的命令, 会被安装到
GOPATH/bin/目录下
内部目录internal directories
这里描述一下pacakge的引用规则.
假设当前的目录叫做internal, 那么internal目录/子目录下的package只能被其父目录中的代码导入使用.
目录结构如下:
/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 的import path是 foo/internal/baz, 但是这个导入语句只能出现在foo的目录树的源文件中. 如: foo/f.go, foo/bar/x.go, foo/quux/y.go都可以使用导入语句import foo/internal/baz. 但是源文件crash/bang/b.go则不能使用.
Vendor Directories
用的少, 就不翻译了. 有兴趣就看go help gopath相应的部分.
import path
翻译自go help importpath
一个import path用来表示一个存储在本地文件系统中的package. 即: 每个package的importpath都是唯一的
一般来说, 一个import path 要么表示一个标准库包(如: unicode/utf8), 要么表示一个在工作空间中的包(见上面的go path).
相对导入路径 relative import paths
如果一个import后面的路径是以 ./ ../开头, 表示这是一个相对路径relative path.
go命令支持把realtive import path作为import path的缩写, 一共有两种形式:
-
relative path在命令行中作为import path的简写.
如果你所在的文件夹下的
pacakge的import path是unicode, 那么如果要对unicode/utf8的包进行测试, 你就可以这么写:go test ./urf8.除了
./,../, 在相对路径中, 我们也是使用模式匹配.... 如:go test ./..., 这条go命令会对当前目录(包含各层子目录)下的所有包进行测试. -
- 非
workspace(GOPATH/srcGOPATH/bin)环境下,relative path可以在程序中使用
如果你正在编译一个不在
workspace下的Go程序, 你可以在程序中使用一个relative path去导入一个"附近"的包(附近的包也不能在workspace下).这样就使得在工作空间之外的地方, 使用多个包构成的程序变得很方便. 但是这样的程序是没有办法安装(
go install)的, 因为没有地方安装他们(不在工作空间中, 也不是一个module). 所以每次build, 都是从头开始.为了避免歧义, go程序不允许在
workspace工作空间下, 使用relative import path.只能使用完整的import path. - 非
远端导入路径 remote import path
某些import path还描述了如何使用版本控制系统(如git)获取package的源代码。
这里以github举例:
GitHub (Git)
import "github.com/user/project"
import "github.com/user/project/sub/directory"
为了表示代码的位置, import path需要指定哪个仓库, 以及仓库中的代码路径.
repository.vsc/path # vcs后缀可以没有(version contorl system)
依旧以git举例:
import "example.org/repo.git/foo/bar" 表明:pacakge的根目录在git仓库的example.org/repo/, 仓库内的路径是foo/bar/.
当使用GOPATH时, 下载的包都会写入GOPATH环境变量的第一个目录. (go help gopath-get)
当使用modules是, 下载的包会写入module的缓存中.(go help module-get)
packages && import path
go命令, 大多数是这样的形式: go command [packages]. 通常[packages]是一个import paths列表.
我们首先要知道, 我们通常都是使用导入路径 import path去标识一个包 package 的. 所以我们首先要弄明白什么是package的import path.
给出一个import path, 我们要先对其进行解析(通过file system/ GOPATH/ mdoules), 解析出来的目录, 就是pacakge代码的存放目录.
本地包的 import path
本地宝import path会有两种解析方式:
-
case1: 通过
local file system去解析import path针对以下两种情形一个
import path会通过文件系统路径去查找:- 绝对路径
rooted path( /xxx/ooo )只适用于命令行, 程序中不能使用绝对路径! - 或者以
...开头的相对路径 只适用于非workspace的情况
- 绝对路径
-
case2: 通过
GOPATH去解析import path(见上面的章节: gopath)不符合case1的
import path, 表示该pacakge是在$GOPATH/src/<import path>下的.
如果go命令中没有指定import path, 那么action将作用于当前目录下的pacakge.
pattern 模式
如果import path包含一个或者多个... 通配符, 那这个import path就是一个pattern 模式. 每个通配符可以匹配字符串(包括"/"和空字符串). 这种模式会在$GOPATH目录树下, 查找所有符合该模式的package 目录.
关于通配符, 有两种特殊情形需要了解:
- case1:
/...出现在pattern的最后, 可以匹配空字符串.所以模式net/...既可以匹配net, 也可以匹配它子目录中的packages, 如net/http. - case2: 模式中由
/分割的的元素, 如果是一个通配符..., 那么这个通配符不会匹配vendor. 如./...不会去匹配./vendor, 或者./mycode/vendor.
远端包的 import path
一个import path也可以命名一个需要从远端仓库下载的package. (见上面的章节: import path).
程序中的每个package都必须有一个唯一的import path. 按照惯例, import path都会有一个唯一的前缀. 比如在谷歌, 内部使用的import path都是以google开头, 而远端仓库的包的import path都以远端仓库中该包的路径为前缀, 如: github.com/user/repo.
程序中packages的包名不需要唯一(因为我们通过import path去标识一个包). 但是有两个保留的package name: package main 和 package documentation. package main表明当前的包是一个命令 command, 不是一个库文件 library. 命令包会被编译成可执行文件. package documentation中的文件会被go命令忽略.
有一个特殊情况, 如果[packages]是同一个文件夹中的.go文件列表. 则go命令会被作用到由这些文件构成的package上(该目录下的其他文件都会被忽略).
以., _开头的文件或者目录都会被go命令忽略.
名字是testdata的目录也会被忽略.
.go源文件中的package import:
上面说的是go命令中的import path, 那么在源文件中, 需要导入其他package时, 应该怎么处理呢? 这里简单总结一下, 具体看下面的文章:
-
没有
module(module范围外)的情况下, package的import path是指$GOROOT/src/subdirectory path to package- 或者
$GOPATH/src/subdirectory path to package - 或者是
相对路径
-
有
module的情况下, package的import path是指module path/subdirectory path to pacakge
go build
前提要知道: build命令编译是以pacakge为单位的.
build会把命令行中import path指定的包, 联通它的依赖一起进行编译, 但是不会安装(放到指定的目录中).
build的参数类型
-
build没有参数 执行
go build命令时不后跟任何参数,那么命令将试图编译当前目录所对应的package. -
build的参数是一个文件夹的中的
.go文件列表如果build命令的参数是同一个文件夹中的
.go文件列表, 则build命令会认为由这些源文件构成了一个package. 并且在编译这个package的时候, build命令会忽略以_test.go结尾的文件.注意: 以列表形式给出的
.go文件, 必须都在同一个文件夹中, 其实很好理解, 因为build认为他们构成了一个package, 不同的文件夹自然是不同的package. -
build的参数是一个
package的import path -
build的参数是多个
packages的import paths
build的结果/产出
有产出的情况
当build命令编译的是单独一个main package的时候, build命令会产出一个可执行文件, 该文件的命名有两种方式:
- 如果build参数是
.go文件列表, 则可执行文件会以文件列表的第一个源文件命名(go build ed.go rx.go会产出一个ed可执行文件). - 如果build参数是一个
package的import path, 则会以import path中的包所在的目录名进行命名(go build unix/sam, 产出sam可执行文件).
没有产出的情况
如果build命令编译多个packages(指定多个import path), 或者编译一个non-main pacakge, build命令会编译这些pacakges, 但是会丢弃产出的结果, 仅仅测试一下这些包能否被编译.
-o参数: 将编译结果写入到指定文件/目录
当有-o参数的时候, 会强制保留编译结果.
-
-o指定了一个文件名fileName将单个
package的编译结果, 写入到该文件. 此时不管该package是不是main, 都会把编译结果写到这个文件里.go build -o fileName import_path go build -o fileName x.go x2.go ...build命令会生成fileName文件. -
-o 指定一个文件夹
directory将编译产生的可执行文件, 放入到directory下.注意, 存放目录要存在, 否则会报错(当成是文件名)
go build -o direcory import_path1 import_path2 ....会将编译的pacakge产生的可执行文件, 放到
directory/下.
build的常用参数(和clean, get, install, list, run, test命令通用)
-v: 在编译package的时候, 打印出这些包的名字.-work: 打印临时工作目录, 并在编译结束时, 不删除这些目录.
go install
命令go install用于编译并安装指定的代码包及它们的依赖包。当指定的代码包的依赖包还没有被编译和安装时,该命令会先去处理依赖包。
实际上,go install命令只比go build命令多做了一件事,即:安装编译后的结果文件到指定目录。
这个命令在内部实际上分成了两步操作:第一步是生成编译结果文件(可执行文件或者 .a静态链接库文件),第二步会把编译好的结果移到 GOPATH/bin。
- case1: 如果一段程序是属于
package main的(这个包可执行),那么当执行go install <import path>的时候就会将其生成二进制文件,放到$GOPATH/bin/目录下。 - case2: 如果一段程序是属于
package main以外的其他包,那么当执行go install <import path>的时候,就会在$GOPATH/pkg目录创建一个 静态链接库.a文件 。
go get
命令go get可以下载或更新所需的依赖(module),并对它们进行编译和安装。
并会把更新/安装的依赖信息写入当前moduel的go.mod文件.
这个命令在内部实际上分成了两步操作:
-
step1: 第一步是确定添加哪些依赖, 同时确定这些依赖的版本.
go get xxxx@version可以用@version指定要获取的module版本. 如果没有指定@version, 默认是安装/更新依赖到@latest版本.go get github.com/username/repo-name # 默认@latest # 指定module版本 (通过版本号/分支名/commitID指定) go get golang.org/x/text@v0.3.0 go get golang.org/x/text@master注意: 尽管
go get命令, 会安装/更新(提供package的)module到@latest版本, 但是并不会安装/更新这个module自己依赖的module到latest版本.太绕了, 举个例子:
假设
latest版本的module A需要module B v1.2.3, 而module B还有以下几个版本v1.2.4,v1.3.1可供选择 , 那么go get A会安装/更新latest版本的 A, 但是会安装/更新 被A所依赖的版本B v1.2.3.到这里, 就不得不提一下
-u参数, 如果加上这个参数go get -u, 就会让get命令去更新提供命令行中 package依赖的module到@latest版本(最新的次要/修正版本).这句话汉语很难翻译: 我贴一下原文, 比较好理解: The -u flag instructs get to update modules providing dependencies of packages named on the command line to use newer minor or patch releases when available.
依旧是上面的例子, 如果命令式
go get -u A, 会使用latest版本的A, 并且会使用 B的v1.3.1 版本 (not B v1.2.3). 但是如果 B 又依赖于moduleC, 但是 C 又没有在A的build过程中提供任何package(这里不包括_test.go, 因为测试文件不参与编译), 那么 C 不会被更新到latest版本.下载的包会放在
$GOPATH/src/下, 引用的时候如下:import "github.com/username/repo-name" -
step2: 第二步是下载/编译/安装这些package.
如果命令行中的参数是一个
module, 而不是一个package(module的根目录没有go源码文件的情形), 那么就不会执行install.
参考链接--go编译与工具