之前总结了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_library将hello-greet构建为library供hello-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-time和hello-greet库文件,hello-time源代码文件在lib目录下
对于lib/BUILD文件,构建hello-time库,要设置visibility属性,这样才能被hello-world引用到, visibility属性默认是private,具体的规则//表示向外层目录找到的第一个WORKSPACE文件所在的目录,然后main表示main文件夹,//main:__pkg__表示的是main文件夹下的包都可以看到这条rule,rule表示的就是整个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-name是rule 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_repository的WORKSPACE的文件的,然后就可以通过@coworkers_project//:some-lib的方式引用some-lib库
    cc_library(
        name = "some-lib",
        srcs = glob(["**"]),
        visibility = ["//visibility:public"],
    )
构建远程项目
构建远程项目的时候一般需要先加载bazel_tools中获取远程资源的方法,比如http_archive和git_repository,分别通过http的方式获取和git的方式获取
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", )git_repository和new_git_repository方式
同http_archive方式,只是有些参数不太相同缓存
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 │ ├── ...... └── WORKSPACEgoogletest
其中
googletest本身就是bazel构建的项目,所以直接将googletest项目源码从git上面clone下来,放到thirdparty中,如果在common或者main中需要引用googletest中的test_main库,首先需要在common或者main的WORKSPACE文件中将thirdparty定义为local_repository,而项目的path参数需要根据实际的情况来填写,可以填写相对路径,然后就可以在main或者common中引用googletest中的库了上面是单独饮用googletest库的操作,但是对于模块化来说,整个
thirdparty应当看作是一个整体,所以可以将thirdparty下的项目先在thirdparty文件夹下添加WORKSPACE文件,将之定义为模块,然后在common和main中,将整个thirdparty定义为local_repository,而引用thirdparty下的某个其他库,就可以以相对路径的形式引用这样引用之后,在hello-world源代码中,可以直接饮用gtest头文件
#include "gtest/gtest.h"boost
boost和googletest不同,因为boost本身不是bazel构建的项目,如果我们想要和引用gtest一样引用boost,就需要我们自己编写编译boost的BUILD文件
编写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文件里面的一些编写规则
然后就可以和引入googletest中的库类似的方式引入boost中的库了
备注
其实可以将整个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