大白话go入门(2)--go命令解惑

640 阅读1分钟

每天go一点点,只记录学习中, 自己用到和迷惑的东西, 记录精华部分, 不求大而全.

Command go

GOPATH

翻译自go help gopath.

GOPATH被用来解析import语句.

GOPATH环境变量用来表示去哪里查找Go代码. 在Unix系统中, 这个环境变量是一个用:分割的字符串.

如果该环境变量没有设置, 则GOPATH默认是$HOME/go.

GOPATH目录结构

GOPATH/下的每个目录, 都有一个自己的结构规范:

  • src目录存放源代码. src/后面的path确定了①packageimport path或者②package编译后可执行文件的名字(取自import path的最后一个目录名).

  • pkg目录存放package的编译结果. 每个操作系统都有自己的pkg子目录pkg/GOOS_GOARCH.

    举个栗子: 如果DIRGOPATH环境变量的成员之一, 那么存放在DIR/src/foo/bar/下的package的导入路径就是foo/bar. 并且编译结果被放入(installed)DIR/pkg/GOOS_GOARCH/foo/bar.a.

  • bin目录存放编译好的命令commands. 每个命令都是根据他的源文件包的import path的最后一个元素进行命名的.

    举个例子: 命令(package main的编译结果)的源码存放在DIR/src/foo/quux/目录下(假设DIR已经在GOPATH环境变量里面了), 则他的import pathfoo/quux, 那么这个命令会被安装到DIR/bin/quux, 而不是DIR/bin/foo/quux.

    当然, 如果GOBIN环境变量被设置了, 那么所有的commands都会被安装到这个文件夹下.GOBIN必须是一个绝对路径.

下面是一个GOPATH目录布局的例子:

    GOPATH=/home/user/go  # GOPATH环境变量

    /home/user/go/
        src/
            foo/
                bar/               (package bar 源码包)
                    x.go
                quux/              (package main 源码包)
                    y.go
        bin/
            quux                   (package main 安装后的命令/可执行文件)
        pkg/
            linux_amd64/
                foo/
                    bar.a          (package bar 编译后的库文件)

go命令会搜索所有的GOPATH环境变量中的所有目录去查找源文件, 但是新下载的package都会被放到GOPATH的第一个目录中.

GOPATH and Modules

如果你使用了modules(推荐使用), 就不再用GOPATH 来解析import path了. 此时它起的作用就是用来存储:

  • 下载的package源码都会被存放到GOPATH/pkg/mod/目录下.
  • 编译之后的命令, 会被安装到GOPATH/bin/目录下

内部目录internal directories

这里描述一下pacakge引用规则.

假设当前的目录叫做internal, 那么internal目录/子目录下的package只能被其父目录中的代码导入使用.

目录结构如下:

    /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.goimport pathfoo/internal/baz, 但是这个导入语句只能出现在foo的目录树的源文件中. 如: foo/f.go, foo/bar/x.go, foo/quux/y.go都可以使用导入语句import foo/internal/baz. 但是源文件crash/bang/b.go则不能使用.

Vendor Directories

用的少, 就不翻译了. 有兴趣就看go help gopath相应的部分.


import path

翻译自go help importpath

一个import path用来表示一个存储在本地文件系统中的package. 即: 每个packageimportpath都是唯一的

一般来说, 一个import path 要么表示一个标准库包(如: unicode/utf8), 要么表示一个在工作空间中的包(见上面的go path).

相对导入路径 relative import paths

如果一个import后面的路径是以 ./ ../开头, 表示这是一个相对路径relative path.

go命令支持把realtive import path作为import path的缩写, 一共有两种形式:

    1. relative path命令行中作为import path的简写.

    如果你所在的文件夹下的pacakgeimport pathunicode, 那么如果要对unicode/utf8的包进行测试, 你就可以这么写: go test ./urf8.

    除了./, ../, 在相对路径中, 我们也是使用模式匹配.... 如: go test ./..., 这条go命令会对当前目录(包含各层子目录)下的所有包进行测试.

    1. workspace(GOPATH/src GOPATH/bin)环境下, relative path可以在程序中使用

    如果你正在编译一个不在workspace下的Go程序, 你可以在程序中使用一个relative path去导入一个"附近"的包(附近的包也不能在workspace下).

    这样就使得在工作空间之外的地方, 使用多个包构成的程序变得很方便. 但是这样的程序是没有办法安装(go install)的, 因为没有地方安装他们(不在工作空间中, 也不是一个module). 所以每次build, 都是从头开始.

    为了避免歧义, go程序不允许在workspace工作空间下, 使用relative import path.只能使用完整的import path.

远端导入路径 remote import path

某些import path还描述了如何使用版本控制系统(如git)获取package的源代码。

这里以github举例:

GitHub (Git)

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

为了表示代码的位置, import path需要指定哪个仓库, 以及仓库中的代码路径.

repository.vsc/path   # vcs后缀可以没有(version contorl system)

依旧以git举例:

import "example.org/repo.git/foo/bar" 表明:pacakge的根目录在git仓库的example.org/repo/, 仓库内的路径是foo/bar/.

当使用GOPATH时, 下载的包都会写入GOPATH环境变量的第一个目录. (go help gopath-get)

当使用modules是, 下载的包会写入module的缓存中.(go help module-get)

packages && import path

Package lists and patterns

go命令, 大多数是这样的形式: go command [packages]. 通常[packages]是一个import paths列表.

我们首先要知道, 我们通常都是使用导入路径 import path去标识一个包 package 的. 所以我们首先要弄明白什么是packageimport path.

给出一个import path, 我们要先对其进行解析(通过file system/ GOPATH/ mdoules), 解析出来的目录, 就是pacakge代码的存放目录.

本地包的 import path

本地宝import path会有两种解析方式:

  • case1: 通过local file system去解析import path

    针对以下两种情形一个import path会通过文件系统路径去查找:

    • 绝对路径 rooted path( /xxx/ooo ) 只适用于命令行, 程序中不能使用绝对路径!
    • 或者以. ..开头的相对路径 只适用于非workspace的情况
  • case2: 通过GOPATH去解析import path (见上面的章节: gopath)

    不符合case1的import path, 表示该pacakge是在$GOPATH/src/<import path>下的.

如果go命令中没有指定import path, 那么action将作用于当前目录下的pacakge.

pattern 模式

如果import path包含一个或者多个... 通配符, 那这个import path就是一个pattern 模式. 每个通配符可以匹配字符串(包括"/"和空字符串). 这种模式会在$GOPATH目录树下, 查找所有符合该模式的package 目录.

关于通配符, 有两种特殊情形需要了解:

  • case1: /...出现在pattern的最后, 可以匹配空字符串.所以模式net/... 既可以匹配net, 也可以匹配它子目录中的packages, 如net/http.
  • case2: 模式中由/分割的的元素, 如果是一个通配符..., 那么这个通配符不会匹配vendor. 如./...不会去匹配./vendor, 或者./mycode/vendor.

远端包的 import path

一个import path也可以命名一个需要从远端仓库下载的package. (见上面的章节: import path).


程序中的每个package都必须有一个唯一的import path. 按照惯例, import path都会有一个唯一的前缀. 比如在谷歌, 内部使用的import path都是以google开头, 而远端仓库的包的import path都以远端仓库中该包的路径为前缀, 如: github.com/user/repo.

程序中packages的包名不需要唯一(因为我们通过import path去标识一个包). 但是有两个保留的package name: package mainpackage documentation. package main表明当前的包是一个命令 command, 不是一个库文件 library. 命令包会被编译成可执行文件. package documentation中的文件会被go命令忽略.

有一个特殊情况, 如果[packages]是同一个文件夹中的.go文件列表. 则go命令会被作用到由这些文件构成的package上(该目录下的其他文件都会被忽略).

., _开头的文件或者目录都会被go命令忽略. 名字是testdata的目录也会被忽略.


.go源文件中的package import:

上面说的是go命令中的import path, 那么在源文件中, 需要导入其他package时, 应该怎么处理呢? 这里简单总结一下, 具体看下面的文章:

大白话go入门(3)--一篇搞懂Package&&Module

  • 没有module(module范围外)的情况下, package的import path是指

    • $GOROOT/src/subdirectory path to package
    • 或者 $GOPATH/src/subdirectory path to package
    • 或者是相对路径
  • module的情况下, package的import path是指

    • module path/subdirectory path to pacakge

go build

前提要知道: build命令编译是以pacakge为单位的.

build会把命令行中import path指定的包, 联通它的依赖一起进行编译, 但是不会安装(放到指定的目录中).

build的参数类型

  • build没有参数 执行go build命令时不后跟任何参数,那么命令将试图编译当前目录所对应的package.

  • build的参数是一个文件夹的中的.go文件列表

    如果build命令的参数是同一个文件夹中.go文件列表, 则build命令会认为由这些源文件构成了一个package. 并且在编译这个package的时候, build命令会忽略以_test.go结尾的文件.

    注意: 以列表形式给出的.go文件, 必须都在同一个文件夹中, 其实很好理解, 因为build认为他们构成了一个package, 不同的文件夹自然是不同的package.

  • build的参数是一个packageimport path

  • build的参数是多个packagesimport paths

build的结果/产出

有产出的情况

当build命令编译的是单独一个main package的时候, build命令会产出一个可执行文件, 该文件的命名有两种方式:

  • 如果build参数是.go文件列表, 则可执行文件会以文件列表的第一个源文件命名(go build ed.go rx.go会产出一个 ed可执行文件).
  • 如果build参数是一个packageimport path, 则会以import path中的包所在的目录名进行命名(go build unix/sam, 产出sam可执行文件).

没有产出的情况

如果build命令编译多个packages(指定多个import path), 或者编译一个non-main pacakge, build命令会编译这些pacakges, 但是会丢弃产出的结果, 仅仅测试一下这些包能否被编译.

-o参数: 将编译结果写入到指定文件/目录

当有-o参数的时候, 会强制保留编译结果.

  • -o 指定了一个文件名 fileName

    将单个package的编译结果, 写入到该文件. 此时不管该package是不是main, 都会把编译结果写到这个文件里.

    go build -o fileName  import_path
    go build -o fileName  x.go x2.go ...
    

    build命令会生成fileName文件.

  • -o 指定一个文件夹 directory 将编译产生的可执行文件, 放入到directory下.

    注意, 存放目录要存在, 否则会报错(当成是文件名)

    go build -o direcory import_path1 import_path2 ....
    

    会将编译的pacakge产生的可执行文件, 放到directory/下.

build的常用参数(和clean, get, install, list, run, test命令通用)

  • -v : 在编译package的时候, 打印出这些包的名字.
  • -work: 打印临时工作目录, 并在编译结束时, 不删除这些目录.

go install

命令go install用于编译并安装指定的代码包及它们的依赖包。当指定的代码包的依赖包还没有被编译和安装时,该命令会先去处理依赖包。

实际上,go install命令只比go build命令多做了一件事,即:安装编译后的结果文件到指定目录。

这个命令在内部实际上分成了两步操作:第一步是生成编译结果文件(可执行文件或者 .a静态链接库文件),第二步会把编译好的结果移到 GOPATH/pkg或者GOPATH/pkg 或者 GOPATH/bin。

  • case1: 如果一段程序是属于package main的(这个包可执行),那么当执行 go install <import path> 的时候就会将其生成二进制文件,放到$GOPATH/bin/目录下。
  • case2: 如果一段程序是属于package main以外的其他包,那么当执行 go install <import path> 的时候,就会在$GOPATH/pkg目录创建一个 静态链接库 .a 文件 。

go get

命令go get可以下载更新所需的依赖(module),并对它们进行编译安装

并会把更新/安装的依赖信息写入当前moduelgo.mod文件.

这个命令在内部实际上分成了两步操作:

  • step1: 第一步是确定添加哪些依赖, 同时确定这些依赖的版本.

    go get xxxx@version可以用@version指定要获取的module版本. 如果没有指定@version, 默认是安装/更新依赖到@latest版本.

    go get github.com/username/repo-name # 默认@latest
    
    # 指定module版本 (通过版本号/分支名/commitID指定)
    go get golang.org/x/text@v0.3.0 
    go get golang.org/x/text@master
    

    注意: 尽管go get命令, 会安装/更新(提供package的)module@latest版本, 但是并不会安装/更新这个module自己依赖的module到latest版本.

    太绕了, 举个例子:

    假设latest版本module A 需要 module B v1.2.3, 而module B还有以下几个版本 v1.2.4 , v1.3.1可供选择 , 那么 go get A 会安装/更新 latest版本的 A , 但是会安装/更新 被A所依赖的版本B v1.2.3.

    到这里, 就不得不提一下 -u 参数, 如果加上这个参数go get -u, 就会让get命令去更新提供命令行中 package依赖的module@latest版本(最新的次要/修正版本).

    这句话汉语很难翻译: 我贴一下原文, 比较好理解: The -u flag instructs get to update modules providing dependencies of packages named on the command line to use newer minor or patch releases when available.

    依旧是上面的例子, 如果命令式go get -u A, 会使用latest版本A, 并且会使用 B的 v1.3.1 版本 (not B v1.2.3). 但是如果 B 又依赖于module C, 但是 C 又没有在A的build过程中提供任何package (这里不包括_test.go, 因为测试文件不参与编译), 那么 C 不会被更新到latest版本.

    下载的包会放在$GOPATH/src/下, 引用的时候如下:

    import "github.com/username/repo-name"
    
  • step2: 第二步是下载/编译/安装这些package.

    如果命令行中的参数是一个module, 而不是一个package(module的根目录没有go源码文件的情形), 那么就不会执行install.


参考链接--go编译与工具

go install

go get命令

go命令教程