包
在刚学 Go 的时候,尤其是之前有学过 Java 的时候,会对 Go 的导包感觉优点奇怪
首先要明确的是,Go 导入的是文件夹(文件夹下的 package),Java 导入的是具体的文件
当然了关于 Java 上的别名,静态导入,匿名类加载在 Go 上都有这些都有,知识语法不同,概念上也没啥区别
// Go的导包
import (
"fmt"
"github.com/opesun/goquery"
)
//Java的导包
import org.dom4j.DocumentException;
比如经常导入的 fmt 来分析
GO 编译器依次会去 GOROOT/src,GOPATH1/src,GOPATH2/src … 下去寻找名为 fmt 的文件夹
文件夹下面有许多的 go 文件,所有的以大写开头的变量,函数,结构等都具有访问权限
fmt.Println()
的 Println 其实是 print.go 里的一个方法
那么疑问又来了,每个 Go 文件的第一行都会写 package xxx,那么这又是做什么用的呢
其实严格意义上说 Go 语言导入的包后,是通过 package name 来使用
fmt 文件夹下的所有 go 文件的第一行都是package fmt
,包名与文件夹名是相同的,所以体现不出来,当然包名和文件夹名相同是一种规范的做法
比如这么一个文件 tmaize.net/demo/haha/Print.go,我们的包名设为 common
package common
import "fmt"
func P(msg string){
fmt.Println(msg)
}
那么使用的时候
import "tmaize.net/demo/haha"
haha.P("Hello World") //报错
common.P("Hello World") //这才是对的
所以:这就很好理解为什么一个文件夹下所有的 go 文件(一级目录)的 package name 只能取同一个名字了
为什么这么设计我个人理解应该是划分模块避免单个文件过大,抑或是容易针对单个文件做测试案例?
通过下面是 Java 和 Go 的 dmeo 应该比较容易看出来
Go
common/func1.go
package Common
func Func1(){}
common/func2.go
package Common
func Func2(){}
Java
Common.java
public class Common {
// 两个语言的命名规范真的是....
public static void Func1() {}
public static void Func2() {}
}
最后要注意的是最好不要使用 local import
如果使用了,那么针对项目go build tmaize.net/demo
会报错的local import "./haha" in non-local package
local import 的主要用途是写简单的小脚本非项目,直接对文件进行 go run,go build 是没问题的
依赖管理
在看完包的时候,大致就应该明白了 Go 的依赖管理方式
当我们的项目需要依赖自己(不要使用相对路径)/别人项目时候,通过 import 很容易就能把别人的轮子导进来使用
GO 编译器依次会去 GOROOT/src,GOPATH1/src,GOPATH2/src … 下去我们 import 的路径
但是肯定会有这种情况,我们要用到 A 项目,但是 A 项目又依赖 B 项目,但是 A 项目在 GOPATH 是存在的,但是 B 项目我们没有,难道要自己找到 B 项目,然后手动放到 GOPATH 的 src 下吗?
这时候就应该有一个依赖管理工具了,比如 Java 的 Maven 和 Gradle
对应的 Java 的依赖管理工具就是 go get,使用 go get A 项目的时候会自动分析 A 项目的依赖,然后吧他们都下载到 GOPATH 的第一个路径中
但是问题又来了,go get 实际就是 get clone,下载的都是最新的,没办法指定版本,而且我们的两个项目依赖同一个第三方库的不同版本,依赖脱离于项目等等这些都是问题
为此,go 在后来的版本中加入了 vendor。就是优先从项目源码树根目录下的 vendor 目录查找代码,这样就吧依赖和项目合到一起了,类似于 NodeJs 的 node_modules 目录,但是指定版本来事没办法实现,只能手动到 vendor 目录下切换依赖版本
所以又有了 godep,glide,govendor 等工具
golang.org 的官方依赖无法安装
由于网络原因一些 golang.org 下的包无法,比如go get -u golang.org/x/tools/
其实是可以从 github 下载的,在 GOPATH 下的 src 目录下新建golang.org/x
再在下面使用git clone
拉取 github 上的镜像就行了
镜像地址:https://github.com/golang
项目构建
以编译 godoc 为例
编译到当前目录
go build golang.org/x/tools/cmd/godoc
编译到 GOPATH/bin
go get golang.org/x/tools/cmd/godoc
交叉编译
所谓交叉编译不是跨平台,而是就是在一个平台上生成另一个平台上的可执行代码
Go 是支持交叉编译的,只需要设置两个环境变量就行了,下面是两个平台下的构建脚本
Windows 系统,build.bat
set GOOS=windows
set GOARCH=amd64
go build -o dist/win_demo.exe demo.go
set GOOS=linux
set GOARCH=amd64
go build -o dist/linux_demo demo.go
Linux 系统,build.sh
,注意脚本的换行格式为 LF
#!/bin/bash
export GOOS=windows
export GOARCH=amd64
go build -o dist/win_demo.exe demo.go
export GOOS=linux
export GOARCH=amd64
go build -o dist/linux_demo demo.go
Go Mod
对于 go 使用 GOPATH 的麻烦就不说了,终于在 1.11 版本后,官方推出了 Go Mod
在 GoLand 中 File -> Settings -> Go -> Go Modules(vgo) 可以开启
以前建立一个项目的做法是在 GOPATH 的 src 下面建立xxx.com/username/projectname
目录
使用 go mod 直接在任意一个位置建立目录就行了,然后执行go mod init [module name]
。它会初始化一个go.mod
文件,一般内容如下
module github.com/tmaize/file-manager
go 1.12
require gopkg.in/yaml.v2 v2.2.2
# exclude 排除依赖,多个项目依赖同一个项目的不同版本,可以设置排除某个版本
# replace 有时候网络不行的时候可以把golang.org/x/.. 下的包 使用github.com/golang/x进行替换
# replace 也可以替换为本地项目路径
终端输入go mod
可以查看所支持的命令,下面是几个常用的命令
download 下载go.mod文件中的依赖到本地,${GOPATH/pkg/mod}下面
edit 这个就算了,直接编辑文件更省事
graph 列出所有依赖
init 初始化项目
tidy 分析项目代码,在go.mod文件中添加缺少,删除多余的依赖
vendor 复制依赖到项目的verdor目录中
verify 验证依赖是否缺少
why 展示依赖关系
最后还有一个环境变量GO111MODULE
,取值有 off,
on
或者auto
(默认)
off: 在 vendor 目录下和 GOPATH 目录中查找依赖包
on: GOPATH 不再在 build 时扮演导入的角色,但是尽管如此,它还是承担着存储下载依赖包的角色。它会将依赖包放在 GOPATH/pkg/mod 目录下
auto: 只有当前目录在GOPATH/src
目录之外而且当前目录包含go.mod
文件或者其子目录包含 go.mod 文件才会启用。
GOPROXY
由于网络原因,一些包下载不下来,在 Go 1.11 后,开始支持配置 GOPROXY 代理下载,只需要在环境变量里配置 GOPROXY 即可
这里推荐https://goproxy.io,提供了现成的免费服务,同时也提供了工具方便自己搭建
测试是否生效可已下载个golang.org
下的包试试,一般这个网络是访问不到的比如:
go get -u golang.org/x/tools/cmd
GOSUMDB
在 Go 1.13 中新增的,可以用来校验你下载的库的哈希是否和官方的哈希值是否一样,避免被 proxy 给修改了
默认的 GOSUMDB 服务器是 google 的,有时候会提示找不到,可以设置如下值来解决
# 关闭
off
# 国内的代理
sum.golang.google.cn
# 七牛云的代理
goproxy.cn
Go Get
有了 Go Mod 后,go get
的功能也有些许变化
go mod 项目下,执行go get -u
,依赖会被下载的到GOPATH/pkg/mod
目录,同时也会在项目的 go.mod,go.sun 这个两个依赖描述文件中添加依赖的版本信息
go mod 项目下,go get ./...
会查找依赖,并记录在 go.mod 文件中
在任何路径下执行go get domain/user/project/...
相当于go get
和go install
,命令行工具会安装到$GOPATH/bin
中
依赖版本升级
依赖的版本是使用 git 的 tag 来控制的,当go get
的时候会优先使用最新 tag 对应的版本,即最新 v0 或者 v1 的稳定版,你也可以指定版本号go get -u xxx/xxx@1.0.1
如果没有 tag 的话会使用 master 分支最新提交,但是有时候go get
下来的并不是 master 分支的最新代码,可能是由于 goproxy 缓存的原因。所以当升级项目后一定要更新 tag
因此 tag 的起名需要符合规范vMAJOR.MINOR.PATCH
- v 所有版本号都是 v 开头。
- MAJOR 主版本号,如果有大的版本更新,导致 API 和之前版本不兼容
- MINOR 次版本号,当你做了向下兼容的新 feature
- PATCH 修订版本号,当你做了向下兼容的修复 bug fix
对于没有 tag 的项目,系统会自动起一个版本号,一般是v0.0.0-yyyymmddhhmmss-hash
- 运行
go get -u
将会升级到最新的次要版本或者修订版本 - 运行
go get -u=patch
将会升级到最新的修订版本 - 运行
go get package@version
将会升级到指定的版本号
升级 V2
所有的go get -u
来升级项目依赖,只会在 MINOR 和 PATCH 上进行升级,不会进行 MAJOR 的升级
同时你会发现 MAJOR 一般都是 0 或者 1,很少见到 v2.0.0 以上的项目
应为这里有个规定go get
下来的是 v0 和 v1 的最新版本,当升级到 v2,v3 时,依赖的路径有变化
下面是一个项目在 v1 和 v2 下的依赖路径的差异
github.com/caddyserver/caddy v1.0.4
github.com/caddyserver/caddy/v2 v2.0.0-beta9 // indirect
同时下面也是一个错误的指定版本的方式
go get -u github.com/caddyserver/caddy@v2.0.0-beta9
# 正确做法
go get -u github.com/caddyserver/caddy/v2@v2.0.0-beta9
所以自己的项目升级 v2 的最佳方式就是新建一个 v2 的分支,然后修改 go.mod 文件的第一行,在后面添加/v2。然后把项目内的对自己的依赖的 import 后面加上 v2,然后新建 v2.0.0 的 tag。
这样的好处是新用户可以使用到最新的版本,同时 v1 的不用担心升级后 API 不兼容的问题,因为 v1 和 v2 用户的 mod name 不同