【轉】編譯構建工具-bazel

  • 簡介

    bazelGoogle開源的一套類似於Make的編譯構建工具。

    • 運作原理
      • 運行構建或測試時,Bazel執行以下操作
        1. 加載BUILD與目標相關的文件。
        2. 分析輸入及其依賴關係,應用指定的構建規則。並生產action
        3. 對輸入執行構建操作,直到生成最終構建輸出。
      • action圖表示各個構建輸入和他們之間的關係,以及Bazel將執行的構建操作。
  • 優點

    • 構建快。支持增量編譯, 對依賴關係進行了優化,從而支持併發執行。
    • 可構建多種語言。bazel可用來構建Java C++ Android iOS等很多語言和框架,並支持mac windows linux等不同平臺。
    • 可伸縮。可處理任意大小的代碼庫,可處理多個庫,也可以處理單個庫
    • 可擴展。使用bazel擴展語言可支持新語言和新平臺。
  • 安裝方法

    • 使用Homebrew安裝

      1
      2
      3
      4
      5
      6
      7
      8
      9
      
      brew uninstall bazel
      
      brew tap bazelbuild/tap
      brew tap-pin bazelbuild/tap
      brew install bazelbuild/tap/bazel
      
      bazel version
      
      brew upgrade bazelbuild/tap/bazel
      
    • 官網下載指定版本

入門用法

項目結構

​ 根目錄下爲工作區workspaceworkspace下包含多個package,每個package又包含多個編譯目標target

wrokspace

  • Bazel的編譯是基於工作區(workspace)的概念。工作區是一個存放了所有源代碼和Bazel編譯輸出文件的目錄,也就是整個項目的根目錄。同時它也包含一些Bazel認識的文件:
    • WORKSPACE文件,用於指定當前文件夾就是一個Bazel的工作區。所以**WORKSPACE文件總是存在於項目的根目錄下**。
    • 一個或多個BUILD文件,用於告訴Bazel怎麼構建項目的不同部分。(如果工作區中的一個目錄包含BUILD文件,那麼它就是一個package。
  • 要指定一個目錄爲Bazel的工作區,就只要在該目錄下創建一個空的WORKSPACE文件即可
  • 當Bazel編譯項目時,所有的輸入和依賴項都必須在同一個工作區。屬於不同工作區的文件,除非linked否則彼此獨立。
  • WORKSPACE採用類似Python的語法

package

  • 如果工作區中的一個目錄包含BUILD文件,那麼它就是一個package

target

  • 一個BUILD文件包含了幾種不同類型的指令。其中最重要的是編譯指令,它告訴Bazel如何編譯想要的輸出,比如可執行二進制文件或庫
  • BUILD文件中的每一條編譯指令被稱爲一個target,它指向一系列的源文件和依賴,一個target也可以指向別的target。

編譯示例

示例使用官方的c++編譯示例

  • 編譯示例目錄結構

    examples
    └── cpp-tutorial
    ​ ├──stage1
    ​ │ ├── main
    ​ │ │ ├── BUILD
    ​ │ │ └── hello-world.cc
    ​ │ └── WORKSPACE
    ​ │
    ​ ├──stage2
    ​ │ ├── main
    ​ │ │ ├── BUILD
    ​ │ │ ├── hello-world.cc
    ​ │ │ ├── hello-greet.cc
    ​ │ │ └── hello-greet.h
    ​ │ └── WORKSPACE
    ​ │
    ​ └──stage3
    ​ ├── main
    ​ │ ├── BUILD
    ​ │ ├── hello-world.cc
    ​ │ ├── hello-greet.cc
    ​ │ └── hello-greet.h
    ​ ├── lib
    ​ │ ├── BUILD
    ​ │ ├── hello-time.cc
    ​ │ └── hello-time.h
    ​ └── WORKSPACE

如何編譯

1
2
3
4
# 在WORKSPACE文件所在根目錄下執行
# //main:是BUILD文件相對於WORKSPACE文件的位置
# hello-world則是我們在BUILD文件中命名好的target的名字
bazel build //main:hello-world

Stage1 如何構建單個package中的單個target

1
2
3
4
5
# BUILD 文件內容
cc_binary(
    name = "hello-world",
    srcs = ["hello-world.cc"],
)

Stage2 如何構建單個package的多個target

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# BUILD 文件內容
cc_library(
    name = "hello-greet",
    srcs = ["hello-greet.cc"],
    hdrs = ["hello-greet.h"],
)

cc_binary(
    name = "hello-world",
    srcs = ["hello-world.cc"],
    deps = [
        ":hello-greet",
    ],
)

Stage3 如何構建多個package的多個target

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# lib包
## 默認情況, targets只對同一BUILD文件裏的其他targets可見
## 若要對其他包可見, 需聲明爲顯示可見
## 防止像共有API中庫的實現細節泄露等情況 
cc_library(
    name = "hello-time",
    srcs = ["hello-time.cc"],
    hdrs = ["hello-time.h"],
    visibility = ["//main:__pkg__"],
)


# main包
cc_library(
    name = "hello-greet",
    srcs = ["hello-greet.cc"],
    hdrs = ["hello-greet.h"],
)

cc_binary(
    name = "hello-world",
    srcs = ["hello-world.cc"],
    deps = [
        ":hello-greet",
        "//lib:hello-time",
    ],
)

如何查看依賴圖

  • 生成依賴圖

    1
    2
    3
    4
    
    # 查找target爲 //main:hello-world 的所有依賴
    # --nohost_deps 		表示不包括host依賴
    # --noimplicit_deps 	表示不包括隱式依賴 e.g: @bazel_tools//tools/cpp:stl
    bazel query --nohost_deps --noimplicit_deps 'deps(//main:hello-world)' --output graph
    
  • 將生成的輸出圖文字描述, 粘貼到 GraphViz, 生成的依賴圖如下

進階用法

  • bazel 可選命令

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    
    用法:bazel <command> <options> ...
    
    可用命令:
    	build                構建指定的目標。
    	clean                刪除輸出文件,並可選擇停止服務器。
    	run                  運行指定的目標。
    	test                 構建並運行指定的測試目標。
    	
    	help                 打印命令或索引的幫助。
    	info                 顯示有關bazel服務器的運行時信息。
    	version              打印Bazel的版本信息。
    
      analyze-profile      分析構建配置文件數據。
      aquery               對分析後的操作圖執行查詢。
      cquery               執行分析後依賴圖查詢。
      query                執行依賴關係圖查詢。
    
    	canonicalize-flags   Canonicalize Bazel flags。
    	dump                 轉儲Bazel服務器進程的內部狀態。
    
      fetch                獲取目標的所有外部依賴項。
      mobile-install       在移動設備上安裝應用程序。
    
    	shutdown             停止Bazel服務器。
    

構建

  • 用法
1
bazel build <options> <targets>

指定包路徑

--package_path 指定搜索BUILD文件的目錄集合

1
2
3
4
5
6
7
mkdir -p build && cd build
touch WORKSPACE

## 絕對路徑
bazel build --package_path /some/other/path //main:hello-world
## 相對路徑
bazel build --package_path %workspace%/relative/to/other/path //main:hello-world

編譯警告檢查

--output_filter regex 顯示那些符合正則表達式的構建和編譯的警告,如果目標不匹配則標準輸出和標準錯誤將會被丟棄。用於幫助找到某些特定的警告。

1
2
3
4
5
6
7
8
9
10
11
## 顯示所有警告
bazel build --output_filter= --package_path /some/other/path 

## 只顯示指定的某幾個包(project)的警告
--output_filter='^//(first/project|second/project):'

## 不顯示指定的某幾個包的警告
--output_filter='^//((?!(first/bad_project|second/bad_project):).)*$'

## 所有警告均不顯示
--output_filter=DONT_MATCH_ANYTHING

編譯器的編譯選項

編譯C的Flag

--copt gcc-option 指定給 gcc 傳遞什麼參數, 等價於 cmake 的 CMAKE_C_FLAGS

1
2
## 參數項一次只能指定一個
bazel build --copt="-O3" --copt="-fpic" --copt="-std=c++11" //main:hello-world

只適用編譯C的Flag

--conlyopt gcc-option 與 —copt 相似, 不同的是只能指定 c 的編譯選項

1
2
## 參數項一次只能指定一個
bazel build --conlyopt="-Wno-pointer-sign" //main:hello-world

gcc中 char 和 unsigned char 有時候傳遞參數類型不匹配會有報警,增加該編譯選項可以關閉該報警。

編譯C++的Flag

--cxxopt gcc-option 指定編 c++ 的參數項, 等價於 cmake 的 CMAKE_CXX_FLAGS

1
2
## 參數項一次只能指定一個
bazel build --cxxopt="-fno-implicit-templates" //main:hello-world

用**-fno-implicit-templates**編譯代碼,會令隱式的模板實例化失效,他會顯示的初始化所需模板。雖然這種方法需要精確瞭解正在使用的是哪種模板實例,但這種方法確實令源代碼更加清楚。

編譯器鏈接Flag

--linkopt linker-option 指定給 gcc 的鏈接庫選項

1
bazel build --linkopt="-pthread" //main:hello-world

調試與符號信息的去除

--strip (always|never|sometimes) 指定是否從二進制文件和共享庫文件中去除調試信息

1
2
# 默認是 sometimes, 當語義項 --compilation_mode=fastbuild 時
bazel build --strip=always //main:hello-world

注:

  1. 即使使用了 --strip=never 也不會保留 debug 信息, 如果想要得到完整符號庫, 需指定使用 -c dbg 或者 --copt -g 方可

    1
    
    bazel build -c dbg --strip=never //main:hello-world
    
  2. --strip 項默認是 –strip-debug, 如果不只是想去除 debug 信息, 而是去除所有符號信息, 則需指定鏈接項 --strip-all

    1
    
    bazel build --strip=always --linkopt=-Wl,--strip-all //main:hello-world
    

完整的編譯選項文檔Commands and Options

語義選項

影響構建的命令和輸出的內容。

指定編譯模式

--compilation_mode (fastbuild|opt|dbg) (-c)

  • fastbuild 快速編譯, 會產生最小的調試信息(-gmlt -Wl,-S), 同時將會設置 -DNDEBUG, 默認該模式
  • opt 最優化, 不會產生任何調試信息, 除非手動指定了 –copt -g, 同時設置 -O2 -DNDEBUG
  • dbg 調試, 產生調試信息(-g)
1
2
3
bazel build --compilation_mode "dbg" //main:hello-world
## 或者使用縮寫
bazel build -c "dbg" //main:hello-world

動態鏈接模式

--dynamic_mode (auto|default|fully|off)

  • auto 根據平臺選擇: linux 上是 defaultcygwin 上是 off
  • default 由 bazel 去選擇是否進行動態鏈接
  • fully 動態鏈接所有目標, 將會加速鏈接時間和減小輸出庫大小
  • off 靜態鏈接所有目標, 當 linkopts 指定設置 -static, 將自動轉成該模式

輸出構建過程的執行語句

--explain logfile 寫到一個日誌文件當中, 配合 —-verbose_explanations, 效果等同 cmake 開啓 CMAKE_VERBOSE_MAKEFILE

1
bazel build --explain "log.txt" -—verbose_explanations //main:hello-world

輸出構建過程的性能

--profile file 會把構建的分析數據寫到一個二進制文件當中, 可使用 bazel analyze-profile 命令進行解析

1
2
3
4
5
## 生成 profile
bazel build --profile "profile.bin" //main:hello-world

## 分析構建性能
bazel analyze-profile profile.bin

**更多構建命令選項可參考: ** bazel help build

查看構建的信息內容

  • 命令用法

    1
    
    bazel info <options> [key]
    
  • --show_make_env 顯示構建環境, 默認 false

**更多信息查詢命令選項可參考: ** bazel help info

分析

  • 命令用法
1
bazel query <options> <query-expression>

是否分析host依賴相關

--host_deps 指定分析其依賴關係, 默認開啓

--nohost_deps 指定不分析其依賴關係

是否分析其隱式依賴關係

--implicit_deps 指定分析其隱式依賴關係, 例如 @bazel_tools//tools/cpp:malloc默認開啓

--noimplicit_deps 指定不分析其隱式依賴關係

指定輸出格式

--output (build|graph|label|label_kind|location|maxrank|minrank|package|proto|xml)

  • build 輸出指定分析目標的 BUILD 文件內容
1
2
3
4
5
# /Users/wangzhuxing/Desktop/Project/repository/wzx3/bazel-examples/cpp-tutorial/stage1/main/BUILD:1:1
cc_binary(
  name = "hello-world",
  srcs = ["//main:hello-world.cc"],
)
  • graph 輸出可進行圖可視化的格式文本

    1
    2
    3
    4
    5
    6
    
    digraph mygraph {
      node [shape=box];
    "//main:hello-world"
    "//main:hello-world" -> "//main:hello-world.cc"
    "//main:hello-world.cc"
    }
    
  • label 輸出指定分析目標的依賴文件默認配置

    1
    2
    
    //main:hello-world
    //main:hello-world.cc
    
  • label_kind 分類輸出指定分析目標的依賴

    1
    2
    
    cc_binary rule //main:hello-world
    source file //main:hello-world.cc
    
  • location 輸出指定分析目標的代碼行位置

    1
    2
    
    /Users/.../main/BUILD:1:1: cc_binary rule //main:hello-world
    /Users/.../main/BUILD:3:12: source file //main:hello-world.cc
    
  • maxrank|minrank 按等級排列指定分析目標的依賴

    1
    2
    
    0 //main:hello-world
    1 //main:hello-world.cc
    
  • package 輸出指定分析目標的包名

    1
    
    main
    

實用流程

  • 生成可視化圖結構的信息文本
1
2
3
4
bazel query --nohost_deps                                   \
            --noimplicit_deps                               \
            'deps(//main:hello-world)'              				\
            --output graph > output-graph.gv
  • 通過 python 的 graphviz模塊 進行圖解析
1
python -m bazel_graphviz --input_file="output-graph.gv"

附上本人的解析代碼

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# encoding: utf-8

import argparse, os

from graphviz import Source

def _render(args):
    if args.input_file:
        output_file_name, output_file_extension = os.path.splitext(str(args.input_file))
        src = Source.from_file(str(args.input_file))
        src.format = 'png'
        src.render(filename=output_file_name, view=True, cleanup=True)
        return True
    return False

def _get_parser():
    parser = argparse.ArgumentParser()
    parser.add_argument(
        '--input_file',
        default=None,
        help='Path to the model weights file of the external tool (e.g caffe/onnx weights proto binary)')
    return parser

def _main():
    parser = _get_parser()
    args, unknown_args = parser.parse_known_args()
    if not _render(args):
        print('Render bazel graph failed!')

if __name__ == '__main__':
    _main()

**更多構建命令選項可參考: ** bazel help query

清理

只清理編譯目錄

刪除編譯項目的編譯過程中產生的目錄樹,不會刪除bazel產生的臨時文件

1
bazel clean

完全清理

刪除輸出文件的目錄樹,刪除Bazel產生的所有臨時文件,刪除下載的臨時文件等

1
bazel clean --expunge

測試

  • 命令用法
1
bazel test <options> <test-targets>

:

  1. 命令運行前會先執行一次 bazel build
  2. 測試命令無法直接傳入測試程序的形參, 也並不需要

**更多測試命令選項可參考: ** bazel help test

運行

  • 命令用法
1
bazel run <options> -- <binary target> <flags to binary>

**更多運行命令選項可參考: ** bazel help run

高階用法

BUILD 文件語法

概念和術語

通用定義

  • Bourne shell 標記化

    根據 Bourne shell 的標記化規則,某些規則的某些字符串屬性被拆分爲多個單詞

    1. 未加引號的空格分隔單獨的單詞
    2. 單引號和雙引號字符和反斜槓用於防止標記化

    受此標記化影響的屬性在本文檔的定義中明確指出。

    受**“Make”變量擴展和Bourne shell標記化的屬性通常用於將任意選項傳遞給編譯器和其他工具**。此類屬性的示例是cc_library.coptsjava_library.javacopts。這些替換一起允許單個字符串變量擴展爲特定於配置的選項字列表。

  • 標籤表達式

  • 編譯(build)規則的通用屬性

  • 測試(test)規則的通用屬性 (*_test)

  • 生成binary規則的通用屬性 (*_binary)

  • 可配置的屬性

  • 隱式輸出目標

"Make"變量

函數接口

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章