Kitex源码阅读——脚手架代码是如何通过命令行生成的(一)

网友投稿 1099 2022-11-27

Kitex源码阅读——脚手架代码是如何通过命令行生成的(一)

Kitex源码阅读——脚手架代码是如何通过命令行生成的(一)

前言

Kitex是字节跳动内部的Golang微服务RPC框架,先已开源。

在Kitex体验的文章中,我们使用Kitex从零构建了自己的服务,只要定义好​​IDL​​​(接口描述语言),按照Kitex提供的命令行规则,就可以生成支持​​Thrift​​​、​​Protobuf​​的客户端和服务端相关的脚手架代码,使得我们可以直接着手编写服务端的响应实现和客户端的请求发起逻辑。

那么Kitex究竟是怎么生成脚手架代码的?这系列文章将围绕此展开源码阅读,并最终解答这个疑问。

源码分析

初览kitex命令行工具

在最初安装或者更新Kitex的时候,用到了下面这条命令-了Kitex可执行文件(用于脚手架生成):

go install github.com/cloudwego/kitex/tool/cmd/kitex

​​kitex​​​是一个可执行文件,因为​​go install​​​做了两件事(编译+安装),它将​​github.com/cloudwego/kitex/tool/cmd/kitex​​​目录下的​​main.go​​​及其依赖库编译成了一个可执行文件,再将其-到本地的​​$GOPATH/bin​​路径下。

换句话说,你完全可以通过下面这命令将整个​​kitex​​依赖库全部-下来:

go get github.com/cloudwego/kitex@latest

然后进入​​github.com/cloudwego/kitex/tool/cmd/kitex​​​ 目录去手动执行​​go build​​​命令,根据目录名(包名)将其编译成一个可执行文件​​kitex​​​,再将其移动到​​$GOPATH/bin​​​目录下,就能复现上面​​go install​​的工作。

go build -o ~/go/bin/kitexx # 使用-o参数可以将编译的可执行文件指定位置和名称

比如我构建了一个功能强大的​​kitexx​​工具!(可以在终端中调用,只是它还没有接受命令行参数的能力,别担心!随着源码的分析我们将会扩展kitexx的功能! )

先回归Kitex,​​go install​​之后,我们在命令行中输入下面的命令就可以实现项目脚手架代码的生成:

kitex -module

​​kitex​​​就指代​​$GOPATH/bin​​​下的可执行文件​​kitex​​​,后面的​​-module xxx..​​都是指定的命令行参数。

下面让我们看一下kitex负责脚手架代码生成的可执行文件编译前的代码:

# 使用tree命令查看$GOPATH/pkg/mod/github.com/cloudwego/kitex@v0.2.1/tool/cmd/kitex的目录结构,也就是这两个文件中编写了接受命令行参数、创建服务脚手架的核心代码

分析main.go的初始化函数

下面这是​​main.go​​​的​​init函数​​​,看到初始化过程就是args调用了​​addExtraFlag​​​方法,并且传入了一个​​extraFlag​​。

那么我们来看一下​​extraFlag​​​的结构,通过首行注释得知,这个结构是用于添加与代码生成无关的​​flag​​的(每一个flag可以理解成kitex工具命令行需要解析的参数,后面会讲)。

结构体有两个成员函数,第二个用于检查需要添加的flag的合法性,第一个用于添加flags到​​FlagSet​​​,​​FlagSet​​出自于Go标准命令行解析库flag。

看到这你大致能猜测解析命令行的工作最终还是落到了Go标准库头上,只是kitex在此基础上定制了自己需要的功能。

这篇文章介绍了命令行解析库flag的使用:​​segmentfault.com/a/119000002…​​。

那么让我们看一下​​FlagSet结构​​​的源码(这里没放出来),注释描述​​FlagSet​​​是一个用于存放flags的集合,并介绍了​​Usage​​的作用和触发条件。

这时候我们已经深入源码第三层了,先不急着深入,容易迷失方向。先回到最初​​init函数​​​中,我们已经知道​​apply​​​方法用于添加​​flag​​​到​​FlagSet​​中,那么是如何添加的呢?

我们来看一下​​FlagSet​​​的​​BoolVar​​​方法源码,​​newBoolValue​​​的作用是将​​value​​​的值赋给​​p​​​然后返回(可以点进去看源码),​​BoolVar​​方法的作用由注释得知,为了定义一个bool类型的flag(由name、value、usage定义)

然后调用了​​f.Var​​​方法,猜测是用于将这个定义的​​bool类型​​​的flag添加入​​FlagSet​​​集合,看一下源码。首先检测要添加的flag的name不能以-或者=开头,然后判断map中是否存在相同名称的flag,如果有则panic,然后按步添加flag到​​f.formal​​​中(​​map[string]*Flag​​)

现在大致明白,init函数的作用就是调用了​​args.addExtraFlag​​方法,添加了一个额外的不是用于代码生成的flag,至于check部分就是当遇到指定错误的时候需要终止程序。

为​​os.Exit()​​指定状态码,0表示成功,1表示内部错误,2表示无效参数

到目前为止,init函数部分基本已经分析了一遍,你可能好奇,既然在初始化阶段已经为FlagSet添加了一个和​​version​​​相关​​flag​​(其实并没有完成添加,这里先卖个关子!下面解释),那么​​FlagSet​​本身是在哪里初始化的?

这里我们留意到​​init​​​函数上方的全局变量​​args​​​,并且留意到​​args.addExtraFlag​​​也是调用自​​args​​​,那么势必要看一下​​arguments​​​的源码,看看能否找到​​FlagSet​​的初始化工作。

我们的目标是找到一个类似NewFlagSet的函数,那么就进入args.go使用command+f吧!果然找到了,而且只有一处!

那么再看这个buildFlags函数在哪被调用的,没办法,看来还得接着查一下parseArgs函数的调用情况。

终于你在​​main.go​​​的主函数里找到了,但是问题来了,​​main.go​​​文件的​​init()​​​初始化函数你分析了之后是给​​FlagSet​​添加flag的,而且应该是先于主函数体执行的,那此时FlagSet还没初始化啊?这不是惊天大BUG? 事实上这就是上面我卖的那个关子

我们再看一下​​args.addExtraFlag​​​方法和​​args的结构体​​​,事实上,初始化的时候只是将​​extraFlag​​​创建了出来,加入了一个切片,真正为​​FlagSet​​​添加flag必然是等到​​flag.NewFlagSet​​方法初始化FlagSet之后。

上面这个乌龙其实在阅读源码的时候很可能遇到,因为我们在没有全面的视角的情况下,往往很多问题的出现只是缺少对源码的熟悉,只有反复推敲,才能逐渐梳理清楚。

丰富kitexx框架的功能

事实上,main.go文件的init函数初始化只添加了一个flag,说明了这个flag的name、value还有usage,但是并没有涉及到自动化构建脚手架的工作,当然这部分我相信通过继续阅读main函数的其余部分可以得到解答。但是考虑到篇幅原因,我打算将其放在下一篇文章中。

先来丰富一下我们的kitexx框架,为其添加解析命令行的功能。(现阶段只是简单使用flag标准库的一些API,后续再作更多的解释)。

flag库也可以看这篇文章:​​segmentfault.com/a/119000002…​​

将上述代码手动编译到​​$GOPATH/bin​​​目录下,并且尝试通过命令行运行​​kitexx​​​,输入事先添加好的两个​​flag​​(bool类型的flag后面可以不加参数),实现用我们输入的参数值替换b和s的默认值并打印。

小结

通过这篇文章,我们初步分析了kitex框架的脚手架代码生成工具的源代码的init函数。并且体验了一下实现自己的命令行解析框架kitexx。

在后续的文章中将继续分析main函数的剩余部分,并继续扩展kitexx框架的功能。

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:高并发下restTemplate的错误分析方式
下一篇:深入浅出RPC框架|青训营笔记
相关文章

 发表评论

暂时没有评论,来抢沙发吧~