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