Go指南-GOPATH依赖管理

2,647 阅读2分钟

前言

环境:go1.11.13 linux/amd64
修订:2020.09.17

GOPATH是Golang早期依赖管理的一种方式,随着Go的不断发展,现在已经慢慢被GoModule替代,本文算是GOPATH的一篇纪念文(爷青结)。

环境模拟

本次环境模拟使用 Golang1.11 镜像

# docker 三件套
$ docker pull golang:1.11

$ docker run -itd --name gopath golang:1.11 bash

$ docker exec -it gopath /bin/bash

# 查看GOPATH
root@75bf2f4ce5e5:/go# export
declare -x GOLANG_VERSION="1.11.13"
declare -x GOPATH="/go"

运行入口

一般来讲,学习一门编程语言时,第一件事应该是让我们编写的代码跑起来,Golang也不例外。

要想让Go程序跑起来非常简单,随便找个目录,新建一份 test.go 文件,写入下面的内容:

// package main 代表这是主程序, 可运行的
package main

import "fmt"

func main() {
   fmt.Println("Hello world")
}

接着运行 go run test.go 就会出现 "Hello world" 了,Golang的运行就是如此简单。

GOPATH

就个人的使用而言,GOPATH既是一种依赖管理机制,也是一种项目管理机制。

GOPATH的主要作用有两个:

  • 1.指定我们的工作区目录,并规定bin、pkg、src三个目录来存放不同类型的文件(体现了项目管理)
  • 2.指定我们的依赖下载路径以及导入路径(体现了依赖管理)

GOPATH项目管理

工作区其实就是将所有Go代码放在同一个地方/目录,它不像Python项目那样可以随处创建,随处运行(后面GoModule支持了),具体项目结构如下:

$ pwd
/go

# 单工作区管理形式
$ tree -L 3
.
├── bin
│   ├── glide
│   ├── gocode
│   ├── ...
├── pkg
│   └── linux_amd64
└── src
    ├── github.com  # 外部库
    └── gopkg.in
    ├── git.own.com  # 自己的项目集合
    │   ├── project1 # 项目1
    │   │     ├── .git
    │   │     ├── main.go  # 主程序
    │   ├── project2  # 项目2
    │   
    ├── git.alibaba.com  # 如果你在阿里巴巴工作过,就可以建一个阿里巴巴项目集合
    ├── git.tencent.com # 如果你在腾讯工作过,就可以建一个腾讯项目集合
    ├── git.baidu.com # 百度亦是如此

这种方式的核心概念有:

  • 1.必须在环境变量中声明GOPATH路径,即我们的工作区根路径,如上文中的 GOPATH="/go"
  • 2.每个工作区下包含三个目录:bin、pkg和src。bin目录存放的是可执行文件,pkg目录存放的是静态链接库文件,src目录存放的是源码文件,即我们编写的代码。

由于这种方式将所有Go代码都集中在一起,所以怎么管理以及命名也是一个问题,在上面我也提供了一个思路(具体可以看个人):
1.首先是按公司名进行第一层级划分,比如 git.alibaba.com,这个名字一般是用公司Gitlab/Github仓库的域名。
2.按项目名称进行第二层级的划分,如上文的 project1project2,每个项目单独管理。

以上是一种单工作区的管理方式,实际上由于需要,也衍生出了多工作区的概念。不过多工作区的管理比较麻烦,需要随时切换GOPATH环境变量,因为go get下载的依赖的路径是依据GOPATH的,所以如果没有及时切换工作区路径,就有可能出现想要在A工作区下载依赖,但却不小心下载到B工作区。

GOPATH依赖管理

前面提到,GOPATH其实也规定了依赖下载路径以及导入路径,即 $GOPATH/src 目录下。

自己封装的依赖

从下面的例子可以看到,依赖的导入路径是相对于 $GOPATH/src 的。

# 进入源码目录,我的GOPATH路径是 /go
$ cd /go/src

# 创建项目以及自定义依赖
$ mkdir -p gopath-test/hello

# 新建一份hello.go; vim gopath-test/hello/hello.go
package hello

func HelloWorld() string {
  return "Hello world"
}

# 新建一份main.go用于测试: vim gopath-test/main.go
package main

import (
    "fmt"
    "gopath-test/hello"
)

func main() {
    text := hello.HelloWorld()
    fmt.Println(text)
}

下载第三方依赖

这里以依赖第三方库gin,并启动一个HTTP服务器为例:

1.编写测试代码:

package main

import "github.com/gin-gonic/gin"

func main() {
	r := gin.Default()
	r.GET("/ping", func(c *gin.Context) {
		c.JSON(200, gin.H{
			"message": "pong",
		})
	})
	r.Run() // listen and serve on 0.0.0.0:8080 (for windows "localhost:8080")
}

可以看到,直接go run并不能自动下载依赖

$ go run main.go 
main.go:3:8: cannot find package "github.com/gin-gonic/gin" in any of:
        /home/go/src/github.com/gin-gonic/gin (from $GOROOT)
        /home/go-projects/src/github.com/gin-gonic/gin (from $GOPATH)

2.手动下载依赖并测试

$ go get -v github.com/gin-gonic/gin

# 可以看到,源码已经下载到src目录了
$ ls /go/src/github.com/
gin-contrib  gin-gonic	go-kit	go-playground  golang  leodido	mattn  ugorji

# 再次执行,运行成功
$ go run main.go 
[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.

[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
 - using env:   export GIN_MODE=release
 - using code:  gin.SetMode(gin.ReleaseMode)

[GIN-debug] GET    /ping                     --> main.main.func1 (3 handlers)
[GIN-debug] Environment variable PORT is undefined. Using port :8080 by default
[GIN-debug] Listening and serving HTTP on :8080

可以看到,当使用GOPATH进行依赖管理时,第三方下载的依赖也会放在 $GOPATH/src 目录下

补充

1.GOBIN VS GOROOT VS GOPATH

这应该是初学者最困惑的问题之一,这三个环境变量到底有什么区别?

GOPATH:这是我们的工作区路径,上面已经讲过,这里不再阐述,一般设置这个即可。

GOBIN: 当使用 go install xx.go 时, 生成的可执行文件就会放在此目录,如果没有设置,一般会报这样的错

$ go install test.go 
go install: no install location for .go files listed on command line (GOBIN not set)

GOROOT:Go的安装位置,对于我来说是 /usr/local/go, 一般用于寻找标准库。

2.源码文件类型

  • 1.command source:这是命令源码文件,即包名是 main 的源码文件,能够被执行
  • 2.package source:这是包源码文件, 即包名非 main 的源码文件,不能够被执行,一般是自己封装的一些功能函数
  • 3.test source:这是测试源码文件,用于单元测试

3.Go常用命令

Go 常用的命令一般有以下几个:

  • go get: 安装外部库
  • go install: 编译源码文件, 生成可执行文件, 可执行文件存放在GOBIN目录
  • go run: 运行源码文件, 不会生成可执行文件
  • go build: 编译源码文件,生成可执行文件, 可执行文件在当前目录
  • go test: 运行测试源码文件

4.其他依赖管理方式

虽然 Golang 提供了 go get 命令下载外部库,不过细心的你可能发现,go get并没有版本管理,它默认安装的是外部库的最新版本。

这会导致一个问题,即我们本地环境与正式环境下载的依赖版本不一定一致,从而导致一些意想不到的问题。比如我们本地用的是1.1.1版本,但是上到服务器部署时,外部库更新了版本,变成了1.1.2,如果外部库改动大,难免就会发生很多不兼容的问题。

所以在早期的时候,出现了很多的依赖管理工具,比如 go vendorglide等,不过现在推荐使用 GoModule,毕竟是亲儿子。