Bazel使用了解
Bazel產生的背景
1、開源成為當前軟件開發的主旋律。哪怕你是商業軟件,也逃離不了社區的包圍。如何方便地獲取依賴,並做到平滑升級很重要。如果構建工具能夠很方便地獲取源代碼,那就太好了。
2、混合多語言編程成為一種選擇。每種語言都有自己適用的場景,但是構建多語言的軟件系統非常具有挑戰性。例如,Python社區很喜歡搭配C/C++,高性能計算扔個C/C++,Python提供編程接口。如果構建工具能夠無縫支持多語言構建,真的很方便。
3、代碼復用。我只想復用第三方的一個頭文件,而不是整個系統。拒絕拷貝是優秀程序員的基本素養,如果構建工具能幫我方便地獲取到所依賴的組件,剔除不必要的依賴,那就太完美了。
4、增量構建。當只修改了一行代碼,構建系統能夠准確計算需要構建的依賴目標,而不是全構建;否則生命都浪費在編譯上了。
5、雲構建。大型軟件公司,復用計算資源,可以帶來巨大的收益。
什么是Bazel
Bazel
是一個支持多語言、跨平台的構建工具。Bazel
支持任意大小的構建目標,並支持跨多個倉庫的構建,是Google
主推的一種構建工具。
bazel
優點很多,主要有
-
構建快。支持增量編譯。對依賴關系進行了優化,從而支持並發執行。
-
可構建多種語言。bazel可用來構建Java C++ Android ios等很多語言和框架,並支持mac windows linux等不同平台
-
可伸縮。可處理任意大小的代碼庫,可處理多個庫,也可以處理單個庫
-
可擴展。使用bazel擴展語言可支持新語言和新平台。
快(Fast)
Bazel
的構建過程很快,它集合了之前構建系統的加速的一些常見做法。包括:
1、增量編譯。只重新編譯必須的部分,即通過依賴分析,只編譯修改過的部分及其影響的路徑。
2、並行編譯。將沒有依賴的部分進行並行執行,可以通過--jobs
來指定並行流的個數,一般可以是你機器CPU
的個數。遇到大項目馬力全開時,Bazel
能把你機器的CPU
各個核都吃滿。
3、分布式/本地緩存。Bazel
將構建過程視為函數式的,只要輸入給定,那么輸出就是一定的。而不會隨着構建環境的不同而改變(當然這需要做一些限制),這樣就可以分布式的緩存/復用不同模塊,這點對於超大項目的速度提升極為明顯。
可伸縮(scalable)
Bazel
號稱無論什么量級的項目都可以應對,無論是超大型單體項目monorepo
、還是超多庫的分布式項目multirepo
。Bazel
還可以很方便的集成CD/CI
,並在雲端利用分布式環境進行構建。
它使用沙箱機制進行編譯,即將所有編譯依賴隔絕在一個沙箱中,比如編譯golang
項目時,不會依賴你本機的GOPATH
,從而做到同樣源碼、跨環境編譯、輸出相同,即構建的確定性。
跨語言(multi-language)
如果一個項目不同模塊使用不同的語言,利用Bazel
可以使用一致的風格來管理項目外部依賴和內部依賴。典型的項目如 Ray。該項目使用C++
構建Ray
的核心調度組件、通過Python/Java
來提供多語言的API
,並將上述所有模塊用單個repo
進行管理。如此組織使其項目整合相當困難,但Bazel
在此處理的游刃有余,大家可以去該repo
一探究竟。
可擴展(extensible)
Bazel
使用的語法是基於Python
裁剪而成的一門語言:Startlark
。其表達能力強大,往小了說,可以使用戶自定義一些rules
(類似一般語言中的函數)對構建邏輯進行復用;往大了說,可以支持第三方編寫適配新的語言或平台的rules
集,比如rules go
。 Bazel
並不原生支持構建golang
工程,但通過引入rules go
,就能以比較一致的風格來管理golang
工程。
Bazel中的主要文件
使用Bazel
管理的項目一般包含以下幾種Bazel
相關的文件:WORKSPACE,BUILD(.bazel),.bzl
和 .bazelrc
等。其中 WORKSPACE
和 .bazelrc
放置於項目的根目錄下,BUILD.bazel
放項目中的每個文件夾中(包括根目錄),.bzl
文件可以根據用戶喜好自由放置,一般可放在項目根目錄下的某個專用文件夾(比如 build)中。
WORKSPACE
1、定義項目根目錄和項目名。
2、加載 Bazel 工具和 rules 集。
3、管理項目外部依賴庫。
BUILD.bazel
存在於根目錄以及源文件所在目錄,用來標記源文件編譯以及依賴情況,一般是自動生成。拿 go 來說,構建目標可以是 go_binary、go_test、go_library
等。
自定義 rule (*.bzl)
如果你的項目有一些復雜構造邏輯、或者一些需要復用的構造邏輯,那么可以將這些邏輯以函數形式保存在 .bzl
文件,供WORKSPACE
或者BUILD
文件調用。其語法跟Python
類似:
def third_party_http_deps():
http_archive(
name = "xxxx",
...
)
http_archive(
name = "yyyy",
...
)
配置項 .bazelrc
對於Bazel
來說,如果某些構建動作都需要某個參數,就可以將其寫在此配置中,從而省去每次敲命令都重復輸入該參數。舉個 Go 的例子:由於國情在此,構建、測試和運行時可能都需要GOPROXY
,則可以配置如下:
# set GOPROXY
test --action_env=GOPROXY=https://goproxy.io
build --action_env=GOPROXY=https://goproxy.io
run --action_env=GOPROXY=https://goproxy.io
使用Bazel部署go應用
1、安裝Bazel
mac中直接通過brew安裝
$ brew install Bazel
centos中的安裝可參考centos7安裝bazel
2、安裝gazelle
$ go get github.com/bazelbuild/bazel-gazelle/cmd/gazelle
手動通過Bazel部署go應用
創建go的運行文件
package main
import "fmt"
func main() {
fmt.Println("hello world")
}
創建WORKSPACE
文件
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
# download rules_go
http_archive(
name = "io_bazel_rules_go",
sha256 = "8663604808d2738dc615a2c3eb70eba54a9a982089dd09f6ffe5d0e75771bc4f",
urls = [
"https://mirror.bazel.build/github.com/bazelbuild/rules_go/releases/download/v0.23.6/rules_go-v0.23.6.tar.gz",
"https://github.com/bazelbuild/rules_go/releases/download/v0.23.6/rules_go-v0.23.6.tar.gz",
],
)
# load rules_go
load("@io_bazel_rules_go//go:deps.bzl", "go_rules_dependencies", "go_register_toolchains")
go_rules_dependencies()
go_register_toolchains()
# download gazelle
http_archive(
name = "bazel_gazelle",
sha256 = "cdb02a887a7187ea4d5a27452311a75ed8637379a1287d8eeb952138ea485f7d",
urls = [
"https://mirror.bazel.build/github.com/bazelbuild/bazel-gazelle/releases/download/v0.21.1/bazel-gazelle-v0.21.1.tar.gz",
"https://github.com/bazelbuild/bazel-gazelle/releases/download/v0.21.1/bazel-gazelle-v0.21.1.tar.gz",
],
)
# load gazelle
load("@bazel_gazelle//:deps.bzl", "gazelle_dependencies", "go_repository")
gazelle_dependencies()
創建BUILD.bazel
文件
load("@io_bazel_rules_go//go:def.bzl", "go_binary")
go_binary(
name = "test",
srcs = ["main.go"],
importpath = "test",
visibility = ["//visibility:private"],
)
查看目錄
test
├── BUILD.bazel
├── WORKSPACE
└── main.go
運行
$ bazel run //:test
DEBUG: /root/.cache/bazel/_bazel_root/1bc6a4d389355f502b77b0dd6dd1fdb4/external/bazel_tools/tools/cpp/lib_cc_configure.bzl:118:5:
Auto-Configuration Warning: CC with -fuse-ld=gold returned 0, but its -v output didn't contain 'gold', falling back to the default linker.
INFO: Analyzed target //:test (0 packages loaded, 0 targets configured).
INFO: Found 1 target...
Target //:test up-to-date:
bazel-bin/linux_amd64_stripped/test
INFO: Elapsed time: 0.254s, Critical Path: 0.00s
INFO: 0 processes.
INFO: Build completed successfully, 1 total action
INFO: Build completed successfully, 1 total action
hello world
成功輸出hello world
使用gazelle自動生成BUILD.bazel文件
在實際的項目中,里面的BUILD.bazel
我們肯定是使用工具自動生成的,來看下如何自動生成的
創建t1和t2兩個文件夾,寫入兩個文件
package main
import "fmt"
func main() {
fmt.Println("hello t1")
}
在項目的根目錄的BUILD.bazel
中配置加載並配置Gazelle
load("@bazel_gazelle//:def.bzl", "gazelle")
# gazelle:prefix test
gazelle(name = "gazelle")
需要注意的是 # 后面的內容對於Bazel
而言是注釋,對於Gazelle
來說卻是一種語法,會被Gazelle
運行時所使用。當然Gazelle
除了可以通過bazel rule
運行,也可以單獨在命令行中執行。
查看下目錄
test
├── BUILD.bazel
├── WORKSPACE
├── main.go
├── t1
│ └── main.go
└── t2
└── main.go
在根目錄下面執行bazel run //:gazelle
test
├── BUILD.bazel
├── main.go
├── t1
│ ├── BUILD.bazel
│ └── main.go
├── t2
│ ├── BUILD.bazel
│ └── main.go
└── WORKSPACE
發現對應的目錄下面已經生成了我們需要的BUILD.bazel
文件
在根目錄下,運行下
$ bazel run t1:t1
INFO: Analyzed target //t1:t1 (0 packages loaded, 0 targets configured).
INFO: Found 1 target...
Target //t1:t1 up-to-date:
bazel-bin/t1/t1_/t1
INFO: Elapsed time: 0.486s, Critical Path: 0.33s
INFO: 3 processes: 1 internal, 2 darwin-sandbox.
INFO: Build completed successfully, 3 total actions
INFO: Build completed successfully, 3 total actions
hello t1
參考
【帶你深入AI(6)- 詳解bazel】https://blog.csdn.net/u013510838/article/details/80102438
【bazel文檔】https://docs.bazel.build/versions/4.1.0/skylark/concepts.html
【Bazel 構建 golang 項目】https://zhuanlan.zhihu.com/p/95998597
【如何評價 Google 開源的 Bazel ?】https://www.zhihu.com/question/29025960
【使用bazel編譯go項目】https://juejin.cn/post/6844903892757528590
【Bazel學習筆記】https://blog.gmem.cc/bazel-study-note