狼窝 2020-02-02
本文介绍一些关于golang工程管理相关的东西。首先介绍golang一些重要的环境变量,有关golang的环境变量可以用以下命令查看: go env
# 此设置依赖go版本为1.11或以上 # 打开.bashrc或其他shell配置文件 export GO111MODULE=on export GOPROXY=https://goproxy.io
$GOPATH环境变量所指定的目录称为go的工作目录,$GOPATH可以配置多个目录,工作目录有相同的目录结构,包含src/pkg/bin三个子目录。
src是工程的源码所在目录,一般src下的第一层目录是工程根目录,工程根目录一般采用公司的域名+工程名或用户名的格式,比如常见的github上的工程源代码组织形式:
$GOPATH/src/github.com/github/ $GOPATH/src/github.com/golang/
工程根目录下才是工程各个项目的目录,项目目录下可以是其源代码文件和各种包的源码,这是一种推荐的代码组织形式。举个例子:$GOPATH/src/github.com/github/go-ost。$GOPATH/src/github.com/github/是github工程根目录,go-ost是具体的项目目录,gh-ost内是该项目的源代码和包。
$GOPATH环境变量可以配置多个目录,使用go get下载第三方的包时,默认会将包下载到$GOPATH的第一个目录里面,很多人喜欢在$GOPATH里配置两个目录,第一个专门用于下载第三方包,第二个目录用于内部工程目录,但官方推荐的做法是只配置一个目录,通过dep来管理。
下面介绍golang的交叉编译方法。golang对交叉编译有很好的支持,唯一的不足是不支持CGO。在go1.4及以前版本中,由于编译器是使用C语言写的,交叉编译比较麻烦,先要在当前平台构建一个目标平台的编译环境,然后才能通过设置$GOOS和$GOARCH进行交叉编译。golang编译工具在1.5及以后版本中完全使用golang重写,golang编译器内置交叉编译的功能,只需要设置$GOOS和$GOARCH就可以进行交叉编译。下面看一个具体示例:
package main import ( "fmt" "runtime" ) func main() { fmt.Printf("OS: %s\nArchitecture: %s\n", runtime.GOOS, runtime.GOARCH) }
打开终端,执行 CGO_ENABLED=0 GOGOS=linux GOARCH=amd64 go build ./main.go 即可编译。
下面介绍命名空间(namespace)。命名空间在编程语言中常用来表示标识符(identifier)的可见范围。编程语言借助命名空间来解决标识符不能同名的问题,命名空间实际上相当于给标识符添加了标识前缀,使标识符变得全局唯一。另外,命名空间使程序组织更加模块化,降低了程序内部的耦合性。
一个标识符可在多个命名空间中定义,它在不同命名空间中的含义的互不相干的。在一个新的命名空间中可定义任意的标识符,它们不会与位于其他命名空间上的同名标识符发生冲突,当然自定义标识符尽量不要使用语言自身的关键字,这些标识符具有全局作用域。golang继承了命名空间的概念,采用包来组织代码,包名构成go命名空间的一部分,不同的包就是一个独立的命名空间。golang除了包级别显式的命名空间,还有隐式的命名空间。函数、方法、以及if、for、switch等和“{}”一起构成一个个代码块,代码块可以嵌套,每个代码块都构成一个隐式的命名空间。不同命名空间可以声明相同的标识符,所以不同的隐式的命名空间同样允许声明相同的标识符(包括变量),这里就有变量覆盖的问题。在介绍变量覆盖之前,先介绍作用域。
在高级语言编程中,作用域(scope)是指名字与实体(可以理解为特定内存地址)的绑定保持有效的那部分程序逻辑区间。golang是静态作用域的语言。所谓静态作用域就是变量的作用域不依赖程序执行时的因素,变量作用域在编译期就能确定。
golang有三种类型作用域:
golang编译器解析变量名到引用实体采用的是从里到外的搜索模式,里层的局部变量能够覆盖外层变量,使得外层的同名变量不可见,这种现象称为变量覆盖。
golang是使用包来组织源代码,并实现命名空间的管理。任何源代码文件必须属于某个包。源码文件第一行必须是package packageName,通过该语句声明自己所在的包。golang的包借助了目录树的组织形式,一般包的名称就是其源文件所在目录的名称,虽然golang没有限制包名必须和其所在目录名同名,但还是建议同名,这样结构更清晰。包可以定义在很深的目录中,包的定义是不包括目录名称的,但是包的引用一般是全路径引用。
标准包的源码位于$GOROOT/src/下面,标准包可以直接引用。自定义的包和第三方包的源码必须放到$GOPATH/src/目录下才能被引用。包引用路径有两种写法,一种是全路径,另一种是相对路径。全路径就是“$GOROOT/src/和$GOPATH/src/”后面包的源码的全路径。相对路径只能用于引用$GOPATH下的包,标准包的引用只能使用全路径引用。
包引用有四种引用格式,以fmt标准库为例:
golang包的初始化有以下特点:
golang的版本管理直到go1.5引入vendor才有。vecdor机制就是包中引入vecdor目录,将依赖的外部包复制到vecdor目录下,编译器在查找外部依赖包时,优先在vecdor目录下查找。整个查找第三方包的流程如下:
vecdor将原来放在$GOPATH/src/的第三方包放到当前工程的vecdor目录中进行管理。它为工程独立的管理自己依赖的第三方包提供了保证,多个工程独立地管理自己的第三方依赖包,它们之间不会相互影响。vecdor将原来包共享模式转换为每个工程独立维护的模式,vendor的另一个好处是保证了工程目录下代码的完整性,将工程代码复制到其他go编译环境,不需要再去下载第三方包,直接就能编译,这种隔离和解耦的设计思路是一大进步。
然而vendor有一个重要的问题没有解决,那就是对外部依赖的第三方包的版本管理。通常使用go get -u更新第三方包。默认的是将工程的默认分支的最新版本拉取到本地,并不能指定第三方包的版本。go官方的包依赖管理工具dep就是为了解决该问题而出现的,与此同时社区也有很多包管理工具,如godep、govendor、glide等。目前dep并不会取代go get,go get只是一个便捷的方式。
建议使用以下方式安装dep:
curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh
如果需要,请使用proxychains(笔者就是这么做的)。
使用dep init命令初始化工程,该命令可以用于新项目,也可以用于已存在的项目。该命令会在当前目录创建三个文件: Gopkg.toml Gopkg.lock vecdor 。dep通过两个元文件来管理依赖:manifest文件Gopkg.toml和lock文件Gopkg.lock。Gopkg.toml可以由用户自由配置,包括依赖的source、branch、version等。Gopkg.lock仅描述工程当前第三方包版本视图。Gopkg.toml可以通过命令产生,也可以被用户手动修改;Gopkg.lock是自动生成的,不可以手动修改;vendor目录下存放具体依赖的外部代码。
dep init会做如下的事情:
下面来看一下Gopkg.toml的几个配置项。
[[constraint]]:指定直接依赖的包的相关元信息,是用户重点维护的信息,其格式为:
[[constraint]] name = "github.com/user/project" version = "1.0.0" [[constraint]] name = "github.com/user/project2" branch = "dev" source = "github.com/myfork/project2"
[[constraint]]必须指定依赖包的如下属性中的一个:version(相当于git中的tag)、branch、revision(相当于git中的commit)、source(备选仓库源)
[[override]]:强制设置包的版本元信息,既可用于直接依赖,也可用于间接依赖。通过override声明的包信息会覆盖所有constraint声明的包信息,在实际工程中尽量避免使用override管理依赖包
constraints和overrides被用户用来指定依赖包的哪些版本是需要管理的,以及从哪里获取该版本的包。required和ignored被用来控制哪些包纳入管理,哪些被忽略。
dep的整个工作流如下: