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中報的每個錯誤,一兩個晚上就能搞定。無奈工作太忙了,不過還好,拋的坑總算補了一半,萬事開頭難,環境都搭好了。接下來可以把重心放在寫代碼上啦!

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