【Go命令教程】4. go get

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

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

通常状况下,为了分离本身与第三方的代码,咱们会设置两个或更多的工做区。咱们如今有一个目录路径为 /home/hc/golang/lib 的工做区,而且它是环境变量 GOPATH 值中的第一个目录路径。注意,环境变量 GOPATH 中包含的路径不能与环境变量 GOROOT 的值重复。好了,若是咱们使用 go get 命令下载和安装代码包,那么这些代码包都会被安装在上面这个工做区中。咱们暂且把这个工做区叫作Lib 工做区。在咱们运行 go get github.com/hyper-carrot/go_lib/logging 以后,这个代码包就应该会被保存在Lib工做的src目录下,而且已经被安装稳当,以下所示:linux

/home/hc/golang/lib:
    bin/
    pkg/
        linux_386/
            github.com/
            hyper-carrot/
        go_lib/
            logging.a
    src/
        github.com/
            hyper-carrot/
                 go_lib/
                     logging/
    ...

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

实际上,像 goc2p 项目这样直接以项目根目录的路径做为工做区路径的作法是不被推荐的。之因此这样作主要是想让读者更容易的理解 Go 语言的工程结构和工做区概念,也可让读者看到另外一种项目结构。固然,若是你的项目使用了 gb 这样的工具那就是另一回事了。这样的项目的根目录就应该被视为一个工做区(可是你没必要把它加入到 GOPATH 环境变量中)。它应该由 git clone 下载到 Go 语言工做区以外的某处,而不是使用 go get 命令。github

远程导入路径分析

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

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

该命令所支持的 VCS 的信息以下表:安全

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

名称 主命令 说明
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 Project Hosting 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
IBM DevOps Services hub.jazz.net Git hub.jazz.net/git/user/project
hub.jazz.net/git/user/project/sub/directory

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

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

自定义代码包远程导入路径

若是你想把你编写的(被托管在不一样的代码托管网站上的)代码包的远程导入路径统一块儿来,或者不但愿让你的代码包中夹杂某个代码托管网站的域名,那么你能够选择自定义你的代码包远程导入路径。这种自定义的实现手段叫作“导入注释”。导入注释的写法示例以下:

package analyzer // import "hypermind.cn/talon/analyzer"

代码包 analyzer 实际上属于个人一个网络爬虫项目。这个项目的代码被托管在了 Github 网站上。它的网址是:https://github.com/hyper-carrot/talon。若是用标准的导入路径来下载 analyzer 代码包的话,命令应该这样写 go get github.com/hyper-carrot/talon/analyzer。不过,若是咱们像上面的示例那样在该代码包中的一个源码文件中加入导入注释的话,这样下载它就行不通了。咱们来看一看这个导入注释。

导入注释的写法如同一条代码包导入语句。不一样的是,它出如今了单行注释符//的右边,所以 Go 语言编译器会忽略掉它。另外,它必须出如今源码文件的第一行语句(也就是代码包声明语句)的右边。只有符合上述这两个位置条件的导入注释才是有效的。再来看其中的引号部分。被双引号包裹的应该是一个符合导入路径语法规则的字符串。其中,hypermind.cn 是我本身的一个域名。实际上,这也是用来替换掉我想隐去的代码托管网站域名及部分路径(这里是 github.com/hyper-carrot)的那部分。在 hypermind.cn 右边的依次是个人项目的名称以及要下载的那个代码包的相对路径。这些与其标准导入路径中的内容都是一致的。为了清晰起见,咱们再来作下对比。

github.com/hyper-carrot/talon/analyzer // 标准的导入路径
hypermind.cn           /talon/analyzer // 导入注释中的导入路径                   

你想用你本身的域名替换掉标准导入路径中的哪部分由你本身说了算。不过通常状况下,被替换的部分包括代码托管网站的域名以及你在那里的用户ID就能够了。这足以达到咱们最开始说的那两个目的。

虽然咱们在talon项目中的全部代码包中都加入了相似的导入注释,可是咱们依然没法经过 go get hypermind.cn/talon/analyzer 命令来下载这个代码包。由于域名 hypermind.cn 所指向的网站并无加入相应的处理逻辑。具体的实现步骤应该是这样的:

  1. 编写一个可处理HTTP请求的程序。这里无所谓用什么编程语言去实现。固然,我推荐你用 Go 语言去作。

  2. 将这个处理程序与hypermind.cn/talon这个路径关联在一块儿,并老是在做为响应的HTML文档的头中写入下面这行内容:

    <meta name="go-import" content="hypermind.cn/talon git https://github.com/hyper-carrot/talon">

    hypermind.cn/talon/analyzer 熟悉 HTML 的读者都应该知道,这行内容会被视为 HTML 文档的元数据。它实际上 go get 命令的文档中要求的写法。它的模式是这样的:

<meta name="go-import" content="import-prefix vcs repo-root">

实际上,content 属性中的 import-prefix 的位置上应该填入咱们自定义的远程代码包导入路径的前缀。这个前缀应该与咱们的处理程序关联的那个路径相一致。而 vsc 显然应该表明与版本控制系统有关的标识。还记得表0-2中的主命令列吗?这里的填入内容就应该该列中的某一项。在这里,因为 talon 项目使用的是 Git,因此这里应该填入 git。至于 repo-root,它应该是与该处理程序关联的路径对应的 Github 网站的 URL。在这里,这个路径是 hypermind.cn/talon,那么这个 URL 就应该是 https://github.com/hyper-carrot/talon。后者也是 talon 项目的实际网址。

好了,在咱们作好上述处理程序以后,go get hypermind.cn/talon/analyzer 命令的执行结果就会是正确的。analyzer 代码包及其依赖包中的代码会被下载到 GOPATH 环境变量中的第一个工做区目录的 src 子目录中,而后被编译并安装。

注意,具体的代码包源码存放路径会是 /home/hc/golang/lib/src/hypermind.cn/talon/analyzer。也就是说,存放路径(包括代码包源码文件以及相应的归档文件的存放路径)会遵循导入注释中的路径(这里是 hypermind.cn/talon/analyzer),而不是原始的导入路径(这里是 github.com/hyper-carrot/talon/analyzer)。另外,咱们只需在 talon 项目的每一个代码包中的某一个源码文件中加入导入注释,但这些导入注释中的路径都必须是一致的。在这以后,咱们就只能使用 hypermind.cn/talon/ 做为 talon 项目中的代码包的导入路径前缀了。一个反例以下:

hc@ubt:~$ go get github.com/hyper-carrot/talon/analyzer
package github.com/hyper-carrot/talon/analyzer: code in directory /home/hc/golang/lib/src/github.com/hyper-carrot/talon/analyzer expects import "hypermind.cn/talon/analyzer"
# 反例
#

与自定义的代码包远程导入路径有关的内容咱们就介绍到这里。从中咱们也能够看出,Go 语言为了让使用者的项目与代码托管网站隔离所做出的努力。只要你有本身的网站和一个不错的域名,这就很容易搞定而且很是值得。这会在你的代码包的使用者面前强化你的品牌,而不是某个代码托管网站的。固然,使你的代码包导入路径整齐划一是最直接的好处。

OK,言归正传,我下面继续关注 go get 这个命令自己。

命令特有标记

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

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

标记名称 标记描述
-d 让命令程序只执行下载动做,而不执行安装动做。
-f 仅在使用-u标记时才有效。该标记会让命令程序忽略掉对已下载代码包的导入路径的检查。若是下载并安装的代码包所属的项目是你从别人那里Fork过来的,那么这样作就尤其重要了。
-fix 让命令程序在下载代码包后先执行修正动做,然后再进行编译和安装。
-insecure 容许命令程序使用非安全的scheme(如HTTP)去下载指定的代码包。若是你用的代码仓库(如公司内部的Gitlab)没有HTTPS支持,能够添加此标记。请在肯定安全的状况下使用它。
-t 让命令程序同时下载并安装指定的代码包中的测试源码文件中依赖的代码包。
-u 让命令利用网络来更新已有代码包及其依赖包。默认状况下,该命令只会从网络上下载本地不存在的代码包,而不会更新已有的代码包。
-v 打印出被构建的代码包的名字
-x 打印出用到的命令

为了更好的理解这几个特有标记,咱们先清除 Lib 工做区的 src 目录和 pkg 目录中的全部子目录和文件。如今咱们使用带有 -d 标记的 go get 命令来下载一样的代码包:

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

如今,让咱们再来看一下 Lib 工做区的目录结构:

/home/hc/golang/lib:
    bin/
    pkg/
    src/
        github.com/
            hyper-carrot/
                go_lib/
                    logging/
    ...

咱们能够看到,go get 命令只将代码包下载到了 Lib 工做区的 src 目录,而没有进行后续的编译和安装动做。这个加入-d标记的结果。

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

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

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

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

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

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

其中,“(download)”后缀意味着命令从远程仓库检出或更新了该行显示的代码包。若是咱们要查看附带 -u 的 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 get 命令的使用方式。go get 命令与以前介绍的两个命令同样,是咱们编写 Go 语言程序、构建 Go 语言项目时必不可少的辅助工具。

 

 

摘自:

http://wiki.jikexueyuan.com/project/go-command-tutorial/0.3.html

 

 


 

自定义路径

即使将代码托管在 GitHub,但咱们依然但愿使用 自有域名定义下载导入路径。方法很简单,在 Web 服务器对应路径返回中包含 “go-import”跳转信息便可。

myserver.go

package main

import (
	"fmt"
	"net/http"
)

func handler(w http.ResponseWriter, r *http.Request) {
	fmt.Fprint(w, `
		<meta name="go-import" content="qyunhen.com/test git https://githua.com/qyuhen/test">
	`)
}

func main() {
	http.HandleFunc("/test", handler)
	http.ListenAndServe(":80", nil)
}

编译并启动服务器后,用新路径下载该包。

$ go get -v -insecure qyuhen.com/test

Fetching http://qyuhen.com/test?go-get=1
Parsing meta tags from http://qyuhen.com/test?go-get=1 (status code 200)

get "qyuhen.com/test"; found meta tag main.meta Import(
    Prefix:"qyuhen.com/test",
    VCS:"git"
    RepoRoot:"https://github.com/qyuhen/test"
) at http://test.com/test?go-get=1

qyuhen.com/test (download)

从输出信息中,能够看到解析过程。最终保存路径再也不是 github.com,而是自有域名。只是如此一来,该包就有 2 个下载路径,本地也可能所以存在 2 个副本。为避免版本不一致等状况发生,可添加“import comment”,让编译器检查导入路径是否与该注释一致。

github.com/qyuhen/test/hello.go

package lib // import "qyuhen.com/test"

func Hello() {
	println("Hello!")
}

如此,就要求该包必须以“qyuhen.com/test”路径导入,不然编译会出错。由于 go get 也会执行编译操做,因此用“github.com/qyuhen/test”下载安装一样失败。

$ go build

can't load package: package test:
   code in directory github.com/qyuhen/test expects import "qyuhen.com/test"

使用惟一的导入路径,方便往后迁移存储端。糟心的是,此方式对 vendor 机制失效。

 

 

摘自:《Go语言学习笔记 . 雨痕》 

相关文章
相关标签/搜索