Linux开发环境学习--bazel+gtest在c++项目中的使用
2020年03月16日09:36:08未完成…前言Linux开发环境包括编辑器(vim),构建工具(bazel),版本控制(Git),调试工具(gdb)。bazelhttps://blog.csdn.net/elaine_bao/article/details/78668657这里是较为简单的教程,另外Github上有对应的examples https://github.com/ba...
前言
在进行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的概念,相关内容请请参考以上的博客,我再写就有点造轮子了),只对重点概念进行摘录。
注
- WORKSPACE文件用于引入其他文件系统或网络的第三方库(因此在不依赖第三方库构建时,WORKSPACE基本没用,可以看到examples中的WORKSPACE是个空文件)。
- BUILD文件用于构建时,定义编译、链接选项。
依赖第三方库的构建
官方教程把依赖第三方库的构建放到了另一页面中,
官方教程: https://docs.bazel.build/versions/2.0.0/external.html
翻译过来的教程 : https://www.betaflare.com/3714.html
简单总结一下:
支持依赖的第三方库类型:
- Bazel编译的项目
- 非Bazel编译的项目(如make)
- 外部包(从Maven仓库进行获取)
在编写WORKSPACE文件的时候,可以通过三种方式进行依赖库的获取,
当第三方库是Bazel编译的项目时,可以用
- local_repository:本地
- git_repository:git仓库
- http_archive:网络下载
当第三方库是非Bazel 编译的项目时,使用
- new_local_repository:本地
- new_git_repository:git仓库
- 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获取的方式,这里就不粘了。
- workspace(name = “”) ,设置该项目的名称
- load加载http.bzl工具,用于以http_archive的方式进行依赖的下载,其源代码如下:https://github.com/bazelbuild/bazel/blob/master/tools/build_defs/repo/http.bzl,(在每次编译的时候,bazel会将用到的工具fetch回来,缓存在本地)
- 使用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…
不要太惊讶。
遇到这种情况,有几个建议:
- 使用git clone --recursive https://github.com/google/googletest.git 进行相关项目的下载,其中“–recursive”选项会把该项目依赖到的东西进行下载。(download zip只会下载当前项目文件,不会下载依赖项)
- 在该项目中,进行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中报的每个错误,一两个晚上就能搞定。无奈工作太忙了,不过还好,抛的坑总算补了一半,万事开头难,环境都搭好了。接下来可以把重心放在写代码上啦!
更多推荐
所有评论(0)