這是關於Bazel的第二篇blog,前一篇寫了安裝、配置相關的東西,這一篇則是4個逐步推進的例子,改編自官方demo;以及相應的概念、文檔鏈接等。
前提
- Linux(Ubuntu, etc)或Mac OSX系統,會點兒命令行(包括brew/apt)
- 裝好了zsh和oh-my-zsh(用於
bazel build
等命令的補全) - 裝好了bazel;
- 學過C/C++;
- 用過make/cmake
- 最好會一點git
- bazel版本:目前我用0.21版本,最新版刪過東西(https://docs.bazel.build/versions/0.21.0/be/workspace.html)
基本概念
WORKSPACE
: 空文件;標識了項目根目錄;只有一個BUILD
:WORKSPACE下的子目錄里,如果放了一個名為BUILD
的文件,則這個目錄是一個package
;BUILD
里寫一些構建規則 (rules)cc_binary
: C/C++package
的最常用的構建規則- bazel的C/C++在線文檔:https://docs.bazel.build/versions/master/be/c-cpp.html
target
的概念:cmake中的target包括executable、library兩種情況rule
的概念:類似於cmake中target
概念的推廣,bazel構建C/C++時的規則有:- 即使是bazel build官方文檔,也不明確區分target和rule字眼,可以認為是一個意思
- 大體上,
.bzl
相當於.cmake
文件,BUILD
相當於CMakeLists.txt
WORKSPACE
,BUILD
中用到了一些預定義的函數或變量,具體看這里:https://docs.bazel.build/versions/master/skylark/lib/skylark-overview.html
速查鏈接匯總
workspace規則
starlark預設全局變量
完整代碼
stage1: 一個package, 一個target
這是最簡單的bazel構建例子
目錄結構
├── WORKSPACE
└── main
├── BUILD
└── hello.c
其中,main
為包名,因為它包含了BUILD
文件
hello.c
:
#include <stdio.h>
int main(void){
printf("hello from C!\n");
return 0;
}
BUILD
:
cc_binary(
name = "hello",
srcs = ["hello.c"],
)
執行構建
bazel build main:all
- 語法是
bazel build 包名:任務名
- 輸入完
bazel build
后按tab
鍵補全提示,比較方便 - 因為目前只有一個target,也可以輸入
bazel build main:hello
運行
bazel run main:all
它其實除了輸出bazel相關的信息,執行的是./bazel-bin/hello
目錄下的可執行文件hello
等
執行清除
bazel clean
stage2: 一個package,多個target
典型場景:寫一個庫,然后調用它。這里寫一個神經網絡激活函數庫,然后寫一個測試程序。
目錄結構
├── WORKSPACE
└── main
├── BUILD
├── activations.c
├── activations.h
└── testbed.c
1 directory, 5 files
BUILD
:
cc_library(
name = "actv",
srcs = ["activations.c"],
hdrs = ["activations.h"],
)
cc_binary(
name = "actv-testbed",
srcs = ["testbed.c"],
deps = [
":actv",
],
)
activations.h
:
#ifndef __ACTIVATIONS_H__
#define __ACTIVATIONS_H__
float relu(float x);
float sigmoid(float x);
#endif
activations.c
:
#include "activations.h"
#include <math.h>
float relu(float x){
if (x>=0) return x;
return 0;
}
float sigmoid(float x){
return 1.0f / (1.0f + exp(-x));
}
testbed.c
:
#include <stdio.h>
#include <time.h>
#include <stdlib.h>
#include "activations.h"
// return a random float in (s, t)
float get_random(float s, float t){
float v = (float)(rand()) / RAND_MAX;
v = v * (t-s) + s;
return v;
}
int main(){
const int maxn = 5;
srand(time(0));
for(int i=0; i<maxn; i++) {
float x = get_random(-2.0f, 2.0f);
float res_relu = relu(x);
float res_sigmoid = sigmoid(x);
printf("x=%f\n", x);
printf("relu(x)=%f\n", res_relu);
printf("sigmoid(x)=%f\n", res_sigmoid);
printf("\n");
}
return 0;
}
構建庫
bazel build main:actv
構建測試
bazel build main:actv-testbed
執行
bazel run main:actv-testbed
stage3: 多package,多target
主要是弄清楚,如何在不同package的target之間設定依賴關系,比如一個庫target在其他包中是否可用(visibility),比如頭文件的包含路徑。
目錄結構:
├── lib
│ ├── BUILD
│ ├── random.c
│ ├── random.h
│ ├── timer.c
│ └── timer.h
├── main
│ ├── activations.c
│ ├── activations.h
│ ├── BUILD
│ └── testbed.c
└── WORKSPACE
其中,lib/BUILD
:
cc_library(
name = "timer",
srcs = ["timer.c"],
hdrs = ["timer.h"],
visibility = ["//main:__pkg__"]
)
cc_library(
name = "random",
srcs = ["random.c"],
hdrs = ["random.h"],
visibility = ["//main:__pkg__"]
)
而main/BUILD
:
cc_library(
name = "actv",
srcs = ["activations.c"],
hdrs = ["activations.h"],
)
cc_binary(
name = "actv-testbed",
srcs = ["testbed.c"],
deps = [
":actv",
"//lib:random",
"//lib:timer"
],
)
各個源碼文件其實都很簡單,這里就不貼出來的,只需要注意在包含lib包里面的頭文件時,main/testbed.c
是這樣寫的:
#include "activations.h"
#include "lib/timer.h"
#include "lib/random.h"
stage4: 使用外部依賴
這里說的外部依賴,包括:本地的其他目錄,git倉庫或http地址下載的。外部依賴可以是基於bazel構建的,也可以不是。
anyway,google家開源的abseil框架的hello-world例子可能是最典型的demo了。但是它同時用了gtest、gmock和abseil,感覺太麻煩了,干脆直接引入abseil外部庫來寫hello-world,如下:
目錄結構
├── BUILD
├── hello.cc
└── WORKSPACE
這里看到並沒有子目錄,這也是可以的
WORKSPACE
# 非必須
workspace(name = "com_google_absl_hello_world")
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
# Abseil
http_archive(
name = "absl",
strip_prefix = "abseil-cpp-master",
urls = ["https://github.com/abseil/abseil-cpp/archive/master.zip"],
)
BUILD
cc_binary(
name = "hello",
srcs = ["hello.cc"],
deps = [
"@absl//absl/strings"
]
)
說明:因為在WORKSPACE
中載入了absl,所以BUILD
中可以使用@absl
。
hello.cc
#include <iostream>
#include <string>
#include "absl/strings/str_cat.h"
#include "absl/strings/string_view.h"
using std::string;
using std::cout;
using std::endl;
string Greet(absl::string_view person) {
return absl::StrCat("Hello ", person);
}
int main(){
cout << Greet("world") << endl;
cout << Greet("ChrisZZ") << endl;
return 0;
}
執行構建
bazel build :hello
它根據WORKSPACE
中的設定(也就是http_archive
),從http下載。其他的方法還包括git_repository
的方式,不過最新版bazel中把這些都踢掉了,必須手動load一下bazel工具中的.bzl文件,才能用它們。。
參考:
https://stackoverflow.com/questions/45814669/c-project-with-bazel-and-gtest?noredirect=1&lq=1
https://github.com/vincent-picaud/Bazel_with_GTest
https://docs.bazel.build/versions/master/external.html
最佳實踐
https://docs.bazel.build/versions/master/external.html#best-practices