Bazel入门
上一篇博文中讲解了以Java工程bazel的使用,接下来讲解在C++工程中bazel的使用。
C++教程
*建立工作区*
假设目录中已经有了一个项目,对应 ~/gitroot/my-project/ 目录,先创建一个空的 ~/gitroot/my-project/WORKSPACE 工作区配置文件,用于表示这是Bazel项目对应的根目录。 在这里以下面目录结构创建一个小的hello world工程:
└── my-project
├── lib
│ ├── BUILD
│ ├── hello-greet.cc
│ └── hello-greet.h
├── main
│ ├── BUILD
│ ├── hello-time.cc
│ ├── hello-time.h
│ └── hello-world.cc
└── WORKSPACE
*创建源文件*
使用下面的命令创建所需的源文件:
$ # If you're not already there, move to your workspace directory.
$ cd ~/gitroot/my-project
$ mkdir ./main
$ cat > main/hello-world.cc <<'EOF'
#include "lib/hello-greet.h"
#include "main/hello-time.h"
#include <iostream>
#include <string>
int main(int argc, char** argv) {
std::string who = "world";
if (argc > 1) {
who = argv[1];
}
std::cout << get_greet(who) <<std::endl;
print_localtime();
return 0;
}
EOF
$ cat > main/hello-time.h <<'EOF'
#ifndef MAIN_HELLO_TIME_H_
#define MAIN_HELLO_TIME_H_
void print_localtime();
#endif
EOF
$ cat > main/hello-time.cc <<'EOF'
#include "main/hello-time.h"
#include <ctime>
#include <iostream>
void print_localtime() {
std::time_t result = std::time(nullptr);
std::cout << std::asctime(std::localtime(&result));
}
EOF
$ mkdir ./lib
$ cat > lib/hello-greet.h <<'EOF'
#ifndef LIB_HELLO_GREET_H_
#define LIB_HELLO_GREET_H_
#include <string>
std::string get_greet(const std::string &thing);
#endif
EOF
$ cat > lib/hello-greet.cc <<'EOF'
#include "lib/hello-greet.h"
#include <string>
std::string get_greet(const std::string& who) {
return "Hello " + who;
}
EOF
*添加BUILD文件*
从上面的源代码可知,main/hello-world.cc要用到lib/hello-greet.h和main/hello-time.h。首先在lib目录下为hello-greet.cc创建BUILD:
cc_library(
name = "hello-greet",
srcs = ["hello-greet.cc"],
hdrs = ["hello-greet.h"],
visibility = ["//main:__pkg__"],
)
注意 visibility = [“//main:pkg“] 表示hello-greet对于main/BUILD是可见的。接下来在main目录下创建BUILD文件:
cc_library(
name = "hello-time",
srcs = ["hello-time.cc"],
hdrs = ["hello-time.h"],
)
cc_binary(
name = "hello-world",
srcs = ["hello-world.cc"],
deps = [
":hello-time",
"//lib:hello-greet",
],
)
注意当依赖的包在同一目录下,只需用 :hello-time,当依赖的包在不同的目录下,需要用全路径://lib:hello-greet。
现在可以建立hello world 的C++二进制程序了:
$ bazel build main:hello-world
INFO: Found 1 target...
Target //main:hello-world up-to-date:
bazel-bin/main/hello-world
INFO: Elapsed time: 2.869s, Critical Path: 1.00s
$ ./bazel-bin/main/hello-world
Hello world
Thu Jun 23 18:51:46 2016
$ ./bazel-bin/main/hello-world Bazel
Hello Bazel
Thu Jun 23 18:52:10 2016
恭喜你刚刚成功构建了第一个Bazel项目了!
*传递依赖Transitive includes)*
如果一个文件包含一个头文件,那么这个文件的规则也应该依赖与头文件的库,相反的,只有直接依赖需要被指定为依赖。例如,假设sandwich.h包括bread.h,而且bread.h包括flour.h,sandwich.h不包括flour.h,因此这个BUILD文件应该是这样:
cc_library(
name = "sandwich",
srcs = ["sandwich.cc"],
hdrs = ["sandwich.h"],
deps = [":bread"],
)
cc_library(
name = "bread",
srcs = ["bread.cc"],
hdrs = ["bread.h"],
deps = [":flour"],
)
cc_library(
name = "flour",
srcs = ["flour.cc"],
hdrs = ["flour.h"],
)
在这里,sandwich库依赖于bread库,而bread库依赖于flour库。
*添加包含路径(Adding include paths)*
有时不能(或不愿)让依赖文件包含在工作区根目录的路径。现有的库可能已经拥有了包括与工作空间不匹配路径的目录。例如,假设有以下目录结构:
└── my-project
├── third_party
│ └── some_lib
│ ├── BUILD
│ ├── include
│ │ └── some_lib.h
│ └── some_lib.cc
└── WORKSPACE
Bazel 希望 some_lib.h被包含在hird_party/some_lib/include/some_lib.h中,但假定some_lib.c要依赖于include/some_lib.h。为了使包含路径有效, third_party/some_lib/BUILD需要指定some_lib是一个包含目录:
cc_library(
name = "some_lib",
srcs = ["some_lib.cc"],
hdrs = ["some_lib.h"],
copts = ["-Ithird_party/some_lib"],
)
这对外部的依赖尤其有用,因为它们的头文件,否则必须被包含于外部/ [库名称]/前缀。
*包含外部库*
假设正在用Google Test,可以在WORKSPACE文件中用一种新的库函数下载Google Test,使它在库中更适合:
new_http_archive(
name = "gtest",
url = "https://googletest.googlecode.com/files/gtest-1.7.0.zip",
sha256 = "247ca18dd83f53deb1328be17e4b1be31514cedfc1e3424f672bf11fd7e0d60d",
build_file = "gtest.BUILD",
)
然后创建gtest.BUILD,这个BUILD是用来编译Google Test的。Google Test有几个特殊的要求使它的cc_library规则更加复杂:
- gtest-1.7.0/src/gtest-all.cc #includes all of the other files in gtest-1.7.0/src/, so we need to exclude it from the compile or we'll get link errors for duplicate symbols.
- It uses header files that relative to the gtest-1.7.0/include/ directory ("gtest/gtest.h"), so we must add that directory the include paths.
- It needs to link in pthread, so we add that as a linkopt.
最终的规则像这样:
cc_library(
name = "main",
srcs = glob(
["gtest-1.7.0/src/*.cc"],
exclude = ["gtest-1.7.0/src/gtest-all.cc"]
),
hdrs = glob([
"gtest-1.7.0/include/**/*.h",
"gtest-1.7.0/src/*.h"
]),
copts = [
"-Iexternal/gtest/gtest-1.7.0/include"
],
linkopts = ["-pthread"],
visibility = ["//visibility:public"],
)
这有点凌乱:一切都以前缀GTEST-1.7.0作为体系结构的副产品。可以通过添加strip_prefix属性使new_http_archive带这个前缀:
new_http_archive(
name = "gtest",
url = "https://googletest.googlecode.com/files/gtest-1.7.0.zip",
sha256 = "247ca18dd83f53deb1328be17e4b1be31514cedfc1e3424f672bf11fd7e0d60d",
build_file = "gtest.BUILD",
strip_prefix = "gtest-1.7.0",
)
此时的gtest.BUILD是这个样子:
cc_library(
name = "main",
srcs = glob(
["src/*.cc"],
exclude = ["src/gtest-all.cc"]
),
hdrs = glob([
"include/**/*.h",
"src/*.h"
]),
copts = ["-Iexternal/gtest/include"],
linkopts = ["-pthread"],
visibility = ["//visibility:public"],
)
现在cc_rules依赖于//external:gtest/main。
编写并运行C++测试程序
例如,可以创建一个测试程序 ./test/hello-test.cc:
#include "gtest/gtest.h"
#include "lib/hello-greet.h"
TEST(FactorialTest, Negative) {
EXPECT_EQ(get_greet("Bazel"), "Hello Bazel");
}
然后为测试程序创建./test/BUILD文件:
cc_test(
name = "hello-test",
srcs = ["hello-test.cc"],
copts = ["-Iexternal/gtest/include"],
deps = [
"@gtest//:main",
"//lib:hello-greet",
],
)
注意:为了使hello-greet对于hello-test可见,必须在./lib/BUILD添加”//test:pkg“可见属性。
现在可以用bazel运行测试了。
$ bazel test test:hello-test
INFO: Found 1 test target...
Target //test:hello-test up-to-date:
bazel-bin/test/hello-test
INFO: Elapsed time: 4.497s, Critical Path: 2.53s
//test:hello-test PASSED in 0.3s
Executed 1 out of 1 tests: 1 test passes.
在预编译上添加依赖
如果你想使用一个库,你只有(例如,头和一个.so)包装在一个cc_library规则的编译版本:
cc_library(
name = "mylib",
srcs = ["mylib.so"],
hdrs = ["mylib.h"],
)
然后在工作区中其他的C ++程序,可以依赖这条规则。