package & directory & import
import path & package clause & export
这里,首先,要澄清一个概念,就是directory 与 package,我们看到package,总翻译成包,但是在go中,package其实是.go的文件。我们看下上图。
另外,package的name是在哪里定义的?package的name不是由文件名决定的,而是有package clause,来决定的。看下示例:
我们可以看到,这个文件名,叫formatter.go,但是package clause 是 package print,所以这个package的name,应该是print。
当我们在其他的package,调用这个Format function时,需要使用print.Format,即package.functionName
下面,看一个完整示例:
package print
import "fmt"
func Format(num int) string{
return fmt.Sprintf("the num is %d",num)
}
package math
func Double(num int) int {
return num * 2
}
package main
import (
"demo1/src/formatter"
"demo1/src/math"
"fmt"
)
func main() {
num := math.Double(2)
output := print.Format(num)
fmt.Println(output)
}
输出
the num is 4
上面,我们使用import,导入的,其实是directory,是包所在的文件路径,不是package name。这一点,需要注意。
import 后面填写的,其实叫做import path ,注意,与package clause区分开
上面的print才是真正的 package name
import path 通常是由module unique path + 文件夹路径,共同组成的
how do you export an identifier in Go?
Rather than use a special keyword, Go uses capitalization to determine if a package-level identifier is visible outside of the package where it is declared.
An identifier whose name starts with an upper-case letter is exported.
Conversely, an identifier whose name starts with a lowercase letter or underscore can only be accessed from within the package where it is declared.
override a package name
package main
import (
crand "crypto/rand"
"encoding/binary"
"math/rand"
)
func main() {
}
func seedRand() *rand.Rand {
var b [8]byte
_, err := crand.Read(b[:])
if err != nil {
panic("cannot seed with crypto random number generator")
}
r := rand.New(rand.NewSource(int64(binary.LittleEndian.Uint64(b[:]))))
return r
}
internal Package
Sometimes you want to share a function, type, or constant between packages in your module, but you don’t want to make it part of your API.
Go supports this via the special internal package name.
When you create a package called internal, the exported identifiers in that package and its sub-packages are only accessible to the direct parent package of internal and the
sibling packages of internal
注意,这里说的internal,是指package clause 必须是internal,至于文件夹目录,不要求必须是internal
在internal.go中定义一个函数,只有在foo.go 和 sibling.go中,才可以调用这个函数。在bar.go和example.go中,不能调用这个函数。
init function & package be referenced & _
When you declare a function named init that takes no parameters and returns no values, it runs the first time the package is referenced by another package.
Some packages, like database drivers, use init functions to register the database driver. However, you don’t use any of the identifiers in the package.
As mentioned earlier, Go doesn’t allow you to have unused imports. To work around this, Go allows blank imports, where the name assigned to an import is the underscore (_).
Just as an underscore allows you to skip an unused return value from a function, a blank import triggers the init function in a package but doesn’t give you access to any of the exported identifiers in the package
import (
"database/sql"
_ "github.com/lib/pq"
)
module
go.mod
go.mod的作用是,将一系列go的源代码,组建成一个module。
go.mod file must be in root directory of module.
module github.com/learning-go-book/money
go 1.15
require (
github.com/learning-go-book/formatter v0.0.
0-20200921021027-5abc380940ae
github.com/shopspring/decimal v1.2.0
)
Every go.mod file starts with a module declaration that consists of the word module and the module’s unique path.
Next, the go.mod file specifies the minimum compatible version of Go.
Finally, the require section lists the modules that your module depends on and the minimum version that’s required.
There are two optional sections as well. The replace section lets you override the location where a dependent module is located, and the exclude section prevents a specific version of a module from being used.
总结下:Go引入了go.mod文件来标记每个依赖包的版本,在构建过程中go命令会下载go.mod中的依赖包,下载的依赖包会缓存在本地,以便下次构建。
go.sum
考虑到下载的依赖包有可能是被黑客恶意篡改的,以及缓存在本地的依赖包也有被篡改的可能,单单一个go.mod文件并不能保证一致性构建。
为了解决Go module
的这一安全隐患,Go开发团队在引入go.mod
的同时也引入了go.sum
文件,用于记录每个依赖包的哈希值,在构建时,如果本地的依赖包hash值与go.sum文件中记录得不一致,则会拒绝构建。
go.sum文件中每行记录由module
名, 版本和哈希组成,并由空格分开,格式如下
<module> <version>[/go.mod] <hash>
比如某个go.sum
文件记录中记录了github.com/google/uuid
这个依赖的v.1.1
版本的哈希值:
github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
在Go module
机制下,我们需要同时使用依赖包的名称和版本才可以准确的描述一个依赖,为了方便叙述,下面我们使用依赖包版本来指代依赖包名称和版本。
正常情况下,每个依赖包版本会包含两条记录:
- 第一条记录为该依赖包版本整体(所有文件)的哈希值,
- 第二条记录仅表示该依赖包版本中go.mod文件的哈希值
如果该依赖包版本没有go.mod
文件,则只有第一条记录。如上面的例子中,v1.1.1表示该依赖包版本整体,而v1.1.1/go.mod
表示该依赖包版本中go.mod文件。
依赖包版本中任何一个文件(包括go.mod
)改动,都会改变其整体哈希值
,此处再 **额 外记录依赖包版本 **的go.mod
文件主要用于计算依赖树时不必下载完整的依赖包版本,只根据go.mod即可计算依赖树。
每条记录中的哈希值前均有一个表示哈希算法的h1:,表示后面的哈希值是由算法SHA-256计算出来的,自Go module从v1.11版本初次实验性引入,直至v1.14 ,只有这一个算法。
go.sum
文件中记录的依赖包版本数量往往比go.mod
文件中要多,这是因为二者记录的粒度不同导致的。
go.mod
只需要记录直接依赖的依赖包版本,只在依赖包版本不包含go.mod
文件时候才会记录间接依赖包版本
go.sum
则是要记录构建用到的所有依赖包版本。
如何生成go.sum
假设我们在开发某个项目,当我们在GOMODULE模式下引入一个新的依赖时,通常会使用go get命令获取该依赖,比如:
require (
github.com/google/uuid v1.0.0
)
go.sum
文件中则会记录依赖包的哈希值(同时还有依赖包中go.mod的哈希值),如:
github.com/google/uuid v1.0.0 h1:b4Gk+7WdP/d3HZH8EJsZpvV7EtDOgaZLtnaNGIu1adA=
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
值得一提的是,在更新go.sum
之前,为了确保下载的依赖包是真实可靠的,go命令在下载完依赖包后还会查询GOSUMDB
环境变量所指示的服务器,以得到一个权威的依赖包版本哈希值。如果go命令计算出的依赖包版本哈希值与GOSUMDB
服务器给出的哈希值不一致,go命令将拒绝向下执行,也不会更新go.sum文件。
go.sum存在的意义在于,我们希望别人或者在别的环境中构建当前项目时所使用依赖包跟go.sum中记录的是完全一致的,从而达到一致构建的目的。
校验
假设我们拿到某项目的源代码并尝试在本地构建,go命令会从本地缓存中查找所有go.mod中记录的依赖包,并计算本地依赖包的哈希值,然后与go.sum中的记录进行对比,即检测本地缓存中使用的依赖包版本是否满足项目go.sum文件的期望。
如果校验失败,说明本地缓存目录中依赖包版本的哈希值和项目中go.sum中记录的哈希值不一致,go命令将拒绝构建。
这就是go.sum存在的意义,即如果不使用我期望的版本,就不能构建。
当校验失败时,有必要确认到底是本地缓存错了,还是go.sum记录错了。
需要说明的是,二者都可能出错,本地缓存目录中的依赖包版本有可能被有意或无意地修改过,项目中go.sum中记录的哈希值也可能被篡改过。
当校验失败时,go命令倾向于相信go.sum,因为一个新的依赖包版本在被添加到go.sum前是经过GOSUMDB(校验和数据库)验证过的。此时即便系统中配置了GOSUMDB(校验和数据库),go命令也不会查询该数据库。
校验和数据库
环境变量GOSUMDB
标识一个checksum database
,即校验和数据库,实际上是一个web服务器,该服务器提供查询依赖包版本哈希值的服务。
该数据库中记录了很多依赖包版本的哈希值,比如Google官方的sum.golang.org
则记录了所有的可公开获得的依赖包版本。
除了使用官方的数据库,还可以指定自行搭建的数据库,甚至干脆禁用它(export GOSUMDB=off
)。
如果系统配置了GOSUMDB,在依赖包版本被写入go.sum
之前会向该数据库查询该依赖包版本的哈希值进行二次校验,校验无误后再写入go.sum
。
如果系统禁用了GOSUMDB
,在依赖包版本被写入go.sum
之前则不会进行二次校验,go命令会相信所有下载到的依赖包,并把其哈希值记录到go.sum
中。
go如何引用github的第三方包
在需要用到的地方的terminal中,使用如下命令:
go get github.com/learning-go-book/formatter
然后,我们在goPath中的pkg,就能看到这个包了
最后,我们在代码中,直接import就ok了
那如果,我想下载指定的版本,怎么办呢?
1.查看,有哪些版本
PS E:\lagouRes\Go\goBorn\workmodule> go list -m -versions github.com/shopspring/decimal
github.com/shopspring/decimal v1.0.0 v1.0.1 v1.1.0 v1.2.0 v1.3.0 v1.3.1
2.下载指定的版本
go get github.com/shopspring/decimal@v1.3.1
Importing Third-Party Code
when you import a third-party package, you specify the location in the source code
repository where the package is located.
package main
import (
"fmt"
"log"
"os"
"github.com/learning-go-book/formatter"
"github.com/shopspring/decimal"
)
The two imports github.com/learning-go-book/formatter and github.com/shopspring/decimal specify third-party imports.
Note that they include the location of the package in the repository.
If you look in the go.mod file now, you’ll see:
module github.com/learning-go-book/money
go 1.15
require (
github.com/learning-go-book/formatter v0.0.
0-20200921021027-5abc380940ae
github.com/shopspring/decimal v1.2.0
)
Whenever you run any go command that requires dependencies (such as go run , go build, go test, or even go list), any imports that aren’t already in go.mod are downloaded to a cache.
The go.mod file is automatically updated to include the module path that contains the package and the version of the module.