Linux开发环境学习--bazel+gtest在c++项目中的使用

前言

在进行Linux下开发的时候,需要对整个开发环境有一些了解。主要有以下的工具:

  • 编辑器(vim或emacs)
  • 构建工具(bazel或cmake)
  • 版本控制工具(Git)
  • 调试工具(gdb)

这是在开发过程中需要掌握的四大基础套件,如果将开发内容进一步定位到**C++**时,那么还需要额外掌握一些依赖库的用法,比如:

  • 命令行参数解析(gflags)
  • 单元测试(gtest)
  • Log信息(glog)

发现了啥?是不是前面都带个“g”,√,这些基础库全部来自Google。
练掌握Google全家桶的用法可以避免造轮子,减少引入Bug的机会。

就我本人而言,主要选择vim+bazel进行代码编写和构建,网上关于vim的教程很多,这里不再赘述,接下来的内容主要是“如何使用bazel进行项目的构建”(适用于刚接触bazel的同学),以及用一个Hello示例进行实践。

bazel

关于bazel的使用,网上已经有了很多翻译的教程。比如:
官方教程:https://docs.bazel.build/versions/2.0.0/tutorial/cpp.html#build-with-bazel

官方教程是英文版的,也有很多人进行教程的翻译,质量也很不错,如下:
https://blog.csdn.net/elaine_bao/article/details/78668657
https://blog.gmem.cc/bazel-study-note

在熟悉bazel的时候,建议下载官方examples,按照官网示例进行手动编译,不过遗憾的是该examples只涉及到了本地代码的构建,没有涉及如何依赖第三方库(如gtest)的构建。

这里不打算对基础内容进行复习(比如package,target的概念,相关内容请请参考以上的博客,我再写就有点造轮子了),只对重点概念进行摘录。

  1. WORKSPACE文件用于引入其他文件系统或网络的第三方库(因此在不依赖第三方库构建时,WORKSPACE基本没用,可以看到examples中的WORKSPACE是个空文件)。
  2. BUILD文件用于构建时,定义编译、链接选项

依赖第三方库的构建

官方教程把依赖第三方库的构建放到了另一页面中,

官方教程: https://docs.bazel.build/versions/2.0.0/external.html
翻译过来的教程 : https://www.betaflare.com/3714.html

简单总结一下:

支持依赖的第三方库类型:

  1. Bazel编译的项目
  2. 非Bazel编译的项目(如make)
  3. 外部包(从Maven仓库进行获取)

在编写WORKSPACE文件的时候,可以通过三种方式进行依赖库的获取,

当第三方库是Bazel编译的项目时,可以用

  1. local_repository:本地
  2. git_repository:git仓库
  3. http_archive:网络下载

当第三方库是非Bazel 编译的项目时,使用

  1. new_local_repository:本地
  2. new_git_repository:git仓库
  3. new_http_archive:网络下载

以brpc框架的WORKSPACE文件分析:

workspace(name = "com_github_brpc_brpc")

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

skylib_version = "0.8.0"
http_archive(
    name = "bazel_skylib",
    type = "tar.gz",
    url = "https://github.com/bazelbuild/bazel-skylib/releases/download/{}/b    azel-skylib.{}.tar.gz".format (skylib_version, skylib_version),
    sha256 = "2ef429f5d7ce7111263289644d233707dba35e39696377ebab8b0bc701f781    8e",
)
http_archive(
    name = "com_google_protobuf",
    strip_prefix = "protobuf-3.6.1.3",
    sha256 = "9510dd2afc29e7245e9e884336f848c8a6600a14ae726adb6befdb4f786f0be2    ",
    type = "zip",
 	url = "https://github.com/protocolbuffers/protobuf/archive/v3.6.1.3.zip",
)
......//以下均为http_archive获取的方式,这里就不粘了。
  1. workspace(name = “”) ,设置该项目的名称
  2. load加载http.bzl工具,用于以http_archive的方式进行依赖的下载,其源代码如下:https://github.com/bazelbuild/bazel/blob/master/tools/build_defs/repo/http.bzl,(在每次编译的时候,bazel会将用到的工具fetch回来,缓存在本地
  3. 使用http_archive的方式进行下载,

基于Bazel的Hello程序

这里我们写一个简易的程序进行实践,分成两个模块,并使用第三方库gtest对模块进行测试。

目录框架

likewinddeMacBook-Pro:bazel_begin likewind$ tree 
.
├── BUILD
├── WORKSPACE
├── build.sh
├── hello
│   ├── BUILD
│   ├── hello.cc
│   ├── hello.h
│   └── hello_test.cc
├── main
│   ├── BUILD
│   └── main.cc
├── run.sh
└── utest.sh

2 directories, 11 files

在bazel_begin目录下,WORKSAPCE文件的内容为:

#WORKSPACE
load("@bazel_tools//tools/build_defs/repo:git.bzl", "git_repository" )

local_repository(
    name = "gtest",
    path = "/Users/likewind/lib/googletest"
)

因为我们使用gtest框架对hello模块中hello.cc的函数进行了单元测试,所以在WORKSPACE中需要引入gtest,引入的方式为“local_repository”,即提前将gtest项目clone到本地,再进行引用。

在该目录下有两个模块,一个是hello模块,其中hello.cc只有一个函数Hello,当string参数不为空时,输出“Hello, ”字样。

//hello.cc
#include "hello.h"

bool Hello(std::string& name) {
    if(name.empty())
        return false;
    std::cout<<"Hello, "<<name<<std::endl;
    return true;
}

另外在该目录下,hello_test.cc利用gtest框架,对上述的Hello函数进行了测试,其内容如下:

//hello_test.cc
#include "gtest/gtest.h"

#include "hello.h"

TEST(TestHello, HelloMethod) {
    std::string empty_name;
    
    EXPECT_EQ(false, Hello(empty_name));

    std::string name("likewind1993"); 

    EXPECT_EQ(true, Hello(name));
}

hello模块的BUILD文件内容

load("@rules_cc//cc:defs.bzl", "cc_library", "cc_test")
#指定该包对其他包可见
package(default_visibility = ["//visibility:public"])

cc_library(
    name = "hello",
    hdrs = ["hello.h"],
    srcs = ["hello.cc"],
)
cc_test(
    name = "hello_test",
    srcs = ["hello_test.cc"],
    deps = [ 
        ":hello",
        "@gtest//:gtest",
        "@gtest//:gtest_main"    
    ],  
)

该package编译了两个target,一个是cc_library(hello), 一个是cc_test(hello_test)。其中,因为Hello函数会在main模块里用到,因此设置该包对其他包可见,否则,main在引用的时候会报错。
在main模块中,main.cc的内容如下:

//main.cc
#include "hello/hello.h"

int main() {
    std::string name("likewind1993");

    Hello(name);

    return 0;
}

main模块的BUILD文件如下:

load("@rules_cc//cc:defs.bzl", "cc_binary")

cc_binary(
    name = "main",
    srcs = [ 
        "main.cc", 
    ],  
    #依赖hello模块中的hello library.
    deps = [ 
        "//hello:hello", 
    ],  
)

最后,我们在含有WORKSPACE文件的根目录下定义了三个脚本文件,分别是build.sh, run.sh,以及用於单元测试的utest.sh

这样在执行相关命令的时候,我们只需要运行脚本文件即可,不需要每次都输入长长的命令以及参数, 其内容分别如下:

#build.sh
#!/bin/bash

bazel build -s ...
#run.sh
!/bin/bash 

./bazel-bin/main/main
#utest.sh
#!/bin/bash

bazel test ...

bazel build …与bazel test …

里面有个命令很关键“bazel build …”,如果你看了官方教程,以及各个讲bazel如何使用的博客,你会发现很少有人会提到的这个“简洁到极致”的命令。

其中"…"表示编译该项目中的所有模块,包括依赖项目

举个简单例子,如果你运行过官网examples示例。

在官方教程以及翻译的各种教程中,都会教你这样分模块编译:

在这里插入图片描述
即,

bazel build 模块名

但其实用“bazel build …”会更加简洁方便,尤其是当你的工程项目很大而你又不想每次编译的时候都输一次模块名。

下面是使用该命令编译官方教程中cpp-tutorial中的stag3:

likewinddeMacBook-Pro:stage3 likewind$ pwd
/Users/likewind/examples/examples/cpp-tutorial/stage3
likewinddeMacBook-Pro:stage3 likewind$ ls
README.md	WORKSPACE	lib		main
likewinddeMacBook-Pro:stage3 likewind$ bazel build ...
Starting local Bazel server and connecting to it...
INFO: Writing tracer profile to '/private/var/tmp/_bazel_likewind/45be0d302e0f726ba3585f382b6c2dd2/command.profile.gz'
INFO: Analyzed 3 targets (15 packages loaded, 115 targets configured).
INFO: Found 3 targets...
INFO: Deleting stale sandbox base /private/var/tmp/_bazel_likewind/45be0d302e0f726ba3585f382b6c2dd2/sandbox
INFO: Elapsed time: 5.948s, Critical Path: 0.02s
INFO: 0 processes.
INFO: Build completed successfully, 1 total action
likewinddeMacBook-Pro:stage3 likewind$ ./bazel-bin/main/hello-world
Hello world
Sun Apr  5 11:01:32 2020
likewinddeMacBook-Pro:stage3 likewind$ 

Bigo! 编译成功!

需要注意的一点,在使用这个命令的时候,会编译所有模块,包括依赖项目(比如上文中提到的gtest框架),因此如果你只是下载了gtest到本地(使用download zip方式),在使用bazel build …编译bazel_begin的时候可能会报错,报错的原因可能千奇百怪,比如:

  • 提示编译gtest错误
  • 提示Label ‘//tools/python:private/defs.bzl’ is invalid…

不要太惊讶。

遇到这种情况,有几个建议:

  1. 使用git clone --recursive https://github.com/google/googletest.git 进行相关项目的下载,其中“–recursive”选项会把该项目依赖到的东西进行下载。(download zip只会下载当前项目文件,不会下载依赖项
  2. 在该项目中,进行bazel build …编译,预先将该项目进行编译(这步bazel会将编译该项目用到的tools预先缓存,在编本地项目的时候不用再下载)

与build命令类似,运行测试用例也以使用…, 如下

bazel test ...

上面这个命令会运行该项目中所有编译好的测试用例,并给出测试结果

好了,万事俱备,cd到bazel_begin的目录下,开始运行我们的脚本文件

likewinddeMacBook-Pro:bazel_begin likewind$ ls
WORKSPACE	build.sh	hello		main		run.sh		utest.sh
likewinddeMacBook-Pro:bazel_begin likewind$ ./build.sh 
INFO: Writing tracer profile to '/private/var/tmp/_bazel_likewind/ba185afea731627d24b8431d0b49202f/command.profile.gz'
INFO: Analyzed 3 targets (0 packages loaded, 0 targets configured).
INFO: Found 3 targets...
INFO: Elapsed time: 0.079s, Critical Path: 0.00s
INFO: 0 processes.
INFO: Build completed successfully, 1 total action
likewinddeMacBook-Pro:bazel_begin likewind$ ./run.sh 
Hello, likewind1993
likewinddeMacBook-Pro:bazel_begin likewind$ ./utest.sh 
INFO: Writing tracer profile to '/private/var/tmp/_bazel_likewind/ba185afea731627d24b8431d0b49202f/command.profile.gz'
INFO: Analyzed 3 targets (0 packages loaded, 0 targets configured).
INFO: Found 2 targets and 1 test target...
INFO: Elapsed time: 0.064s, Critical Path: 0.00s
INFO: 0 processes.
INFO: Build completed successfully, 1 total action
//hello:hello_test                                              (cached) PASSED in 0.1s

Executed 0 out of 1 test: 1 test passes.
INFO: Build completed successfully, 1 total action
likewinddeMacBook-Pro:bazel_begin likewind$ 

成功!

另外,如果在你电脑上的输出和我的不一样的话,那么可能是我的bazel提前build过,对用到的编译脚本进行了缓存,不过并不会有太大的问题。

整个项目文件我放到了GitHub上,
地址:https://github.com/likewind1993/bazel_begin
如果有帮助到你,请点个star。

后记

磨磨蹭蹭了快一个月,终于把这个环境给整明白了,其实只要静下心来,仔细的去搜build中报的每个错误,一两个晚上就能搞定。无奈工作太忙了,不过还好,抛的坑总算补了一半,万事开头难,环境都搭好了。接下来可以把重心放在写代码上啦!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章