Tags bazel
2021-01-15 19:01:18
近期我自己做的项目已经用上 Bazel 这款产品了。 它是我目前学过的最难学的一门技术(工具)了。 下面总结一下我的学习和使用过程和方法。 一方面我想提升自己的总结能力,另外一方面我自己学习的时候很寂寞身边没有人会这门技术,大多都是用一用编译就好了, 并不想深入了解。
我最近在做3d建模视频我的主要业务逻辑使用的go语言,blender只能使用python语言,不少操作的驱动需要使用python实现。 不少SVG从矢量到标量的计算用到了NodeJS,并且我的项目并不能统一语言去做。
依赖复杂,我有很多种action而且未来还会有更多。例如目前我有的操作:
所以这是一个相当复杂的 Dag (有向无环图) 因此每当我动一个比较底层的算法我需要 快速的 正确的 构建并且执行Dag依赖树。
当我编译好我的blender工程之后我就可以把blender文件上传到GPU集群来渲染视频。
一个更简单的为什么要使用bazel的例子:
当你自己写了一个 golang 程序帮助你输入一个 SQL 语句输出一个对应的驱动包来避免使用 ORM 当你修改了 code gen 的模板时你怎么知道该更新那些和测试那些依赖于它的目标 ( Target ) 呢?
并且你并不想写一大堆重复的类似的代码 干体力活 希望机器生成代码, 并且你知道 Golang 没有泛型造成很多重复代码时, 我们唯一的选择就只能是设计一个Code Generator。
这里记录一下我的学习路线方便当我自己忘记知识点可以看这篇文字帮助自己复习, 我的学习目标并不是 使用 bazel 而是熟练的掌握开发 rules 应对各类情况。
举个例子:
Transitive dependencies
Bazel only reads dependencies listed in your WORKSPACE file. If your project (A) depends on another project (B) which lists a dependency on a third project (C) in its WORKSPACE file, youll have to add both B and C to your projects WORKSPACE file. This requirement can balloon the WORKSPACE file size, but limits the chances of having one library include C at version 1.0 and another include C at 2.0.
当你的 Workspace 越来越大时如果你能想起这里来。 你会发现并不是忘记读了什么文档使用什么方法而是就这样设计的。
Concepts 里面有非常多设计者的意图,是一个系统设计非常好的学习资料。
Extending Bazel 其实就是写rules来处理自己的工作情况。 这里一定都要读完,意义很大比如一个重要的概念 Depsets 你读完文档之后并不知道它是 immutable variable。 如果你并不理解文档的情况下就会一脸懵逼的碰到 rules 的设计问题,很多变量是只读的文档里面没有提到但是你要反应过来。 所以前期文档阅读量不足会耽误很多时间。因为遇到问题你反应不过来。
当我看完上面的文档时还不知道怎么下手, 云里雾里, 这时候有一个重要的 blog 出现: https://www.jayconrod.com/posts/106/writing-bazel-rules--simple-binary-rule
这些文章需要拿出来反复琢磨:
rules_go
一样下载 golang 的 toolchain以上是写的非常好的非官方的tutorial。
我看到这时还是不会写。接下来开始读源码推荐阅读:
当我看到这里时我基本上已经写了个大概。因为已经大概熟悉了 Skylark 这门语言。进入爬坑阶段。
Actions https://docs.bazel.build/versions/master/skylark/lib/actions.html 这是核心中的核心 Files https://docs.bazel.build/versions/master/skylark/lib/File.html 写 rules 其实就是灵活的处理你的代码文件与编译器之间做沟通。 DefaultInfo https://docs.bazel.build/versions/master/skylark/lib/DefaultInfo.html 理解好这个概念可以帮助你与其他开发者写的 Rules 很好的做对接和依赖。
windows 注意:
因为 Proxifier 是按照进程为最基本单位的, 当你看不到具体那个进程网络卡住了可以使用工具,可以看到进程树:
process monitor: https://docs.microsoft.com/en-us/sysinternals/downloads/procmon
目前我的进程rules: bazel.exe;java.exe;fetch_repo.exe;Conhost.exe;go.exe;git.exe;git-remote-https.exe;
供参考
系统外依赖的定义是: 你并不关心实现的代码。仅仅是下载, 比如说 GJSON 这类的
Cons:
你在A项目中引用了B项目 B项目引用了GJSON 那么A的workspace 也需要管理 GJSON 依赖。 这时你的 WORKSPACE 文件会像气球一样膨胀 我遇到这个坑是因为我对 Concept 文档理解的不够深入。
Pros:
可以实现 Shadowing dependencies 在文档: https://docs.bazel.build/versions/master/external.html 提到的
这些坑都是希望假如我身边有人会这技术我希望他能提前告诉我的
declare_file
一定要放到output里面。 这样当你的 actions.run 结束时文件没创建, 会显示编译错误。inputs
一定要把你依赖的所有文件都放到这个数组里面。 就算 bazel query 时的确显示出了正确的依赖关系, 但是 inputs 没有声明这些文件在actions中依赖了你会你修改了文件不会重新编译。 会破坏编译的正确性。mnemonic
只能是一个单词,放入要给动词可以帮助你理解正在并行编译时在做什么动作。executable
不要依赖与你的path或者一个绝对路径那就出现了环境依赖,要使用 target 来保证整个系统时封闭的来保证你的 正确性
use_default_shell_env
False
= 有节操 True
= 没节操 如果你选择省事至少放到Docker容器里面定义好Path运行。不然会出现环境依赖以及脱离 bazel 环境管理的软件依赖。tools
假如你在构建中依赖了其他的二进制文件, 例如我在运行 blender 我的入口时 python 的 RPC Server, 我用 Golang 写 RPC client 你很熟练的通过 arguments 或者 env 把你的 Golang client 二进制文件放到了 执行的地方,但是你会新奇的发现 bazel 系统的构建 DAG 树的 Graphviz 图纸是有依赖的但是它并不构建, 然而你读了很多遍文档也不知道为什么你依赖的二进制 bazel target 不编译因为文档的描述是这样的: tools: List or depset of any tools needed by the action. Tools are inputs with additional runfiles that are automatically made available to the action.
现在我可以高兴的告诉你这时候要放到tools里面。出现这个问题并不是文档有问题,是 bazel 的设计者经验丰富抽象层次高造成的。但是我给跪了。例如说你依赖了 py_library
这个 bazel native rule
第一你需要debug看这个rules返回给你什么info
下面时rules label attr
"util":attr.label(),
输入参数label例子: 下面这个例子的util其实就是一个 py_library rule
util = "//blenderutil:util",
下面时skylark 调试代码, 可以看到有什么Info
print(ctx.attr.util)
一点点的调试最后可以得到下面的代码:
for f in ctx.attr.util[PyInfo].transitive_sources.to_list():
input_list.append(f)
你会得到你所有依赖的内容如果你的依赖很复杂 比如说 A->B->C
A->D->C
你会发现C重复了。可以直接用depset解决这个问题 new_var = depset([这里时你的数组,或者直接把变量放进去])
定义label
"_blenderRunner":attr.label(
default = Label("//blenderRunner:blenderRunner"),
executable = True,
cfg = "exec"
),
action.run 参数例子
executable = ctx.executable._blenderRunner,
目前 gomod 已经做的够好了,就算在我的这个工程里面也会使用 golang gomod 来开发和测试我的代码。
但是与 bazel 结合其实成本并不高有重要的工具叫 gazelle
他可以帮助你转换 gomod 项目成 bazel 项目。
也可以帮助你同步 gomod 到 bazel。
通过如下命令:
bazel run //:gazelle # 初始化
bazel run //:gazelle -- update-repos -from_file=go.mod -to_macro=deps.bzl%go_dependencies # 更新
需要注意:
go_repository
rule 来定义依赖。从actions.run这里可以看到, 每个参数设计的都很到位, 尤其是限制的很到位, 并且每个每个参数都不能删掉, 并且很好的兼顾了各种情况。 新手只可能对概念的理解和知识的掌握不到位, 很难产生一些错误, 这大概就是牛逼的架构师设计的接口。 把安全做到语言级别, 给你报错而不是你在写代码的时候给你各种需要主动遵守的规范。
如果你并不享受这些对你的限制,还不如用 python 或者 make 之类作为构建工具。
正确
快速
实至名归很开心我看到了 Bazel 的门, 希望我能早日入门。