Go 语言中程序是怎么编译的?
CSDN 2024-06-10 17:35:11 阅读 56
在当今快速发展的软件开发领域,Go 语言(又称 Golang)已经成为了开发高性能应用程序的热门选择。由 Google 开发并在 2009 年公开发布,Go 语言因其简洁的语法、出色的并发支持以及优秀的性能而受到广泛欢迎。尽管 Go 的语法相对简单,但它的编译过程和模块管理系统可能会让初学者感到有些困惑,特别是那些从动态语言背景转来的开发者。
本文旨在深入探讨 Go 语言的编译机制和最新的模块管理系统——Go Modules。通过详细的示例和步骤,我们将演示从简单的 “Hello World” 程序到使用第三方库的更复杂项目的开发过程。我们将开始于基本的编译命令,探索如何手动处理依赖,然后过渡到使用 Go Modules 管理依赖,这在现代 Go 项目开发中已成为标准实践。
文章目录
1、编译和运行 Go 程序的基础2、Go Modules 机制2.1、复杂项目下 Go 程序的编译2.2、go mod2.3、go mod 示例
1、编译和运行 Go 程序的基础
在上一篇我们实现 HelloWorld 程序的过程中,我们在运行 “helloworld” 程序之前,是输入了一个 go build
命令的:
$go build main.go
对此,如果有使用 C/C++ 和 gcc 或 clang 的开发经验的同学家就会发现 Go 的编译步骤与它们非常类似。都是在编译成功后,生成一个二进制可执行文件。
在 Linux 系统、macOS 系统或 Windows 系统的 PowerShell 中,可以通过执行以下 ls 命令来查看这个新生成的可执行文件:
$lsmain* 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.gohello, world
不过像 go run
这类命令更多用于开发调试阶段,真正的交付成果还是需要使用 go build 命令
构建的。
然而,在我们的生产环境中,编译 Go 程序通常不像之前提到的基于单个 Go 源文件构建像"hello, world"这样的简单示例那么直接。随着项目接近真实的生产条件,它的规模会增大、涉及的协作人员会增多,同时项目的依赖及其版本也会变得更为复杂。
2、Go Modules 机制
2.1、复杂项目下 Go 程序的编译
现在,我们将启动一个名为"hellomod"的新项目,在这个项目中,我们会使用两个第三方库:zap
和 fasthttp
。这将使 go build
的构建过程更具挑战性。
如同我们之前的"helloworld"示例一样,我们可以通过以下命令来创建“hellomod”项目:
$mkdir hellomod$cd hellomod
接着,我们在"hellomod"下创建并编辑我们的示例源码 main.go
文件:
package mainimport ( "github.com/valyala/fasthttp" "go.uber.org/zap")var logger *zap.Loggerfunc init() { logger, _ = zap.NewProduction()}func fastHTTPHandler(ctx *fasthttp.RequestCtx) { logger.Info("hello, go module", zap.ByteString("uri", ctx.RequestURI()))}func main() { fasthttp.ListenAndServe(":8081", fastHTTPHandler)}
这个示例中,我们创建了一个在 8081 端口上监听的 HTTP 服务。当向该服务发起请求时,它会在终端的标准输出上打印一段访问日志。可
以看出,相比于“hello, world”示例,这个项目明显更加复杂。但目前,我们不需要深入了解每行代码的具体作用。只要应该我们在这个稍微复杂的示例中使用了两个第三方依赖库:zap
和 fasthttp
。
接下来,让我们尝试使用编译"hello, world"时的方法来编译“hellomod”项目中的 main.go
源文件,看看 Go 编译器会给出什么样的输出结果:
$go build main.gomain.go:4:3: 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:3: 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'
从编译器的输出来看,go build 似乎在找一个名为 go.mod 的文件,来解决程序对第三方包的依赖决策问题。
2.2、go mod
Go 1.11 版本推出 modules 机制,简称 mod,更加易于管理项目中所需要的模块。模块是存储在文件树中的 Go 包的集合,其根目录中包含 go.mod 文件。 go.mod 文件定义了模块的模块路径,它也是用于根目录的导入路径,以及它的依赖性要求。每个依赖性要求都被写为模块路径和特定语义版本。
从 Go 1.11
开始,Go
允许在 $GOPATH/src
外的任何目录下使用 go.mod
创建项目。在 $GOPATH/src
中,为了兼容性,Go
命令仍然在旧的 GOPATH
模式下运行。从 Go 1.13
开始,go.mod
模式将成为默认模式。
2.3、go mod 示例
现在,我们将通过以下命令为“hellomod”示例程序创建一个 go.mod
文件:
go mod init github.com/lizhengi/hellomodgo: creating new go.mod: module github.com/lizhengi/hellomodgo: to add module requirements and sums:go mod tidy
Ps:注意模块命名方式,如果你的项目要发布到 github,则命名为:
github.com/<USERNAME>/<MODULE_NAME>
此时可以看到,go mod init
命令的执行结果是在当前目录下生成了一个 go.mod 文件:
$cat go.modmodule github.com/lizhengi/hellomodgo 1.17
实际上,一个 module 是多个包的集合,这些包与 module 一起进行版本控制、发布和分发。go.mod
文件所在的目录被视为声明的 module 的根目录。此时的 go.mod
文件内容相对简单,首行是用来声明 module 路径(module path)的。Module 本质上引入了命名空间的概念,这意味着 module 下每个包的导入路径都由 module path 和包所在的子目录名共同构成。
例如,在 hellomod
下有一个子目录 pkg/pkg1
,那么 pkg1
中的包的导入路径将是 github.com/lizhengi/hellomod/pkg/pkg1
。此外,go.mod
文件的最后一行是 Go 版本指示符,表示该 module 是基于特定 Go 版版本的 module 语义编写的。
此时,我们执行一下构建,Go 编译器输出结果是这样的
$go build main.gomain.go:4:2: no required module provides package github.com/valyala/fasthttp; to add it: go get github.com/valyala/fasthttpmain.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: finding module for package go.uber.org/zapgo: finding module for package github.com/valyala/fasthttpgo: downloading github.com/valyala/fasthttp v1.54.0go: downloading go.uber.org/zap v1.27.0go: found github.com/valyala/fasthttp in github.com/valyala/fasthttp v1.54.0go: found go.uber.org/zap in go.uber.org/zap v1.27.0go: downloading go.uber.org/multierr v1.10.0go: downloading github.com/stretchr/testify v1.8.1go: downloading go.uber.org/goleak v1.3.0go: downloading github.com/andybalholm/brotli v1.1.0go: downloading github.com/klauspost/compress v1.17.7go: downloading github.com/valyala/bytebufferpool v1.0.0go: downloading gopkg.in/yaml.v3 v3.0.1go: downloading github.com/davecgh/go-spew v1.1.1go: downloading github.com/pmezard/go-difflib v1.0.0
Ps:超时的话换个代理即可
go env -w GO111MODULE=ongo env -w GOPROXY=https://goproxy.cn,direct
Go 工具不仅下载并添加了 hellomod 直接依赖的 zap 和 fasthttp 包的信息,还下载了这两个包的相关依赖包。go mod tidy 执行后,我们 go.mod 的最新内容变成了这个样子:
module github.com/bigwhite/hellomodgo 1.16require ( github.com/valyala/fasthttp v1.28.0 go.uber.org/zap v1.18.1)
此时,go.mod
文件已经包含了 hellomod
直接依赖的包的信息。此外,在 hellomod
目录下还出现了一个名为 go.sum
的文件,这个文件记录了 hellomod
的直接依赖和间接依赖包的版本 hash 值,这用于校验本地包的真实性。在构建过程中,如果本地依赖包的 hash 值与 go.sum
文件中的记录不匹配,构建会被拒绝。
有了 go.mod 以及 hellomod 依赖的包版本信息后,我们再来执行构建:
$go build main.gogo: downloading go.uber.org/zap v1.27.0go: downloading github.com/valyala/fasthttp v1.54.0go: downloading go.uber.org/multierr v1.10.0go: downloading github.com/andybalholm/brotli v1.1.0go: downloading github.com/klauspost/compress v1.17.7go: downloading github.com/valyala/bytebufferpool v1.0.0$lsgo.mod go.sum main* main.go
这次我们成功构建出了可执行文件 main,运行这个文件,新开一个终端窗口,在新窗口中使用 curl 命令访问该 http 服务:curl localhost:8081/foo/bar
,我们就会看到服务端输出如下日志:
$./main{ "level":"info","ts":1716950836.531074,"caller":"hellomod/main.go:15","msg":"hello, go module","uri":"/foo/bar"}
声明
本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。