【转】编译构建工具-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"变量

函数接口

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值