sqlserver如何定义一个静态变量_[Bazel]自定义规则实现将多个静态库合并为一个动态库或静态库...
1 前言
2 自定義規則實現
2.1 規則功能
2.2 實現規則的理論基礎
2.3 規則代碼實現
3 總結
4 參考資料
1 前言
為了實現如標題所述的將多個靜態庫合并為一個動態庫,內置的 Bazel 規則是沒有這個功能的,Bazel C/C++ 相關的內置規則有:
- cc_binary :生成可執行文件
- cc_import :允許用戶導入預編譯的 C/C++ 庫,包括動態庫、靜態庫
- cc_library :生成動/靜態庫
- cc_proto_library :從 .proto 文件生成 C++ 代碼
- fdo_prefetch_hints :表示位于工作區中或位于指定絕對路徑的 FDO 預取提示配置文件
- fdo_profile :表示工作區中或位于指定絕對路徑的 FDO 配置文件
- cc_test :測試 C/C++ 樣例
- cc_toolchain :表示一個 C++ 工具鏈
- cc_toolchain_suite :表示 C++ 工具鏈的集合
而我們知道規則(Rule)定義了 Bazel 對輸入執行的一系列操作,以生成一組輸出。例如 cc_binary 規則可能:
- 輸入(Inputs):獲取一組 .cpp 文件
- 動作(Action):基于輸入運行 g++
- 輸出(Output):返回一個可執行文件
從 Bazel 的角度來看,g++ 和標準 C++ 庫也是這個規則的輸入。作為規則編寫人員,你不僅必須考慮用戶提供的規則輸入,還必須考慮執行操作(Actions)所需的所有工具和庫。比如我們手動的將多個靜態庫(libA.a、libB.a、libC.a)合并為一個動態庫(libcombined.so):
$?gcc?-shared?-fPIC?-Wl,--whole-archive?libA.a?libB.a?libC.a?-Wl,--no-whole-archive?-Wl,-soname?-o?libcombined.so“
注:-Wl,option 后面接的選項最終會作為鏈接器 ld 的參數,即上面的命令最終還調用了 ld 命令。而 -Wl,--whole-archive {xxx} -Wl,--no-whole-archive 所包圍的庫表示將 {xxx} 庫列表中所有 .o 中的符號都鏈接進來,這樣會導致鏈接不必要的代碼進來,從而導致生成的庫會相對很大。目前還沒有找到相關辦法是否可以做到只鏈接進上層模塊庫所調用到的函數。
在編寫規則中我們就需要獲取當前的編譯器,我們不能直接使用固定的路徑,比如 Linux 下 /usr/bin/gcc,因為可能是交叉編譯器,路徑就不一樣了。另外我們還需要傳入 gcc 將多個靜態庫合并成一個動態庫的相關參數、待合成的靜態庫列表、最后要生成的動態庫名稱和路徑。這樣就是一個比較完善的自定義規則了。
2 自定義規則實現
2.1 規則功能
- 將多個靜態庫合并成一個動態庫
- 將多個靜態庫合并成一個靜態庫
- 可以設置生成庫的名稱和生成路徑
- 靜態庫作為規則依賴
2.2 實現規則的理論基礎
將多個靜態庫合并成一個動態庫:
$?gcc?-shared?-fPIC?-Wl,--whole-archive?libA.a?libB.a?libC.a?-Wl,--no-whole-archive??-Wl,-soname?-o?libcombined.so將多個靜態庫合并成一個靜態庫:
方式一:
$?cd?temp$?ar?x?libA.a
$?ar?x?libB.a
$?ar?x?libC.a
$?ar?rc?libcombined.a?*.o
用這種方式無法指定庫的輸出目錄。笨方法就是,將每個待合并的靜態庫都拷貝到目標目錄里去,然后一一 ar -x 操作,然后再到目標目錄里操作 ar rc。這就涉及到了中間文件的產生,有一個很重要的點就是中間文件的產生只能在當前 Bazel 包中創建。中間文件的創建我們可以使用 File actions.declare_file(filename, *, sibling=None) 聲明然后結合 Action 去真實創建。
方式二(需安裝libtool):
#?MacOS系統$?libtool?-static?-o?libcombined.a?libA.a?libB.a?libC.a
在 Unix-like 系統上:
$?sudo?apt-get?install?libtool-bin#?生成的libcombined.a ar -x 解壓出來是 libA.a libB.a libC.a ,而不是?*.o 文件。
$?libtool?--mode=link?gcc?-o?libcombined.a?libA.a?libB.a?libC.a
#?這樣可以指定生成路徑,但是?*.o?的生成還是需要?ar?-x?來生成
$?libtool?--mode=link?gcc?-o?libcombined.a?*.o
另外我們需要規則具有參數輸入功能,參數輸入類型定義可以詳見:https://docs.bazel.build/versions/3.4.0/skylark/lib/attr.html ,比如定義一個決定是否合成動態庫或靜態庫的布爾參數(genstatic),以及帶依賴項配置(deps):
my_cc_combine?=?rule(????implementation?=?_combine_impl,
????attrs?=?{
????????"genstatic"?:?attr.bool(default?=?False),
????????"deps":?attr.label_list(),
????}
)
Action 描述了如何從一組輸入生成一組輸出,例如 “在 hello.c 上運行 gcc 并獲取 hello.o”。創建操作(Action)時,Bazel 不會立即運行命令。它將其注冊在依賴關系圖中,因為一個 Action 可以依賴于另一個 Action 的輸出(例如,在 C 語言中,必須在編譯后調用鏈接器)。在執行階段,Bazel 會決定必須以何種順序運行哪些操作。所有創建 Action 的函數都定義在 ctx.actions 中:
- ctx.actions.run :運行一個可執行文件
- ctx.actions.run_shell :運行一個腳本命令
- ctx.actions.write :將一個字符串寫入文件
- ctx.actions.expand_template :從模板文件中創建一個文件
因此我們可以通過創建一個運行腳本命令的 Action 來運行上面所述的打包命令,即使用 ctx.actions.run_shell 函數。
如前言中講到的,如果是交叉編譯器呢? 那我們還需要在規則中獲取到當前編譯器的信息,包括 gcc、ld、ar 工具。需要在規則中傳入當前編譯器信息:
my_cc_combine?=?rule(????implementation?=?_combine_impl,
????attrs?=?{
????????"_cc_toolchain":?attr.label(default?=?Label("@bazel_tools//tools/cpp:current_cc_toolchain")),
????????"genstatic"?:?attr.bool(default?=?False),
????????"deps":?attr.label_list(),
????}
)
然后在 _combine_impl 中通過 load("@bazel_tools//tools/cpp:toolchain_utils.bzl", "find_cpp_toolchain") 中的 find_cpp_toolchain(ctx) 獲取當前編譯器信息。
還有一個比較重要的問題就是,如果依賴還有依賴呢? 比如 libA.a 依賴了 libD.a 和 libE.a,那我們還需要將 libD.a 和 libE.a 也合并到 libcombined.so 中。這種依賴也分為兩種,一種是 libD.a 是外部已經編譯好的靜態庫,而 libE.a 是有 cc_library 規則編譯出來的靜態庫。那如何能夠把這兩種方式的庫都最后合并到 libcombined.so 呢?
depset 是一種專門的數據結構,支持有效的合并操作,并定義了遍歷順序。通常用于從 rules 和 aspects 的傳遞依賴中積累數據。depset 的成員必須是可散列的(hashable),并且所有元素都是相同類型。具體的其他特性和用法這里就不展開了,我們只需要知道這種數據結構保存了 rules 里目標的依賴關系信息。Depsets 可能包含重復的值,但是使用 to_list() 成員函數可以獲取一個沒有重復項的元素列表,遍歷所以成員。
我們在 _combine_impl 中可以用 ctx.attr.deps 獲得當前目標的依賴列表,每個元素的組成為,即包含一個目標和目標的三個信息體,目標里結構具體可以參考官方文檔并獲取相關信息,比如用 {Target}.files.to_list() 可以獲取 Target 直接生成的一組文件列表,意思就是比如 A 目標,直接生成的就是 libA.a。目標 A 的依賴目標 E 信息在 CcInfo 結構體內,這里先不展開如何獲取了,這里只做個提示:
x?=?dep_target[CcInfo].linking_context.linker_inputs.to_list()for?linker_in?in?x:
????#?]bazel-out/k8-fastbuild/bin]libA/_objs/A/liba.pic.o],?pic_static_library=File:[[]bazel-out/k8-fastbuild/bin]libA/libA.a,?alwayslink=false)>,?],?userLinkFlags=[],?nonCodeInputs=[])>
????for?linker_in_lib?in?linker_in.libraries:
????????#?
????????#?
????????internal_link_lib?=?linker_in_lib.pic_static_library
????????#?
????????external_link_lib?=?linker_in_lib.static_library
????????
2.3 規則代碼實現
my_cc_combine.bzl:
load("@bazel_tools//tools/cpp:toolchain_utils.bzl",?"find_cpp_toolchain")def?_combine_impl(ctx):
????cc_toolchain?=?find_cpp_toolchain(ctx)????
????target_list?=?[]
????for?dep_target?in?ctx.attr.deps:????????
????????#?CcInfo,?InstrumentedFilesInfo,?OutputGroupInfo??????
????????cc_info_linker_inputs?=?dep_target[CcInfo].linking_context.linker_inputs
????????target_dirname_list?=?[]
????????for?linker_in?in?cc_info_linker_inputs.to_list():????????????
????????????for?linker_in_lib?in?linker_in.libraries:????????????????
????????????????if?linker_in_lib.pic_static_library?!=?None:
????????????????????target_list?+=?[linker_in_lib.pic_static_library]????????????????????
????????????????if?linker_in_lib.static_library?!=?None:
????????????????????target_list?+=?[linker_in_lib.static_library]
????
????output?=?ctx.outputs.output
????if?ctx.attr.genstatic:
????????cp_command??=?""???????
????????processed_list?=?[]
????????processed_path_list?=?[]
????????for?dep?in?target_list:
????????????cp_command?+=?"cp?-a?"?+?dep.path?+?"?"?+?output.dirname?+?"/?&&?"
????????????processed?=?ctx.actions.declare_file(dep.basename)
????????????processed_list?+=?[processed]
????????????processed_path_list?+=?[dep.path]
????????cp_command?+=?"echo?'starting?to?run?shell'"
????????processed_path_list?+=?[output.path]
??
????????ctx.actions.run_shell(
????????????outputs?=?processed_list,
????????????inputs?=?target_list,
????????????command?=?cp_command,
????????)
????????command?=?"cd?{}?&&?ar?-x?{}?{}".format(
????????????????output.dirname,
????????????????"?&&?ar?-x?".join([dep.basename?for?dep?in?target_list]),
????????????????"?&&?ar?-rc?libauto.a?*.o"
????????????)
????????print("command?=?",?command)
????????ctx.actions.run_shell(
????????????outputs?=?[output],
????????????inputs?=?processed_list,
????????????command?=?command,
????????)
????else:
????????command?=?"export?PATH=$PATH:{}?&&?{}?-shared?-fPIC?-Wl,--whole-archive?{}?-Wl,--no-whole-archive?-Wl,-soname?-o?{}".format(
????????????cc_toolchain.ld_executable,
????????????cc_toolchain.compiler_executable,
????????????"?".join([dep.path?for?dep?in?target_list]),
????????????output.path)
????????print("command?=?",?command)
????????ctx.actions.run_shell(
????????????outputs?=?[output],
????????????inputs?=?target_list,
????????????command?=?command,
????????)
my_cc_combine?=?rule(
????implementation?=?_combine_impl,
????attrs?=?{
????????"_cc_toolchain":?attr.label(default?=?Label("@bazel_tools//tools/cpp:current_cc_toolchain")),
????????"genstatic"?:?attr.bool(default?=?False),
????????"deps":?attr.label_list(),
????????"output":?attr.output()
????},
)
在 BUILD 文件中調用我們創建的規則示例:
load(":my_cc_combine.bzl",?"my_cc_combine")my_cc_combine(
????name?=?"hello_combined",
????#?這里將所有的靜態庫合并成一個靜態庫
????genstatic?=?True,
????output?=?"libcombined.a",
????deps?=?["//libA:A",?"//libB:B",?"//libC:C"]
)?
3 總結
至此自定義規則實現完成,中間遇到了一些麻煩,不過最終都解決了,因為 Bazel 的中文社區目前為止并不是很完善,可以說中文資料大都是概念性介紹和簡單入門,很多內容都需要參考官方文檔或者去 https://groups.google.com/forum/#!forum/bazel-discuss 提問題,有 Bazel bug 的話就只有去 https://github.com/bazelbuild/bazel/issues 提 issue 了。最后在實現自定義規則中將多個靜態庫合并為一個動態庫示例中,這里有幾個點我們需要注意下:
- 在實現我們中間文件的拷貝過程中,如果最后沒有實現輸出 output Action,那么中間文件也不會產生,這在我調試過程中帶給了我一陣疑惑
- 另外創建的中間文件因為是拷貝過程,實際生成的中間文件,Bazel 已經做了處理,居然是軟鏈接到沙箱(sandbox)源文件,這中間的原理我暫未弄清楚,或許就是沙箱優化
- 對于交叉編譯器,我們必須使用 find_cpp_toolchain(ctx),而不是直接使用 /usr/bin/gcc 等工具鏈
- 這里實現自定義規則,我們只使用了 action.run_shell。其他的比如還可以編寫測試規則(類名需以_test結尾)、actions.write(適合小文件生成)、actions.expand_template(用模板生成文件)、用 aspect 從依賴中搜集信息等等規則的具體用法
4 參考資料
- https://docs.bazel.build/versions/3.4.0/skylark/rules.html
- https://docs.bazel.build/versions/3.4.0/skylark/lib/actions.html
- https://docs.bazel.build/versions/3.4.0/skylark/tutorial-creating-a-macro.html
- https://docs.bazel.build/versions/3.4.0/skylark/depsets.html
- https://docs.bazel.build/versions/3.4.0/skylark/lib/Target.html
- https://docs.bazel.build/versions/3.4.0/skylark/lib/attr.html
- https://docs.bazel.build/versions/3.4.0/rules.html
- https://docs.bazel.build/versions/3.4.0/skylark/lib/ctx.html
- https://docs.bazel.build/versions/3.4.0/be/c-cpp.html
- https://sourceware.org/binutils/docs/ld/Options.html
總結
以上是生活随笔為你收集整理的sqlserver如何定义一个静态变量_[Bazel]自定义规则实现将多个静态库合并为一个动态库或静态库...的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 怎么查网站的外链(怎么查网站的外链数据)
- 下一篇: 常用的数据交换格式有哪些_高程数据格式介