#

# 基本信息

包(package)由同一目录下的多个源文件构成。

.go.c.s 等文件。
.go 头部通过 package 定义所属包。

源文件必须是 UTF-8 格式。

包是成员作用域边界,包内成员可相互访问。
名称首字母大写为 导出成员(exported),可外部访问。

包名可与目录名不同,通常小写单数模式。同一目录下源文件必须使用相同包名。

package mylib                  // 包名通常与目录一致。
func ddd(x, y int) int {       // 私有成员。
	return x + y
}
func Hello() {                 // 导出成员,可外部访问。
	println("Hello, World!")
}

另有几个特殊含义的包名:

main : 用户可执行文件入口包。
all : 所有包,包括标准库和依赖项。
std : 标准库。
cmd : 工具链。

documentation : 文档。(工具链忽略,无法导入)

为命令行提供包名为参数时,有以下几种方式。

直接提供包名,如标准库。( go list math
以。或 .. 开始的相对路径。( go build ./mylib
用 ... 作为通配符,表示任意字符串。( go build ./...

提示: net/... 表示 net 和其所有子包,但不包括 vendor
除非特别指定,如 ./vendor/..../mylib/vendor/...

# 访问权限

同一包内不同源文件的成员可相互访问,但只有首字母大写为导出成员。
此规则适用于全局变量、全局常量、函数、类型、字段和方法等。

// test/mylib/add.go
package mylib
func add(x, y int) int {
	return x + y
}
// test/main.go
package main
import (
	"test/mylib"
)
func main() {
	z := mylib.add(1, 2) // ~ undefined: mylib.add
	println(z)
}

某些时候,需临时访问私有成员,可参考如下手段。

// test/mylib/data.go
package mylib
type data struct {
    x   int
    y   int
}
func NewData() *data {
	return &data{1, 2}
}
package main
import (
	"fmt"
	"unsafe"
	"test/mylib"
)
func main() {
	p := mylib.NewData()
	
	// p.x = 100 // ~ p.x undefined
	d := (*struct{ 
		_ int
		y int              // 仅需要访问的字段
	})(unsafe.Pointer(p))
	d.y = 100
	fmt.Println(*d)        // {1, 100}
}

还可用别名。

// test/mylib/data.go
package mylib
type data struct {
    x   int
    y   int
}
func (d *data) test() {
	println(d.x, d.y)
}
type DataTmp = data
func (d *DataTmp) SetY(y int) {
	d.y = y
}
func (d *DataTmp) Test() {
	d.test()
}
// test/main.go
package main
import (
	"test/mylib"
)
func main() {
	d := mylib.DataTmp{}
	d.SetY(100)
	d.Test()
}

临时提升访问权限,应避免直接修改原目标。
可将临时代码单独放在 _tmp.go 文件内,随后直接删除即可。

# 初始化

包内任意 .go 文件内都可定义一到多个 init 初始化函数。
初始化函数由编译器生成代码自动执行(仅执行一次),不能被其他代码调用。

所有初始化函数被编译器整合到一个特殊数据结构内。
在程序启动初始化阶段,在同一 goroutine 内依次执行。

包内全局变量按照依赖关系,逐步初始化(或零值)。
每个初始化函数只完成一组相关逻辑,且相互之间不应该有执行次序依赖。

1.21 起,包初始化次序有了正式规范,但依然不建议对其有所依赖。

package main
var x = 100
func init() {
	println("a", x)  // a 100
}
func init() {
	println("b")
}
func main() {
	// init() // ~ undefined: init    
}

可使用 GODEBUG 查看初始化执行情况。

$ GODEBUG=inittrace=1 ./test
init internal/bytealg @0.008 ms, 0.008 ms clock, 0 bytes, 0 allocs
init runtime          @0.040 ms, 0.048 ms clock, 0 bytes, 0 allocs
init main             @0.31  ms, 0.014 ms clock, 0 bytes, 0 allocs
#    包名             启动时刻     执行耗时         堆内存分配数量和次数。

# 内部包

代码重构时,将一些内部模块陆续分离出来,以独立包形式维护。此时,首字母大小写访问控制就过于粗旷。因为我们希望其 导出成员 仅限特定范围内访问,而不是向所有用户公开。

内部包(internal package)机制相当于增加了新访问权限控制:

内部包(含自身)只能被其父目录(含所有层次子目录)访问。
内部包私有成员,依然只能在自己包内访问。

test/
  |
  +-- main.go
  |
  +-- mylib/              # 内部包 internal、a、b 的导出成员仅能
        |                 # 被 mylib、mylib/x、mylib/x/y 访问。
        |
        +-- internal/     # 内部包之间可相互访问。
        |      |          # 可导入外部包。
        |      +-- a/
        |      |
        |      +-- b/
        |
        +-- x/
            |
            +-- y/
package main
import (
	"test/mylib"
	"test/mylib/x"
	"test/mylib/x/y"
	// "test/mylib/internal/a" // ~ use of internal package not allowed
)
func main() {
}

# 导入

使用包前,须用 import 导入包路径。

导入完整的模块路径(module path),非包名。
编译器依次搜索标准库、项目根目录,及缓存目录。
引用包成员,用包名(package),而非导入路径。

默认为 module 模式,不支持 GOPATHrelative import 等老旧内容。

import "net/http"         // $GOROOT/src/net/http
import "github.com/xxx"   // $GOMODCACHE/github.com/xxx

使用别名,解决同名冲突问题。

import (
    osx  "osx/lib"     // 为包取别名,而非导入路径。
    nix  "linux/lib"
)

不同导入方式,及其成员引用。

import (
       "math"     默认: math.Sin
    m  "math"     别名: m.Sin
    .  "math"     简便: Sin
    _  "math"     初始化: 无法引用,仅用来初始化目标包。
)

简便:相当于 from math import * 。 (常用于单元测试中)
初始化:目的是让目标包的初始化函数得以执行,而非引用其成员。
不能直接或间接导入自己,不支持任何形式的循环导入。

未使用的导入(不包括初始化方式)被编译器视为错误。

package main
import (
    "fmt" // ~ imported and not used: "fmt"
)
func main() {
}

# 模块

模块(module)是包(packages)和其依赖项(dependency)的集合。

直接体现是 go.mod 文件,存储了模块路径、编译器版本,以及依赖项列表。
如此,模块是依赖管理方式,是发布和版本控制单元。

module = go.mod + package + subs...

模块路径(module path):在 go.mod 中声明的名称标识。
根目录(module root directory):包含 go.mod 文件的目录。
主模块(main module):执行 go 命令时,所在目录对应的模块。

# 初始化

模块对应一个包含 go.mod 的源码目录,所有不含 go.mod 的子目录都是其成员。

test/
    |
    +-- go.mod        (github.com/twelveeee/test)
    |
    +-- main.go
    |
    +-- mylib/        (github.com/twelveeee/test/mylib)
          |
          +-- add.go
$ cd test
$ go mod init github.com/twelveeee/test
go: creating new go.mod: module github.com/twelveeee/test
$ cd test/mylib
$ go list -m                     # 向外查找 go.mod,主模块。
github.com/twelveeee/test

模块路径描述了线上存储位置,通过 go get 下载到本地缓存目录。
以 import 导入模块时,使用模块路径而非本地路径。

// go.mod
module github.com/twelveeee/test    // 模块路径
go 1.18                          // 编译器最低版本号
// main.go
package main
import (
    // "test/mylib" // ~ package test/mylib is not in GOROOT
    
	"github.com/twelveeee/test/mylib"
)
func main() {
	println(mylib.Add(1, 22))
}

如果子目录包含 go.mod,那么它将是独立模块,不再属于当前模块。

$ cd test/mylib
$ go mod init github.com/twelveeee/mylib
go: creating new go.mod: module github.com/twelveeee/mylib
$ cd ..
$ go build
no required module provides package github.com/twelveeee/test/mylib; 
to add it: go get github.com/twelveeee/test/mylib

# 依赖管理

命令 go get 添加、下载(更新)依赖项,其他操作可用 go mod 完成。

GOPROXY 下载源码到本地缓存 GOMODCACHE 目录。
go.modgo.sum 添加依赖项和验证信息。
使用 go clean -modcache 清除缓存。(自动重新下载)

$ go get .                        # 分析源码,添加所有依赖。
$ go get example.com/my           # 指定模块,下载最新版本。
$ go get example.com/my@v1.3.4    # 指定版本号。
$ go get example.com/my@lastet    # 最新版本。
$ go get example.com/my@4cf76c2   # 伪版本号(commit hash)
$ go get example.com/my@bufix     # 分支(branch)

用 go mod 管理模块。

init : 初始化模块,创建 go.mod 文件。
tidy : 添加遗漏,移除不需要的依赖项。

edit : 命令行方式编辑模块设置。
-fmt :格式化 go.mod 文件。
-module :模块路径。
-go :编译器版本。
-require , -droprequire :添加直接或间接依赖项。
-replace , -dropreplace :替换模块路径或版本。
-exclude , -dropexclude :排除模块或版本。
-retract , -dropretract :标识有问题需要忽略的版本。

# 版本标识

版本标识模块不可变快照。

v 开头,然后是语义版本(semantic version)。

不兼容(incompatible)更新,递增主要版本号。
添加功能的兼容更新,递增次要版本号。
优化和修复缺陷,不影响导出接口,递增补丁版本号。
正式发布前的预发行版本,添加 -pre 后缀。

使用 git tag 之类的功能标记语义化版本号。
如没有标记,则使用提交(commit)信息(time, ident)构成伪版本号。

v(major).(minor).(patch)-(pre|beta)
  主要     次要     补丁    预发行或测试版
v1.2.3
v1.2.3-pre
v1.5.0-beta
v0.0.0-20191109021931-daa7c04131f5 --> go get example.com/my@daa7c041

查看所有版本。

$ go list -m -versions github.com/shirou/gopsutil
v2.0.0+incompatible 
v2.16.10+incompatible 
v2.16.11+incompatible ...
v2.21.11+incompatible 
v3.20.10+incompatible ... 
v3.21.11+incompatible
$ go list -m -versions all

更新依赖项。

$ go get -u example.com/my@v1.3          # minor or patch releases
$ go get -u=patch example.com/my@v1.3.4  # patch release

可使用 >>=<<= 操作符指定版本查询范围(module query)。

选择最接近条件的版本。例如: >=1.0 选择 1.0<1.0 选择 0.9 ,而不是 0.8
注意,命令行添加引号,否则显示版本错误。

$ go get "github.com/shirou/gopsutil@>v2.16.10"

兼容性规则

如新版和旧版有相同导入路径,则必须保证向后兼容(backwards compatible)。
主版本号出现在模块路径尾部,表示重大不兼容更新。

mod/v2mod/v1 不兼容。
v0v1 不需要使用版本后缀。
v0 本身就表示不稳定且不具有兼容性保证, v1 为默认版本。
另外,有预发行后缀也表示该版本不稳定,不受兼容性约束。

导入语句(import)须使用包含版本号( v2 )的全路径。
通常建议在原模块下建立 /v2/go.mod 模块(子目录)。

import github.com/twelveeee/test/v2

# 工作空间

以工作空间(workspace)解决多模块开发遇到的问题。

编译时,找不到未发布模块(非子包)。
只能以 replace 将模块路径替换为本地路径。
污染的 go.mod 意外提交到代码仓库。

app/                  (workspace)
    |
    +-- go.work
    |
    +-- test/            (github.com/twelveeee/test)
    |    |
    |    +-- go.mod
    |    |
    |    +-- main.go
    |
    +-- mylib/           (github.com/twelveeee/mylib)
         |
         +-- go.mod
         |
         +-- add.go
$ go work init                   # 初始化工作空间。
$ go work use ./test ./mylib     # 添加模块。
// go.work
go 1.18
use (
    ./mylib
    ./test
)

导入(import)工作空间内其他模块,无需 require 指令。

// mylib/go.mod
module github.com/qyuhen/mylib
go 1.18
// test/go.mod
module github.com/qyuhen/test
go 1.18
//require github.com/qyuhen/mylib v0.0.1   // 不需要
// test/main.go
package main
import (
	"github.com/qyuhen/mylib"
)
func main() {
	println(mylib.Add(11, 22))
}

若必须添加 require,编译时可关闭 GOPROXY,阻止在线获取模块验证信息。

GOPROXY=off go build

模块还可放在空间目录外,或加入多个工作空间。

// go.work
go 1.18
use (
	../mylib
	./test
)

# 环境变量

模块感知(module-aware)模式,相关环境变量说明。

GO111MODULE :模块感知模式开关。(默认:on)
GOMODCACHE :模块缓存路径。
GOPROXY :模块代理服务列表。
GOSUMDB :模块数据安全校验数据库。
GOPRIVATE :私有模块路径,绕过 GOPROXY 直接获取。

GOPRIVATE=*.example.com ,以逗号分隔多个地址。
GOPRIVATEGONOPROXYGONOSUMDB 的默认值。

# 离线编译

打包含依赖在内的所有源码,进行离线编译。

$ go mod vendor                    # 将依赖复制到 ./vendor 目录下。
$ GOWORK=off go build -mod vendor  # 禁用工作空间,以 vendor 方式编译。
更新于
-->