李成笔记网

专注域名、站长SEO知识分享与实战技巧

Go程序怎么编译的? go程序怎么编译的快一点

在运行"hello, world"程序之前,输入了 go build 命令,还有它附带的源文件名参数来编译它:


$go build main.go

假如你曾经有过 C/C++ 语言的开发背景,那么你就会发现这个步骤与 gcc 或 clang 编译十分相似。一旦编译成功,我们就会获得一个二进制的可执行文件。在 Linux 系统、macOS 系统,以及 Windows 系统的 PowerShell 中,我们可以通过输入下面这个 ls 命令看到刚刚生成的可执行文件:


$ls
main*    main.go

上面显示的文件里面有我们刚刚创建的、以.go 为后缀的源代码文件,还有刚生成的可执行文件(Windows 系统下为 main.exe,其余系统下为 main)。

如果你之前更熟悉某种类似于 Ruby、Python 或 JavaScript 之类的动态语言,你可能还不太习惯在运行之前需要先进行编译的情况。Go 是一种编译型语言,这意味着只有你编译完 Go 程序之后,才可以将生成的可执行文件交付于其他人,并运行在没有安装 Go 的环境中。

而如果你交付给其他人的是一份.rb、.py 或.js 的动态语言的源文件,那么他们的目标环境中就必须要拥有对应的 Ruby、Python 或 JavaScript 实现才能解释执行这些源文件。

当然,Go 也借鉴了动态语言的一些对开发者体验较好的特性,比如基于源码文件的直接执行,Go 提供了 run 命令可以直接运行 Go 源码文件,比如我们也可以使用下面命令直接基于 main.go 运行:


$go run main.go
hello, world

当然像 go run 这类命令更多用于开发调试阶段,真正的交付成果还是需要使用 go build 命令构建的。

但是在我们的生产环境里,Go 程序的编译往往不会像我们前面,基于单个 Go 源文件构建类似“hello,world”这样的示例程序那么简单。越贴近真实的生产环境,也就意味着项目规模越大、协同人员越多,项目的依赖和依赖的版本都会变得复杂。

复杂项目下 Go 程序的编译是怎样的

我们还是直接上项目吧,给 go build 一个机会,看看它的复杂依赖管理到底怎么样。现在我们创建一个新项目“hellomodule”,在新项目中我们将使用两个第三方库,zap 和 fasthttp,给 go build 的构建过程增加一些难度。和“hello,world”示例一样,我们通过下面命令创建“hellomodule”项目:


$cd ~/goprojects
$mkdir hellomodule
$cd hellomodule

接着,我们在“hellomodule“下创建并编辑我们的示例源码文件:


package main

import (
  "github.com/valyala/fasthttp"
  "go.uber.org/zap"
)

var logger *zap.Logger

func init() {
  logger, _ = zap.NewProduction()
}

func fastHTTPHandler(ctx *fasthttp.RequestCtx) {
  logger.Info("hello, go module", zap.ByteString("uri", ctx.RequestURI()))
}

func main() {
  fasthttp.ListenAndServe(":8082", fastHTTPHandler)
}

这个示例创建了一个在 8082端口监听的 http 服务,当我们向它发起请求后,这个服务会在终端标准输出上输出一段访问日志。

你会看到,和“hello,world“相比,这个示例显然要复杂许多。但不用担心,你现在大可不必知道每行代码的功用,你只需要我们在这个稍微有点复杂的示例中引入了两个第三方依赖库,zap 和 fasthttp 就可以了。

我们尝试一下使用编译“hello,world”的方法来编译“hellomodule”中的 main.go 源文件,go 编译器的输出结果是这样的:


$go build main.go
main.go:4:2: no required module provides package github.com/valyala/fasthttp: go.mod file not found in current directory or any parent directory; see 'go help modules'
main.go:5:2: no required module provides package go.uber.org/zap: go.mod file not found in current directory or any parent directory; see 'go help modules'

看这结果,main.go 的编译失败了!

从编译器的输出来看,go build 似乎在找一个名为 go.mod 的文件,来解决程序对第三方包的依赖决策问题。

Go module 登场

Go module 构建模式是在 Go 1.11 版本正式引入的,为的是彻底解决 Go 项目复杂版本依赖的问题,在 Go 1.16 版本中,Go module 已经成为了 Go 默认的包依赖管理机制和 Go 源码构建机制。

Go Module 的核心是一个名为 go.mod 的文件,在这个文件中存储了这个 module 对第三方依赖的全部信息。接下来,我们就通过下面命令为“hello,module”这个示例程序添加 go.mod 文件:


$go mod init github.com/bigwhite/hellomodule
go: creating new go.mod: module github.com/bigwhite/hellomodule
go: to add module requirements and sums:
  go mod tidy

你会看到,go mod init 命令的执行结果是在当前目录下生成了一个 go.mod 文件:


$cat go.mod
module github.com/bigwhite/hellomodule

go 1.16

其实,一个 module 就是一个包的集合,这些包和 module 一起打版本、发布和分发。go.mod 所在的目录被我们称为它声明的 module 的根目录。

不过呢,这个时候的 go.mod 文件内容还比较简单,第一行内容是用于声明 module 路径(module path)的。而且,module 隐含了一个命名空间的概念,module 下每个包的导入路径都是由 module path 和包所在子目录的名字结合在一起构成。

比如,如果 hellomodule 下有子目录 pkg/pkg1,那么 pkg1 下面的包的导入路径就是由 module path(github.com/bigwhite/hellomodule)和包所在子目录的名字(pkg/pkg1)结合而成,也就是 github.com/bigwhite/hellomodule/pkg/pkg1。

另外,go.mod 的最后一行是一个 Go 版本指示符,用于表示这个 module 是在某个特定的 Go 版本的 module 语义的基础上编写的。

我们执行一下构建,Go 编译器输出结果是这样的:


$go build main.go
main.go:4:2: no required module provides package github.com/valyala/fasthttp; to add it:
  go get github.com/valyala/fasthttp
main.go:5:2: no required module provides package go.uber.org/zap; to add it:
  go get go.uber.org/zap

你会看到,Go 编译器提示源码依赖 fasthttp 和 zap 两个第三方包,但是 go.mod 中没有这两个包的版本信息,我们需要按提示手工添加信息到 go.mod 中。这个时候,除了按提示手动添加外,我们也可以使用 go mod tidy 命令,让 Go 工具自动添加:


$go mod tidy       
go: downloading go.uber.org/zap v1.18.1
go: downloading github.com/valyala/fasthttp v1.28.0
go: downloading github.com/andybalholm/brotli v1.0.2
... ...

从输出结果中,我们看到 Go 工具不仅下载并添加了 hellomodule 直接依赖的 zap 和 fasthttp 包的信息,还下载了这两个包的相关依赖包。go mod tidy 执行后,我们 go.mod 的最新内容变成了这个样子:


module github.com/bigwhite/hellomodule

go 1.16

require (
  github.com/valyala/fasthttp v1.28.0
  go.uber.org/zap v1.18.1
)

这个时候,go.mod 已经记录了 hellomodule 直接依赖的包的信息。不仅如此,hellomodule 目录下还多了一个名为 go.sum 的文件,这个文件记录了 hellomodule 的直接依赖和间接依赖包的相关版本的 hash 值,用来校验本地包的真实性。在构建的时候,如果本地依赖包的 hash 值与 go.sum 文件中记录的不一致,就会被拒绝构建。

有了 go.mod 以及 hellomodule 依赖的包版本信息后,我们再来执行构建:


$go build main.go
$ls
go.mod    go.sum    main*    main.go

这次我们成功构建出了可执行文件 main,运行这个文件,新开一个终端窗口,在新窗口中使用 curl 命令访问该 http 服务:curl localhost:8082/foo/bar,我们就会看到服务端输出如下日志:


$./main
{"level":"info","ts":1627614126.9899719,"caller":"hellomodule/main.go:15","msg":"hello, go module","uri":"/foo/bar"}

我们的“ hellomodule”程序可算创建成功了。我们也看到使用 Go Module 的构建模式,go build 完全可以承担其构建规模较大、依赖复杂的 Go 项目的重任

发表评论:

控制面板
您好,欢迎到访网站!
  查看权限
网站分类
最新留言