一直对onos的编译运行过程很是迷惑,这次就花了些时间仔细研究了一下。
Bazel基本概念
在探究onos的整体编译结构前,先要了解一下Bazel的几个基本概念:
Workspace
工作区是项目的根目录,每个工作区都有一个WORKSPACE(或WORKSPACE.bazel)文件(可以无内容)。每个工作区相互独立,也就是说工作区A的子目录下有个工作区B,则编译A的时候并不会编译B。
Repositories
所有的代码都由仓库管理,它包含了所有待构建的源文件、数据和构建脚本。当前代码仓库(工作区所在目录)由@标识,外部仓库由@external_repository标识。对于外部仓库,一定要在当前WORKSPACE文件中进行声明(如http_archive、local_repository、new_local_repository等)
Packages
仓库中管理代码的基本单位是包。包由相关文件和它们之间的依赖说明组成的。一个包必须包含BUILD或BUILD.bazel文件。需要注意的是,在一个包中递归包含该目录下的所有文件,但是若某个子目录也含有BUILD文件,则该子目录独立为一个新包。
Targets
包中含有一个或多个目标。大多数目标主要是files或是rules(package group不常见)。
- files又可细分为两种:Source file(源文件)和Generated file(生成文件,编译时根据特殊的规则得到)
- rules定义了输入和输出间的关系(包括必要的从输入到输出的步骤)。输出永远是生成文件,而输入可能是源文件或生成文件。注意:一个rule的输出可能是另一个rule的输入。另外,规则生成的文件永远与规则属于同一个包。
Labels
每个目标只能属于一个包,目标的名字就是标签,并且每个标签只能表示一个包。比如:
@repo_name//@package_name:target_name
比如@myrepo//my/app/main:app_binary
也就是myrepo工作区下my/app/main包中的app_binary目标,通常情况下可省略开头的@myrepo。关于标签的一些小语法如下:
//my/app === //my/app:app
# 在my/app中的BUILD文件中,对于该文件中的其他目标
//my/app === :app === app
一般来讲文件会省略冒号,rules则不会。
Rules
规则规定了输入输出之间的关系以及构建输出的步骤。Rules可以生成编译后的可执行文件、库、测试执行文件或其他支持的输出。比如:
cc_binary(
name = "my_app",
src = ["my_app.cc"],
deps = [
"//absl/base",
"//absl/string",
],
)
可以看到这个C++库my_app的源文件是my_app.cc,其依赖于//absl/base:base和//absl/string:string。当然有些规则是依赖于具体的语言的。
BUILD files
BUILD文件采用Starlark语言。Build文件中各个变量的声明顺序是随意的,也就是说,变量可以在使用后声明。并且BUILD文件不能包含函数定义、for语句和if语句,函数定义只能出现在.bzl文件中。另外,*args和**kwargs不能出现在BUILD文件中,需要显示声明参数。
bazel其他细节
其他细节可参考https://blog.gmem.cc/bazel-study-note或官网https://docs.bazel.build/versions/master/build-ref.html。
onos之编译运行
源码版本为onos2.3.0
WORKSAPCE文件
WORKSPACE文件是用来给当前工作区引入外部依赖的。可以看到onos目录下的WORKSPACE文件,也确实导入外部仓库,如bazel_skylib、build_bazel_rules_nodejs等,当然也调用了一些函数。需要注意的是:
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
load能够加载扩展文件的某些符号到当前的工作环境,注意此时第一个参数:后面不是目标名,而是.bzl文件,后面的参数才是要加载的符号。符号包括规则、函数或常量。
其中的bazel_tools是内置仓库,相应目录为/home/user_name/.cache/bazel/_bazel_user_name/install/xxxx…x/_embedded_binaries/embedded_tools。
另外就是调用一些函数进行环境准备。
BUILD文件
现在打开onos目录下的BUILD文件,探究它是怎么编译的。
load("//tools/build/bazel:variables.bzl", "ONOS_VERSION")
首先加载ONOS_VERSION,为2.3.0。
load(
"//tools/build/bazel:modules.bzl",
"CORE",
"FEATURES",
"apps",
"extensions",
"profiles",
)
从tools/build/bazel包下的modules.bzl中加载CORE、FEATURES等。需要注意三个函数的作用:
- apps:负责在很多XXX_MAP中筛选出符合条件的key。
- extensions:从PROTOCOL_MAP和PROVIDER_MAP中筛选出符合条件的key。
- profiles:根据给定的参数批量生成本地config_setting。
接下来通过profiles生成三个config_setting。
profiles([
"minimal",
"seba",
"stratum",
])
这三个配置的作用就是控制打包的范围。
filegroup(
name = "onos",
srcs = CORE + [
"//tools/build/conf:onos-build-conf",
":onos-package-admin",
":onos-package-test",
":onos-package",
] + select({
":minimal_profile": extensions("minimal") + apps("minimal"),
":seba_profile": extensions("seba") + apps("seba"),
":stratum_profile": extensions("stratum") + apps("stratum"),
"//conditions:default": extensions() + apps(),
}),
visibility = ["//visibility:public"],
)
filegroup是为一组目标指定一个名字,方便bazel监控文件变化。其中select()根据配置选择不同的目标:
bazel build onos-package --define profile=minimal #以最小形式打包
默认情况下完整打包。之后是几个打包规则,如:
# 这里仅举例onos-karaf打包规则
genrule(
name = "onos-karaf",
srcs = [
KARAF,
BRANDING,
] + glob([
"tools/package/bin/*",
"tools/package/etc/*",
"tools/package/init/*",
"tools/package/runtime/bin/*",
]),
outs = ["karaf.zip"],
cmd = "$(location tools/package/onos-prep-karaf) $(location karaf.zip) $(location %s) %s $(location %s) '' tools/package" %
(KARAF, ONOS_VERSION, BRANDING),
tools = ["tools/package/onos-prep-karaf"],
)
genrule是通过用户自定义的bash命令生成一个或多个文件。
分别打包karaf.zip、onos.tar.gz、onos-admin.tar.gz和onos-test.tar.gz。
alias(
name = "onos-local",
actual = select({
":run_with_absolute_javabase": ":onos-local_absolute-javabase",
"//conditions:default": ":onos-local_current-jdk",
}),
)
之后则是onos-local目标,这里会根据启动参数选择jdk进行运行,默认采用bazel自带的JDK(openJDK 11)。
genrule(
name = "onos-local_current-jdk",
srcs = [
":onos-local-base",
"//tools/build/jdk:current_jdk_tar",
],
outs = ["onos-runner_current-jdk"],
cmd = "sed \"s#JDK_TAR=#JDK_TAR=$(location //tools/build/jdk:current_jdk_tar)#\" " +
"$(location :onos-local-base) > $(location onos-runner_current-jdk); ",
executable = True,
output_to_bindir = True,
visibility = ["//visibility:private"],
)
所以使用bazel run onos-local
运行onos的话,其实运行的是onos-runner_current-jdk,executable表示输出可否运行,output_to_bindir则是将输出文件放在bin目录下。
onos-runner_current-jdk文件
该文件由很多bash命令组成,主要过程是:
- 杀死正在运行的实例
- 使用ABSOLUTE_JAVABASE作为JAVA_HOME或是将JDK_TAR解压使用。
- 如果之前的onos不存在或者和现在的md5摘要不同,则重新解压,并配置一些参数和准备工作。
- 进入ONOS_DIR目录,并执行./bin/onos-service
注意,解压后的onos目录位于/tmp/onos-2.3.0,其目录结构为:
onos-service文件
该文件位于bin目录下,主要是解析参数,并且在需要预激活的app目录下生成active文件,最后启动karaf。
Karaf运行
可执行文件karaf位于bin目录下,但是需要注意的是etc/org.apache.karaf.features.cfg,该文件内容如下:
其中标明了Karaf启动时加载的Feature仓库和Feature,通过这些Feature的加载正式启动了onos的功能。值得一说的是,我一开始以为mvn:org.onosproject/onos-features/2.3.0/xml/features是在~/.m2目录下,但是一直找不到,最后发现其实是在system目录下。