hello:bazel

Bazel是什么

    bazel是一个工程编译工具。

Bazel的特点

    它适合构建和测试拥有庞大代码库的项目,用(多种)需要编译的语言写的项目,在多平台上部署的项目,及有大量测试的工程的项目。

    bazel可以成倍的提高构建速度,因为它只重新编译需要重新编译的文件,所以,对于没有发生变化的文件,它是不会对其进行重新编译的。当然为了能够让bazel很好的识别哪些文件是增量文件,在BUILD文件中,每个库,测试程序,二进制文件必须明确完整地指定直接依赖。当修改源代码文件后,Bazel使用这个依赖信息就可以知道哪些必须重新构建,哪些任务可以并行执行。这意味者所有的构建都是增量形式的并能够每次都生成相同的结果。这样一来,也就意味着它的两个特点,第一,它会比较占用缓存。也就是说它会在第一次构建工程的时候,将所有代码编译并缓存起来,后面在构建的时候,只重新编译那些有变化的文件即可。第二就是,他不适合去处理解释性语言的过程,比如python,作为解释性语言,每次的构建,都会重新加载所有文件,这样以来就无法发挥bazel的优势了。

   bazel本身是支持多语言的,如:java, c, c++, 也支持拓展为任意的编程语言。而且它同样支持多平台,同一套工具和同样的BUILD文件可以用来构建不同架构和不同平台的软件。

ok,接下看看,bazel的具体概念和使用吧~

Bazel的概念:

    workspace 工作空间:每个工作空间目录都有一个名为WORKSPACE的文本文件,该文件可能为空,或者可能包含对构建输出所需的外部依赖项外部依赖项的引用。简单来说,每一个Bazel项目就对应着一个名为WORKSPACE的文本文件,WORKSPACE的文本文件所在的文件夹,就是bazel项目的工作空间。所以工作空间的另一个特点就是:它(WORKSPACE的文本文件所在的文件夹)包括了单个或多个项目所有源文件。

在构建这个工作空间的时候,需要先到包括了单个或多个项目所有源文件的文件夹里,执行命令:

$touch WORKSPACE
$bazel info workspace

这时候我们应该可以看到输出当前路径,这说明workspace创建成功(注意这里的WORKSPACE文件本身还是空的)。

    package 程序包:每个程序包中包含一个BUILD文件,此文件中描述了此工具包的生成构建方式。简单来说就是,工程中,我们往往会将具有一定功能的模块进行封装,然后就会形成一个具备一定功能包,那么这个包说白了,就是由源码文件组成而来的具备一定功能的包。ok,对于bazel的package程序包,就是指刚才说的功能包,唯一的区别就是,需要在这个工具包的里面添加一个BUILD文件。

    target 目标:package程序包里的每一个元素都称之为目标。所以,目标分为两类,一个就是源文件,一个就是BUILD文件了。由于BUILD文件里写的东西,叫规则,因此,也有人将目标的2个分类,分为源文件和规则。除此之外,还有一类,叫包组,但它们的数量要少得多,包组就相当于包的嵌套。

Bazel的具体使用:

    通常就是修改各个BUILD文件,为新的包添加BUILD文件,同时将新建的BUILD文件添加到外层BUILD文件的依赖里。

看看,BUILD文件(示例最外层构建的BUILD):

android_binary是规则的一种,name, mainfest, deps都是规则的属性。

name属性绑定的就是,这个规则对外的名称。 

android_binary(
    name = "app",
    manifest = "AndroidManifest.xml",
    deps = ["//src/main/java/com/example/bazel:greeter_activity"],
)

中间层BUILD文件(注意最后一行中添加的对最内层BUILD的依赖的添加):

deps = ["//src/main/java/com/example/bazel/util:util"]

最内层BUILD文件:

    

 最后回到工作目录下执行命令:

bazel build //src/main:app

 这里分析下他的命令~

 bazel的构建命令为:bazel  build  包目录:规则名称

 注意,包目录的目录起始是从工作目录开始算的,而不是从整个的工程的根目录开始算的。

 如上目录的目录结构为:

 注意这里最后一个目录于中间目录的变化,这是因为util是后加的,所以,也就有了util依赖添加到中间那个BUILD的那一幕了。

现在,来扯扯 target 的编写(BUILD文件的编写):

一个普通的BUILD是这样写的:

package(
    default_visibility = ["//src:__subpackages__"],
)

android_library(
    name = "greeter_activity",
    srcs = [
        "Greeter.java",
        "MainActivity.java",
    ],
    manifest = "AndroidManifest.xml",
    resource_files = glob(["res/**"]),
)

那么如果,在使用命令, bazel build //src/main:app 的时候调用 greeter_activity 的时候,这个对象有其他依赖,怎么办呢,通常就可以在这个规则里面添加依赖属性(deps),依赖是个列表对象。

package(
    default_visibility = ["//src:__subpackages__"],
)

android_library(
    name = "greeter_activity",
    srcs = [
        "Greeter.java",
        "MainActivity.java",
    ],
    manifest = "AndroidManifest.xml",
    resource_files = glob(["res/**"]),
    deps = ["//src/main/java/com/example/bazel/util:util"],
)

注意这里的deps里面的内容,是给的绝对路径。

当然,也有一种情况,在同一个BUILD文件里,有法则之间存在互相引用的情况,又该当如何?

这里举个例子:

这也是一个BUILD文件:

cc_library(
    name = "hello-greet",
    srcs = ["hello-greet.cc"],
    hdrs = ["hello-greet.h"],
)

cc_binary(
    name = "hello-world",
    srcs = ["hello-world.cc"],
    deps = [
        ":hello-greet",
    ],
)

请注意,红色的deps,这里由于是在同一个文件中的引用,所以就直接省略了文件夹的绝对路径这一段。(这里特别注意:依赖的引用都是直接写BUILD文件做在的文件夹的路径就行了,然后给 ‘:’ , 加具体target的name属性的值就行

再举个例子:

现有工程目录为此:

└──stage3
   ├── main
   │   ├── BUILD
   │   ├── hello-world.cc
   │   ├── hello-greet.cc
   │   └── hello-greet.h
   ├── lib
   │   ├── BUILD
   │   ├── hello-time.cc
   │   └── hello-time.h
   └── WORKSPACE

然后,我们看下main下的BUILD:

cc_library(
    name = "hello-greet",
    srcs = ["hello-greet.cc"],
    hdrs = ["hello-greet.h"],
)

cc_binary(
    name = "hello-world",
    srcs = ["hello-world.cc"],
    deps = [
        ":hello-greet",
        "//lib:hello-time",
    ],
)

这下应该足够形象了吧。

接着看下lib下的BUILD:

cc_library(
    name = "hello-time",
    srcs = ["hello-time.cc"],
    hdrs = ["hello-time.h"],
    visibility = ["//main:__pkg__"],
)

ok, 这里迎来了 target 的另一个属性:visibility

visibility属性是target显式可见属性,因为默认情况下,targets只对同一个BUILD文件里的其他targets可见,所以添加这个属性,绑定一个列表,你想让那些包可以访问这个targe,那么就将其添加到这个列表里就可以了。

顺便介绍下其他属性:

name属性是target被编译成静态库后叫什么名字。

srcs属性是target下有哪些源文件需要被编译。它是一个列表。

hdrs属性是有哪些头文件是public。

这里再看一种BUILD:

load("@build_bazel_rules_apple//apple:ios.bzl", "ios_application")

objc_library(
    name = "universal_lib",
    srcs = [
        "universal/main.m",
        "universal/AppDelegate.m",
        "universal/ViewController.m",
    ],
    hdrs = [
        "universal/AppDelegate.h",
        "universal/ViewController.h",
    ],
    enable_modules = 1,
)

load("//foo/bar:file.bzl", "some_library")

上面的语句加载foo/bar/file.bzl并添加其中定义的符号some_libraray到当前环境中

load属性是load语句可以用来加载规则、函数、常量(字符串、列表等)。有点import的意思。

load语句必须出现在顶级作用域,不能出现在函数中。第一个参数说明扩展的位置,你可以为导入的符号设置别名。

这里的 load("@build_bazel_rules_apple//apple:ios.bzl", "ios_application") 是为了告诉bazel,需要用build_bazel_rulers_apple这个库里的那个规则来编译这个target.

这里是不是有点奇怪,为啥有的 load(...) 里用的@xxx,有的就直接 //xxx.

这里加个小插曲:

Bazel允许依赖其它项目中定义的目标,这些来自其它项目的依赖叫做“外部依赖“。当前工作空间的WORKSPACE文件声明从何处下载外部依赖的源码。

外部依赖可以有自己的1-N个BUILD文件,其中定义自己的目标。当前项目可以使用这些目标。例如下面的两个项目结构:

/
  home/
    user/
      project1/
        WORKSPACE
        BUILD
        srcs/
          ...
      project2/
        WORKSPACE
        BUILD
        my-libs/

如果project1需要依赖定义在project2/BUILD中的目标:foo,则可以在其WORKSPACE中声明一个存储库(repository),名字为project2,位于/home/user/project2。然后,可以在BUILD中通过标签@project2//:foo引用目标foo。

所以,以@来引用的包,就属于外部依赖包。

举个例子:

bazel项目可以使用local_repository、git_repository或者http_archive这几个规则来引用外部依赖包。

引用本地Bazel项目的例子:

在目录my_project/WORKSPACE下

local_repository(
    name = "coworkers_project",
    path = "/path/to/coworkers-project",
)

在BUILD中,引用coworkers_project中的目标//foo:bar时,使用标签@coworkers_project//foo:bar 

非bazel的项目,可以使用new_local_repository、new_git_repository或者new_http_archive这几个规则来引用。你需要自己编写BUILD文件来构建这些项目。

在WORKSPACE中:

new_local_repository(
    name = "coworkers_project",
    path = "/path/to/coworkers-project",
    build_file = "coworker.BUILD",
)
在BUILD中:

cc_library(
    name = "some-lib",
    srcs = glob(["**"]),
    visibility = ["//visibility:public"],
) 

在BUILD文件中,使用标签@coworkers_project//:some-lib引用上面的库。

在这里不难发现,我们都是直接引用规则,那么规则如何自定义呢?

存储库规则用于定义外部存储库。外部存储库是一种规则,这种规则只能用在WORKSPACE文件中,可以在Bazel加载阶段启用非封闭性( non-hermetic,所谓封闭是指自包含,不依赖于外部环境)操作。每个外部存储库都创建自己的WORKSPACE,具有自己的BUILD文件和构件。

外部存储库可以用来:

  1. 加载第三方依赖,例如Maven打包的库
  2. 为运行构件的主机生成特化的BUILD文件

在bzl文件中,调用repository_rule函数可以创建一个存储库规则,你需要将其存放在全局变量中:

local_repository = repository_rule(
    # 实现函数
    implementation=_impl,
    local=True,
    # 属性列表
    attrs={"path": attr.string(mandatory=True)}) 

每个存储库规则都必须提供实现函数,其中包含在Bazel加载阶段需要执行的严格的逻辑。该函数具有唯一的入参repository_ctx:

def _impl(repository_ctx):
  # 你可以通过repository_ctx访问属性值、调用非密封性函数(例如查找、执行二进制文件,创建或下载文件到存储库)
  repository_ctx.symlink(repository_ctx.attr.path, "") 

引用存储库中规则时,可以使用 @REPO_NAMAE//package:target这样的标签。

这里加个小总结:

    其实我们不管是在WORKSPACE中还是BUILD中,往往会使用很多规则,这些规则有些是内建的可以直接用,但是有些可能就需要我们自己去创建,去定义。但是有一点是肯定的,不管是内建还是自定义,都是需要一个实现函数的。一般的规则都是给个变量名就可以代表规则,然后绑定规则函数即可 rule(...),这样一来就可以创建一个规则,rule函数中必须绑定一个实现函数,该函数又必须有唯一参数ctx,而对于外部引用规则,又有一些小的区别,他的规则函数是repository_rule(...),同样的他也需要一个实现函数,实现函数就是自定义一个函数即可,但是其必须拥有唯一参数repository_ctx。东西总体来说和一般的rule规则差不多,当然这里选择介绍了引用第三方的规则。当然,正如上面所演示的,repository_rule(...)是有一些属性的,这些属性并不是随便定义的,也是有一定的规则遵守的,这里可以参考:绿色记忆:Bazel学习笔记中的ctx部分。

再来一个更详细的说明:

在xxx.bzl文件里有如下内容:

load("//third_party/mkl:build_defs.bzl", "mkl_repository")
load("//third_party:repo.bzl", "tf_http_archive")


mkl_repository(
        name = "mkl_linux",
        build_file = clean_dep("//third_party/mkl:mkl.BUILD"),
        sha256 = "a936d6b277a33d2a027a024ea8e65df62bd2e162c7ca52c48486ed9d5dc27160",
        strip_prefix = "mklml_lnx_2019.0.5.20190502",
        urls = [
            "https://storage.googleapis.com/mirror.tensorflow.org/github.com/intel/mkl-dnn/releases/download/v0.20-rc/mklml_lnx_2019.0.5.20190502.tgz",
            "https://github.com/intel/mkl-dnn/releases/download/v0.20-rc/mklml_lnx_2019.0.5.20190502.tgz",
        ],
)

mkl_repository 在文件//third_party/mkl:build_defs.bzl 中定义:

_TF_MKL_ROOT = "TF_MKL_ROOT"

def _enable_local_mkl(repository_ctx):
    return _TF_MKL_ROOT in repository_ctx.os.environ
    
def _mkl_autoconf_impl(repository_ctx):
    """Implementation of the local_mkl_autoconf repository rule."""

    if _enable_local_mkl(repository_ctx):
        # Symlink lib and include local folders.
        mkl_root = repository_ctx.os.environ[_TF_MKL_ROOT]
        mkl_lib_path = "%s/lib" % mkl_root
        repository_ctx.symlink(mkl_lib_path, "lib")
        mkl_include_path = "%s/include" % mkl_root
        repository_ctx.symlink(mkl_include_path, "include")
        mkl_license_path = "%s/license.txt" % mkl_root
        repository_ctx.symlink(mkl_license_path, "license.txt")
    else:
        # setup remote mkl repository.
        repository_ctx.download_and_extract(
            repository_ctx.attr.urls,
            sha256 = repository_ctx.attr.sha256,
            stripPrefix = repository_ctx.attr.strip_prefix,
        )

    # Also setup BUILD file.
    repository_ctx.symlink(repository_ctx.attr.build_file, "BUILD")
    
mkl_repository = repository_rule(
    implementation = _mkl_autoconf_impl,
    environ = [
        _TF_MKL_ROOT,
    ],
    attrs = {
        "build_file": attr.label(),
        "urls": attr.string_list(default = []),
        "sha256": attr.string(default = ""),
        "strip_prefix": attr.string(default = ""),
    },
)

可以看出在这里定义了repository_rule, attrs 里面定义了build_file (这里这个文件主要是 mkl.BUILD 文件,在mkl.BUILD文件内定义了相关的操作library)。 定义attrs ,就可以在implementation 的函数内部使用repository_ctx.attr.<attribute_name> 进行获取。而 repository_rule 的name,可以通过repository_ctx.name 得到。

这里需要注意,在xxx.bzl文件里,使用规则 mkl_repository 时,是使用了许多参数的,那么这些参数,就需要在定义规则 mkl_repository 时,有所体现,就必须声明这些参数的性质,所以,就需要在 repository_rule 里使用 attrs 参数,对  mkl_repository 中除了 name 参数以外的参数做出声明。

关于repository_ctx参数的使用,详见:repository_ctx - Bazel main

关于 attrs 都有哪些参数,详见:attr - Bazel 3.4.0

ok书接上文,进一步看一个BUILD:

load("@build_bazel_rules_apple//apple:ios.bzl", "ios_application")

objc_library(
    name = "universal_lib",
    srcs = [
        "universal/main.m",
        "universal/AppDelegate.m",
        "universal/ViewController.m",
    ],
    hdrs = [
        "universal/AppDelegate.h",
        "universal/ViewController.h",
    ],
    enable_modules = 1,
)

ios_application(
    name = "universal",
    bundle_id = "com.sunxxxx.universal",
    families = [
        "iphone",
        "ipad",
    ],
    minimum_os_version = "9.0",
    infoplists = [":universal/Info.plist"],
    visibility = ["//visibility:public"],
    deps = [
        ":universal_lib"
    ],
)

bundle_id属性是app的唯一标识符。

familier属性是支持iphone还是ipad还是都支持。

munimum_os_version属性是最低支持的系统版本。

infoplists属性是info.plist文件路径。

copts属性是指定编译选项,如 "-I<include-paths>"。

data属性是依赖的一种,不属于源码,不影响目标如何构建,但是目标在运行时可能依赖之。

关于BUILD的一些属性先扯这么多。

接下来,看看BUILD里的一些规则:

filegroup

为一组目标指定一个名字,你可以从其它规则中方便的引用这组目标。

Bazel鼓励使用filegroup,而不是直接引用目录。Bazel构建系统不能完全了解目录中文件的变化情况,因而文件发生变化时,可能不会进行重新构建。而使用filegroup,即使联用glob,目录中所有文件仍然能够被构建系统正确的监控。

filegroup(
    name = "exported_testdata",
    srcs = glob([
        "testdata/*.dat",
        "testdata/logs/**/*.log",
    ]),
)

要引用filegroup,只需要使用标签:

cc_library(
    name = "my_library",
    srcs = ["foo.cc"],
    data = [
        "//my_package:exported_testdata",
        "//my_package:mygroup",
    ],
)

test_suite

定义一组测试用例,给出一个有意义的名称,便于在特定时机  —— 例如迁入代码、执行压力测试 —— 时执行这些测试用例。

# 匹配当前包中所有small测试

test_suite(

    name = "small_tests",

    tags = ["small"],

)

# 匹配不包含flaky标记的测试

test_suite(

    name = "non_flaky_test",

    tags = ["-flaky"],

)

# 指定测试列表

test_suite(

    name = "smoke_tests",

    tests = [

        "system_unittest",

        "public_api_unittest",

    ],

)

alias

为规则设置一个别名:

filegroup(

    name = "data",

    srcs = ["data.txt"],

)

# 定义别名

alias(

    name = "other",

    actual = ":data",

)

config_setting

通过匹配以Bazel标记或平台约束来表达的“配置状态”,config_setting能够触发可配置的属性。

下面这个例子,匹配针对ARM平台的构建:

1

2

3

4

config_setting(

    name = "arm_build",

    values = {"cpu": "arm"},

)

下面的例子,匹配任何定义了宏FOO=bar的针对X86平台的调试(-c dbg)构建:

1

2

3

4

5

6

7

8

config_setting(

    name = "x86_debug_build",

    values = {

        "cpu": "x86",

        "compilation_mode": "dbg",

        "define": "FOO=bar"

    },

)

下面的库,通过select来声明可配置属性:

1

2

3

4

5

6

7

8

9

10

11

12

cc_binary(

    name = "mybinary",

    srcs = ["main.cc"],

    deps = select({

        # 如果config_settings arm_build匹配正在进行的构建,则依赖arm_lib这个目标

        ":arm_build": [":arm_lib"],

        # 如果config_settings x86_debug_build匹配正在进行的构建,则依赖x86_devdbg_lib

        ":x86_debug_build": [":x86_devdbg_lib"],

        # 默认情况下,依赖generic_lib

        "//conditions:default": [":generic_lib"],

    }),

select声明就像一个选项,就是说这里的依赖不会同时加载,而是匹配到了哪个,才加载哪个。

上面的描述不够形象,看这里:

cc_library(
    name = "multiplatform_lib",
    srcs = select({
        ":x86_mode": ["x86_impl.cc"],
        ":arm_mode": ["arm_impl.cc"]
    })
)
config_setting(
    name = "x86_mode",
    values = { "cpu": "x86" }
)
config_setting(
    name = "arm_mode",
    values = { "cpu": "arm" }
)

在属性值的设置中使用select()可以根据输入的config_setting的值来进行匹配。比如

bazel build :multiplatform_lib --cpu=arm

在执行bazel命令的时候,可以附带参数,那么这里的参数就由config_setting而来。换个说法,config_setting就是用来搞bazel命令里的参数的。

glob:

glob(include, exclude=[], exclude_directories=1)

Glob是一个辅助函数,用于在任何位置获得想要的文件列表。可以使用*通配符以及目录分隔符/,另外**表示递归通配符只能在目录分隔符/之间使用,比如"x/**/*.java" is valid, but “test**/testdata.xml” and “**.java” are both invalid. No other wildcards are supported.
select:

select(
    {conditionA: valuesA, conditionB: valuesB, ...},
    no_match_error = "custom message"
)

这也是一个辅助函数,可以使得rule的属性被配置,选取的方式通过读Bazel的命令行flag。

genrule

一般性的规则 —— 使用用户指定的Bash命令,生成一个或多个文件。使用genrule理论上可以实现任何构建行为,例如压缩JavaScript代码。但是在执行C++、Java等构建任务时,最好使用相应的专用规则,更加简单。

不要使用genrule来运行测试,如果需要一般性的测试规则,可以考虑使用sh_test。

genrule在一个Bash shell环境下执行,当任意一个命令或管道失败(set -e -o pipefail),整个规则就失败。你不应该在genrule中访问网络。

genrule(
    name = "foo",
    # 不需要输入
    srcs = [],
    # 生成一个foo.h
    outs = ["foo.h"],
    # 运行当前规则所在包下的一个Perl脚本
    cmd = "./$(location create_foo.pl) > \"$@\"",
    tools = ["create_foo.pl"],
)

再扯一个bazel的目录布局:

workspace-name>/                          # 工作空间根目录
  bazel-my-project => <...my-project>     # execRoot的符号链接,所有构建动作在此目录下执行
  bazel-out => <...bin>                   # outputPath的符号链接
  bazel-bin => <...bin>                   # 最近一次写入的二进制目录的符号链接,即$(BINDIR)
  bazel-genfiles => <...genfiles>         # 最近一次写入的genfiles目录的符号链接,即$(GENDIR)
 
 
 
/home/user/.cache/bazel/                  # outputRoot,所有工作空间的Bazel输出的根目录
  _bazel_$USER/                           # outputUserRoot,当前用户的Bazel输出的根目录
    install/
      fba9a2c87ee9589d72889caf082f1029/   # installBase,Bazel安装清单的哈希值
        _embedded_binaries/               # 第一次运行时从Bazel可执行文件的数据段解开的可执行文件或脚本
    7ffd56a6e4cb724ea575aba15733d113/     # outputBase,某个工作空间根目录的哈希值
      action_cache/                       # Action cache目录层次
      action_outs/                        # Action output目录
      command.log                         # 最近一次Bazel命令的stdout/stderr输出
      external/                           # 远程存储库被下载、链接到此目录
      server/                             # Bazel服务器将所有服务器有关的文件存放在此
        jvm.out                           # Bazel服务器的调试输出
      execroot/                           # 所有Bazel Action的工作目录
        <workspace-name>/                 # Bazel构建的工作树
          _bin/                           # 助手工具链接或者拷贝到此
          bazel-out/                      # outputPath,构建的实际输出目录
            local_linux-fastbuild/        # 每个独特的BuildConfiguration实例对应一个子目录
              bin/                        # 单个构建配置二进制输出目录,$(BINDIR)
                foo/bar/_objs/baz/        # 命名为//foo/bar:baz的cc_*规则的Object文件所在目录
                  foo/bar/baz1.o          # //foo/bar:baz1.cc对应的Object文件
                  other_package/other.o   # //other_package:other.cc对应的Object文件
                foo/bar/baz               # //foo/bar:baz这一cc_binary生成的构件
                foo/bar/baz.runfiles/     # //foo/bar:baz生成的二进制构件的runfiles目录
                  MANIFEST
                  <workspace-name>/
                    ...
              genfiles/                   # 单个构建配置生成的源文件目录,$(GENDIR)
              testlogs/                   # Bazel的内部测试运行器将日志文件存放在此
              include/                    # 按需生成的include符号链接树,符号链接bazel-include指向这里
            host/                         # 本机的BuildConfiguration
        <packages>/                       # 构建引用的包,对于此包来说,它就像一个正常的WORKSPACE 

这个可以了解一下。

接下来扯个别的, 叫 .bzl 文件:

其实,.bzl 文件是bazel的配置文件,这里就很惊喜了,来瞅瞅。

Bazel配置文件使用Starlark(原先叫Skylark)语言,具有短小、简单、线程安全的特点。

这种语言的语法和Python很类似,Starlark是Python2/Python3的子集。所以,他的写法与python非常类似。

这里特别说明下两者之间的不同之处:

不支持的特性说明
隐含字符串连接需要明确使用 + 操作符
链式比较操作符例如:1 < x < 5
class使用struct函数
import使用load语句
is使用==代替
以下关键字:while、yield、try、raise、except、finally 、global、nonlocal
以下数据类型:float、set
生成器、生成器表达式
lambda以及嵌套函数
绝大多数内置函数、方法
Starlark支持的数据类型包括:None、bool、dict、function、int、list、string,以及两种Bazel特有的类型:depset、struct。

给个例子看看:

# 定义一个数字
number = 18
 
# 定义一个字典
people = {
    "Alice": 22,
    "Bob": 40,
    "Charlie": 55,
    "Dave": 14,
}
 
names = ", ".join(people.keys())
 
# 定义一个函数
def greet(name):
  """Return a greeting."""
  return "Hello {}!".format(name)
# 调用函数
greeting = greet(names)
 
 
def fizz_buzz(n):
  """Print Fizz Buzz numbers from 1 to n."""
  # 循环结构
  for i in range(1, n + 1):
    s = ""
    # 分支结构
    if i % 3 == 0:
      s += "Fizz"
    if i % 5 == 0:
      s += "Buzz"
    print(s if s else i)

另外,具体在写BUILD的时候,可以设定变量。

COPTS = ["-DVERSION=5"]
 
cc_library(
  name = "foo",
  copts = COPTS,
  srcs = ["foo.cc"],
)
 
cc_library(
  name = "bar",
  copts = COPTS,
  srcs = ["bar.cc"],
  deps = [":foo"],
)

但是,如果要声明跨越多个BUILD文件共享的变量,必须把变量放入.bzl文件中,然后通过load加载bzl文件。

以上内容为对bazel的一些使用

====================================================================

一下内容为bazel在python工程中的使用

一般情况下,我们需要在WORKSPACE中添加pip的依赖项。这里需要使用 pip_install(..) 方法。通过这个方法他将创建包含第三方库的中央外部存储库。

load("@rules_python//python:pip.bzl", "pip_install")

# Create a central external repo, @my_deps, that contains Bazel targets for all the
# third-party packages specified in the requirements.txt file.
pip_install(
   name = "my_deps",
   requirements = "//path/to:requirements.txt",
)

使用pip相关的包,如果要从储存库中提取相关的包,则必须借助标签 py_library(..) 表示引用内容,同时,还需要使用中央仓库//:requirements.bzl文件中定义的函数requirement(),来将 pip 包名称映射到标签。

load("@my_deps//:requirements.bzl", "requirement")

py_library(
    name = "mylib",
    srcs = ["mylib.py"],
    deps = [
        ":myotherlib",
        requirement("some_pip_dep"),
        requirement("another_pip_dep"),
    ]
)

这里提一句,文中的 rules_python 是bazel的一个第三方库,在上面的用法中,相当于requirements.txt 中的依赖包会下载到外部储存库,该库的标签就是我们给的name即上述的my_deps,同时也会生成一个 requirements.bzl 文件,该文件中会有 requirement方法。

同样的,rules_python这个第三方库中,还有个叫 pip_import 的方法。用法类似:

load("@rules_python//python:pip.bzl", "pip_import")

pip_import(
    name = "py2_common_deps",
    requirements = "//:support/bazel/external/py2/requirements.txt",
)

load("@py2_buildapi_deps//:requirements.bzl", "requirement")

py_library(
    name = "django",
    deps = [requirement("django")],
)

这里注意,经过py_library处理出来的东西,是二进制文件。

他的实际用法:

这是一个BUILD文件:

package(default_visibility = ["//visibility:public"])

load("//support/bazel/rules/deploy:deploy.bzl", "fab_deploy_service", "py2_service")
load("@py2_buildapi_deps//:requirements.bzl", "requirement")

py_library(
    name = "django",
    deps = [requirement("django")],
)

py2_service(
    name = "buildapi",
    srcs = glob(["**/*.py"]),
    bin_path = ".binary.runfiles",
    data = [
        "restapi/templates/doc.xhtml",
        "restapi/templates/general_help.xhtml",
        "static/error-404.html",
        "static/index.html",
    ],
    imports = ["."],
    main = "manage.py",
    port = "80",
    deps = [
        ":django",
        "//lib/build/utils:global_config",
        "//support/bazel/external/py2:django-vhealth-check",
        "//support/bazel/external/py2:gunicorn",
        "//support/bazel/external/py2:httplib2",
        "//support/bazel/external/py2:netaddr",
        "//support/bazel/external/py2:progressbar",
        "//support/bazel/external/py2:psycopg2",
        "//support/bazel/external/py2:python-dateutil",
        "//support/bazel/external/py2:pyyaml",
        "//support/bazel/external/py2:requests",
        "//support/bazel/external/py2:simplejson",
        "//support/bazel/external/py2:six",
    ],
)

当然,对于py_library(..) 的用法也可以打包来用:

py_library(
    name = "flake8",
    deps = [
        requirement("configparser"),
        requirement("contextlib2"),
        requirement("enum34"),
        requirement("flake8"),
        requirement("functools32"),
        requirement("importlib-metadata"),
        requirement("mccabe"),
        requirement("pycodestyle"),
        requirement("pyflakes"),
        requirement("pathlib2"),
        requirement("scandir"),
        requirement("six"),
        requirement("typing"),
        requirement("zipp"),
    ],
)

总体来说,bazel的用法不是很难,只是相关的资料实在太少了,我们对bazel的许多应用实在是太陌生了,如果想了解bazel的语法还是要多看官方文档,只不过官方文档,缺乏实例,确实有时候看了,还是不知道怎么用。Bazel overview - Bazel 4.2.0

bazel简介 | 万广鲁

Bazel 构建工具介绍_tomcat的专栏-CSDN博客_bazel是什么

Google软件构建工具Bazel FAQ - 生栋 - 博客园

Bazel入门(3. 简单Demo) - 知乎

绿色记忆:Bazel学习笔记  详细的介绍了bazel的一些信息

bazel 工具函数_TH_NUM的博客-CSDN博客  一些配置的信息

bazel 链接_[Bazel]自定义规则实现将多个静态库合并为一个动态库或静态库_weixin_39612733的博客-CSDN博客https://docs.bazel.build/versions/4.2.2/    这是bazel官方文档

  • 0
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值