Tensorflow的Bazel编程(二)
安装官网:https://bazel.build/versions/master/docs/tutorial/java.html
Build Java
创建一个java项目,然后
cd /home/mi/git/TF_pro/Bazel/project在终端运行:
$ mkdir -p src/main/java/com/example $ cat > src/main/java/com/example/ProjectRunner.java <<EOF package com.example; public class ProjectRunner { public static void main(String args[]) { Greeting.sayHi(); } } EOF $ cat > src/main/java/com/example/Greeting.java <<EOF package com.example; public class Greeting { public static void sayHi() { System.out.println("Hi!"); } } EOF在项目根目录下建一个BUILD文件:
java_binary( name = "my-runner", srcs = glob(["**/*.java"]), main_class = "com.example.ProjectRunner", )然后build
bazel build //:my-runner进入生成的文件夹
bazel-bin运行可执行文件
my-runner输出:Hi!
成功构建了第二个Bazel项目了!
随着项目的增加,我们讲java项目拆成两个部分独立构建,同时设置它们之间的依赖关系。
重新构建一下BUILD文件:
java_binary( name = "my-other-runner", srcs = ["src/main/java/com/example/ProjectRunner.java"], main_class = "com.example.ProjectRunner", deps = [":greeter"], ) java_library( name = "greeter", srcs = ["src/main/java/com/example/Greeting.java"], )虽然源文件是一样的,但是现在Bazel将采用不同的方式来构建:首先是构建
greeter
库,然后是构建my-other-runner
。可以在构建成功后立刻运行//:my-other-runner
:$ bazel run //:my-other-runner INFO: Found 1 target... Target //:my-other-runner up-to-date: bazel-bin/my-other-runner.jar bazel-bin/my-other-runner INFO: Elapsed time: 2.454s, Critical Path: 1.58s INFO: Running command line: bazel-bin/my-other-runner Hi!更多的包
对于更大的项目,我们通常需要将它们拆分到多个目录中。你可以用类似//path/to/directory:target-name的名字引用在其他BUILD文件定义的目标。假设src/main/java/com/example/有一个cmdline/子目录,包含下面的文件:
$ mkdir -p src/main/java/com/example/cmdline $ cat > src/main/java/com/example/cmdline/Runner.java <<EOF package com.example.cmdline; import com.example.Greeting; public class Runner { public static void main(String args[]) { Greeting.sayHi(); } } EOFRunner.java依赖com.example.Greeting,因此我们需要在src/main/java/com/example/cmdline/BUILD构建文件中添加相应的依赖规则:
# src/main/java/com/example/cmdline/BUILD java_binary( name = "runner", srcs = ["Runner.java"], main_class = "com.example.cmdline.Runner", deps = ["//:greeter"] )然而,默认情况下构建目标都是 私有 的。也就是说,我们只能在同一个BUILD文件中被引用。这可以避免将很多实现的细节暴漏给公共的接口,但是也意味着我们需要手工允许runner所依赖的//:greeter目标。就是类似下面这个在构建runner目标时遇到的错误:
$ bazel build //src/main/java/com/example/cmdline:runner ERROR: /home/user/gitroot/my-project/src/main/java/com/example/cmdline/BUILD:2:1: Target "//:greeter" is not visible from target "//src/main/java/com/example/cmdline:runner". Check the visibility declaration of the former target if you think the dependency is legitimate. ERROR: Analysis of target "//src/main/java/com/example/cmdline:runner" failed; build aborted. INFO: Elapsed time: 0.091s可用通过在BUILD文件增加visibility = level属性来改变目标的可间范围。下面是通过在~/gitroot/my-project/BUILD文件增加可见规则,来改变greeter目标的可见范围:
java_library( name = "greeter", srcs = ["src/main/java/com/example/Greeting.java"], visibility = ["//src/main/java/com/example/cmdline:__pkg__"], )这个规则表示//:greeter目标对于//src/main/java/com/example/cmdline包是可见的。现在我们可以重新构建runner目标程序:
$ bazel run //src/main/java/com/example/cmdline:runner INFO: Found 1 target... Target //src/main/java/com/example/cmdline:runner up-to-date: bazel-bin/src/main/java/com/example/cmdline/runner.jar bazel-bin/src/main/java/com/example/cmdline/runner INFO: Elapsed time: 1.576s, Critical Path: 0.81s INFO: Running command line: bazel-bin/src/main/java/com/example/cmdline/runner Hi!
部署
如果你查看 bazel-bin/src/main/java/com/example/cmdline/runner.jar 的内容,可以看到里面只包含了Runner.class,并没有保护所依赖的Greeting.class:
$ jar tf bazel-bin/src/main/java/com/example/cmdline/runner.jar META-INF/ META-INF/MANIFEST.MF com/ com/example/ com/example/cmdline/ com/example/cmdline/Runner.class这只能在本机正常工作(因为Bazel的runner脚本已经将greeter jar添加到了classpath),但是如果将runner.jar单独复制到另一台机器上讲不能正常运行。如果想要构建可用于部署发布的自包含所有依赖的目标,可以构建runner_deploy.jar目标(类似<target-name>_deploy.jar以_deploy为后缀的名字对应可部署目标)。
$ bazel build //src/main/java/com/example/cmdline:runner_deploy.jar INFO: Found 1 target... Target //src/main/java/com/example/cmdline:runner_deploy.jar up-to-date: bazel-bin/src/main/java/com/example/cmdline/runner_deploy.jar INFO: Elapsed time: 1.700s, Critical Path: 0.23srunner_deploy.jar中将包含全部的依赖。
Build C++
首先创建头文件和源文件:
切记一定要先建一个WORKSPACE
,这个可以为空,不然找不到工程下面的一些目录,build会报错。
在project1下
$ 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在lib下添加BUILD文件:
cc_library( name = "hello-greet", srcs = ["hello-greet.cc"], hdrs = ["hello-greet.h"], visibility = ["//main:__pkg__"], )在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", ], )
依赖传递
如果包含了一个头文件,那么需要将头文件对应的库添加到依赖中。不过,只需有添加直接依赖的库。假设三明治对应的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
对应的面粉库。添加头文件路径
很多时候你可能并不希望基于工作区根路径的相对路径来包含每个头文件。因为很多已经存在的第三方库的头文件包含方式并不是基于工作区的根路径。假设有以下目录结构:
[workspace]/ WORKSPACE third_party/ some_lib/ include/ some_lib.h BUILD some_lib.cc
Bazel希望用third_party/some_lib/include/some_lib.h方式包含some_lib.h,但是some_lib.cc可能跟希望用"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"], )
这对于依赖的外部第三方库特别有效,因为可以避免在头文件路径中出现无关的external/[repository-name]/前缀。
添加外部库:
假设使用了Google Test。可以在WORKSPACE文件中使用new_开头的仓库相关的函数,下载依赖的GTest代码到当前仓库中:
new_http_archive( name = "gtest", url = "https://googletest.googlecode.com/files/gtest-1.7.0.zip", sha256 = "247ca18dd83f53deb1328be17e4b1be31514cedfc1e3424f672bf11fd7e0d60d", build_file = "gtest.BUILD", )创建gtest.BUILD文件,对应Google Test的构建配置文件。配置文件中有几个需要特别注意的地方:
- gtest-1.7.0/src/gtest-all.cc文件已经采用#include语法包含了gtest-1.7.0/src/目录中其它*.cc文件,因此需要将它排除在外(也可以只包含它一个文件,但是需要正确配置包含路径)。
- 它的头文件在gtest-1.7.0/include/目录,需要将它添加到头文件包含路径列表中
- GTest依赖pthread多线程库,通过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为前缀的其实都是生成的临时文件。我们可以通过new_http_archive函数中的strip_prefix属性来忽略它:
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_相关的规则可以通过//external:gtest/main引用GTest了。
测试:./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", ], )lib/BUILD:
cc_library( name = "hello-greet", srcs = ["hello-greet.cc"], hdrs = ["hello-greet.h"], visibility = ["//test:__pkg__"], )
依赖预编译的库
如果要依赖一个已经编译好的库(可能只有头文件和对应的*.so库文件),可以使用cc_library规则包装一个库对象:
cc_library( name = "mylib", srcs = ["mylib.so"], hdrs = ["mylib.h"], )其它的目标就可以依赖这个包装的库对象了。 参考:
https://my.oschina.net/chai2010/blog/674110
https://my.oschina.net/chai2010/blog/701807
https://bazel.build/versions/master/docs/tutorial/cpp.html
https://bazel.build/versions/master/docs/tutorial/java.html
https://bazel.build/versions/master/docs/bazel-user-manual.html