关于谷歌开源编译工具bazel的使用

bazel

优点

  • 分布式构建工具,增量编译速度快,支持remote方式,命令简单易用
  • 适用于单一代码仓库,所有的代码都在一个文件夹树里面(由WORKSPACE指定)
  • 每一个目标都可以指定可见性,可以指定什么目标是其他项目组可以用的
    • 比如内部测试的类可以被隐藏
  • 漏写了依赖会提示找不到头文件,可以保证所有代码中用到的头文件对应的目标都在当前目标的依赖列表里面
  • bazel支持依赖远端代码库,可以指定依赖GitHub上的代码本地进行调用
    • 由于网络问题可能会造成超时(默认是500秒),则可以先手动的将代码下载到本地,然后自己上传到自己本地的代码库中,或者是打成包,调整workspace的依赖路径
  • 构建系统背后是一个集群,用来跑编译和测试。并行编译
  • 测试也可以并行跑。可以指定每一个测试可以拆分成几个并行的目标来跑
  • 测试还可以反复跑,只需要加上“–runs_per_test”
  • Bazel在使用100k+源文件处理构建时仍然保持良好的性能表现
  • 可以扩展Bazel以支持选择的语言
  • 适合在多平台上部署的项目、有大量测试的工程、有庞大代码库的项目
  • 可以在工程中直接添加谷歌的cpplint(代码风格检查工具)(只需要加入相关规则,就可直接从git仓获取加入项目)

缺点

  • 比较繁琐,入门较慢
  • 每个目标(target)都要写上每一个依赖的目标,目标名称很长,包含了完整路径
  • 所有的目标都在WORKSPACE指定的文件夹树里面
  • 有自动添加依赖的工具,但是没有开源
  • bazel是个“流氓”工具,即一个组用了bazel,其他组为了协同工作,也必须用bazel,否则作为一个项目整体没法管理
    • 例如,如果项目想打包成一个整体,一部分用bazel编译;一部分用makefile编译,则无法生成一个完整的动态库
    • 如果能够接受多个动态库打包,则可以一部分用bazel编译;一部分用makefile编译
  • 支持IDE,均跨平台**:
    • VSCode(开源),插件形式,相关资料仅有插件的介绍bazelbuild/vscode-bazel,搭建有问题
    • Clion(收费,可PJ),插件形式,相关资料教程比较多,只支持java和python

注意

  • 必须要有WORKSAPCE
    • 在一开始的时候就定好WOEKSPACE的路径
    • 会影响到BUILD文件里的路径
    • WORKSPACE文件所在的目录为该工程的根目录,该文件可空或者可能包含对构建输出所需的外部依赖项的引用
    • 根目录下可以有多个子目录和子BUILD
    • 具体编写规则见Workspace Rules
  • 源文件/proto文件的编译都需要BUILD文件
    • BUILD中定义了编译的相关依赖关系

使用情况

环境:Ubuntu16.04 Ubuntu18.04

  • 安装很方便,几分钟就能搞定

  • 官方有example和文档(很多),example都很简单,实际应用复杂得多

    • 主要涉及WORKSPACE、BUILD文件的编写规则,需要深究
  • 然后,自己基于标准库"iostream",写了实例也能编译通过

    • 编译命令很简单

    • 在特定目录生成编译缓存

    • 支持bazel clean,清除之前编译缓存及文件夹

    • 没有找到类似cmake的 set(CMAKE_PREFIX_PATH /opt/****) 选项

    • 所有与项目相关的头文件都必须写完整的绝对路径

      • 不仅仅是应用程序需要这么写
      • 所有头文件都必须这么写
    • 通过在WORKSPACE中引用外部依赖的选项new_local_repository,但还是无法直接使用相对路径

  • 若要使用bazel构建工具,则:

    1. 将项目文件夹也包含到WORKSPACE的目录树,类似百度apollo的目录结构,在根目录"apollo/"下创建WORKSPACE
    2. 全部使用绝对路径

关于缓存(远程,摘抄)

Remote Caching

  • 开发人员和持续集成(CI)系统可以使用远程缓存来共享构建输出
    • 在一台机器上编译一次就会被缓存在远程服务器上
    • 然后另外的同事就不需要重新编译,直接使用服务器上的缓存即可
    • 提高构建速度
  • bazel将一次BUILD拆分为不连续的步骤,那么称每个步骤为action
    • 每个action都有输入名称/输出名称/命令行/环境变量
    • 每个action都被明确声明了必须的输入和预期的输出
    • 可以为编译缓存构建一个远程服务器
    • 这些缓存由文件名列表和他们内容的hash值
    • 可以重用其他用户的编译缓存,而不用自己编译

Windows下安装

用这种方法装是最快的

打开powershell(管理员)

#安装choco
Set-ExecutionPolicy Bypass -Scope Process -Force; iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1'))
#安装bazel
choco install bazel
bazel version

遇到一个坑,要求msys2(一个shell工具),但是装完后用不了系统的环境变量

解决办法:新建一个环境变量name:“MSYS2_PATH_TYPE=inherit”(随便取) value:“inherit”

Ubuntu下安装

sudo apt install curl
curl https://bazel.build/bazel-release.pub.gpg | sudo apt-key add -
echo "deb [arch=amd64] https://storage.googleapis.com/bazel-apt stable jdk1.8" | sudo tee /etc/apt/sources.list.d/bazel.list
#Step 1: Install the JDK
#Install JDK 8:
sudo apt-get install openjdk-8-jdk
#On Ubuntu 14.04 LTS you must use a PPA:
sudo add-apt-repository ppa:webupd8team/java
sudo apt-get update && sudo apt-get install oracle-java8-installer

#Step 2: Add Bazel distribution URI as a package source
#Note: This is a one-time setup step.
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 -
#If you want to install the testing version of Bazel, replace stable with testing.

#Step 3: Install and update Bazel
sudo apt-get update && sudo apt-get install bazel
#Once installed, you can upgrade to a newer version of Bazel with the following command:
sudo apt-get install --only-upgrade bazel
bazel version
export LD_LIBRARY_PATH=/usr/bin

BUILD编写

includes不需要加-I,直接是相对路径;copts只在当前目标里有效

如果相对于主目录是./sub/subsub/a.h,sub/BUILD里,includes只需要写[‘subsub’], 如果是copts则需要写[’-Isub/subsub/’]

cc_library(
    name = "some_lib",
    srcs = ["some_lib.cc"],
    hdrs = ["include/some_lib.h"],
    copts = ["-Ilegacy/some_lib/include"],
)
# 通用方法,定义的值会作用到下面的每个子rule中。default_visibility指定了这个包的默认可见规则。可见的情况下才能被其他package调用。
package(default_visibility = ["//visibility:public"],)
# c++库文件,name指定了编译为库文件后的文件名,srcs和hdrs指定源文件和头文件,deps指定需要依赖的其他文件
cc_library(
    name = "gradient_checker",
    srcs = ["framework/gradient_checker.cc"],
    hdrs = ["framework/gradient_checker.h"],
    deps = [
        ":cc_ops",
    ],
)

# c++测试文件
tf_cc_test(
    name = "gradients_array_grad_test",
    srcs = ["gradients/array_grad_test.cc"],
    deps = [
        ":array_grad",
    ],
)

# c++编译目标文件,为一个二进制可执行文件。name必须唯一,srcs指定了源文件,linkopts指定了链接规则,deps指定了依赖文件
tf_cc_binary(
    name = "tutorials_example_trainer",
    srcs = ["tutorials/example_trainer.cc"],
    copts = tf_copts(),
    linkopts = select({
        "//tensorflow:windows": [],
        "//tensorflow:windows_msvc": [],
        "//tensorflow:darwin": [
            "-lm",
            "-lpthread",
        ],
        "//tensorflow:ios": [
            "-lm",
            "-lpthread",
        ],
        "//conditions:default": [
            "-lm",
            "-lpthread",
            "-lrt",
        ],
    }),
    deps = [
        ":cc_ops",
        "//tensorflow/core:core_cpu",
    ],
)

# 为多个编译目标target指定一个名字,glob是一个帮助函数,指定了目录中哪些文件会include,哪些会exclude。visibility指定了target的可见性,也就是可以被哪些package调用
filegroup(
    name = "all_files",
    srcs = glob(
        ["**/*"],
        exclude = [
            "**/METADATA",
            "**/OWNERS",
        ],
    ),
    visibility = ["//tensorflow:__subpackages__"],
)

编译

#bazel build 包名:任务名
$ bazel build //src:hello-world

生成动态链接库

cc_binary(
name = "libmylib.so", #mylib是头文件的名字
srcs = ["mylib.cpp",
    "mylib.h", #头文件和源文件,必须都有
],
deps = ["//XX:XX", #依赖,注意要把头文件和源文件中include的头文件所在的BUILD包都加进去
],
copts = ["-g"#编译时候的命令
],
linkopts = ["-lstdc++", #链接时候的命令
],
linkshared = True,
linkstatic = True,
)
$ bazel build //XX:libmylib.so

在libmylib.so目录下输入“ldd libmylib.so”,可查看libmylib.so正常被调用时需要在哪些位置找到哪些更底层的动态链接库文件,可以根据这个目录在新环境里安装相应的依赖

$ ldd libmylib.so

你可能感兴趣的:(linux)