Go Import Path

eternal-flame-AD
Tue, Dec 25, 2018
Golang 
Golang 技术 
https://eternalflame.info/blog/2018/12/go-import-path/
CC-BY-NC 4.0
Comments

Golang是如何处理import path的

今天研究了一下Golang到底是如何处理import path的,为了强化一下记忆记录一下~

C

C这个import path是保留的,用作CGO。

General Rules

一般的import path(例如net/http, encoding/json)最直接:go会按 (I) vendor (II) 标准库 (III) GOPATH(GO111MODULE=off)/module cache(GO111MODULE=on) 的顺序寻找这个包。

1
2
3
4
import (
    "net/http" // Standard library
    "github.com/eternal-flame-AD/ifttt" // GOPATH
)

Remote Import Paths && go get

我们的程序需要引入各种来自其他repo的包,但手工将每个repo导入GOPATH并维护版本的关系非常不优雅。go get很好的解决了个这问题,我们可以使用Remote Import Path来告诉go get这个包可以在那里获取。例如:

1
2
3
4
5
import (
    "github.com/eternal-flame-AD/example"     // Repo at GitHub
    "github.com/eternal-flame-AD/example/dir" // Directory inside a Repo at GitHub
    "rsc.io/pdf"                              // Repo at an arbitrary domain
)
不过,这个URL既不是机器能直接读取的序列化数据,也不是vcs的clone URL,go get是怎么找到这个repo在哪里的呢?go get会按这样的模式寻找对应Remote Import Path的clone URL:

  1. go get内置了几个host对应的clone URL,所以这些域名下的Remote Import Path有专门的处理逻辑:
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    
    // 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"
    PS: 实际尝试了一下,其实有的这个表里的服务(比如GitHub)是有下面其他域名Import Path所必需的meta标签的,也就是其实应该不需要额外逻辑就能clone,但像Bitbucket好像就没有
  2. 可以通过在路径中标出vcs的类型和位置,指示go get去某个URL clone:
    1
    2
    3
    4
    5
    6
    7
    
    import (
      "example.org/repo.git"         // Git repository at example.org/repo.git import repository root
      "example.org/repo.git/foo/bar" // Git repository at example.org/repo.git import subdirectory foo/bar
    
      "example.org/repo.svn"         // SVN repository at example.org/repo.git import repository root
      "example.org/repo.svn/foo/bar" // SVN repository at example.org/repo.git import subdirectory foo/bar
    )
    支持的VCS一共有:
    VCSQualifier
    Bazaar.bzr
    Fossil.fossil
    Git.git
    Mercurial.hg
    Subversion.svn
  3. 对于除上面的几个特殊的域名之外的域名,go get会带?go-get=1通过https GET尝试获取这个页面,并从其中的名为go-import的meta标签搜索路径,这个标签的格式是这样的:
    <meta name="go-import" content="{{.ImportPrefix}} {{.VCSType}} {{.CloneURL}}">
    • 先看导入repo根目录的情况:
      1
      2
      3
      4
      
      import (
        "example.org/repo"         // root directory
        "example.org/repo/foo/bar" // subdirectory
      )
      对于这个路径,go get会先访问https://example.org/repo?go-get=1,从返回的HTML中搜索名为go-import的meta标签:
      <meta name="go-import" content="example.org/repo git https://example.org/pkg/repo.git">
      go get会发现这个meta标签的Import Prefix和Import Path相同,于是直接使用githttps://example.org/pkg/repo.gitclone到$GOPATH/src/example.org/repo
    • 那如果我们导入的是一个repo下的子目录呢?
      1
      2
      3
      4
      
      import (
        "example.org/repo"         // root directory
        "example.org/repo/foo/bar" // subdirectory
      )
      刚开始显然go get是一脸懵的,手上只有example.org/repo/foo/bar这个URL,于是只能去访问https://example.org/repo/foo/bar?go-get=1,获得了如下的meta标签:
      <meta name="go-import" content="example.org/repo git https://example.org/pkg/repo.git">
      go get根据这个就知道了Import Path里example.org/repo这一部分是指repo的路径,foo/bar这一部分指repo下的子目录 为了防止错误,go get还会去一次https://example.org/repo?go-get=1确定这个repo的clone path就是https://example.org/pkg/repo.git,然后使用githttps://example.org/pkg/repo.gitclone到$GOPATH/src/example.org/repo,然后就可以在GOPATH下找到Import Path了。
    • 除了上面说的两种情况之外,还有一种特殊的描述第三方域名Import机制的meta tag:
      <meta name="go-import" content="example.org mod https://example.org/modproxy">
      这个只在GO111MODULE模式下生效,表示example.org下的Import Path需要通过https://example.org/modproxy这个GOPROXY获得,go get会不使用VCS而是在$GOPROXY/<module>/@v/<version>.zip下载对应版本的zip文件。而且既然是GO111MODULE了,结果也是存在module cache而不是GOPATH里。

Relative Import Path

go也支持./../开头的相对引用,不过只是为了临时在工作区外创建多个包组成的package,所以对其做了严格的限制:

曾经用过这个来在非go项目里使用go,不过现在有了GO111MODULE基本用不到了应该……

Internal and Vendor Directory

internal是一个特殊的文件夹,internal下的其他包只能被internal文件夹所在的路径之下的包使用,例如:

而vendor在internal的基础上还有一点变化:对于scope内的package,可以直接相对于vendor文件夹import,例如: example/repo下的go源码可以使用import “helper”引入example/repo/vendor/helper,vendor的优先级高于除”C”外的所有包,包括标准库。