Bazel Note

本文记录编译工具 bazel 的笔记

Bazel

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

Two files that Bazel recognizes as special:

  • The WORKSPACE file, which identifies the directory and its contents as a Bazel workspace and lives at the root of the project’s directory structure,

  • One or more BUILD files, which tell Bazel how to build different parts of the project. (A directory within the workspace that contains a BUILD file is a package.)

To designate a directory as a Bazel workspace, create an empty file named WORKSPACE in that directory.

Understand the BUILD file

A BUILD file contains several different types of instructions for Bazel. The most important type is the build rule, which tells Bazel how to build the desired outputs, such as executable binaries or libraries. Each instance of a build rule in the BUILD file is called a target and points to a specific set of source files and dependencies. A target can also point to other targets.

lib/BUILD file:

cc_library(
    name = "hello-time",
    srcs = ["hello-time.cc"],
    hdrs = ["hello-time.h"],
    visibility = ["//main:__pkg__"],
)

main/BUILD file:

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",
    ],
)

Notices we make the //lib:hello-time target in lib/BUILD explicitly visible to targets in main/BUILD using the visibility attribute. This is because by default targets are only visible to other targets in the same BUILD file.

build project

bazel build //main:hello-world

The //main: means the location of our BUILD file, // means the location of WORKSPACE file, hello-world is our target name. Output is store is bazel-bin/main/hello-worl

  • cc_library
    cc_library(
      name = "memory_store",
      srcs = glob([
        "memory_store/*.cpp",
      ]),
      hdrs = glob([
        "memory_store/*.hpp",
      ]),
      deps = [
        "@com_github_google_glog//:glog",
      ],
    )
    

see dependency graph

First need install GraphViz and xdot

1
sudo apt update && sudo apt install graphviz xdot
bazel query --notool_deps --noimplicit_deps "deps(//main:hello-world)" \
  --output graph
xdot <(bazel query --notool_deps --noimplicit_deps "deps(//main:hello-world)" \
  --output graph)

Working with external dependencies

Dependencies from these other projects are called external dependencies.

Suppose there are two projects on a system:

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

If project1 wanted to depend on a target, :foo, defined in /home/user/project2/BUILD, it could specify that a repository named project2 could be found at /home/user/project2. Then targets in /home/user/project1/BUILD could depend on @project2//:foo.

Depending on other Bazel projects

If you want to use targets from a second Bazel project, you can use local_repository, git_repository or http_archive to symlink it from the local filesystem, reference a git repository or download it (respectively).

For example, suppose you are working on a project, my-project/, and you want to depend on targets from your coworker’s project, coworkers-project/. Both projects use Bazel, so you can add your coworker’s project as an external dependency and then use any targets your coworker has defined from your own BUILD files. You would add the following to my_project/WORKSPACE:

package(default_visibility = ["//visibility:public"])
local_repository(
    name = "coworkers_project",
    path = "/path/to/coworkers-project",
)

If your coworker has a target //foo:bar, your project can refer to it as @coworkers_project//foo:bar. External project names must be valid workspace names, so (valid) is used to replace (invalid) in the name coworkers_project.

Depending on non-Bazel projects

Rules prefixed with new_, e.g., new_local_repository, allow you to create targets from projects that do not use Bazel.

For example, suppose you are working on a project, my-project/, and you want to depend on your coworker’s project, coworkers-project/. Your coworker’s project uses make to build, but you’d like to depend on one of the .so files it generates. To do so, add the following to my_project/WORKSPACE:

new_local_repository(
    name = "coworkers_project",
    path = "/path/to/coworkers-project",
    build_file = "coworker.BUILD",
)

build_file specifies a BUILD file to overlay on the existing project, for example:

cc_library(
    name = "some-lib",
    srcs = glob(["**"]),
    visibility = ["//visibility:public"],
)

You can then depend on @coworkers_project//:some-lib from your project’s BUILD files.

Depending on external packages

By default, external dependencies are fetched as needed during bazel build. If you would like to prefetch the dependencies needed for a specific set of targets, use bazel fetch. To unconditionally fetch all external dependencies, use bazel sync. As fetched repositories are stored in the output base, fetching happens per workspace.

using with grpc

  • proto_library:

    Recommended code organization:

    • One proto_library rule per .proto file.
    • A file named foo.proto will be in a rule named foo_proto, which is located in the same package.
    • A [language]proto_library that wraps a proto_library named foo_proto should be called foo[language]_proto, and be located in the same package.
  • cc_proto_library: cc_proto_library generates C++ code from .proto files. deps must point to proto_library rules.

proto_library(
  name = "helloworld_proto",
  srcs = ["helloworld.proto"],
)

cc_proto_library(name = "helloworld_cc_proto",
                  deps = [":helloworld_proto"],)
cc_grpc_library(
    name = "helloworld_cc_grpc",
    srcs = [":helloworld_proto"],
    grpc_only = True,
    deps = [":helloworld_cc_proto"],
)
  • cc_grpc_library
     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
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    
    load("@rules_proto//proto:defs.bzl", "proto_library")
    load("//bazel:generate_cc.bzl", "generate_cc")
    load("//bazel:protobuf.bzl", "well_known_proto_libs")
    def cc_grpc_library(
            name,
            srcs,
            deps,
            proto_only = False,
            well_known_protos = False,
            generate_mocks = False,
            use_external = False,
            grpc_only = False,
            **kwargs):
        """Generates C++ grpc classes for services defined in a proto file.
        If grpc_only is True, this rule is compatible with proto_library and
        cc_proto_library native rules such that it expects proto_library target
        as srcs argument and generates only grpc library classes, expecting
        protobuf messages classes library (cc_proto_library target) to be passed in
        deps argument. By default grpc_only is False which makes this rule to behave
        in a backwards-compatible mode (trying to generate both proto and grpc
        classes).
        Assumes the generated classes will be used in cc_api_version = 2.
        Args:
            name (str): Name of rule.
            srcs (list): A single .proto file which contains services definitions,
              or if grpc_only parameter is True, a single proto_library which
              contains services descriptors.
            deps (list): A list of C++ proto_library (or cc_proto_library) which
              provides the compiled code of any message that the services depend on.
            proto_only (bool): If True, create only C++ proto classes library,
              avoid creating C++ grpc classes library (expect it in deps).
              Deprecated, use native cc_proto_library instead. False by default.
            well_known_protos (bool): Should this library additionally depend on
              well known protos. Deprecated, the well known protos should be
              specified as explicit dependencies of the proto_library target
              (passed in srcs parameter) instead. False by default.
            generate_mocks (bool): when True, Google Mock code for client stub is
              generated. False by default.
            use_external (bool): Not used.
            grpc_only (bool): if True, generate only grpc library, expecting
              protobuf messages library (cc_proto_library target) to be passed as
              deps. False by default (will become True by default eventually).
            **kwargs: rest of arguments, e.g., compatible_with and visibility
        """
    
  • grpc_cc_library
     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
    32
    33
    34
    35
    36
    37
    38
    
    def grpc_cc_library(
            name,
            srcs = [],
            public_hdrs = [],
            hdrs = [],
            external_deps = [],
            defines = [],
            deps = [],
            select_deps = None,
            standalone = False,
            language = "C++",
            testonly = False,
            visibility = None,
            alwayslink = 0,
            data = [],
            use_cfstream = False,
            tags = [],
            linkstatic = False):
        """An internal wrapper around cc_library.
        Args:
          name: The name of the library.
          srcs: The source files.
          public_hdrs: The public headers.
          hdrs: The headers.
          external_deps: External depdendencies to be resolved.
          defines: Build defines to use.
          deps: cc_library deps.
          select_deps: deps included conditionally.
          standalone: Unused.
          language: The language of the library, e.g. C, C++.
          testonly: Whether the target is for tests only.
          visibility: The visibility of the target.
          alwayslink: Whether to enable alwayslink on the cc_library.
          data: Data dependencies.
          use_cfstream: Whether to use cfstream.
          tags: Tags to apply to the rule.
          linkstatic: Whether to enable linkstatic on the cc_library.
        """
    
  • cc_library
    cc_library(
        name = "some_lib",
        srcs = ["some_lib.cc"],
        hdrs = ["some_lib.h"],
        copts = ["-Ithird_party/some_lib"], # 引入第三方头文件
    )
    

    依赖预编译的库

    cc_library(
      name = "mylib",
      srcs = ["mylib.so"],
      hdrs = ["mylib.h"],
    )
    
  • 利用 bazel 来使用 grpc 很方便,只需要在 WORKSPACE 文件添加如下:
workspace(name = "grpc_test")
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")

http_archive(
    name = "rules_proto",
    sha256 = "66bfdf8782796239d3875d37e7de19b1d94301e8972b3cbd2446b332429b4df1",
    strip_prefix = "rules_proto-4.0.0",
    urls = [
        "https://mirror.bazel.build/github.com/bazelbuild/rules_proto/archive/refs/tags/4.0.0.tar.gz",
        "https://github.com/bazelbuild/rules_proto/archive/refs/tags/4.0.0.tar.gz",
    ],
)
load("@rules_proto//proto:repositories.bzl", "rules_proto_dependencies", "rules_proto_toolchains")
rules_proto_dependencies()
rules_proto_toolchains()


local_repository(
    name = "com_github_grpc_grpc",
    path = "/home/reas/Software/grpc"
)


load("@com_github_grpc_grpc//bazel:grpc_deps.bzl", "grpc_deps")
grpc_deps()
load("@com_github_grpc_grpc//bazel:grpc_extra_deps.bzl", "grpc_extra_deps")
grpc_extra_deps()

BUILD 文件:

package(default_visibility = ["//visibility:public"])
load("@com_github_grpc_grpc//bazel:grpc_build_system.bzl", "grpc_proto_library")

grpc_proto_library(
  name = "helloworld",
  srcs = ["helloworld.proto"],
)


cc_binary(
    name = "greeter_client",
    srcs = ["greeter_client.cc"],
    defines = ["BAZEL_BUILD"],
    deps = [
        "@com_github_grpc_grpc//:grpc++",
        ":helloworld",
    ],
)

include third-libs

cc_library(
    name = "some_lib",
    srcs = ["some_lib.cc"],
    hdrs = ["some_lib.h"],
    copts = ["-Ithird_party/some_lib"],
)

bazel 命令

  • run bazel run //cpp:reas -- --head 其中的 -- 是必须要的,代表后面的为程序执行传入的参数。

bazelrc

.bazelrc 可以直接放到项目的根目录下。

build --cxxopt='--std=c++17'
build --test_tmpdir=/tmp/foo --verbose_failures
build --test_tmpdir=/tmp/bar
build --color=yes

编译 cython 代码

BUILD 文件中加入

package(default_visibility = ["//visibility:public"])
load("@rules_python//python:defs.bzl", "py_library")
load("@rules_cc//cc:defs.bzl", "cc_binary", "cc_library", "cc_proto_library", "cc_test")
load("@com_github_grpc_grpc//bazel:cc_grpc_library.bzl", "cc_grpc_library")
load("@com_github_grpc_grpc//bazel:cython_library.bzl", "pyx_library")

cc_library (
  name = "c_driver",
  srcs = ["driver/driver.cpp",],
  hdrs = ["driver/driver.hpp"],
  deps = [
    "@com_github_grpc_grpc//:grpc++",
    "@com_github_grpc_grpc//:grpc++_reflection",
    "@com_github_google_glog//:glog",
    "//grpc:task_grpc",
    "//cpp:utils",
  ],
)

pyx_library (
  name = "py_driver",
  srcs = glob([
    "driver/*.pxd",
    "driver/*.pyx",
  ]),
  deps = [
    ":c_driver",
 ],
)

更改 bazel output 目录

bazel --output_base=/tmp/bazel/output build ://main

Problems

  1. name 'git_repository' is not defined
load("@bazel_tools//tools/build_defs/repo:git.bzl", "git_repository")
  1. ERROR: /home/reas/Workspace/example/foo/BUILD:1:10: Linking of rule '//foo:foo' failed (Exit 1): gcc failed: error executing command /usr/bin/gcc @bazel-out/k8-fastbuild/bin/foo/foo-2.params

升级 bazel 至 4 以上

  1. src/greeter_client.cc:24:10: fatal error: helloworld.grpc.pb.h: No such file or directory #include "helloworld.grpc.pb.h" 出现不能找到 .h 文件时,需要注意在代码中 #include "" 的地址需要是相对于 \\ 项目地址

  2. bring non-source tree absolute paths into your workspace It suggests to set up a local repository: WORKSPACE:

new_local_repository(
    name = "llvm",
    path = "/usr/lib/llvm-6.0/include",
    build_file_content = """
package(default_visibility = ["//visibility:public"])
cc_library(
    name = "headers",
    hdrs = glob(["**/*.h"])
)
"""
)

BUILD:

cc_binary(
    name = "my-llvm-project",
    srcs = glob(["*.cc"]),
    copts = ["-Iexternal/llvm"],
    deps = ["@llvm//:headers"]
)

编译定义宏

bazel build --cxxopt=-DNDEBUG

以上定义了宏 NDEBUG, 亦可在 .bazelrc 文件中加入

build --cxxopt=-DNDEBUG

Reference

  1. specifying-targets-to-build
  2. rules_proto
  3. native rules
updatedupdated2022-10-172022-10-17