bazel实践

之前总结了c++开发的一些基础,现在总结一下使用bazel做c++项目构建工具实践中的一些注意点。

helloworld

官方提供了几个helloworld的构建例子,这里简单过一下项目结构,
git clone https://github.com/bazelbuild/examples,写好BUILD文件后,执行bazel对应的命令即可完成构建

    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

从这几个helloworld的例子里,基本就能学会构建简单的项目,不过这几个例子里没有讲到引用第三方库的情况

1.单构建目标

srcs说明源文件的相对路径,cc_binary表示构建为可执行二进制文件

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

2.多构建目标

deps表示hello-world依赖hello-greet库,cc_libraryhello-greet构建为libraryhello-world链接,hdrs指明头文件,因为在同一目录下,所以直接写文件名就可以找得到对应的文件

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

3.跨目录构建

最终构建的可执行文件为hello-world,需要链接hello-timehello-greet库文件,hello-time源代码文件在lib目录下
对于lib/BUILD文件,构建hello-time库,要设置visibility属性,这样才能被hello-world引用到, visibility属性默认是private,具体的规则//表示向外层目录找到的第一个WORKSPACE文件所在的目录,然后main表示main文件夹,//main:__pkg__表示的是main文件夹下的包都可以看到这条rulerule表示的就是整个cc_library表示的内容,也就是可以引用hello-time

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

对于main/BUILD文件,不同的地方就是deps里增加了引用hello-time的内容,//lib:hello-time表示lib文件夹下的hello-time库,如果hello-time的构建规则里没有visibility那一项,这里引用就会报错

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

4.引用目标库的路径规则

    //path/to/package:target-name 

1.如果target-namerule target,path/to/package就是到BUILD文件目录的路径,target-name就是BUILD文件里定义的需要编译的库
2.如果target-name包含路径的target,那么path/to/package就是到BUILD的根WORKSPACE文件的路径,target-name路径最后一项表示构建目标
3.当在项目根目录内执行构建的时候,可以直接使用//表示根目录
4.当在BUILD文件目录内执行的时候,可以直接使用:target-name表示构建目标

5.构建外部依赖

bazel项目一般来说一个WORKDPACE文件就标志一个独立的项目,也可以说是一个独立的模块,当要引用其他项目中的库的时候,有几种情况

构建本地bazel项目

引用coworkers_project中的库,在my_project/WORKSPACE中定义local_repository, coworkers_project中的项目//foo:bar,在my_project的BUILD中可以通过@coworkers_project//foo:bar方式引用

    local_repository(
        name = "coworkers_project",
        path = "/path/to/coworkers-project",
    )
构建本地非bazel项目

my_project/WORKSPACE中使用new_local_repository创建本地非bazel构建的项目,另外需要自己写一个BUILD文件给coworkers_project定义编译的规则

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

coworker.BUILD文件中,就是构建coworker库的相关的内容。这个BUILD文件的路径,是相对于写new_local_repositoryWORKSPACE的文件的,然后就可以通过@coworkers_project//:some-lib的方式引用some-lib

    cc_library(
        name = "some-lib",
        srcs = glob(["**"]),
        visibility = ["//visibility:public"],
    )
构建远程项目

构建远程项目的时候一般需要先加载bazel_tools中获取远程资源的方法,比如http_archivegit_repository,分别通过http的方式获取和git的方式获取

  1. http_archive方式
    其实和构建本地项目类似

     load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
    
     http_archive(
           name = "my_ssl",
           urls = ["http://example.com/openssl.zip"],
           sha256 = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
           build_file = "@//:openssl.BUILD",
       )
    
  2. git_repositorynew_git_repository方式
    http_archive方式,只是有些参数不太相同

  3. 缓存

     ls $(bazel info output_base)/external
    

    可以查看通过引用外部依赖的方式加载的项目,比如上面说的几种方式

    最佳实践

    这里通过构建具体项目来熟悉bazel的用法,这里主要写两个,一个是bazel构建项目,一个是非bazel构建项目
    项目结构

     dir
     └── project
         ├──WORKSPACE
         ├──main
         │  ├── helloworld
         │  │   ├── BUILD
         │  │   └── hello-world.cc
         │  └── WORKSPACE
         ├──common
         │  ├── libs
         │  │   ├── BUILD
         │  │   ├── hello-greet.cc
         │  │   └── hello-greet.h
         │  └── WORKSPACE
         └──thirdparty
            ├── googletest
            │   ├── BUILD
            │   ├── ......
            ├── boost
            │   ├── boost.BUILD
            │   ├── boost.bzl
            │   ├── ......
            └── WORKSPACE
    

    googletest

  4. 其中googletest本身就是bazel构建的项目,所以直接将googletest项目源码从git上面clone下来,放到thirdparty中,如果在common或者main中需要引用googletest中的test_main库,首先需要在common或者mainWORKSPACE文件中将thirdparty定义为local_repository,而项目的path参数需要根据实际的情况来填写,可以填写相对路径,然后就可以在main或者common中引用googletest中的库了

  5. 上面是单独饮用googletest库的操作,但是对于模块化来说,整个thirdparty应当看作是一个整体,所以可以将thirdparty下的项目先在thirdparty文件夹下添加WORKSPACE文件,将之定义为模块,然后在commonmain中,将整个thirdparty定义为local_repository,而引用thirdparty下的某个其他库,就可以以相对路径的形式引用

  6. 这样引用之后,在hello-world源代码中,可以直接饮用gtest头文件

      #include "gtest/gtest.h"
    

    boost

  7. boost和googletest不同,因为boost本身不是bazel构建的项目,如果我们想要和引用gtest一样引用boost,就需要我们自己编写编译boost的BUILD文件

  8. 编写BUILD的文件的时候,可以自定义处理函数,将这些自定义函数放在.bzl的文件中,然后通过类似于引用bazel_tool库的方式引用里面的方法

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

    这里如果自己定义了一些处理方法,然后放在boost.bzl文件中,那么就可以以下面的方式引入

      load("@workspace_name//path/to/boost:boost.bzl", "some-function")
    

    通过这种方式可以简化BUILD文件里面的一些编写规则

  9. 然后就可以和引入googletest中的库类似的方式引入boost中的库了

    备注

  10. 其实可以将整个thirdpary中的库都以http_archive的方式或者git_repository的方式代替local_repository的方式加载,将源码下载在本地后加载的方式和在线获取外部依赖的方式相比,可能会适用于有些不能在线获取资源的情况,而且将第三方资源和本地代码放在一起会直观一些

    参考资料

    [1].Introduction to Bazel: Building a C++ Project
    [2].c/cpp rules
    [3].common.visibility
    [4].Working with external dependencies
    [5].http_archive
    [6].git_repository