Bazel&C++ customs clearance manual

Table of contents

Install

Ubuntu

Bazelisk

Concepts and Terminology

Workspace

Package

Target

Label

Rule

BUILD file

Dependency

bzl file

Build the C++ project

Build with Bazel

View dependency graph

Specify multiple targets

use multiple packages

how to cite target

Starlark

variable

Across BUILD variables

Make variable

General predefined variables

genrule predefined variables

input and output path variables

general rule

rule list

filegroup

test_suite

alias

config_setting

genrule

C++ rules

rule list

cc_binary

cc_import

cc_library

common use case

wildcard

transitive dependency

Add header file path

import compiled library

Include external libraries

use external library

external dependencies

external dependency type

Bazel project

Non-Bazel projects

outer envelope

Dependency pull

use proxy

Dependency cache

.bazelrc

Location

grammar

expand

build phase

macro

rule

custom rules

rule attribute

default property

special attribute

private property

Target

rule implementation

common commands

build

Debug

View dependencies

Clean


Install

Ubuntu

Follow the steps below to install Bazel:

echo "deb [arch=amd64] http://storage.googleapis.com/bazel-apt stable jdk1.8" | sudo tee /etc/apt/sources.list.d/bazel.list
curl https://bazel.build/bazel-release.pub.gpg | sudo apt-key add -
 
sudo apt-get update && sudo apt-get install bazel

You can upgrade to the latest version of Bazel with the following command:

sudo apt-get install --only-upgrade bazel

Bazelisk

This is a Bazel launcher written based on the Go language. It will download the most suitable Bazel for your workspace and transparently forward commands to the Bazel.

Since Bazellisk provides the same interface as Bazel, it is usually named bazel directly:

sudo wget -O /usr/local/bin/bazel https://github.com/bazelbuild/bazelisk/releases/download/v0.0.8/bazelisk-linux-amd64 
sudo chmod +x /usr/local/bin/bazel

I personally recommend the latter.

Concepts and Terminology

Workspace

A workspace is a directory that contains:

  1. The source code files required to build the target, and the corresponding BUILD files
  2. A symbolic link to the build result
  3. WORKSPACE file, can be empty, can contain references to external dependencies

Here is an example:

.
├── BUILD
├── main.cc
├── MODULE.bazel
├── README.md
├── WORKSPACE
└── WORKSPACE.bzlmod

load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")

http_archive(
    name = "com_github_google_glog",
    sha256 = "eede71f28371bf39aa69b45de23b329d37214016e2055269b3b5e7cfd40b59f5",
    strip_prefix = "glog-0.5.0",
    urls = ["https://github.com/google/glog/archive/refs/tags/v0.5.0.tar.gz"],
)

# We have to define gflags as well because it's a dependency of glog.
http_archive(
    name = "com_github_gflags_gflags",
    sha256 = "34af2f15cf7367513b352bdcd2493ab14ce43692d2dcd9dfc499492966c64dcf",
    strip_prefix = "gflags-2.2.2",
    urls = ["https://github.com/gflags/gflags/archive/refs/tags/v2.2.2.tar.gz"],
)

Package

A package is the main code organization unit in a workspace, which contains a series of related files (mainly code) and a BUILD file that describes the relationship between these files

A package is a subdirectory of a workspace, and its root must contain the file BUILD.bazel or BUILD. Except for those subdirectories with BUILD files - subpackages - other subdirectories are part of the package

├── lib
│   ├── BUILD
│   ├── hello-time.cc
│   └── hello-time.h
├── main
│   ├── BUILD
│   ├── hello-greet.cc
│   ├── hello-greet.h
│   └── hello-world.cc
├── README.md
└── WORKSPACE

Target

A package is a container whose elements are defined in the BUILD file, including:

  1. Rules (Rule), specify the relationship between the input set and the output set, and declare the necessary steps to produce the output from the input. The output of one rule can be the input of another rule
  2. Files can be divided into two categories:
    1. Source File
    2. Automatically generated files (Derived files), generated by the build tool according to the rules
  1. Package group: A group of packages that are used to limit the visibility of specific rules. Package groups are defined by the function package_group, which takes a list of packages and a package group name as arguments. You can refer to package groups in the visibility attribute of rules, declaring that those package groups can refer to rules in the current package

Files generated by any package belong to the current package, and files cannot be generated for other packages. but can read input from other packages

Label

A "tag" is required when referring to a target. The standardized representation of the label: @ project //my/app/main:app_binary , the package name before the colon, and the target name after it. If no target name is specified, the last segment of the package path will be used as the target name by default, for example:

//my/app
//my/app:app

The two are equivalent. In the BUILD file, when referring to the target in the current package, the package name part can be omitted, so the following four writing methods can be equivalent:

# The current package is my/app 
//my/app:app 
//my/app 
:app 
app

In the BUILD file, when referring to the rules defined in the current package, the colon cannot be omitted. When referring to files in the current package, the colon can be omitted. For example : generate.cc .

However, when referencing from other packages or from the command line, the full label must be used: //my/app:generate.cc

This part of @project is usually not used. When referencing a target in an external repository, project fills in the name of the external repository.

Rule

Rules specify the relationship between inputs and outputs, and describe the steps to produce the output.

There are many types of rules. Each rule has a name attribute, which is also the target name. For some rules, this name is the filename of the output produced.

When declaring the syntax of a rule in BUILD:

Rule type ( 
    name = "...", 
    other attributes = ... 
)
load("@rules_cc//cc:defs.bzl", "cc_binary", "cc_library")
load("//tools/install:install.bzl", "install", "install_src_files")
load("//tools:cpplint.bzl", "cpplint")

package(default_visibility = ["//visibility:public"])

MONITOR_COPTS = ['-DMODULE_NAME=\\"monitor\\"']

cc_binary(
    name = "libmonitor.so",
    linkshared = True,
    linkstatic = True,
    deps = [
        ":monitor_lib",
    ],
)

cc_library(
    name = "monitor_lib",
    srcs = ["monitor.cc"],
    hdrs = ["monitor.h"],
    copts = MONITOR_COPTS,
    visibility = ["//visibility:private"],
    deps = [
        "//cyber",
        "//modules/common/util:util_tool",
        "//modules/monitor/common:recurrent_runner",
        "//modules/monitor/hardware:esdcan_monitor",
        "//modules/monitor/hardware:gps_monitor",
        "//modules/monitor/hardware:resource_monitor",
        "//modules/monitor/hardware:socket_can_monitor",
        "//modules/monitor/software:camera_monitor",
        "//modules/monitor/software:channel_monitor",
        "//modules/monitor/software:functional_safety_monitor",
        "//modules/monitor/software:latency_monitor",
        "//modules/monitor/software:localization_monitor",
        "//modules/monitor/software:module_monitor",
        "//modules/monitor/software:process_monitor",
        "//modules/monitor/software:recorder_monitor",
        "//modules/monitor/software:summary_monitor",
    ],
    alwayslink = True,
)

filegroup(
    name = "runtime_data",
    srcs = glob([
       "dag/*.dag",
       "launch/*.launch",
    ]),
)

install(
    name = "install",
    library_dest = "monitor/lib",
    data_dest = "monitor",
    targets = [
        ":libmonitor.so",
    ],
    data = [
       ":runtime_data",
        ":cyberfile.xml",
        ":monitor.BUILD",
    ],
)

install_src_files(
    name = "install_src",
    deps = [
        ":install_monitor_src",
        ":install_monitor_hdrs"
    ],
)

install_src_files(
    name = "install_monitor_src",
    src_dir = ["."],
    dest = "monitor/src",
    filter = "*",
)

install_src_files(
    name = "install_monitor_hdrs",
    src_dir = ["."],
    dest = "monitor/include",
    filter = "*.h",
) 

cpplint()

BUILD file

The BUILD file defines all the metadata for the package. The statements in it are interpreted one by one from top to bottom. The order of some statements is important, for example, variables must be defined first and then used, but the order of rule declarations does not matter.

BUILD files can only contain ASCII characters, and cannot declare functions and use for/if statements. You can declare functions and control structures in Bazel extensions—files with the extension .bzl. And load the Bazel extension with the load statement in the BUILD file:

load("//foo/bar:file.bzl", "some_library")

The above statement loads foo/bar/file.bzl and adds the symbol some_libraray defined in it to the current environment. The load statement can be used to load rules, functions, constants (strings, lists, etc.).

The load statement must appear at the top-level scope, not inside a function. The first parameter specifies the location of the extension, and you can set aliases for imported symbols.

The type of rule is generally prefixed with a programming language, such as cc, java, and the suffix is ​​usually:

  1. *_binary for building executables for the target language
  2. *_test is used for automated testing, its target is an executable, and should exit 0 if the test passes
  3. *_library for building libraries for the target language

Dependency

Target A depends on B, which means that A needs B during construction or execution. The dependencies of all targets form a directed acyclic graph (DAG) called a dependency graph.

A dependency with a distance of 1 is called a direct dependency, and a dependency with a distance greater than 1 is called a transitive dependency.

Dependencies are divided into the following categories:

  1. srcs dependencies: files directly consumed by the current rule
  2. deps dependencies: independently compiled modules that provide header files, symbols, libraries, and data for the current rule
  3. data dependency: does not belong to the source code and does not affect how the target is built, but the target may depend on it at runtime

bzl file

Where global variables need to be stored

module(
    name = "example",
    version = "0.0.1",
)

# 1. The metadata of glog is fetched from the BCR, including its dependencies (gflags).
# 2. The `repo_name` attribute allows users to reference this dependency via the `com_github_google_glog` repo name.
bazel_dep(name = "glog", version = "0.5.0", repo_name = "com_github_google_glog")

Build the C++ project

Build with Bazel

The first step is to create a workspace. The workspace contains the following special files:

  1. WORKSPACE, this file is located in the root directory and defines the current directory as the Bazel workspace
  2. BUILD, which tells Bazel how the different parts of the project are built. A directory in a workspace that contains a BUILD file is called a package

When Bazel builds a project, all inputs and dependencies must be in the workspace. It doesn't matter if files in different workspaces are separate from each other unless they are linked.

Each BUILD file contains several Bazel instructions, the most important type of instruction is the build rule (Build Rule), the build rule explains how to produce the desired output - such as executable files or libraries. Each build rule in BUILD is also called a target (Target), which points to several source files and dependencies, and can also point to other targets.

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

Here a target called hello-world is defined, which uses the built-in cc_binary rules. This rule tells Bazel to build a self-contained executable from the source hello-world.cc.

Execute the following command to trigger the build:

# //main: The location of the BUILD file relative to the workspace 
# hello-world is the target bazel build defined in the BUILD file 
//main:hello-world

After the build is complete, directories such as bazel-bin will appear in the root directory of the workspace, which are symbolic links pointing to a descendant directory of $HOME/.cache/bazel. implement:

bazel-bin/main/hello-world

The built binary can be run.

View dependency graph

Bazel will generate a dependency graph based on the declaration in BUILD, and implement precise incremental builds based on this dependency graph.

To view the dependency graph, first install:

sudo apt install graphviz xdot

Then execute:

bazel query --nohost_deps --noimplicit_deps 'deps(//main:hello-world)' --output graph | xdot

Specify multiple targets

Large projects are often divided into multiple packages, multiple targets for faster incremental builds, parallel builds. The workspace stage2 contains a single package, two targets:

# First build the hello-greet library, cc_library is the built-in rule 
cc_library( 
    name = "hello-greet", 
    srcs = ["hello-greet.cc"], 
    # header file 
    hdrs = ["hello-greet.h"], 
) 
 
# Then build the hello-world binary file 
cc_binary( 
    name = "hello-world", 
    srcs = ["hello-world.cc"], 
    deps = [ 
        # Prompt Bazel, you need hello-greet to build the current target 
        # Depend on the current package hello-greet object in 
        ":hello-greet", 
    ], 
)

use multiple packages

The workspace stage3 further divides new packages to provide the function of printing time:

cc_library( 
    name = "hello-time", 
    srcs = ["hello-time.cc"], 
    hdrs = ["hello-time.h"], 
    # Make the current target visible to the main package of the workspace. By default the target Visible only by the current package 
    visibility = ["//main:__pkg__"], 
)
cc_library( 
    name = "hello-greet", 
    srcs = ["hello-greet.cc"], 
    hdrs = ["hello-greet.h"], 
) 
 
cc_binary( 
    name = "hello-world", 
    srcs = ["hello -world.cc"], 
    deps = [ 
        # Rely on the hello-greet target in the current package 
        ":hello-greet", 
        # Rely on the hello-time target in the lib package in the root directory of the workspace 
        "//lib:hello- time", 
    ], 
)

how to cite target

In the BUILD file or the command line, you use labels (Label) to refer to the target, the syntax is:

//path/to/package:target-name 
 
# When referring to other targets in the current package, you can: 
//:target-name 
# When referencing other targets in the current BUILD file, you can: : 
target-name

Starlark

The data types supported by Starlark include: None, bool, dict, function, int, list, string, and two Bazel-specific types: depset and struct.

# define a number 
number = 18 
 
# define a dictionary 
people = { 
    "Alice": 22, 
    "Bob": 40, 
    "Charlie": 55, 
    "Dave": 14, 
} 
 
names = ", ".join(people.keys ()) 
 
# Define a function 
def greet(name): 
  """Return a greeting.""" 
  return "Hello {}!".format(name) 
# Call the function 
greeting = greet(names) 
 
 
def fizz_buzz(n): 
  """Print Fizz Buzz numbers from 1 to n.""" 
  # loop structure 
  for i in range(1, n + 1): 
    s = "" 
    # branch structure 
    if i % 3 == 0: 
      s += "Fizz "
    if i % 5 == 0:
      s += "Buzz"
    print(s if s else i)

variable

You can declare and use variables in BUILD files. Duplicated code can be reduced by using variables:

COPTS = ["-DVERSION=5"]
 
cc_library(
  name = "foo",
  copts = COPTS,
  srcs = ["foo.cc"],
)
 
cc_library(
  name = "bar",
  copts = COPTS,
  srcs = ["bar.cc"],
  deps = [":foo"],
)

Across BUILD variables

If you want to declare a variable that is shared across multiple BUILD files, you must put the variable into a .bzl file, and then load the bzl file through load.

Make variable

The so-called Make variable is a special kind of expandable string variable, which is similar to the expansion of variable substitution in Shell.

Bazel provides:

  1. Predefined variables that can be used in any rule
  2. Custom variables, defined in rules. These variables can be used only in those rules that depend on this rule

Only those rule attributes marked as Subject to 'Make variable' substitution can use Make variables. For example:

my_attr = "prefix $(FOO) suffix"

If you want to use the $ character, you need to replace it with $$ .

General predefined variables

Execute the command: bazel info -- show_make _ env [ build options ] to view a list of all predefined variables.

Any rule can use the following variables

variable

illustrate

COMPILATION_MODE

Compilation mode: fastbuild, dbg, opt

OVERLOAD

The root directory of the binary tree for the target architecture

GENDIR

The root directory of the generated code tree for the target architecture

TARGET_CPU

CPU of the target architecture

genrule predefined variables

The variables in the following table can be used in the cmd property of the genrule rule:

variable

illustrate

OUTS

The outs list of genrule, if there is only one output file, you can use $ @

SRCS

The srcs list of genrule, if there is only one input file, you can use $ <

@D

output directory if:

  1. outs contains only a filename, expands to the directory containing the file
  2. outs contains multiple files, this variable expands to the root directory of the current package in the genfiles tree

input and output path variables

The variables in the following table take Bazel's Label as a parameter to obtain a certain type of input/output path of the package:

general rule

rule list

filegroup

Specify a name for a set of goals that you can easily refer to from other rules.

Bazel encourages the use of filegroups instead of direct references to directories. The Bazel build system is not fully aware of changes to files in directories, so it may not rebuild when files change. With filegroup, even with glob, all files in the directory can still be correctly monitored by the build system.

Example:

filegroup(
    name = "exported_testdata",
    srcs = glob([
        "testdata/*.dat",
        "testdata/logs/**/*.log",
    ]),
)

To refer to a filegroup, just use the tag:

cc_library(
    name = "my_library",
    srcs = ["foo.cc"],
    data = [
        "//my_package:exported_testdata",
        "//my_package:mygroup",
    ],
)

test_suite

Define a set of test cases, give them a meaningful name, and make it easy to execute these test cases at specific times—such as moving in code, performing stress tests.

# Match all small tests in the current package 
test_suite( 
    name = "small_tests", 
    tags = ["small"], 
) 
# Match tests that do not contain flaky tags test_suite 
( 
    name = "non_flaky_test", 
    tags = ["-flaky"], 
) 
# Specify test list 
test_suite( 
    name = "smoke_tests", 
    tests = [ 
        "system_unittest", 
        "public_api_unittest", 
    ], 
)

alias

Set an alias for the rule:

filegroup(
    name = "data",
    srcs = ["data.txt"],
)
# 定义别名
alias(
    name = "other",
    actual = ":data",
)

config_setting

config_setting can trigger configurable properties by matching "configuration state" expressed in Bazel tags or platform constraints.

config_setting(
    name = "arm_build",
    values = {"cpu": "arm"},
)

The following library declares configurable properties through select:

cc_binary( 
    name = "mybinary", 
    srcs = ["main.cc"], 
    deps = select({ 
        # If config_settings arm_build matches the build in progress, rely on the arm_lib target 
        ":arm_build": [":arm_lib"], 
        # If config_settings x86_debug_build matches the build in progress, depend on x86_devdbg_lib 
        ":x86_debug_build": [":x86_devdbg_lib"], 
        # By default, depend on generic_lib 
        "//conditions:default": [":generic_lib"], 
    }), 
)

The following example matches any debug (-c dbg) build for the x86 platform that defines the macro FOO=bar:

config_setting(
    name = "x86_debug_build",
    values = {
        "cpu": "x86",
        "compilation_mode": "dbg",
        "define": "FOO=bar"
    },
)

genrule

General rules - use user-specified Bash commands to generate one or more files. Using genrule can theoretically achieve any build behavior, such as compressing JavaScript code. However, when performing construction tasks such as C++ and Java, it is better to use the corresponding dedicated rules, which is simpler.

Do not use genrule to run tests, if you need general test rules, consider using sh_test.

The genrule is executed in a Bash shell environment, and when any command or pipe fails (set -e -o pipefail), the entire rule fails. You should not access the network in genrule.

genrule( 
    name = "foo", 
    # No need to enter 
    srcs = [], 
    # Generate a foo.h 
    outs = ["foo.h"], 
    # Run a Perl script under the package where the current rule is located 
    cmd = "./$ (location create_foo.pl) > \"$@\"", 
    tools = ["create_foo.pl"], 
)

C++ rules

rule list

cc_binary

implicit output:

  1. name.stripped, this output is only built when explicitly asked to, run strip -g against the resulting binary to strip out debug symbols. Additional strip options can be passed on the command line --stripopt=-foo
  2. name.dwp, this output will only be built when explicitly requested, if Fission is enabled, this file contains debugging information for remote debugging, otherwise it is an empty file

Attributes

illustrate

name

target name

dept

A list of other libraries that need to be linked into this binary target, referenced by Label

These libraries can be targets defined by cc_library or objc_library

srcs

List of C/C++ source files, referenced by Label

These files are C/C++ source code files or header files, which can be automatically generated or manually written.

All cc/c/cpp files will be compiled. If a declared file is in the outs list of other rules, the current rule automatically depends on that rule

All .h files are not compiled and are only included in source files. All .h/.cc etc. files can include header files declared in srcs and hdrs of targets declared in deps. That is, any #included files are either declared in this attribute or in the hdrs attribute of the dependent cc_library

If a rule's name appears in the srcs list, the current rule automatically depends on that rule:

  1. If the output of that rule is C/C++ source files, they are compiled into the current target
  2. If the output of that rule is a library file, it is linked into the current target

Allowed file types:

  1. C/C++ source code, extensions .c, .cc, .cpp, .cxx, .c++, .C
  2. C/C++ header files with extensions .h, .hh, .hpp, .hxx, .inc
  3. Assembly code, extension .S
  4. Archive files with extensions .a, .pic.a
  5. Shared library, extension .so, .so.version, version is soname version number
  6. Object file, extension .o, .pic.o
  7. Any rule capable of producing the above document

copts

list of strings

Options provided for the C++ compiler, which are added in order to COPTS before compiling the target. These options only affect the compilation of the current target, not its dependencies. Any paths in options are relative to the current workspace not the current package

It can also be passed in via the --copts option during bazel build, for example:

Shell

--copt"-DENVOY_IGNORE_GLIBCXX_USE_CXX11_ABI_ERROR=1"

defines

list of strings

Passing macro definitions to the C++ compiler will actually be prefixed with -D and added to COPTS. Unlike the copts attribute, these macro definitions are added to the current target, and all targets that depend on it

includes

list of strings

The header include directories passed to the C++ compiler are actually prefixed with -isystem and added to COPTS. Unlike the copts attribute, these header inclusions affect the current target, and all targets that depend on it

If you don't know what the side effects are, you can pass -I to copts instead of using the current properties

lincopts

list of strings

Pass options to the C++ linker, each string in this attribute is added to LINKOPTS before linking the binary

In this attribute list, any item that does not start with $ and - is considered to be the Label of a target declared in deps, and the files generated by the target will be added to the link options

linkshared

Boolean, default False. for creating shared libraries

要创建共享库,指定属性linkshared = True,对于GCC来说,会添加选项-shared。生成的结果适合被Java这类应用程序加载

需要注意,这里创建的共享库绝不会被链接到依赖它的二进制文件,而只适用于被其它程序手工的加载。因此,不能代替cc_library

如果同时指定 linkopts=['-static']和linkshared=True,你会得到一个完全自包含的单元。如果同时指定linkstatic=True和linkshared=True会得到一个基本是完全自包含的单元

linkstatic

布尔,默认True

对于cc_binary和cc_test,以静态形式链接二进制文件。对于cc_binary此选项默认True,其它目标默认False

如果当前目标是binary或test,此选项提示构建工具,尽可能链接到用户库的.a版本而非.so版本。某些系统库可能仍然需要动态链接,原因是没有静态库,这导致最终的输出仍然使用动态链接,不是完全静态的

链接一个可执行文件时,实际上有三种方式:

  1. STATIC,使用完全静态链接特性。所有依赖都被静态链接,GCC命令示例:gcc -static foo.o libbar.a libbaz.a -lm
  2. STATIC,所有用户库静态链接(如果存在静态库版本),但是系统库(除去C/C++运行时库)动态链接,GCC命令示例:
    gcc foo.olibfoo.alibbaz.a-lm
  3. DYNAMIC,所有依赖被动态链接(如果存在动态库版本),GCC命令示例:gcc foo.olibfoo.solibbaz.so-lm

对于cc_library来说,linkstatic属性的含义不同。对于C++库来说:

  1. linkstatic=True表示仅仅允许静态链接,也就是不产生.so文件
  2. linkstatic=False表示允许动态链接,同时产生.a和.so文件

malloc

指向标签,默认@bazel_tools//tools/cpp:malloc

覆盖默认的malloc依赖,默认情况下C++二进制文件链接到//tools/cpp:malloc,这是一个空库,这导致实际上链接到libc的malloc

nocopts

字符串

从C++编译命令中移除匹配的选项,此属性的值是正则式,任何匹配正则式的、已经存在的COPTS被移除

stamp

整数,默认-1

用于将构建信息嵌入到二进制文件中,可选值:

  1. stamp = 1,将构建信息嵌入,目标二进制仅仅在其依赖变化时重新构建
  2. stamp = 0,总是将构建信息替换为常量值,有利于构建结果缓存
  3. stamp = -1 ,由--[no]stamp标记控制是否嵌入

toolchains

标签列表

提供构建变量(Make variables,这些变量可以被当前目标使用)的工具链的标签列表

win_def_file

标签

传递给链接器的Windows DEF文件。在Windows上,此属性可以在链接共享库时导出符号

cc_import

导入预编译好的C/C++库。

属性列表:

属性

说明

hdrs

此预编译库对外发布的头文件列表,依赖此库的规则(dependent rule)会直接将这些头文件包含在源码列表中

alwayslink

布尔,默认False

如果为True,则依赖此库的二进制文件会将此静态库归档中的对象文件链接进去,就算某些对象文件中的符号并没有被二进制文件使用

interface_library

用于链接共享库时使用的接口(导入)库

shared_library

共享库,Bazel保证在运行时可以访问到共享库

static_library

静态库

system_provided

提示运行时所需的共享库由操作系统提供,如果为True则应该指定interface_library,shared_library应该为空

cc_library

对于所有cc_*规则来说,构建所需的任何头文件都要在hdrs或srcs中声明。

对于cc_library规则,在hdrs声明的头文件构成库的公共接口。这些头文件可以被当前库的hdrs/srcs中的文件直接包含,也可以被依赖(deps)当前库的其它cc_*的hdrs/srcs直接包含。位于srcs中的头文件,则仅仅能被当前库的hdrs/srcs包含。

cc_binary和cc_test不会暴露接口,因此它们没有hdrs属性。

属性列表:

属性

说明

name

库的名称

deps

需要链接到(into)当前库的其它库

srcs

头文件和源码列表

hdrs

导出的头文件列表

copts/nocopts

传递给C++编译命令的参数

defines

宏定义列表

include_prefix

hdrs中头文件的路径前缀

includes

字符串列表

需要添加到编译命令的包含文件列表

linkopts

链接选项

linkstatic

是否生成动态库

strip_include_prefix

字符串

需要脱去的头文件路径前缀,也就是说使用hdrs中头文件时,要把这个前缀去除,路径才匹配

textual_hdrs

标签列表

头文件列表,这些头文件是不能独立编译的。依赖此库的目标,直接以文本形式包含这些头文件到它的源码列表中,这样才能正确编译这些头文件

常见用例

通配符

可以使用Glob语法为目标添加多个文件:

cc_library(
    name = "build-all-the-files",
    srcs = glob(["*.cc"]),
    hdrs = glob(["*.h"]),
)

传递性依赖

如果源码依赖于某个头文件,则该源码的规则需要dep头文件的库,仅仅直接依赖才需要声明:

# 三明治依赖面包
cc_library(
    name = "sandwich",
    srcs = ["sandwich.cc"],
    hdrs = ["sandwich.h"],
    # 声明当前包下的目标为依赖
    deps = [":bread"],
)
# 面包依赖于面粉,三明治间接依赖面粉,因此不需要声明
cc_library(
    name = "bread",
    srcs = ["bread.cc"],
    hdrs = ["bread.h"],
    deps = [":flour"],
)
 
cc_library(
    name = "flour",
    srcs = ["flour.cc"],
    hdrs = ["flour.h"],
)

添加头文件路径

有些时候你不愿或不能将头文件放到工作空间的include目录下,现有的库的include目录可能不符合

导入已编译库

导入一个库,用于静态链接:

cc_import(
  name = "mylib",
  hdrs = ["mylib.h"],
  static_library = "libmylib.a",
  # 如果为1则libmylib.a总会链接到依赖它的二进制文件
  alwayslink = 1,
)

导入一个库,用于共享链接(UNIX):

cc_import(
  name = "mylib",
  hdrs = ["mylib.h"],
  shared_library = "libmylib.so",
)

通过接口库(Interface library)链接到共享库(Windows):

cc_import(
  name = "mylib",
  hdrs = ["mylib.h"],
  # mylib.lib是mylib.dll的导入库,此导入库会传递给链接器
  interface_library = "mylib.lib",
  # mylib.dll在运行时需要,链接时不需要
  shared_library = "mylib.dll",
)

在二进制目标中选择链接到共享库还是静态库(UNIX):

cc_import(
  name = "mylib",
  hdrs = ["mylib.h"],
  # 同时声明共享库和静态库
  static_library = "libmylib.a",
  shared_library = "libmylib.so",
)
 
# 此二进制目标链接到静态库
cc_binary(
  name = "first",
  srcs = ["first.cc"],
  deps = [":mylib"],
  linkstatic = 1, # default value
)
 
# 此二进制目标链接到共享库
cc_binary(
  name = "second",
  srcs = ["second.cc"],
  deps = [":mylib"],
  linkstatic = 0,
)

包含外部库

你可以在WORKSPACE中调用new_*存储库函数,来从网络中下载依赖。下面的例子下载Google Test库:

# 下载归档文件,并让其在工作空间的存储库中可用
new_http_archive(
    name = "gtest",
    url = "https://github.com/google/googletest/archive/release-1.7.0.zip",
    sha256 = "b58cb7547a28b2c718d1e38aee18a3659c9e3ff52440297e965f5edffe34b6d0",
    # 外部库的构建规则编写在gtest.BUILD
    # 如果此归档文件已经自带了BUILD文件,则可以调用不带new_前缀的函数
    build_file = "gtest.BUILD",
    # 去除路径前缀
    strip_prefix = "googletest-release-1.7.0",
)

构建此外部库的规则如下:

cc_library(
    name = "main",
    srcs = glob(
        # 前缀去除,原来是googletest-release-1.7.0/src/*.cc
        ["src/*.cc"],
        # 排除此文件
        exclude = ["src/gtest-all.cc"]
    ),
    hdrs = glob([
        # 前缀去除
        "include/**/*.h",
        "src/*.h"
    ]),
    copts = [
        # 前缀去除,原来是external/gtest/googletest-release-1.7.0/include
        "-Iexternal/gtest/include"
    ],
    # 链接到pthread
    linkopts = ["-pthread"],
    visibility = ["//visibility:public"],
)

使用外部库

沿用上面的例子,下面的目标使用gtest编写测试代码:

cc_test(
    name = "hello-test",
    srcs = ["hello-test.cc"],
    # 前缀去除
    copts = ["-Iexternal/gtest/include"],
    deps = [
        # 依赖gtest存储库的main目标
        "@gtest//:main",
        "//lib:hello-greet",
    ],
)

外部依赖

Bazel允许依赖其它项目中定义的目标,这些来自其它项目的依赖叫做“外部依赖“。当前工作空间的WORKSPACE文件声明从何处下载外部依赖的源码。

外部依赖可以有自己的1-N个BUILD文件,其中定义自己的目标。当前项目可以使用这些目标。例如下面的两个项目结构:

/
  home/
    user/
      project1/
        WORKSPACE
        BUILD
        srcs/
          ...
      project2/
        WORKSPACE
        BUILD
        my-libs/

如果project1需要依赖定义在project2/BUILD中的目标:foo,则可以在其WORKSPACE中声明一个存储库(repository),名字为project2,位于/home/user/project2。然后,可以在BUILD中通过标签@project2//:foo引用目标foo。

除了依赖来自文件系统其它部分的目标、下载自互联网的目标以外,用户还可以编写自己的存储库规则(repository rules )以实现更复杂的行为。

WORKSPACE的语法格式和BUILD相同,但是允许使用不同的规则集

Bazel会把外部依赖下载到 $(bazel info output_base)/external目录中,要删除掉外部依赖,执行:

bazel clean --expunge

外部依赖类型

Bazel项目

可以使用local_repository、git_repository或者http_archive这几个规则来引用。

引用本地Bazel项目的例子:

local_repository(
    name = "coworkers_project",
    path = "/path/to/coworkers-project",
)

在BUILD中,引用coworkers_project中的目标//foo:bar时,使用标签@coworkers_project//foo:bar

非Bazel项目

可以使用new_local_repository、new_git_repository或者new_http_archive这几个规则来引用。你需要自己编写BUILD文件来构建这些项目。

引用本地非Bazel项目的例子:

new_local_repository(
    name = "coworkers_project",
    path = "/path/to/coworkers-project",
    build_file = "coworker.BUILD",
)
cc_library(
    name = "some-lib",
    srcs = glob(["**"]),
    visibility = ["//visibility:public"],
)

在BUILD文件中,使用标签 coworkers_project//:some-lib引用上面的库。

外部包

对于Maven仓库,可以使用规则maven_jar/maven_server来下载JAR包,并将其作为Java依赖。

依赖拉取

默认情况下,执行bazel Build时会按需自动拉取依赖,你也可以禁用此特性,并使用bazel fetch预先手工拉取依赖。

使用代理

Bazel可以使用HTTPS_PROXY或HTTP_PROXY定义的代理地址。

依赖缓存

Bazel会缓存外部依赖,当WORKSPACE改变时,会重新下载或更新这些依赖。

.bazelrc

Bazel命令接收大量的参数,其中一部分很少变化,这些不变的配置项可以存放在.bazelrc中。

位置

Bazel按以下顺序寻找.bazelrc文件:

  1. 除非指定--nosystem_rc,否则寻找/etc/bazel.bazelrc
  2. 除非指定--noworkspace_rc,否则寻找工作空间根目录的.bazelrc
  3. 除非指定--nohome_rc,否则寻找当前用户的$HOME/.bazelrc

语法

元素

说明

import

导入其它bazelrc文件,例如: import %workspace%/tools/bazel.rc

默认参数

可以提供以下行:

startup ... 启动参数
common... 适用于所有命令的参数
command...为某个子命令指定参数,例如buildquery、

以上三类行,都可以出现多次

--config

用于定义一组参数的组合,在调用bazel命令时指定--config=memcheck,可以引用名为memcheck的参数组。此参数组的定义示例:

build:memcheck--strip=never--test_timeout=3600

扩展

所谓Bazel扩展,是扩展名为.bzl的文件。你可以使用load语句加载扩展中定义的符号到BUILD中。

构建阶段

一次Bazel构建包含三个阶段:

  1. 加载阶段:加载、eval本次构建需要的所有扩展、所有BUILD文件。宏在此阶段执行,规则被实例化。BUILD文件中调用的宏/函数,在此阶段执行函数体,其结果是宏里面实例化的规则被填充到BUILD文件中
  2. 分析阶段:规则的代码——也就是它的implementation函数被执行,导致规则的Action被实例化,Action描述如何从输入产生输出
  3. 执行阶段:执行Action,产生输出,测试也在此阶段执行

Bazel会并行的读取/解析/eval BUILD文件和.bzl文件。每个文件在每次构建最多被读取一次,eval的结果被缓存并重用。每个文件在它的全部依赖被解析之后才eval。加载一个.bzl文件没有副作用,仅仅是定义值和函数

宏(Macro)是一种函数,用来实例化(instantiates)规则。如果BUILD文件太过重复或复杂,可以考虑使用宏,以便减少代码。宏的函数在BUILD文件被读取时就立即执行。BUILD被读取(eval)之后,宏被替换为它生成的规则。bazel query只会列出生成的规则而非宏。

编写宏时需要注意:

  1. 所有实例化规则的公共函数,都必须具有一个无默认值的name参数
  2. 公共函数应当具有docstring
  3. 在BUILD文件中,调用宏时name参数必须是关键字参数
  4. 宏所生成的规则的name属性,必须以调用宏的name参数作为后缀
  5. 大部分情况下,可选参数应该具有默认值None
  6. 应当具有可选的visibility参数

要在宏中实例化原生规则(Native rules,不需要load即可使用的那些规则),可以使用native模块:

# 该宏实例化一个genrule规则
def file_generator(name, arg, visibility=None):
  // 生成一个genrule规则
  native.genrule(
    name = name,
    outs = [name + ".txt"],
    cmd = "$(location generator) %s > $@" % arg,
    tools = ["//test:generator"],
    visibility = visibility,
  )

使用上述宏的BUILD文件:

load("//path:generator.bzl", "file_generator")
 
file_generator(
    name = "file",
    arg = "some_arg",
)

执行下面的命令查看宏展开后的情况:

# bazel query --output=build //label
 
genrule(
  name = "file",
  tools = ["//test:generator"],
  outs = ["//test:file.txt"],
  cmd = "$(location generator) some_arg > $@",
)

规则

规则定义了为了产生输出,需要在输入上执行的一系列动作。例如,C++二进制文件规则以一系列.cpp文件为输入,针对输入调用g++,输出一个可执行文件。注意,从Bazel的角度来说,不但cpp文件是输入,g++、C++库也是输入。当编写自定义规则时,你需要注意,将执行Action所需的库、工具作为输入看待。

Bazel内置了一些规则,这些规则叫原生规则,例如cc_library、cc_library,对一些语言提供了基础的支持。通过编写自定义规则,你可以实现对任何语言的支持。

定义在.bzl中的规则,用起来就像原生规则一样 —— 规则的目标具有标签、可以出现在bazel query。

规则在分析阶段的行为,由它的implementation函数决定。此函数不得调用任何外部工具,它只是注册在执行阶段需要的Action。

自定义规则

在.bzl文件中,你可以调用rule创建自定义规则,并将其保存到全局变量:

def _empty_impl(ctx):
    # 分析阶段此函数被执行
    print("This rule does nothing")
 
empty = rule(implementation = _empty_impl)

然后,规则可以通过load加载到BUILD文件:

load("//empty:empty.bzl", "empty")
 
# 实例化规则
empty(name = "nothing")

规则属性

属性即实例化规则时需要提供的参数,例如srcs、deps。在自定义规则的时候,你可以列出所有属性的名字和Schema:

sum = rule(
    implementation = _impl,
    attrs = {
        # 定义一个整数属性,一个列表属性
        "number": attr.int(default = 1),
        "deps": attr.label_list(),
    },
)

实例化规则的时候,你需要以参数的形式指定属性:

sum(
    name = "my-target",
    deps = [":other-target"],
)
 
sum(
    name = "other-target",
)

如果实例化规则的时候,没有指定某个属性的值(且没指定默认值),规则的实现函数会在ctx.attr中看到一个占位符,此占位符的值取决于属性的类型。

使用default为属性指定默认值,使用 mandatory=True 声明属性必须提供。

默认属性

任何规则自动具有以下属性:deprecation, features, name, tags, testonly, visibility。

任何测试规则具有以下额外属性:args, flaky, local, shard_count, size, timeout。

特殊属性

有两类特殊属性需要注意:

  1. 依赖属性:例如attr.label、attr.label_list,用于声明拥有此属性的目标所依赖的其它目标
  2. 输出属性:例如attr.output、attr.output_list,声明目标的输出文件,较少使用

私有属性

某些情况下,我们会为规则添加具有默认值的属性,同时还想禁止用户修改属性值,这种情况下可以使用私有属性。

私有属性以下划线 _ 开头,必须具有默认值。

目标

实例化规则不会返回值,但是会定义一个新的目标。

规则实现

任何规则都需要提供一个实现函数。提供在分析阶段需要严格执行的逻辑。此函数不能有任何读写行为,仅仅用于注册Action。

实现函数具有唯一性入参 —— 规则上下文,通常将其命名为ctx。通过规则上下文你可以:

  1. 访问规则属性
  2. 获得输入输出文件的handle
  3. 创建Actions
  4. 通过providers向依赖于当前规则的其它规则传递信息

常见的命令

build

bazel  build  //path:object

Debug

bazel build //path:object -c dbg

查看依赖关系

bazel query --notool_deps --noimplicit_deps "deps(//path:object)" --output graph

Clean

bazel clean

 

Guess you like

Origin blog.csdn.net/qq_32378713/article/details/129517227