go标准命令详解0.3 go get

搬运自github赫林的go_command_tutorial,绝对干货,感谢作者。

hc@ubt:~$ go get github.com/hyper-carrot/go_lib

命令go get可以根据要求和实际情况从互联网上下载或更新指定的代码包及其依赖包,并对它们进行编译和安装。在上面这个示例中,我们从著名的代码托管站点Github上下载了一个项目(或称代码包),并安装到了环境变量GOPATH中包含的第一个工作区中。在本机中,这个代码包的导入路径就是github.com/hyper-carrot/go_lib。

一般情况下,为了分离自己与第三方的代码,我们会设置两个及以上的工作区。我们现在新建一个目录路径为~/golang/lib的工作区,并把这个工作区路径作为环境变量GOPATH值中的第一个目录路径。注意,环境变量GOPATH中包含的路径不能与环境变量GOROOT的值重复。如此一来,如果我们再使用go get命令下载和安装代码包,那么这些代码包就都会被安装在这个新的工作区中了。我们暂且把这个工作区叫做Lib工作区。假如我们在Lib工作区建立和设置完毕之后运行了上面示例中的命令,那么这个代码包就应该会被保存在Lib工作的src目录下,并且已经被安装妥当,如下所示:

$HOME/golang/lib:
    bin/
    pkg/
        linux_386/
            github.com/
            hyper-carrot/
        go_lib.a
    src/
        github.com/
        hyper-carrot/
        go_lib/
    ...

从另一个角度来说,如果我们想把一个项目上传到Github站点(或其他代码托管站点)上,并被其他人使用的话,那么我们就应该把这个项目当做一个代码包来看待。我们在之前已经提到过原因,go get命令会将项目下的所有子目录和源码文件存放到工作区src目录下,而src目录下的所有子目录都会是某个代码包导入路径的一部分或者全部。也就是说,我们应该直接在项目目录下存放子代码包和源码文件,并且直接存放在项目目录下的源码文件所声明的包名应该与该项目名相同(除非它是命令源码文件)。这样做可以让其他人使用go get命令从Github站点上下载你的项目之后直接就能使用它。

实际上,像goc2p项目这样直接以项目根目录的路径作为工作区路径的做法是不被推荐的。之所以这样做主要是想让读者更容易的理解Go语言的工程结构和工作区概念,也可以让读者看到另一种项目结构。

* 远程导入路径分析*

实际上,go get命令所做的动作也被叫做代码包远程导入,而传递给该命令的作为代码包导入路径的参数又被叫做代码包远程导入路径。

实际上,go get命令不仅可以从像Github这样著名的代码托管站点上下载代码包,还可以从任何命令支持的代码版本控制系统(英文为Version Control System,简称为VCS)检出代码包。任何代码托管站点都是通过某个或某些代码版本控制系统来提供代码上传下载服务的。所以,更严格的讲,go get命令所做的是从代码版本控制系统的远程仓库中检出/更新代码包并对其进行编译和安装。

该命令所支持的VCS的信息如下表:

表0-2 go get命令支持的VCS

| 名称 | 主命令 | 说明 |
| Mercurial | hg | Mercurial是一种轻量级分布式版本控制系统,采用Python语言实现,易于学习和使用,扩展性强。 |
| Git | git | Git最开始是Linux Torvalds为了帮助管理 Linux 内核开发而开发的一个开源的分布式版本控制软件。但现在已被广泛使用。它是被用来进行有效、高速的各种规模项目的版本管理。 |
| Subversion | svn | Subversion是一个版本控制系统,也是第一个将分支概念和功能纳入到版本控制模型的系统。但相对于Git和Mercurial而言,它只算是传统版本控制系统的一员。 |
| Bazaar | bzr | Bazaar是一个开源的分布式版本控制系统。但相比而言,用它来作为VCS的项目并不多。 |

go get命令在检出代码包之前必须要知道代码包远程导入路径所对应的版本控制系统和远程仓库的URL。

如果该代码包在本地工作区中已经存在,则会直接通过分析其路径来确定这几项信息。go get命令支持的几个版本控制系统都有一个共同点,那就是会在检出的项目目录中存放一个元数据目录,名称为“.”前缀加其主命令名。例如,Git会在检出的项目目录中加入名为“.git”的子目录。所以,这样就很容易判定代码包所用的版本控制系统。另外,又由于代码包已经存在,我们只需通过代码版本控制系统的更新命令来更新代码包,因此也就不需要知道其远程仓库的URL了。对于已存在于本地工作区的代码包,除非要求更新代码包,否则go get命令不会进行重复下载。如果想要求更新代码包,可以在执行go get命令时加入-u标记。这一标记会稍后介绍。

如果本地工作区中不存在该代码包,那么就只能通过对代码包远程导入路径进行分析来获取相关信息了。首先,go get命令会对代码包远程导入路径进行静态分析。为了使分析过程更加方便快捷,go get命令程序中已经预置了著名代码托管站点的信息。如下表:

表0-3 预置的代码托管站点的信息

| 名称 | 主域名 | 支持的VCS | 代码包远程导入路径示例 |
| Bitbucket | bitbucket.org | Git, Mercurial | bitbucket.org/user/project
bitbucket.org/user/project/sub/directory |
| GitHub | github.com | Git | github.com/user/project
github.com/user/project/sub/directory |
| Google Code | code.google.com | Git, Mercurial, Subversion | code.google.com/p/project
code.google.com/p/project/sub/directory
code.google.com/p/project.subrepository
code.google.com/p/project.subrepository/sub/directory |
| Launchpad | launchpad.net | Bazaar | launchpad.net/project
launchpad.net/project/series
launchpad.net/project/series/sub/directory
launchpad.net/~user/project/branch
launchpad.net/~user/project/branch/sub/directory |

一般情况下,代码包远程导入路径中的第一个元素就是代码托管站点的主域名。在静态分析的时候,go get命令会将代码包远程导入路径与预置的代码托管站点的主域名进行匹配。如果匹配成功,则在对代码包远程导入路径的初步检查后返回正常的返回值或错误信息。如果匹配不成功,则会再对代码包远程导入路径进行动态分析。至于动态分析的过程,我们就不在这里赘述了。

如果对代码包远程导入路径的静态分析或/和动态分析成功并获取到对应的版本控制系统和远程仓库URL,那么go get命令就会进行代码包检出或更新的操作。随后,go get命令会在必要时以同样的方式检出或更新这个代码包的所有依赖包。

命令特有标记

命令go get可以接受所有可用于go build命令和go install命令的标记。这是因为go get命令的内部步骤中完全包含了编译和安装这两个动作。另外,go get命令还有一些特有的标记,如下表所示:

表0-4 go get命令的特有标记说明

| 标记名称 | 标记描述 |
| -d | 让命令只执行下载动作,而不执行安装动作。 |
| -fix | 让命令在下载代码包后先执行修正动作,而后再进行编译和安装。 |
| -u | 让命令利用网络来更新已有代码包及其依赖包。默认情况下,该命令只会从网络上下载本地不存在的代码包,而不会更新已有的代码包。 |

为了更好的理解这几个特有标记,我们先清除Lib工作区的src目录和pkg目录中的所有子目录和文件。现在我们使用带有-d标记的go get命令来下载同样的代码包:

hc@ubt:~$ go get -d github.com/hyper-carrot/go_lib

现在,让我们再来看一下Lib工作区的目录结构:

$HOME/golang/lib:
    bin/
    pkg/
    src/
        github.com/
        hyper-carrot/
        go_lib/
    ...

我们可以看到,go get命令只将代码包下载到了Lib工作区(环境变量GOPATH中的第一个目录)的src目录,而没有进行后续的编译和安装动作。

我们知道,绝大多数计算机编程语言在进行升级和演进过程中,不可能保证100%的向后兼容(Backward Compatibility)。在计算机世界中,向后兼容是指在一个程序或者代码库在更新到较新的版本后,用旧的版本程序创建的软件和系统仍能被正常操作或使用,或在旧版本的代码库的基础上编写的程序仍能正常编译运行的能力。Go语言的开发者们已想到了这点,并提供了官方的代码升级工具——fixfix工具可以修复因Go语言规范变更而造成的语法级别的错误。关于fix工具,我们将放在本节的稍后位置予以说明。

假设我们本机安装的Go语言版本是1.3,但我们的程序需要用到一个很早之前用Go语言的0.9版本开发的代码包。那么我们在使用go get命令的时候可以加入-fix标记。这个标记的作用是在检出代码包之后,先对该代码包中不符合Go语言1.3版本的语言规范的语法进行修正,然后再下载它的依赖包,最后再对它们进行编译和安装。

标记-u的意图和执行的动作都比较简单。我们在执行go get命令时加入-u标记就意味着,如果在本地工作区中已存在相关的代码包,那么就是用对应的代码版本控制系统的更新命令更新它,并进行编译和安装。这相当于强行更新指定的代码包及其依赖包。我们来看如下示例:

hc@ubt:~$ go get -v github.com/hyper-carrot/go_lib

因为我们在之前已经检出并安装了代码包go_lib,所以我们执行上面这条命令后什么也没发生。还记得加入标记-v标记意味着会打印出被构建的代码包的名字吗?现在我们使用标记-u来强行更新代码包:

hc@ubt:~$ go get -v -u  github.com/hyper-carrot/go_lib
github.com/hyper-carrot/go_lib (download)
github.com/hyper-carrot/go_lib/logging
github.com/hyper-carrot/go_lib

其中,带“(download)”后缀意味着命令从远程仓库检出或更新了代码包。从打印出的信息可以看到,go get命令先更新了参数指定的已存在于本地工作区的代码包,而后编译了它的唯一依赖包,最后编译了该代码包。我们还可以加上一个-x标记,以打印出用到的命令。读者可以自己试用一下它。

智能的下载

命令go get还有一个很值得称道的功能。在使用它检出或更新代码包之后,它会寻找与本地已安装Go语言的版本号相对应的标签(tag)或分支(branch)。比如,本机安装Go语言的版本是1.x,那么go get命令会在该代码包的远程仓库中寻找名为“go1”的标签或者分支。如果找到指定的标签或者分支,则将本地代码包的版本切换到此标签或者分支。如果没有找到指定的标签或者分支,则将本地代码包的版本切换到主干的最新版本。

前面我们说在执行go get命令时也可以加入-x标记,这样可以看到go get命令执行过程中所使用的所有命令。不知道读者是否已经自己尝试了。下面我们还是以代码包github.com/hyper-carrot/go_lib为例,并且通过之前示例中的命令的执行此代码包已经被检出到本地。这时我们再次更新这个代码包:

hc@ubt:~$ go get -v -u -x github.com/hyper-carrot/go_lib
github.com/hyper-carrot/go_lib (download)
cd /home/hc/golang/lib/src/github.com/hyper-carrot/go_lib
git fetch
cd /home/hc/golang/lib/src/github.com/hyper-carrot/go_lib
git show-ref
cd /home/hc/golang/lib/src/github.com/hyper-carrot/go_lib
git checkout origin/master
WORK=/tmp/go-build034263530

在上述示例中,go get命令通过git fetch命令将所有远程分支更新到本地,而后有用git show-ref命令列出本地和远程仓库中记录的代码包的所有分支和标签。最后,当确定没有名为“go1”的标签或者分支后,go get命令使用git checkout origin/master命令将代码包的版本切换到主干的最新版本。下面,我们在本地增加一个名为“go1”的标签,看看go get命令的执行过程又会发生什么改变:

hc@ubt:~$ cd ~/golang/lib/src/github.com/hyper-carrot/go_lib
hc@ubt:~/golang/lib/src/github.com/hyper-carrot/go_lib$ git tag go1
hc@ubt:~$ go get -v -u -x github.com/hyper-carrot/go_lib
github.com/hyper-carrot/go_lib (download)
cd /home/hc/golang/lib/src/github.com/hyper-carrot/go_lib
git fetch
cd /home/hc/golang/lib/src/github.com/hyper-carrot/go_lib
git show-ref
cd /home/hc/golang/lib/src/github.com/hyper-carrot/go_lib
git show-ref tags/go1 origin/go1
cd /home/hc/golang/lib/src/github.com/hyper-carrot/go_lib
git checkout tags/go1
WORK=/tmp/go-build636338114

将这两个示例进行对比,我们会很容易发现它们之间的区别。第二个示例的命令执行过程中使用git show-ref查看所有分支和标签,当发现有匹配的信息又通过git show-ref tags/go1 origin/go1命令进行精确查找,在确认无误后将本地代码包的版本切换到标签“go1”之上。

命令go get的这一功能是非常有用的。我们的代码在直接或间接依赖某些同时针对多个Go语言版本开发的代码包时,可以自动的检出其正确的版本。也可以说,go get命令内置了一定的代码包多版本依赖管理的功能。

你可能感兴趣的:(Go)