您好,登錄后才能下訂單哦!
如何進行Bazel中的自定義工具鏈分析,相信很多沒有經驗的人對此束手無策,為此本文總結了問題出現的原因和解決方法,通過這篇文章希望你能解決這個問題。
小編講述 Bazel 自定義工具鏈的兩種方式,Platform
和 Non-Platform
方式。會存在這兩種方式的原因是 Bazel 的歷史問題。
例如,C++
相關規則使用 --cpu
和 --crosstool_top
來設置一個構建目標 CPU
和 C++
工具鏈,這樣就可以實現選擇不同的工具鏈構建 C++
項目。但是這都不能正確地表達出“平臺”特征。使用這種方式不可避免地導致出現了笨拙且不準確的構建 APIs。這其中導致了對 Java 工具鏈基本沒有涉及,Java 工具鏈就發展了他們自己的獨立接口 --java_toolchain
。因此非平臺方式(Non-Platform)的自定義工具鏈實現并沒有統一的 APIs 來規范不同語言的跨平臺構建。而 Bazel 的目標是在大型、混合語言、多平臺項目中脫穎而出。這就要求對這些概念有更原則的支持,包括清晰的 APIs,這些 API 綁定而不是分散語言和項目。這就是新平臺(platform)和工具鏈(toolchain) APIs 所實現的內容。
如果沒有去了解 Platform
和 Non-Platform
方式區別,可能會對上面說的內容有點不理解,這里通俗的來講下這兩者區別。比如我們編譯 C++
和 Java
混合的相關項目,這個項目需要在多個平臺下可以運行,因此涉及到多個平臺下的工具鏈,而 C++
和 Java
的工具鏈是不一樣的,非平臺方式,對于 C++
,我們需要通過 --crosstool_top
來指定工具鏈集合,--cpu
來指定具體的某設備工具鏈;對于 Java
,則需要通過 --java_toolchain
、--host_java_toolchain
、--javabase
和 --host_javabase
來構建 Java
相關內容。這樣一個 C++
和 Java
的混合項目,需要指定這么多的輸入才能夠完整編譯項目。
如果用了平臺方式,那就簡單了。首先理解平臺概念很簡單,平臺就是一組約束值(constraint_value
)的集合,即比如一個平臺可以由 OS
和 CPU
兩個約束類型來決定,又或者一個平臺可以由 OS
、CPU
和 GLibc_Version
來決定。則我們可以將 C++
相關編譯的平臺約束綁定平臺,將 Java
相關編譯的平臺約束也綁定平臺,這樣就可以將混合語言項目統一到一個平臺,即一旦確定了某個平臺,那么只需要在命令行執行類似如下命令即可編譯混合語言項目:
$ bazel build //:my_mixed_project --platforms==//:myplatform
目前平臺方式構建在 Bazel 中并不完善。這些 APIs 不足以讓所有項目都使用平臺。Bazel 還必須淘汰舊的 APIs。這不是很容易就完成的任務,因為項目的所有語言、工具鏈、依賴項和 select()
都必須支持新的 APIs。這需要一個有序的遷移順序來保持項目正常工作。Bazel 的 C++
相關規則已經支持平臺,而 Android 相關規則不支持。你的 C++
項目可能不關心 Android,但其他人可能會。因此,在全球范圍內啟用所有 C++
平臺構建方式是不安全的。已經完整支持平臺構建方式的有:
C/C++
Rust
Go
Java
未來 Bazel
的目標是實現 $ bazel build //:all
,即一個命令行就可以構建任何項目和目標平臺。
通過上一章節的介紹,Non-Platform
方式,則是通過各項目性質采用對應的獨立構建方式,比如 C++
相關的 --crosstool_top
和 --cpu
。Java
相關的 --java_toolchain
、--host_java_toolchain
、--javabase
和 --host_javabase
。這一節我們僅僅實現 C++
的 Non-Platform
方式構建(當然完整的平臺構建方式并未完善,比如 Apple、Android 都還未支持平臺構建方式)。
在 Bazel 的官方文檔中有一個教程已經詳細地介紹了如何去配置一個 C++
工具鏈,具體見 https://docs.bazel.build/versions/master/tutorial/cc-toolchain-config.html ,主要涉及的內置規則有:cc_common
、cc_toolchain
、cc_toolchain_suite
。
當然這里可以進一步去做一些工程上的優化:
CcToolchainConfigInfo
的規則,可以優化其輸入配置,使得寫一個工具鏈配置規則即可配置所有主流的
C++
編譯器
attrs = {
"include_paths" : attr.string_list(doc = "The compiler include directories."),
"compiler_root" : attr.string(doc = "The compiler root directory."),
"host_os" : attr.string(default = "linux", doc = "The cross toolchain prefix."),
"toolchain_identifier": attr.string(),
"target_os" : attr.string(default = "linux"),
"target_arch" : attr.string(default = "x86-64"),
"cc_compiler" : attr.string(default = "gcc", doc = "The compiler type."),
"extra_features": attr.string_list(),
}
--cpu
可以切換到某個工具鏈
def generate_toolchain_suite():
toolchains = {}
native.filegroup(name = "empty")
for (platform, toolchain_info) in TOOLCHAIN_SUPPORT_MATRIX.items():
host_os = toolchain_info[TOOLCHAIN_HOST_OS]
target_os = toolchain_info[TOOLCHAIN_TARGET_OS]
target_arch = toolchain_info[TOOLCHAIN_TARGET_ARCH]
compiler_root = toolchain_info[TOOLCHAIN_COMPILER_ROOT]
include_paths = toolchain_info[TOOLCHAIN_INCLUDE_PATHS]
toolchain_identifier = toolchain_info[TOOLCHAIN_IDENTIFIER]
cc_compiler = toolchain_info[TOOLCHAIN_CC_COMPILER]
base_name = "{platform}_{target_os}_{target_arch}_{cc_compiler}_{toolchain_identifier}".format(
platform = platform,
target_os = target_os,
target_arch = target_arch,
cc_compiler = cc_compiler,
toolchain_identifier = toolchain_identifier
)
configuration_name = "%s_cc_toolchain_config" % base_name
cc_name = "%s_cc_toolchain" % base_name
toolchain_name = "%s_cc" % base_name
my_cc_toolchain_config(
name = configuration_name,
include_paths = include_paths,
compiler_root = compiler_root,
host_os = host_os,
toolchain_identifier = toolchain_identifier,
target_os = target_os,
target_arch = target_arch,
cc_compiler = cc_compiler,
extra_features = [],
)
cc_toolchain(
name = cc_name,
toolchain_identifier = toolchain_name,
toolchain_config = ":%s" % configuration_name,
all_files = ":empty",
compiler_files = ":empty",
dwp_files = ":empty",
linker_files = ":empty",
objcopy_files = ":empty",
strip_files = ":empty",
supports_param_files = 0,
)
if platform in toolchains.keys():
print("%s already exist!" % platform)
fail("%s already exist!" % platform)
else:
toolchains[platform] = cc_name
print("toolchains = ", toolchains)
cc_toolchain_suite(
name = "compiler_suite",
toolchains = toolchains
)
TOOLCHAIN_SUPPORT_MATRIX = {
"hisi": {
TOOLCHAIN_HOST_OS : "linux",
TOOLCHAIN_TARGET_OS : "linux",
TOOLCHAIN_TARGET_ARCH : "armv7",
TOOLCHAIN_COMPILER_ROOT : "",
TOOLCHAIN_INCLUDE_PATHS : [],
TOOLCHAIN_IDENTIFIER : "",
TOOLCHAIN_CC_COMPILER : "gcc"
},
"ubuntu_gcc": {
TOOLCHAIN_HOST_OS : "linux",
TOOLCHAIN_TARGET_OS : "linux",
TOOLCHAIN_TARGET_ARCH : "x86-64",
TOOLCHAIN_COMPILER_ROOT : "/usr/bin/",
TOOLCHAIN_INCLUDE_PATHS : [
"/usr/include",
"/usr/lib/gcc",
"/usr/local/include"
],
TOOLCHAIN_IDENTIFIER : "",
TOOLCHAIN_CC_COMPILER : "gcc"
},
"ubuntu_clang": {
TOOLCHAIN_HOST_OS : "linux",
TOOLCHAIN_TARGET_OS : "linux",
TOOLCHAIN_TARGET_ARCH : "x86-64",
TOOLCHAIN_COMPILER_ROOT : "",
TOOLCHAIN_INCLUDE_PATHS : [],
TOOLCHAIN_IDENTIFIER : "",
TOOLCHAIN_CC_COMPILER : "clang"
},
"ubuntu_arm_linux_gnueabihf" : {
TOOLCHAIN_HOST_OS : "linux",
TOOLCHAIN_TARGET_OS : "linux",
TOOLCHAIN_TARGET_ARCH : "aarch74",
TOOLCHAIN_COMPILER_ROOT : "/usr/bin/",
TOOLCHAIN_INCLUDE_PATHS : [
"/usr/arm-linux-gnueabihf/include/",
"/usr/lib/gcc-cross/arm-linux-gnueabihf/7/include",
],
TOOLCHAIN_IDENTIFIER : "arm-linux-gnueabihf-",
TOOLCHAIN_CC_COMPILER : "gcc"
}
}
最后--crosstool_top=//toolchains/cpp:{cc_toolchain_suite的名稱}
,--cpu={cc_toolchain的名稱}
,即可實現交叉編譯。整個實現內容這里就不貼出來了。為了簡化 $ bazel build
命令,可以將默認配置項寫入 .bazelrc
文件中:
build:compiler_config --crosstool_top=//toolchains/cpp:compiler_suite
build:compiler_config --cpu=ubuntu_gcc
build:compiler_config --host_crosstool_top=@bazel_tools//tools/cpp:toolchain
Bazel 可以在各種硬件、操作系統和系統配置上構建和測試代碼,使用許多不同版本的構建工具,比如鏈接器和編譯器。為了幫助管理這種復雜性,Bazel 提出了約束(constraints )和平臺(platforms)的概念。約束是構建或生產環境可能不同的維度,比如 CPU 架構、GPU 的存在或缺失,或者系統安裝的編譯器的版本。如第一章所述,平臺是這些約束的指定選擇集合,表示在某些環境中可用的特定資源。
將環境建模為平臺有助于 Bazel 為構建操作自動選擇適當的工具鏈。平臺還可以與 config_setting
規則結合使用來編寫可配置屬性。
Bazel 認為平臺可以扮演三個角色:
“注:這里 Host 平臺只是平臺扮演一個角色的闡述,跟實際編寫 Bazel 規則沒有關系。
toolchain
規則里也只有對執行平臺和目標平臺的約束設置。
Bazel 支持以下針對平臺的構建場景:
平臺的可能選擇空間是通過使用構建文件中的 constraint_setting
和 constraint_value
規則定義的。constraint_setting
創建一個新維度,可以說是一個約束值集合,constraint_value
為給定維度(constraint_setting
)創建一個新值;它們一起有效地定義了枚舉及其可能的值。簡單來說,constraint_setting
和 constraint_value
就是一個單鍵多值的 map ,例如,下面的構建文件片段為系統的 glibc
版本引入了具有兩個可能值的約束。
constraint_setting(name = "glibc_version")
constraint_value(
name = "glibc_2_25",
constraint_setting = ":glibc_version",
)
constraint_value(
name = "glibc_2_26",
constraint_setting = ":glibc_version",
)
約束及其值可以在工作區中的不同包之間定義。它們通過標簽進行引用,并服從通常的可見性控制。如果可見性允許,你就可以通過定義自己的值來擴展現有的約束設置。
平臺規則 `platform`[1] 引入了一個具有特定約束值選擇的新平臺。下面創建了一個名為 linux_x86
的平臺,描述了在 glibc
版本為 2.25
的 x86_64
體系結構上運行 Linux
操作系統的任何環境。
platform(
name = "linux_x86",
constraint_values = [
"@platforms//os:linux",
"@platforms//cpu:x86_64",
":glibc_2_25",
],
)
注意,對于一個平臺來說,同一個約束設置多個值是錯誤的,比如 glibc_2_25
和 glibc_2_26
不能同時設置,因為他們都屬于 glibc_version
約束。
為了保持生態系統的一致性,Bazel 團隊維護了一個存儲庫,其中包含最流行的 CPU 架構和操作系統的約束定義。這些都位于 https://github.com/bazelbuild/platforms
。當然你也可以自己自定義。
Bazel 附帶以下特殊的平臺定義 :@local_config_platform//:host
。會自動檢測主機平臺的值:表示 Bazel 運行的系統的平臺。
你可以使用以下命令行標志為構建指定主機和目標平臺:
--host_platform
:默認為
@bazel_tools//platforms:host_platform
--platforms
:默認為
@bazel_tools//platforms:target_platform
--platforms
,默認是一個表示本地構建機器的平臺,即由
@local_config_platform//:host
自動生成。在“前言”一章節中,可以知道平臺可以實現混合語言項目的構建,而如果對每一種語言實現構建,則需要配置工具鏈以及實現工具鏈的平臺約束設定。這樣就可以將平臺與工具鏈聯合在一起了,原理類似依賴注入。
工具鏈是使用 toolchain[2] 規則定義的目標,該規則將工具鏈實現與工具鏈類型相關聯。工具鏈類型是使用 tooclhain_type()
規則定義的目標(其實用一個字符串常量也可以替代)。工具鏈實現是一個目標,它通過列出作為工具鏈一部分的文件(例如,編譯器和標準庫)以及使用該工具鏈所需的代碼來表示實際的工具鏈。工具鏈實現必須返回 ToolchainInfo
Provider(Provider 可以認為就是一個函數的返回值),ToolchainInfo
存放著工具鏈相關配置信息,對于存放什么內容沒有要求,即你可以定義任何你想要存放的信息。
任何定義工具鏈的人都需要聲明一個 toolchain_type
目標,這是一個字符串標識,用來標志工具鏈類別,以避免在加載了多個語言規則的工作區中出現潛在的沖突。比如 Bazel 官方提供了一個 CPP 的標識:@bazel_tools//tools/cpp:toolchain_type
,而 rules_go
提供了 @io_bazel_rules_go//go:toolchain
用以區分工具鏈類別。
對于 C++
,cc_toolchain
規則即工具鏈實現,跟 Non-Platform
的工具鏈目標實現一致。當然你也可以使用任何返回 ToolchainInfo
的規則,而不僅僅是 cc_toolchain
,比如可以通過 platform_common.ToolchainInfo
創建一個 ToolchainInfo
,然后創建自己的工具鏈實現規則。
HELLOSDK = provider(
fields = {
"os": "The host OS the SDK was built for.",
"arch": "The host architecture the SDK was built for.",
"root_file": "A file in the SDK root directory",
"libs": ("List of pre-compiled .a files for the standard library " +
"built for the execution platform."),
"headers": ("List of .h files from pkg/include that may be included " +
"in assembly sources."),
"srcs": ("List of source files for importable packages in the " +
"standard library. Internal, vendored, and tool packages " +
"may not be included."),
"package_list": ("A file containing a list of importable packages " +
"in the standard library."),
"hello": "The hello binary file",
},
)
def _hello_toolchain_impl(ctx):
return [platform_common.ToolchainInfo(
sdk = ctx.attr.sdk,
cflags = ctx.attr.cflags,
)]
hello_toolchain = rule(
_hello_toolchain_impl,
attrs = {
"sdk": attr.label(
mandatory = True,
providers = [HELLOSDK],
cfg = "exec",
doc = "The SDK this toolchain is based on sdk",
),
"cflags": attr.string_list(),
},
doc = "Defines a hello toolchain based on SDK",
provides = [platform_common.ToolchainInfo],
)
工具鏈實現規則可以認為是面向對象中的類,我們可以 New 很多實例出來,這里 New 出來的就是很多不同平臺架構或者不同版本的工具鏈了。完成工具鏈實例創建,就可以通過 native.toolchain
綁定工具鏈類型、目標平臺、運行平臺約束了。
用戶通過在 WORKSPACE 文件中調用 `register_toolchains`[3] 函數或者在命令行中傳遞 --extra_toolchains
標志來注冊他們想要使用的工具鏈。
最后,當 Bazel 開始構建時,它會檢查執行和目標平臺的約束條件。然后選擇與這些約束兼容的一組合適的工具鏈。Bazel 將向請求它們的規則提供這些工具鏈的 ToolchainInfo 對象。
如果想了解 Bazel 如何選擇或拒絕注冊的工具鏈,可以使用 --toolchain_resolution_debug
標志來調試。
Bazel 的 C++
規則使用平臺來選擇工具鏈,需要設置 --incompatible_enable_cc_toolchain_resolution
,如果不設置,即使顯示的在命令行加上--platforms
也不起作用。
同樣地,Platform
+ Toolchain
實現平臺方式構建,官方文檔也提供了一個樣例,參見:https://docs.bazel.build/versions/master/toolchains.html 。總的步驟這里總結下:
ToolchainInfo
xx_toolchain
,比如
C++
已經有了內置的
cc_toolchain
,則無需第一步和這一步了,即不用自己手動去實現該規則,只需要配置
cc_toolchain
即可native.toolchain
關聯工具鏈實現,并設定
target_compatible_with
,與平臺綁定以及工具鏈類型等,這里關聯相關平臺約束也需要創建。register_toolchains()
或者命令行指定注冊
--extra_toolchains=
--platforms=
就可以通過平臺方式構建了這里同樣跟 Non-Platform
方式一樣,對于 C++
,我們可以復用工具鏈的配置和 cc_toolchain
配置部分。工程上可以優化:
def generate_constraint_set_platform():
available = get_available_unique_platform_idetifier()
native.constraint_setting(
name = "platform",
visibility = ["//visibility:public"],
)
for item in available:
native.constraint_value(
name = item,
constraint_setting = ":platform",
visibility = ["//visibility:public"],
)
Non-Platform
可以批量聲明
native.toolchain(
name = toolchain_name,
exec_compatible_with = [
"@platforms//cpu:%s" % bazel_exec_platform_info["cpu"],
"@platforms//os:%s" % bazel_exec_platform_info["os"],
],
target_compatible_with = [
"//platforms:%s" % platform,
],
toolchain = "//toolchains/cpp:%s" % cc_name,
toolchain_type = "@bazel_tools//tools/cpp:toolchain_type",
)
--incompatible_enable_cc_toolchain_resolution
啟動平臺方式設置我們也可以將其放入 .bazelrc
全局構建配置文件中,從而省去命令行鍵入:
build --incompatible_enable_cc_toolchain_resolution
看完上述內容,你們掌握如何進行Bazel中的自定義工具鏈分析的方法了嗎?如果還想學到更多技能或想了解更多相關內容,歡迎關注億速云行業資訊頻道,感謝各位的閱讀!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。