【翻译】Command Go (下)

File types(文件类型)

go命令检查目录中特定文件的集合。它根据文件的扩展名表示要检查的文件。这些扩展名是:

.go
        Go source files.
.c, .h
        C source files.
        If the package uses cgo or SWIG, these will be compiled with the
        OS-native compiler (typically gcc); otherwise they will
        trigger an error.
.cc, .cpp, .cxx, .hh, .hpp, .hxx
        C++ source files. Only useful with cgo or SWIG, and always
        compiled with the OS-native compiler.
.m
        Objective-C source files. Only useful with cgo, and always
        compiled with the OS-native compiler.
.s, .S
        Assembler source files.
        If the package uses cgo or SWIG, these will be assembled with the
        OS-native assembler (typically gcc (sic)); otherwise they
        will be assembled with the Go assembler.
.swig, .swigcxx
        SWIG definition files.
.syso
        System object files.

除.syso之外的每个类型的文件都可能包含构建约束,但是go命令会停止扫描文件中第一个不是空行或//样式行注释的构建约束。有关更多详细信息,请参阅go / build包文档。
通过Go 1.12版本,非测试Go源文件还可以包含// go:binary-only-package注释,指示包源仅包含在文档中,不得用于构建包二进制文件。这样就可以单独以编译形式分发Go包。即使是仅二进制包也需要准确的导入块来列出所需的依赖关系,以便在链接生成的命令时可以提供这些依赖关系。请注意,此功能计划在Go 1.12发布后删除。

The go.mod file

模块版本由源文件树定义,其根目录中包含go.mod文件。当运行go命令时,它会查找当前目录,然后依次查找父目录,找到go.mod以确定当前模块的模块跟。

go.mod文件是面向行的,可以使用“//”注释,而不能用"/* */ " 注释,每行有一个指令,由一个动词接一个参数组成,例如:

module my/thing
go 1.12
require other/thing v1.0.2
require new/thing/v2 v2.3.4
exclude old/thing v1.2.3
replace bad/thing v1.4.5 => good/thing v1.4.5

动词指令包含:

module, to define the module path;
go, to set the expected language version;
require, to require a particular module at a given version or later;
exclude, to exclude a particular module version from use; and
replace, to replace a module version with a different module version.

exclude, replace只适用于主模块中的go.mod。当作为依赖项的时候被忽略。更多细节参阅https://research.swtch.com/vgo-mvs

前导的动词可以从相邻行中分解出来,创建一个模块,就像Go 的 import 语句一样。

require (
        new/thing v2.3.4
        old/thing v1.2.3
)

go.mod文件的设计既可以直接编辑,也可以通过工具轻松更新。'go mod edit'命令可用于从程序和工具中解析和编辑go.mod文件。请参阅'go help mod edit'。

go命令每次使用模块图时都会自动更新go.mod,以确保go.mod始终准确地反映现实并且格式正确。例如,考虑这个go.mod文件:

module M
require (
        A v1
        B v1.0.0
        C v1.0.0
        D v1.2.3
        E dev
)
exclude D v1.2.3

更新将将非规范的版本标识重写为semver格式,因此 A的v1变为 v1.0.0,E的dev变为其dev分支上最新提交的伪版本号,可能是 v0.0.0-20180523231146-b3f5c0f6e5f1.

更新操作 修改require需求已满足 exclude 排除指令。因此 对于被排除的 D v1.2.3的需求将被更新到下一个可用版本,也许是 D v1.2.4 or D v1.3.0.

更新消除冗余或误导性的require需求。例如 A v1.0.0本身需要B v1.2.0和C v1.0.0.而go.mod 中对B v1.0.0的需求是
具有误导性的(由于A需求B v1.2.0代替),并且它的需求C v1.0.0是冗余的(M与A模块都需要C v1.0.0)因此两者都将被删除。如果模块M包含直接从B或C导入的包,则将保留需求,但更新为正在使用的版本。

最后,更新以规范化格式(canonical formatting)重新格式化go.mod,一遍将来机器修改将导致最小的差异。

因为模块试图定义导入语句的含义,所以加载包的任何命令都会更新go.mod文件,包括go build, go get, go install, go list, go test, go mod graph, go mod tidy, and go mod why.

GOPATH environment variable

Go path 英语接续import语句,它由 go/build 包实现并记录。
(译注:以下内容可能与Go1.11支持Go Modules后不符,故不作翻译)
The GOPATH environment variable lists places to look for Go code. On Unix, the value is a colon-separated string. On Windows, the value is a semicolon-separated string. On Plan 9, the value is a list.

If the environment variable is unset, GOPATH defaults to a subdirectory named "go" in the user's home directory ($HOME/go on Unix, %USERPROFILE%\go on Windows), unless that directory holds a Go distribution. Run "go env GOPATH" to see the current GOPATH.

See https://golang.org/wiki/SettingGOPATH to set a custom GOPATH.

Each directory listed in GOPATH must have a prescribed structure:

The src directory holds source code. The path below src determines the import path or executable name.

The pkg directory holds installed package objects. As in the Go tree, each target operating system and architecture pair has its own subdirectory of pkg (pkg/GOOS_GOARCH).

If DIR is a directory listed in the GOPATH, a package with source in DIR/src/foo/bar can be imported as "foo/bar" and has its compiled form installed to "DIR/pkg/GOOS_GOARCH/foo/bar.a".

The bin directory holds compiled commands. Each command is named for its source directory, but only the final element, not the entire path. That is, the command with source in DIR/src/foo/quux is installed into DIR/bin/quux, not DIR/bin/foo/quux. The "foo/" prefix is stripped so that you can add DIR/bin to your PATH to get at the installed commands. If the GOBIN environment variable is set, commands are installed to the directory it names instead of DIR/bin. GOBIN must be an absolute path.

Here's an example directory layout:GOPATH=/home/user/go

/home/user/go/
    src/
        foo/
            bar/               (go code in package bar)
                x.go
            quux/              (go code in package main)
                y.go
    bin/
        quux                   (installed command)
    pkg/
        linux_amd64/
            foo/
                bar.a          (installed package object)

Go searches each directory listed in GOPATH to find source code, but new packages are always downloaded into the first directory in the list.See https://golang.org/doc/code.html for an example.

GOPATH and Modules

当使用modules时,GOPATH不在用于解决导入问题,然而它依旧用于存放下载的源码(GOPATH/pkg/mod)和编译后的课执行文件

Internal Directories(内部路径)

在一个名为"internal"的目录或子目录下编写的代码,只能被其所在父目录根节点下的代码引用。以下是示例:

/home/user/go/
    src/
        crash/
            bang/              (go code in package bang)
                b.go
        foo/                   (go code in package foo)
            f.go
            bar/               (go code in package bar)
                x.go
            internal/
                baz/           (go code in package baz)
                    z.go
            quux/              (go code in package main)
                y.go

z.go被引用时的导入路径是"foo/internal/baz",不过这样的导入只能用于foo根节点下的源文件(foo/bar/x.go 与 foo/quux/y.go),另一个$GOPATH/src下的项目crash无法导入。
(译注:go 命令源码中cmd目录下的compile, go, link等目录下都包含了 internal目录。以及testdata目录用于存放测试数据)

Vendor Directories

Go1.6支持用本地的依赖副本来导入依赖,使用Vendoring来实现。在"vendor"目录下的代码只能被其父节点的根目录下的文件引用,且导入路径只要从vendor算起,省略vendor与之前的路径。下面例子在上一节的基础上加了"vendor"的内部目录,新增一个"oo/vendor/crash/bang"目录。

/home/user/go/
    src/
        crash/
            bang/              (go code in package bang)
                b.go
        foo/                   (go code in package foo)
            f.go
            bar/               (go code in package bar)
                x.go
            vendor/
                crash/
                    bang/      (go code in package bang)
                        b.go
                baz/           (go code in package baz)
                    z.go
            quux/              (go code in package main)
                y.go

在内部路径vendor下可见性规则以上节一致。不过z.go中的代码被导入时的路径为"baz"而不是"/foo/vendor/bar",如前文所述,vendor以及之前的路径被省略了。
vendor目录中的代码是外部src/crash的复制,在foo/vendor下的源文件中,"crash/bang"导入路径引用的是"foo/vendor/crash/bang"而不是高层级的那个目录。
导入路径检查不包括在vendor目录下的项目。
当"go get"检出或升级git仓储,现在也会更新 submodules。
Vendor目录不影响使用"go get"检出的代码仓储(go get检出的代码总是在GOPATH中)。

总结 路径导入时,先查找项目下的vendor目录,然后是GOPATH,最后是GOROOT

Module proxy protocol(模块代理协议)

go命令默认从版本控制系统直接下载模块,正如“go get”一样,GOPROXY 环境变量允许对源码下载的进一步控制。 如果 GOPROXY未设置,或者是空字符串或者设置为“direct”字符串,下载使用默认的连接到版本控制系统。将GOPROXY设置为“off”不允许从任何源下载代码。否则,GOPROXY应该是模块代理的 URL,在这种情况下,go命令将从该代理获取所有模块。无论从哪下载模块,下载的模块必须与go.sum中的条目匹配。(有关验证的讨论,请参阅“go help modules”)

go模块代理可以是相应GET请求的任何Web服务器。请求没有查询参数,因此即使是从固定文件系统(包括 file:///URL)提供的服务站点也可以是模块代理。

可以发送到Go模块代理的GET请求是:

GET $ GOPROXY / <module> / @ v / list返回给定模块的所有已知版本的列表,每行一个。
GET $ GOPROXY / <module> / @ v / <version> .info返回有关给定模块的该版本的JSON格式的元数据。
GET $ GOPROXY / <module> / @ v / <version> .mod返回给定模块的该版本的go.mod文件。
GET $ GOPROXY / <module> / @ v / <version> .zip返回给定模块的该版本的zip存档。

为避免在区分大小写的文件系统中提供的服务出问题,<module> <version> 元素是case-encoded的,把每个大写字母转换为一个感叹号接一个对应的小写字母:github.com/Azure 被编码为 github.com/!azure

给定模块的JSON格式的元数据对应以下GO结构体,可能在将来进行扩展

type Info struct {
    Version string    // version string
    Time    time.Time // commit time
}

给定模块的特定版本的zip存档是标准zip文件,其包含与模块的源代码和相关文件对应的文件树。存档使用斜杠分隔的路径,存档中的每个文件路径必须以<module> @ <version> /开头,其中模块和版本直接替换,而不是大小写编码。模块文件树的根对应于存档中的<module> @ <version> /前缀。

即使直接从版本控制系统下载,go命令也会合成显式的info,mod和zip文件,并将它们存储在本地缓存中,GOPATH / pkg / mod / cache / download,就像直接使用代理下载一样。缓存布局与代理URL空间相同,因此在(或复制到)https://example.com/proxy上提供 GOPATH / pkg / mod / cache / download 会让其他用户访问这些缓存的模块版本,通过设置 GOPROXY = https://example.com/proxy

Import path syntax(路径导入语法)

路径导入语句指明了一个存储在本地文件系统中的包。总之,导入语句表示导入了标准库中的包(如"unicode/utf8")或在工作目录(GOPATH)下的包。

Relative import paths(相对路径)

以 ./ 或 ../开头的路径被称为相对路径,go工具链有两种方式支持相对路径。

  1. 相对路径可以被用于命令行中,如果你在导入路径为"unicode"的工作目录下,想对"unicode/utf8"运行测试,可以使用"go test ./utf8",而不需要再给出绝对路径。
    类似的当你在"unicode/utf8"目录下执行"go test .."将对“unicode”进行测试。相对模式匹配(Relative patterns)也是可以的“go test ./..”将对当前路线下的所有子目录进行测试。
  2. 如果我们要编译运行的Go程序不在工作空间$GOPATH下,我们可以在导入语句中使用相对路径,导入那些同样不在工作空间中的源码。(已测试,结合Vendor Directories总结理解)
    这使得我们在工作空间外进行多个小程序包的实验时变得方便,但是类似的不在GOPATH中的程序不能被“go install”安装(因为找不到安装的地方),所以每次运行他们时都会编译。(译注:亲测在Go1.12中是可以的,GOPATH外部的项目go install 后,在GOPATH/bin下生成.exe文件)
    为避免歧义,在GOPATH中不允许使用相对路径。(To avoid ambiguity, Go programs cannot use relative import paths within a work space.经测试是可以,不懂这里是啥意思)

Remote import paths

某些导入路径还描述了如何从版本控制系统获得程序包的源代码。

一些常见的代码托管站点具有特殊语法:

Bitbucket (Git, Mercurial)

        import "bitbucket.org/user/project"
        import "bitbucket.org/user/project/sub/directory"

GitHub (Git)

        import "github.com/user/project"
        import "github.com/user/project/sub/directory"

Launchpad (Bazaar)

        import "launchpad.net/project"
        import "launchpad.net/project/series"
        import "launchpad.net/project/series/sub/directory"

        import "launchpad.net/~user/project/branch"
        import "launchpad.net/~user/project/branch/sub/directory"

IBM DevOps Services (Git)

        import "hub.jazz.net/git/user/project"
        import "hub.jazz.net/git/user/project/sub/directory"

对于托管在其他服务器上的代码,导入路径可用使用版本控制类型进行限定,或者go tool 可以通过 https/http 动态获取导入路径,并从HTML中的<meta>标记中发现代码所在位置。

声明代码位置,表单的导入路径

repository.vcs/path

使用指定的版本控制系统指定具有或没有.vcs后缀的给定仓储,然后指定该仓储中的路径。支持的版本控制系统是:

Bazaar      .bzr
Fossil      .fossil
Git         .git
Mercurial   .hg
Subversion  .svn

例如:

import "example.org/user/foo.hg"

表示 Mercurial 仓储的根路径在 example.org/user/foo 或 foo.hg

import "example.org/repo.git/foo/bar"

表示 Git 类型仓储的 foo/bar 目录在 example.org/repo 或 repo.git.

当一个版本控制系统支持多种协议,下载时一次尝试每种协议。例如,Git下载时尝试 https:// 然后是 git + ssh://

默认情况下,下载仅限于已知的的安全协议(如 https, ssh)要覆盖Git下载的此设置,可以设置
GIT_ALLOW_PROTOCOL 环境变量(有关详细信息,请参阅:“go help environment”)。

如果导入路径不是一直的代码托管站点且缺少版本控制限定符,go tool 尝试通过https/http 获取导入,并在 document文档HTML <head>中查找<meta>标记。

meta 标记格式如下

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

导入路径的前缀对应仓储的根目录。它必须是使用“go get”获取的包的前缀或者完全匹配。如果它不是完全匹配,则在前缀处发出另一个http请求以验证<meta>标记是否匹配。

meta标记应该尽可能早的出现在文件中。特别是,它应该出现在任何原生 JavaScript 或 CSS之前,避免混淆Go命令的限制解析器。

vcs 包含 "bzr", "fossil", "git", "hg", "svn".

repo-root 是版本控制系统的根,包含 scheme 且不包含 .vcs 修饰符

例如:

import "example.org/pkg/foo"

将得到以下请求:

https://example.org/pkg/foo?go-get=1 (preferred)
http://example.org/pkg/foo?go-get=1 (fallback, only with -insecure)

如果该页面包含以下meta标记

<meta name="go-import" content="example.org git https://code.org/r/p/exproj">

go 工具将验证 https://example.org/?go-get=1 是否包含相同的meta标记然后调用 git clone 克隆 https://code.org/r/p/exproj 到本地的 GOPATH/src/example.org 路径.

当使用GOPATH模式时,下载的包存放在环境变量GOPATH列表第一个路径下。

当使用modules模式时,下载的包存放在模块缓存(译注:GOPATH/pkg/mod/cache)。(参见'go help module-get'和'go help goproxy'。)

使用模块时,将识别一个额外go-import 元标记,并优先于那些列出的版本控制系统。该变体(variant)使用“mod”作为内容值中的vcs,如:

<meta name="go-import" content="example.org mod https://code.org/moduleproxy">

此标记表示从 URL=https://code.org/moduleproxy的代理上提供的模板代理获取以 example.org 开头的路径模块。
有关代理协议的详细信息,请参阅“go help goproxy”。

Import path checking(导入路径检查)

当上述惯用导入路径功能重定向得到已知代码托管站点时,每个得到的包邮两种可能的导入路径,使用自定义域或者已知的托管站点。

如果package声明语句紧跟着(换行前)以下两种形式之一的注释,则成包语句具有“导入注释”(“import comment”)

package math // import "path"
package math /* import "path" */

go 命令将拒绝一个有导入注释的安装包,,除非引用该包的导入路径。通过这种方式,导入注释让包的作者确保使用自定义导入路径而不是直接指向底层的代码托管路径。

导入路径检查在带有vendor目录树的代码中失效,这允许复制代码到本地的vendor目录,而不需要更改导入注释。

使用模块时也会禁用导入路径检查。导入注释被go.mod文件中的module语句淘汰。

Modules, module versions, and more

模块是一系列go依赖包的集合。模块是源码交换与版本控制的单元。go命令对模块有直接的支持,包括记录和分析对其他模块的支持。模块取代了之前用GOPATH来指定哪些源文件需要被编译。

Preliminary module support(模块预支持)

Go1.11包含了对模块的预支持,包括一个新的可以感知模块(module-aware)的“go get”命令。对模块支持将会持续改进,这期间将保持兼容性直到官方宣布,然后在晚些时候官方将移除项目必须在GOPATH的特性,以及对旧的“go get”命令的支持。

体验Go1.11模块支持最快的方法是将你的代码仓储检出到GOPATH/src外,在仓储中创建go.mod文件(下一节介绍),然后对改仓储下的文件执行go命令。

为了更好的控制,Go1.11的模块支持一个临时的环境变量GO111MODULE,有三个不同的字符串值:off, on, auto(默认)对应三种模式:off 表示不使用新的模块支持,仍然从vendor与GOPATH查找依赖;我们称为“GOPATH mode”。on表示使用模块支持,不会查询GOPATH,我们称之为模块感知或运行与“模块感知模式(module-aware mode)”,auto或未设置环境变量,是否启用模块支持取决于当前文件目录。只有当前仓储在GOPATH/src外且包含go.mod文件或在包含go.mod文件的目录下。

在模块感知模式下,GOPATH不再是定义编译(build)时的导入路径,但仍然会保存下载的依赖包,以及安装(install)的可执行文件(在GOPATH/bin中,除非设置了GOBIN环境变量)

Defining a module(定义模块)

通过在go源文件文件树的根目录创建go.mod可以定义模块,包含go.mod的目录成为模块根(module root),一般情况下,模块跟也是代码仓储的根目录(非必须)。模块包含模块根下所有Go packages集合以及它们的子目录,但不包括那些本身拥有go.mod的文件。

模块路径(module path)是与模块跟一直的导入路径前缀。go.mod文件定义模块路径,通过给模块与版本号,定义了编译时需要导入的模块的指定版本。

下面go.mod文件表示它是“example.com/m”这个模块的模块根,而且还定义了该模块依赖特定版本的golang.org/x/text 与 gopkg.in/yaml.v2

module example.com/m

require (
        golang.org/x/text v0.3.0
        gopkg.in/yaml.v2 v2.1.0
)

当对模块直接编译时,go.mod文件可以指定替换和排除(replacements and excluded)版本,但是当模块被内嵌在更大的模块中编译时将会忽略它们。更多关于go.mod文件的细节参见:'go help go.mod'

要开始一个新模块,'go mod init'命令可以用来在模块的根目录创建一个go.mod文件,包含一个简单的模块声明。

go mod init example.com/m

在一个已经有依赖管理工具(godep,glide,dep)的项目中go mod init命令也会为其添加合适的配置。

一旦go.mod文件被创建,不需要额外的步骤,类似'go build','go test'甚至'go list'都会自动添加新的依赖来满足导入。

The main module and the build list(主模块,编译列表)

主模块(main module)是go命令执行的模块,go命令先在当前目录下查找模块根,然后顺着父目录依次查找。

通过require,replace,exclude关键字,主模块的go.mod文件精确的定义了go命令所需的包(packages)集合,依赖模块提供的包只能通过require定义,replace与exculd语句将被忽略。因此replace与exculd语句允许主模块对自身编译的完全控制,而不受到依赖的影响。

编译列表“build list”提供的包的模块的集合,编译列表最开始只包含主模块,随后go命令递归的将编译列表中模块依赖的模块添加进来,直到没有依赖的模块。如果包含一个模块的多个版本,最后只保留最新的版本(根据语义版本排序)。

go list 命令提供了主模块与编译列表的信息:

go list -m              # print path of main module
go list -m -f={{.Dir}}  # print root directory of main module
go list -m all          # print build list

Maintaining module requirements(模块维护)

go.mod文件意味着它可以被程序与工具阅读,维护。go命令行会自动更新go.mod文件,使其保持标准格式以及require语句的准确。

当go命令发现未知的导入语句时,都会查找包含当前导入的模块,并自动将最新版本添加到go.mod文件。因此,大部分情况下只要在源码中添加导入语句,然后运行'go build','go test'或者'go list',go命令中分析包的部分会解析导入语句,更新go.mod文件。

任何go命令都可以发现必须添加的缺失模块,及时只引用到模块中的一个包(Any go command can determine that a module requirement is missing and must be added, even when considering only a single package from the module)。另一方面,确定一个模块不再需要,且可以删除,需要查看模块中所有的包,包括所有可能的构建配置(体系结构,操作系统,构建标签等)。“go mod tidy”命令建立该视图,然后添加需要的模块,移除不需要的模块。

作为gp.mod文件中维护require语句的部分,go命令追踪哪些些包被当前模块直接引入,哪些包只是被其它模块间接依赖。仅被间接依赖的包在go.mod文件中会被"// indirect"注释标记,一旦间接引用被直接导入,go.mod文件中的间接需求将被删除。间接的模块需求只有以下情况会出现:声明他们自己的依赖失败,或者升级模块依赖。(Indirect requirements only arise when using modules that fail to state some of their own dependencies or when explicitly upgrading a module's dependencies ahead of its own stated requirements.)

因为这种自动维护,go.mod文件中的信息总是最新的,可读的构建描述。

“go test”命令更新go.mod文件以更改构建中使用的模块版本。一个模块的升级意味着其它模块的升级(版本升级),同样的,一个模块的降级可能意味着其它模块的降级。“go get”命令同样会做这样的隐式改变。如果go.mod文件被直接编辑,“go build”,“go list”会假定升级是有意的,并自动进行隐式升级,这样的升级会通过更新go.mod反映出来。

"go mod"命令提供了在维护中使用的其他功能了解模块和go.mod文件。请参见"go help mod".

-mod 构建标识为更新和使用go.mod提供了额外的控制

如果使用-mod=readonly 命令标识,上述的对go.mod的自动隐式升级将不被允许,相反,当需要改变go.mod时会失败。这个设置在检查go.mod文件是否需要升级时很有用,例如在持续集成和测试系统。即使使用-mod=readonly标识,“go get”命令依然可以更新go.mod文件,并且“go mod”命令不接受-mod标识(或者其他标识)

如果使用-mod=vendor,go命令假定vendor目录有正确的依赖副本,且忽略go.mod文件中的依赖。

Pseudo-versions(假版本)

go.mod文件一级go命令一般使用语义化版本作为标准格式来描述模块的版本,所以我们可以比较两个版本哪个是新版本的哪个是旧的版本。类似v1.2.3这样的版本标识引进于源码仓储中的版本标签。未加标签的版本可以使用“假版本”,类似“v0.0.0-yyyymmddhhmmss-abcdefabcdef”UTC时间前缀,而后缀则是最后提交的哈希前缀。时间确保了两个假版本之间可以进行前后比较,提交哈希可以精确定位到某一次提交,本例中的v0.0.0前缀,可以推出距提交最近的一个标签。

有三种假版本的格式:

  • vX.0.0-yyyymmddhhmmss-abcdefabcdef 被用在目标提交之前没有更早的版本提交。(这是最初唯一的格式,因此在一些旧的go.mod文件中使用这个格式,即使提交是带标签的。)
  • vX.Y.Z-pre.0.yyyymmddhhmmss-abcdefabcdef 当目标提交之前的最新版本提交是vX.Y.Z-pre时
  • vX.Y.(Z+1)-0.yyyymmddhhmmss-abcdefabcdef 当目标提交之前的最新版本提交是vX.Y.Z时
    “假版本”不需要手动键入,go命令接收原提交哈希,自动转换成假版本(或者使用标签)。这个转换是模块查询的例子。

Module queries(模块查询)

go命令接受在 命令行与主模块的go.mod文件中使用“模块查询”替换确切的模块版本。(在主模块的go.mod文件执行查询后,go命令使用查询结果更新文件)

  • 一个完整确切的语义化版本:v1.2.3,指定一个特定的版本。
  • 语义化版本前缀:v1 或 v1.2,指定该前缀的最新版本。
  • 语义化版本比较:"<v1.2.3"或者">=1.5.6",指定最接近给定版本的特定版本。(<,<=对应最新版本,>,>=对应最老版本)
  • 字符串“latest”匹配可获得的最新的版本标签,或者源码仓储中最新的未加标签版本。
  • 特定的版本标识,例如提交前缀,标签版本,分支名称,选择特定的代码版本。如果版本标识有语义化版本标签,查询指定那个语义化版本。否则查询指定提交的假版本。

所有的查询将优先考虑发布版本而非预发布版本。例如:“<v1.2.3”将返回“v1.2.2”而不是"v1.2.3-pre1",尽管"v1.2.3-pre1"更接近给定的查询目标。

在主模块go.mod中被exclude语句排除的版本,将无法通过模块查询获得。

下面的例子都是可以的:

go get github.com/gorilla/mux@latest    # same (@latest is default for 'go get')
go get github.com/gorilla/mux@v1.6.2    # records v1.6.2
go get github.com/gorilla/mux@e3702bed2 # records v1.6.2
go get github.com/gorilla/mux@c856192   # records v0.0.0-20180517173623-c85619274f5d
go get github.com/gorilla/mux@master    # records current meaning of master

Module compatibility and semantic versioning(模块兼容于语义化版本控制)

go命令要求模块使用语义化版本控制,并期望版本能准确的表示兼容性:它假定v1.5.4版本向下兼容v1.5.3,v1.4.0甚至v1.0.0 。更一般的说,go命令期望包遵循“导入兼容原则(import compatibility rule)”:

“如果一个旧的包与一个新的包邮相同的导入路径,那么新包必须向下兼容旧的包。”

因为Go命令假定导入兼容规则,对模块的需求只能设置所依赖的最小需求版本:不能设置最大版本或者排除指定版本。但是兼容性规则并不能保证:v1.5.4可能是错误的,而不是向后兼容 v1.5.3的替代品。因此,go 命令永远不会从旧版本主动更新未要求的模块的新版本。

在语义化版本控制中,改变主版本意味着缺少对更早版本的兼容。为确保导入兼容,go 命令要求模块的主版本在v2以上的使用主版本作为模块路径的最后一个元素。例如,example.com/m的v2.0.0版必须使用模块路径example.com/m/v2,该模块中的包将使用该路径作为其导入路径前缀,如example.com/m/v2 /sub/ PKG。以这种方式包括模块路径中的主要版本号和导入路径称为“语义导入版本控制(semantic import versioning)”。主要版本为v2及更高版本的模块的伪版本以该主版本而非v0开头,如v2.0.0-20180326061214-4fc5987536ef。

作为一种特殊情况,以gopkg.in/开头的模块路径继续使用在该系统上建立的约定:主要版本始终存在,并且前面有一个点而不是斜杠:gopkg.in/yaml.v1和gopkg.in/yaml.v2,而不是gopkg.in/yaml和gopkg.in/yaml/v2。

go命令将具有不同模块路径的模块视为不相关:它认为 example.com/m和example.com/m/v2之间没有任何联系。具有不同主要版本的模块可以在一次构建中一起使用,并且由于它们的包使用不同的导入路径而保持独立。

在语义版本控制中,主要版本v0用于初始开发,表示没有期望稳定性或向后兼容性。主要版本v0没有出现在模块路径中,因为这些版本是为v1.0.0做准备,并且v1也没有出现在模块路径中。

在使用语义化版本控制之前编写的代码,可以使用主版本v2或者更高版本来描述与v0和v1一样未版本化的导入路径。
为了适应这样的代码,如果源代码存储库对于没有go.mod的文件树具有v2.0.0或更高版本的标记,则该版本被认为是v1模块的可用版本的一部分,并且在转换时被赋予+incompatible的后缀到模块版本,如在v2.0.0 +incompatible 。+incompatible标记也适用于从此类版本派生的伪版本,如v2.0.1-0.yyyymmddhhmmss-abcdefabcdef +incompatible。

总之,构建列表中有类似v0版本,预发布版本,假版本或"+incompatible"这些依赖('go list -m all' 记录的)表明当升级依赖的时候会存在问题,由于上述那些版本没有考虑版本兼容。

Module code layout

现在,参阅 https://research.swtch.com/vgo-module 查看更多关于版本控制系统的代码如何映射到模块文件树。

Module downloading and verification

go命令维护着主模块中与go.mod文件一起的一个名为go.sum的文件,该文件包含对指定模块版本的预期加密校验码。每次用到一个依赖时,会将校验码添加到go.sum中(如果不存在),或者匹配当前存在的现有条目。

go命令维护缓存下载的包,并在每个包下载的时候计算和记录它们的加密校验码。在正常操作中,go命令根据主模块的go.sum文件检查那些预计算的校验码,而不是每次命令调用都计算它们。“go mod verify”命令检查下载的模块复制缓存是否仍然匹配它们的校验码记录与go.sum中的条目匹配。(译注-疑问:每次调用go命令时,会拿go.sum与什么校验?)

通过设置GOPROXY环境变量,go命令可以通过代理获取模块,而不是直接连接到源版本控制系统。

有关代理的详细信息以及缓存的已下载软件包的格式,请参阅“go help goproxy”。

Modules and vendoring

当使用模块时,go命令完全忽略vendor目录。

默认情况下,go命令从源(版本控制系统)下载模块并使用他们的副本以满足依赖(在验证后)。为了允许与旧版本Go进行交互操作,或者要确保构建需要的所有文件存在同一个文件树下,“go mod vendor”在主模块根目录下创建名为vendor的目录,在该目录下存储所有构建与测试依赖的所有包。

要使用主模块顶层vendor目录来满足构建依赖(不使用网络源和本地缓存),使用命令“go build -mod=vendor”。注意只使用主模块最顶层的vendor目录,其它位置的vendor依然被忽略。

Module-award go get(模块感知的go get)

“go get”命令会根据当前处于模块感知模式或者传统的 GOPATH 模式来改变行为。即使在传统GOPATH模式下,此帮助文档也可以通过“go help module-get”访问,它描述了“go get”在模块感知模式下运行机制。

用法:

go get [-d] [-m] [-u] [-v] [-insecure] [build flags] [packages]

解析并添加依赖到当前开发的模块,然后构建,安装它们。

第一步是解析要添加哪个依赖项。

对于每个指定的包或者包模式,get 必须决定要使用哪个版本对应的模块。默认情况下,get选择最新的发布版本标签,如: v0.4.5 或 v1.2.3.如果没有发布版本标签,get选择最新的预发布版本标签,如 v0.0.1-pre1 。如果根本就没有版本标签,get 选择最新的一次提交作为下载的版本。

当在包参数添加@version后缀时,默认版本选择会被覆盖,像“go ger golang.org/x/text@v0.3.0”。对于存储在版本仓储中的模块,version 后缀还可以是提交的哈希值,分支标识,或者其他源控制系统能识别的语法,就像'go get golang.org/x/text@master'. 版本后缀@latest 作为默认的后缀,它的效果就像上文提到的下载最新版本。

如果考虑的一个模块已经在当前开发模块的依赖中,那么get将更新需求的版本。可以允许指定一个更早的版本来进行模块降级。版本后缀 @none 表明依赖的条目将被移除,降级或者移除模块取决于需求。

虽然默认使用包含命名包的模块的最新版本,但它不使用该模块的最新版本的依赖项。相反,它更喜欢使用该模块请求的特定依赖版本。例如,如果最新的A需要模块B v1.2.3,而B v1.2.4和v1.3.1也可用,那么'go get A'将使用最新的A但是然后使用B v1.2.3,按照A的要求。(如果对特定模块有冲突的需求,那么'go get'通过获取其中最大请求版本来解决这些需求。)

-u 标记指导 go get 命令将依赖模块更新到最新的次版本号与修订号(不是主版本号),继续前面的例子,“go get -u A”将使得A使用最新的B v1.3.1(不是B v1.2.3)(译注:语义化版本的格式:v主版本号.次版本号.修订号 -> v major.minor.patch)

-u=path 标记(不是 -u path)指导get更新可得的最新修订号依赖,继续上述例子,“go get -u=path A”将使用B v1.2.4(不是B v1.2.3)

总之,添加一个新的依赖,可能需要更细现有的依赖,以满足继续编译,go get 自动执行此操作。同样对模块降级引起其他模块降级时,go get也将自动执行操作。

在解析,升级和降级模块以及更新go.mod之后,-m标志指示get停在这里。使用-m时,每个指定的包路径也必须是模块路径,而不是模块根目录下的包的导入路径。

-insecure标志允许从存储库中提取并使用不安全的方案(如HTTP)解析自定义域。谨慎使用。

第二步是下载(若需要)构建和安装指定包

如果参数指定一个模块而不是一个包(因为在模块的根目录下没有Go源码),则跳过该参数的安装步骤而不会报错。例如'go get golang.org/x/perf' 依旧会成功,即使在该导入路径下没有相应的代码

注意允许使用包名的模式匹配,并在解析模块版本后进行扩展。例如'go get golang.org/x/perf/cmd/...' 添加最新的 golang.org/x/perf 然后在最新版本安装命令

-d 标记指示get命令下载构建指定包所需的源码,包括下载需要的依赖,但是不执行构建和安装。

如果没有指定 package参数,“go get”将应用于主模块,并应用于当前目录下的Go包(如果有)。特别的,“go get -u”与 "go get -u=patch" 更新主模块的所有依赖。没有package参数和 -u 参数,“go get”相当于“go install”,“go get -d”相当于“go list”

有关模块的更多信息,请参阅“go help modules”。

有关指定包的更多信息,请参阅“go help packages”。

本文描述了使用模块来管理源代码和依赖关系的行为。如果go命令在GOPATH模式下运行,则get的标志和效果的细节会发生变化,就像'go help get'一样。请参阅'go help modules'和'go help gopath-get'。

另见:go build,go install,go clean,go mod。

Package lists and patterns(包列表与模式匹配)

许多命令应用于一组包的集合:

go action [packages]

通常,[packages]是导入路径列表。

导入路径是根路径或以 '.' 或 '..' 元素开头的路径被理解问文件系统路径,表示包在那个路径下。

否则,导入路径 P 表示该包在路径 DIR/src/P 下,其中DIR是GOPATH环境变量的其中之一。

如果未指定路径,那么指令 action 将应用于当前目录的包。

路径有四个保留名称,不应该用于使用go tool 构建的包:

-"main"表示独立可执行文件中的顶级包。
-"all"扩展到所有GOPATH树中的所有包。例如,'go list all'列出本地系统上的所有软件包。使用模块时,“all”扩展到主模块中的所有包及其依赖项,包括任何测试所需的依赖项。

  • “std”就像 all 一样, 不过扩展到标准Go库中的包。
  • “cmd”扩展为Go存储库的命令及其内部库。

以“cmd/”开头的导入路径仅匹配Go仓储中的源码。

导入路径模式可以包含一个或多个“...”通配符,每个通配符匹配任意字符串,包括空字符串和包含斜杠的字符串。这样的模式匹配扩展到GOPATH树种的所有包目录,查找与模式匹配的名称。

为了使模式匹配更方便,有两种特殊情况:首先在模式匹配的末尾添加 "/..." 可以匹配空字符串,因此 net/... 同时匹配 net 包以及它的所有子包,如 net/http。 其次,任何以斜杠分割的模式元素通配符都不会参与与vendored包路径中“vendor”元素的匹配。所以,“./...”不会匹配子目录中的 "./vendor" 或 "./mycode/vendor" 包,而是匹配 "./vendor/..." 和 "./mycode/vendor/..."。 然而,注意命名为“vendor”路径本身包含源代码时,不算一个 vendored 包:cmd/vendor 是名为vendor 的命令,会被模式 "cmd/..." 匹配。有关vendoring的更多信息,请参阅golang.org/s/go15vendor。

导入路径还可以命名要从远程存储库下载的包。运行'go help importpath'了解详细信息。

程序中的每个包都必须具有唯一的导入路径。按照惯例,这是通过使用属于你的唯一前缀启动每个路径来实现的。例如,Google内部使用的路径都以“google”开头,而表示远程存储库的路径则以代码的路径开头,例如“github.com/user/repo”。

程序中的包名不需要具有唯一性,但有两个具有特殊含义的保留包名。名称main表示命令(源码),而不是库(源码)。命令将被构建为二进制文件,无法导入。名称“documentation”表示目录中非Go程序的文档。go命令会忽略该包中的文件。

作为一种特殊情况,如果包列表是来自单个目录的.go文件列表,则该命令将应用于由这些文件组成的单个合成包,忽略任何构建约束并忽略目录中的任何其他文件。

以“.”或“_”开头的目录和文件名将被go工具忽略,如“testdata”目录一样。

Testing flags

“go test”命令接受适用于它自身的标记和适用于test结果二进制文件的标记。

几个标记控制性能分析并生成适合“go tool pprof”的执行配置文件。执行"go tool pprof -h"获取更多信息。
pprof的 --alloc_space, --alloc_objects, --show_bytes 选项控制如何显示信息。

“go test”识别下面的标记并控制任何测试的执行:

-bench regexp
    Run only those benchmarks matching a regular expression.
    By default, no benchmarks are run.
    To run all benchmarks, use '-bench .' or '-bench=.'.
    The regular expression is split by unbracketed slash (/)
    characters into a sequence of regular expressions, and each
    part of a benchmark's identifier must match the corresponding
    element in the sequence, if any. Possible parents of matches
    are run with b.N=1 to identify sub-benchmarks. For example,
    given -bench=X/Y, top-level benchmarks matching X are run
    with b.N=1 to find any sub-benchmarks matching Y, which are
    then run in full.

-benchtime t
    Run enough iterations of each benchmark to take t, specified
    as a time.Duration (for example, -benchtime 1h30s).
    The default is 1 second (1s).
    The special syntax Nx means to run the benchmark N times
    (for example, -benchtime 100x).

-count n
    Run each test and benchmark n times (default 1).
    If -cpu is set, run n times for each GOMAXPROCS value.
    Examples are always run once.

-cover
    Enable coverage analysis.
    Note that because coverage works by annotating the source
    code before compilation, compilation and test failures with
    coverage enabled may report line numbers that don't correspond
    to the original sources.

-covermode set,count,atomic
    Set the mode for coverage analysis for the package[s]
    being tested. The default is "set" unless -race is enabled,
    in which case it is "atomic".
    The values:
        set: bool: does this statement run?
        count: int: how many times does this statement run?
        atomic: int: count, but correct in multithreaded tests;
                significantly more expensive.
    Sets -cover.

-coverpkg pattern1,pattern2,pattern3
    Apply coverage analysis in each test to packages matching the patterns.
    The default is for each test to analyze only the package being tested.
    See 'go help packages' for a description of package patterns.
    Sets -cover.

-cpu 1,2,4
    Specify a list of GOMAXPROCS values for which the tests or
    benchmarks should be executed. The default is the current value
    of GOMAXPROCS.

-failfast
    Do not start new tests after the first test failure.

-list regexp
    List tests, benchmarks, or examples matching the regular expression.
    No tests, benchmarks or examples will be run. This will only
    list top-level tests. No subtest or subbenchmarks will be shown.

-parallel n
    Allow parallel execution of test functions that call t.Parallel.
    The value of this flag is the maximum number of tests to run
    simultaneously; by default, it is set to the value of GOMAXPROCS.
    Note that -parallel only applies within a single test binary.
    The 'go test' command may run tests for different packages
    in parallel as well, according to the setting of the -p flag
    (see 'go help build').

-run regexp
    Run only those tests and examples matching the regular expression.
    For tests, the regular expression is split by unbracketed slash (/)
    characters into a sequence of regular expressions, and each part
    of a test's identifier must match the corresponding element in
    the sequence, if any. Note that possible parents of matches are
    run too, so that -run=X/Y matches and runs and reports the result
    of all tests matching X, even those without sub-tests matching Y,
    because it must run them to look for those sub-tests.

-short
    Tell long-running tests to shorten their run time.
    It is off by default but set during all.bash so that installing
    the Go tree can run a sanity check but not spend time running
    exhaustive tests.

-timeout d
    If a test binary runs longer than duration d, panic.
    If d is 0, the timeout is disabled.
    The default is 10 minutes (10m).

-v
    Verbose output: log all tests as they are run. Also print all
    text from Log and Logf calls even if the test succeeds.

-vet list
    Configure the invocation of "go vet" during "go test"
    to use the comma-separated list of vet checks.
    If list is empty, "go test" runs "go vet" with a curated list of
    checks believed to be always worth addressing.
    If list is "off", "go test" does not run "go vet" at all.

一下标记同样可以被“go test”识别,并可用于执行期间对测试进行分析:

-benchmem
    Print memory allocation statistics for benchmarks.

-blockprofile block.out
    Write a goroutine blocking profile to the specified file
    when all tests are complete.
    Writes test binary as -c would.

-blockprofilerate n
    Control the detail provided in goroutine blocking profiles by
    calling runtime.SetBlockProfileRate with n.
    See 'go doc runtime.SetBlockProfileRate'.
    The profiler aims to sample, on average, one blocking event every
    n nanoseconds the program spends blocked. By default,
    if -test.blockprofile is set without this flag, all blocking events
    are recorded, equivalent to -test.blockprofilerate=1.

-coverprofile cover.out
    Write a coverage profile to the file after all tests have passed.
    Sets -cover.

-cpuprofile cpu.out
    Write a CPU profile to the specified file before exiting.
    Writes test binary as -c would.

-memprofile mem.out
    Write an allocation profile to the file after all tests have passed.
    Writes test binary as -c would.

-memprofilerate n
    Enable more precise (and expensive) memory allocation profiles by
    setting runtime.MemProfileRate. See 'go doc runtime.MemProfileRate'.
    To profile all memory allocations, use -test.memprofilerate=1.

-mutexprofile mutex.out
    Write a mutex contention profile to the specified file
    when all tests are complete.
    Writes test binary as -c would.

-mutexprofilefraction n
    Sample 1 in n stack traces of goroutines holding a
    contended mutex.

-outputdir directory
    Place output files from profiling in the specified directory,
    by default the directory in which "go test" is running.

-trace trace.out
    Write an execution trace to the specified file before exiting.

Each of these flags is also recognized with an optional 'test.' prefix, as in -test.v. When invoking the generated test binary (the result of 'go test -c') directly, however, the prefix is mandatory.

The 'go test' command rewrites or removes recognized flags, as appropriate, both before and after the optional package list, before invoking the test binary.

For instance, the command

go test -v -myflag testdata -cpuprofile=prof.out -x
will compile the test binary and then run it as

pkg.test -test.v -myflag testdata -test.cpuprofile=prof.out
(The -x flag is removed because it applies only to the go command's execution, not to the test itself.)

The test flags that generate profiles (other than for coverage) also leave the test binary in pkg.test for use when analyzing the profiles.

When 'go test' runs a test binary, it does so from within the corresponding package's source code directory. Depending on the test, it may be necessary to do the same when invoking a generated test binary directly.

The command-line package list, if present, must appear before any flag not known to the go test command. Continuing the example above, the package list would have to appear before -myflag, but could appear on either side of -v.

When 'go test' runs in package list mode, 'go test' caches successful package test results to avoid unnecessary repeated running of tests. To disable test caching, use any test flag or argument other than the cacheable flags. The idiomatic way to disable test caching explicitly is to use -count=1.

To keep an argument for a test binary from being interpreted as a known flag or a package name, use -args (see 'go help test') which passes the remainder of the command line through to the test binary uninterpreted and unaltered.

For instance, the command

go test -v -args -x -v
will compile the test binary and then run it as

pkg.test -test.v -x -v
Similarly,

go test -args math
will compile the test binary and then run it as

pkg.test math
In the first example, the -x and the second -v are passed through to the test binary unchanged and with no effect on the go command itself. In the second example, the argument math is passed through to the test binary, instead of being interpreted as the package list.

Testing functions

"go test" 命令期望在与之对应的" * _test.go " 中找到test, benchmark, and example方法。

test function 命名为 TestXxx(Xxx不能以小写字母开头)且应该有以下方法签名:

func TestXxx(t *testing.T) { ... }

benchmark function 命名为 BenchmarkXxx 且方法签名格式如下:

func BenchmarkXxx(b *testing.B) { ... }

example 方法与 test方法相似,不过不是使用 *testing.T 来报告成功或失败,而是将输出打印到 os.Stdout。如果函数中最后一个注释以“Output:”开头,则输出将与该注释进行完全对比(见下例)。如果注释以"Unorder output:" 开头,则将输出与注释进行对比,但忽略行的顺序。一个没有上述注释的 example 测试将被编译但是不执行。如果“Output:”之后没有任何文本,也会编译执行并预期不会有任何输出。

Godoc显示ExampleXxx的主体以演示函数,常量或变量Xxx的使用。具有接收器类型T或* T的方法M的示例被命名为ExampleT_M。给定函数,常量或变量可能有多个示例,由尾随_xxx区分,其中xxx是不以大写字母开头的后缀。

以下是一个example测试方法示例:

func ExamplePrintln() {
        Println("The output of\nthis example.")
        // Output: The output of
        // this example.
}

以下是另一个忽略输出顺序的示例:

func ExamplePerm() {
        for _, value := range Perm(4) {
                fmt.Println(value)
        }

        // Unordered output: 4
        // 2
        // 1
        // 3
        // 0
}

当一个测试文件包含单个 example 方法,至少一个其他方法,类型,变量或者常亮定义,且没有test 或 benchmark 方法的时候被认为是单个示例。

有关更多信息,请参阅测试包的文档。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 156,907评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,546评论 1 289
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 106,705评论 0 238
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,624评论 0 203
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,940评论 3 285
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,371评论 1 210
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,672评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,396评论 0 195
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,069评论 1 238
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,350评论 2 242
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,876评论 1 256
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,243评论 2 251
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,847评论 3 231
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,004评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,755评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,378评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,266评论 2 259

推荐阅读更多精彩内容