社区所有版块导航
Python
python开源   Django   Python   DjangoApp   pycharm  
DATA
docker   Elasticsearch  
aigc
aigc   chatgpt  
WEB开发
linux   MongoDB   Redis   DATABASE   NGINX   其他Web框架   web工具   zookeeper   tornado   NoSql   Bootstrap   js   peewee   Git   bottle   IE   MQ   Jquery  
机器学习
机器学习算法  
Python88.com
反馈   公告   社区推广  
产品
短视频  
印度
印度  
Py学习  »  docker

构建最小 tomcat docker 镜像

陈顺吉 • 4 年前 • 337 次点击  
阅读 6

构建最小 tomcat docker 镜像

所谓的“最小” tomcat 镜像是相对的,它的大小取决于如下几点:

  1. 基础镜像是否使用 glibc,也就是是否使用 alpine 作为基础镜像;
  2. 使用 jdk 还是只使用 jre 作为 tomcat 运行环境;
  3. 使用 openjdk 还是 oracle jdk。

上述的条件决定了 tomcat 镜像的大小。

总所周知,alpine 算是基础镜像中最小的了,它还自带包管理器,但是它的缺点也同样明显,就是它没有使用 glibc,因此会带来兼容性的问题。

本文会使用基于 glibc 的 distroless 作为基础镜像,然后使用 oracle jdk8 + tomcat8,最终镜像大小为 171M。这个大小是有一定浮动空间的,因为即使是使用 oracle jdk8,不同的小版本之间大小也会相差很大。

以下是和官方镜像的一个对比:

镜像名 java 大小 镜像层 glibc
tomcat:8 openjdk jre 463MB 31 glibc
tomcat:8-alpine openjdk jre 106MB 25 muslc
tomcat:8-slim openjdk jre 223MB 29 glibc
本文编译使用 oracle jdk 171M 3 glibc

可以看出 alpine 的优势非常大,它足够小,但是它使用的是 jre,且不是 glibc。如果你程序编译是针对 glibc,那么运行起来会有问题。我也不确定我公司的开发是否对 glibc 有强依赖,但是不敢冒险,而且 alpine 对我来说并没有什么优势。

你可以看到本文编译使用的 tomcat 的镜像层只有 3 层,你可能会吃惊于它的层数,等你看完你就会明白了,因为这个镜像并不是通过 dockerfile 构建出来的。

因为我已经将编译好的镜像上传到了 dockerhub,你可以直接使用 docker run maxadd/tomcat:8-jdk8-distroless 运行查看,或者使用 dive 查看其构成。

缘由

当你选择 tomcat 镜像时,其实要考虑很多东西:

  • 是否存在必须的命令;
  • java 是否能够满足需要;
  • 是否足够灵活定制;
  • 是否足够安全(命令和库文件足够少);
  • 是否足够小。

真当你需要用的时候,发现官方镜像使用起来或多或少都有些不顺手,总不是那么令人满意。对我而言,最重要的是官方没有 oracle jdk 的镜像提供,因为狗日的 oracle 要对 oracle jdk 收费。虽然也有人自己提供了基于 oracle jdk 的版本,但是镜像实在太大。

总之基于这样或那样的原因,我准备手动创建一个自己的 tomcat 镜像,这让我将目光移向了 distroless 镜像。因为 distroless 镜像是所有基于 glibc 中最小的,只有 16M,里面只包含一个二进制程序应有的最基础的运行环境,没有一个命令提供,包括 shell。

当我准备使用它作为基础镜像来使用的时候,我发现我无法通过它来达成我的目标,因为它里面没有任何命令,当使用 dockerfile 进行构建的时候,我只能通过 ADD 往里面添加文件,但是这样一来,我每添加一个文件都会创建一个镜像层,而我要添加的文件并不少。。

于是我将目光又放到了构建 distroless 的工具 bazel,由于 distroless 是由 bazel 构建的,我有预感它能够实现我心中所想,于是开始研究它。事实证明,我的方向是对的,bazel 完全能够满足我的所有需求。

有了这样的前提之后,我开始规划我的镜像:

  • 要安装 bash,要通过它来设置 jvm 参数;
  • 要安装一些基本的命令,例如 cat、echo 之类;
  • 要安装 jdk,因为需要用到 jdk 中的一些命令;
  • 能不要的命令都不要,除了可以减少镜像的大小,还能提升安全性;
  • 需要自己写脚本启动 tomcat,catalina.sh 脚本中涉及到的命令太多,没必要使用。

下面一步步实现上面的需求。

安装 bash

我的做法是建立一个目录,作为根目录,里面存放需要复制到 distroless 镜像中的所有文件。文件需要放在哪,那就在该目录下创建对应的目录。最终将这个“根目录”打包,并在 distroless 的根目录解压,就能将所有文件一步到位。这也是镜像层只有 3 层的原因,其中 2 层是 distroless 自带的。

第一步是安装 bash,安装 bash 的目的是为了执行脚本,同样也是为了能够登录上去执行 jmap、jstack 之类的命令。前面已经提到了,我这里使用 distroless 作为基础镜像,且因为 distroless 使用的是 debian 的库文件,因此我们可以将 debian 中的命令直接复制下来使用。

Linux 中命令的运行不仅需要命令本身,还需要它依赖的库文件,库文件通过 ldd 命令查看。因此我们不仅需要复制命令本身,还得复制它所需的库文件。

首先启动一个 debian 容器:

# docker run -it --name debian debian:stretch /bin/bash
复制代码

查看 bash 所依赖的库文件:

root@45104fade344:/# ldd /bin/bash
	linux-vdso.so.1 (0x00007ffda05b1000) # 这一行无需理会
	libtinfo.so.5 => /lib/x86_64-linux-gnu/libtinfo.so.5 (0x00007fdf4532f000)
	libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007fdf4512b000)
	libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fdf44d8c000)
	/lib64/ld-linux-x86-64.so.2 (0x0000564175f7f000)
复制代码

上面总共有四个库文件,我们需要将其复制下来,需要注意的是,上面显示的库文件有可能是软链接文件。

我们先在宿主机上创建所谓的“根目录”,这里取名为 debian_files。

然后在这个目录下创建 lib/x86_64-linux-gnubinlib64 这几个目录。

mkdir -p debian_files/{lib/x86_64-linux-gnu,bin,lib64}
复制代码

接着使用 docker cp 命令将 debian 容器中的命令和库文件复制到我们创建的对应的目录下。

docker cp debian:/bin/bash debian_files/bin/
# -L 表示复制链接文件指向的实际文件
docker cp -L debian:/lib/x86_64-linux-gnu/libtinfo.so.5 debian_files/lib/x86_64-linux-gnu/
docker cp -L debian:/lib/x86_64-linux-gnu/libdl.so.2 debian_files/lib/x86_64-linux-gnu/
docker cp -L debian:/lib/x86_64-linux-gnu/libc.so.6 debian_files/lib/x86_64-linux-gnu/
复制代码

通过这种方式可以将你想要使用的其他命令都拷贝下来,这里就不一一演示了。

准备 jdk

因为这里我打算使用 oracle jdk 而非 openjdk,所以先从 oracle 官网上将 jdk8 下载下来。我这里下载的是 162 版本,没有别的原因,只是因为公司使用的是这个版本。

注意下载 tar 包。因为我准备将其放置在镜像的 /opt 目录下,所以需要在 debian_files 下面创建一个 opt 目录:

# ls debian_files/opt
jdk1.8.0_162
复制代码

完整的 jdk 总共有 371M,里面有很多我们用不到的东西,先将其都删除掉:

# cd debian_files/opt/jdk1.8.0_162
# rm -rf *src.zip \
lib/missioncontrol \
lib/visualvm \
lib/*javafx* \
jre/lib/plugin.jar \
jre/lib/ext/jfxrt.jar \
jre/bin/javaws \
jre/lib/javaws.jar \
jre/lib/desktop \
jre/plugin \
jre/lib/deploy* \
jre/lib/*javafx* \
jre/lib/*jfx* \
jre/lib/amd64/libdecora_sse.so \
jre/lib/amd64/libprism_*.so \
jre/lib/amd64/libfxplugins.so \
jre/lib/amd64/libglass.so \
jre/lib/amd64/libgstreamer-lite.so \
jre/lib/amd64/libjavafx*.so \
jre/lib/amd64/libjfx*.so
复制代码

删除之后,只剩 153M😆。然后给 jdk 目录建立一个软链接:

# ln -s jdk1.8.0_162 java
复制代码

jdk 依赖

jdk 的 bin 目录下有很多我们需要的命令,这些命令也有依赖的库文件。虽然 jdk 是我们下载而并不是我们安装,但是由于 jdk 中的命令都是编译好的二进制文件,只要满足内核和 glibc 的需求它们就可以运行。

当然,内核和 glibc 只是硬性要求,软性要求就是它们依赖的库文件。由于我的宿主机 Linux 的 CentOS7 而非 debian,因此我们需要将 jdk 挂载到 debian 镜像中,在镜像中使用 ldd 命令来查看 jdk 中命令所依赖的库文件有哪些。

# cd debian_files/
# docker run -it --name debian -v `pwd`/java:/opt debian:stretch /bin/bash
复制代码

将 jdk 都挂载进容器的 /opt 目录,注意都使用绝对路径。然后你就可以查看 /opt/bin 和 /opt/jre/bin 下面的命令所依赖的库文件有哪些了。

它们所依赖的库文件中,貌似只需要再 cp 一个 /lib/x86_64-linux-gnu/libpthread.so.0 即可。注意这个文件是一个链接文件,你通过 docker cp -L 下载下来之后并不是这个名,你得使用软链接弄成一样的。

接下来就是准备 tomcat 了。

准备 tomcat

tomcat 里面没有任何命令,它本身也是依赖 java 启动的,因此它没有任何的库文件依赖。直接去官网下一个就行,我这里使用的是 apache-tomcat-8.0.36

我这里同样就之放在 opt 目录下:

# ls debian_files/opt/
apache-tomcat-8.0.36  java  jdk1.8.0_162
复制代码

tomcat 的启动我们是通过 catalina.sh 进行的,但是由于它里面用到的命令太多,且我们只需要启动 tomcat,不需要停止或者重启之类的,所以我们完全可以不用 catalina.sh,只需要它启动所需的 java 参数就行。拿到这些参数之后,我们直接传递给 java 后同样可以直接启动。

甚至我怀疑只要在 Linux 服务器上直接执行就行,没必要挂载到 debian 中。

这个参数其实很好获得,你将 tomcat 和 jdk 同时映射到 debian 容器中,然后定义好 JAVA_HOME,就可以通过 sh -x catalina.sh run 看到它最终启动的参数了。

我这里就不演示具体的操作了,直接将它的参数贴出来。当你什么 JAVA 参数都没有设定时,它的启动参数如下:

/opt/java/bin/java -Djava.util.logging.config.file=/opt/tomcat/conf/logging.properties -Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager -Djdk.tls.ephemeralDHKeySize=2048 -Djava.endorsed.dirs=/opt/tomcat/endorsed -classpath /opt/tomcat/bin/bootstrap.jar:/opt/tomcat/bin/tomcat-juli.jar -Dcatalina.base=/opt/tomcat -Dcatalina.home=/opt/tomcat -Djava.io.tmpdir=/opt/tomcat/temp org.apache.catalina.startup.Bootstrap start
复制代码

你如果想要增加 jvm 参数,随便往里面插就行,你定义在 catalina.sh 中的类似与 JAVA_OPS 等,最终都会转换成 java 参数。不过,貌似 jvm 中堆的参数之前要加上 -server

 -server -Xmx128M -Xms128M
复制代码

jmx 监控:

-Djava.rmi.server.hostname=`hostname -I` -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=11111 -Dcom.sun.management.jmxremote.authenticate=true -Dcom.sun.management.jmxremote.password.file=/etc/jmxremote.password -Dcom.sun.management.jmxremote.access.file=/etc/jmxremote.access -Dcom.sun.management.jmxremote.ssl=false
复制代码

gc log:

-Xloggc:/opt/tomcat/logs/gc.log
复制代码

最终你可以定义一个脚本用来启动 tomcat:

#!/bin/bash

/opt/java/bin/java -Djava.util.logging.config.file=/opt/tomcat/conf/logging.properties -Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager -Djdk.tls.ephemeralDHKeySize=2048 -Djava.endorsed.dirs=/opt/tomcat/endorsed -classpath /opt/tomcat/bin/bootstrap.jar:/opt/tomcat/bin/tomcat-juli.jar -Dcatalina.base=/opt/tomcat -Dcatalina.home=/opt/tomcat -Djava.io.tmpdir=/opt/tomcat/temp org.apache.catalina.startup.Bootstrap -server -Xmx128M -Xms128M -Xloggc:/opt/tomcat/logs/gc.log -Djava.rmi.server.hostname=`hostname -I` -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=11111 -Dcom.sun.management.jmxremote.authenticate=true -Dcom.sun.management.jmxremote.password.file=/etc/jmxremote.password -Dcom.sun.management.jmxremote.access.file=/etc/jmxremote.access -Dcom.sun.management.jmxremote.ssl=false start
复制代码

这个脚本你随便放在哪,只要 CMD 指定执行它就行。你还可以写一些简单的逻辑,以便通过环境变量来灵活的控制 jvm 的堆大小,这个今后会提到。

打包

确保所有你需要的文件都在“根目录”下都准备完毕后,我们就可以对该目录(这里是 debian_files)进行打包了,打包的格式必须是 tar/tar.gz/tar.xz 等。

有一点需要注意,使用 tar 命令无法完成这种操作(或许是我不知道方法?),因为 tar 必须在它上级目录打包,但是这样一来 tar 中就包含 debian_files 这个目录名了。这就造成在 distroless 中解压后里面文件都不会直接放在根下,而是还在 debian_files 下。

既然 tar 不行,那就使用 Python,使用 Python 进行打包。你不会 Python 不要紧,跟着我走就不会出任何问题。

首先启动 python3 镜像,注意将 debian_file 映射到 python3 的 /opt 目录下。

docker run -it -v /root/debian_files:/opt python:3.6
复制代码

然后执行下面这些代码:

import tarfile, os
os.chdir("/opt")
tar = tarfile.open("/tmp/x.tar", "w")
for i in os.listdir("."):
  tar.add(i)

tar.close()
复制代码

这会将 debian_files 中的所有文件都打包到容器中的 /tmp/x.tar 文件中。然后使用 docker cp 将其复制到宿主机的 /tmp 下,留作后用。

ok,准备工作都已完成,停止 Python 容器后开始安装 bazel,然后将 /tmp/x.tar 解压到 distroless 镜像中。

安装 bazel

bazel 是 Google 推出的编译工具,用于将各种语言的源代码编译成二进制文件,至于有什么优势我没有具体了解 😜。从这点上来看,编译 docker 镜像只是它附带的功能,事实也确实如此,它并非原生支持 docker 镜像的编译。

关于它的基本概念和基本使用在文章的后面,有需要的可自行查阅。

使用 bazel 编译 docker 镜像的一大优势就是你甚至无需安装 docker。

首先安装 bazel:

# cat >/etc/yum.repos.d/bazel.repo <<'EOF'
[vbatts-bazel]
name=Copr repo for bazel owned by vbatts
baseurl=https://copr-be.cloud.fedoraproject.org/results/vbatts/bazel/epel-7-$basearch/
type=rpm-md
skip_if_unavailable=True
gpgcheck=1
EOF

# yum install bazel
复制代码

bazel 原生并不支持编译 docker 镜像,不过 GitHub 上面有扩展规则可以帮你完成。

首先创建一个目录,以它作为 WORKSPACE:

# mkdir bazel-test
复制代码

然后定义 WORKSPACE,也就是外部依赖。其实我们依赖就是 docker 规则,因此加载它就好。

# cd bazel-test
# vim WORKSPACE
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")

# 加载 docker 规则
http_archive(
    name = "io_bazel_rules_docker",
    sha256 = "aed1c249d4ec8f703edddf35cbe9dfaca0b5f5ea6e4cd9e83e99f3b0d1136c3d",
    strip_prefix = "rules_docker-0.7.0",
    urls = ["https://github.com/bazelbuild/rules_docker/archive/v0.7.0.tar.gz"],
)

# 这一步貌似是和 docker registry 交互用的?具体我也没搞懂,不过不影响使用
load(
    "@io_bazel_rules_docker//repositories:repositories.bzl",
    container_repositories = "repositories",
)
container_repositories()

load(
    "@io_bazel_rules_docker//container:container.bzl",
    "container_pull",
)

# pull 基础镜像,以便在 BUILD 中引用
container_pull(
  name = "base",
  registry = "gcr.io",
  repository = "distroless/base",
)
复制代码

因为 gcr.io 被墙了,我将镜像上传到了 dockerhub 一份,有需要的可以使用我的:

docker pull maxadd/distroless-base
复制代码

当然如果你不放心的话,也可以找找国内的加速平台(搜索“gcr.io加速”)。当然,如果镜像改了,上面 WORKSPACE 文件中的内容也要改。

编译镜像

定义好依赖之后,我们在 WORKSPACE 下创建一个包,用来编译镜像。

# mkdir test
# cp /tmp/x.tar . # 将 tar 包复制到当前目录
# vim BUILD
load(
    "@io_bazel_rules_docker//container:container.bzl",
    "container_image",
)

container_image(
    name = "app",
    base = "@base//image",
    tars = ["x.tar"], # 解压 tar 包
    ports = ["8080"], # 有其他端口可以往里添加
    env = {
        "PATH": "/bin:/opt/java/bin:/opt/java/jre/bin",
        "JAVA_HOME": "/opt/java",
    },
    cmd = ["/opt/tomcat/start.sh"] # 启动镜像后执行的命令
)
复制代码

开始编译:

# cd ..
# bazel build //test:app
复制代码

//test:app 是一个 target,后面会提到,使用它来指定我们要编译哪个目录。test 指的是我们创建的 test 目录,因为它下面有 BUILD 文件,所以它也称为一个包。app 则是指 BUILD 文件中 container_image 下面 name 的值,通过 //test:app 就能定位到它的位置。

我是在国外 vps 上编译,不知道在国内环境会不会有影响。。

编译完成后,在当前目录下执行:

bzael-bin/tese/app
复制代码

然后你可以使用通过 docker images 看到 bazel/test 这个镜像了,注意它的 tag 是 app 而非 latest。

启动镜像

启动它和其他镜像一样:

docker run --cap-add=SYS_PTRACE bazel/test:app
复制代码

之所以加上 --cap-add=SYS_PTRACE 是为了能够使用 jps 等命令。

好了,操作部分到此结束,希望你能从其中学到一些东西,这也是我希望的。下面是 bazel 的一些简单的介绍,完全是我看了官方文档之后写的一些简单的理解,很粗糙,也没时间仔细写。真正有需要的话,还是建议直接阅读官方文档。

Bazel

构建工具,用来编译各类语言的源代码,除此之外,它还可以构建 docker 镜像,也就是 distroless 系列。

基础概念

使用之前,了解其基础概念。

Workspace

workspace 是一个目录,包含你想要编译的源文件,以及指向包含编译输出目录的链接文件。每个 workspace 中包含一个名为 WORKSPACE 的文件,这个文件可以为空,也可以包含此次编译需要的外部依赖。

Workspace 是一个顶级的结构,下面可以有多个包。

Packages

工作空间中代码组织的主要单元是包。包是相关文件的集合,以及它们之间的依赖关系的规范。

包所在的目录下必须存在名为 BUILDBUILD.bazel 的文件。包中包含其目录中的所有文件,以及其下的所有子目录,除了那些本身包含 BUILD 文件的子目录。

包名的命名格式为 my/app,下面的示例中有 my/app 和其子包 my/app/tests 这两个。注意 my/app/data 不是包,但是它属于 my/app 这个包。

src/my/app/BUILD
src/my/app/app.cc
src/my/app/data/input.txt
src/my/app/tests/BUILD
src/my/app/tests/test.cc
复制代码

Targets

包是容器,包中的元素称为 targets。大部分的 targets 分为两种:files 和 rules。除此之外,还有 package groups,不过很少使用。

files 进一步分为两个种类:源文件和生成文件。源文件通过规则转换成生成文件。

第二种主要的 target 是 rule,也就是规则。源代码的编译就是通过规则进行。规则既可以使用源代码作为输入,也可以使用生成文件作为输入,因此你可以自己构成一个构建链。

一个规则的输入也许包含其他规则。

Labels

target 的名称称为它的标签。一个标签的示例:

//my/app/main:app_binary
复制代码

每个标签由两部分构成:包的名称(my/app/main)和 target 的名称(app_binary)。每个标签唯一的标识一个 target。

标签有时还会以另一种形式出现,当冒号 : 和 target 名称都省略时,target 名称会被认为和包名中最后一个单词相同。因此下面两个 target 是等价的:

//my/app
//my/app:app
复制代码

短标签名 //my/app 和包名并不冲突,因为标签名以 // 开头,而包名却不会。因此 my/app 是一个包,并且包含 target //my/app

在一个 BUILD 文件中,标签的包名部分也许会被省略,冒号也同样如此。因此,在一个 my/app 包的 BUILD 文件中(当然这个 BUILD 的标签名为 //my/app:BUILD),下面几种标签的标签形式都是相同的:

//my/app:app
//my/app
:app
app
复制代码

有一个惯例,对于普通文件冒号会被省略,但是规则文件不这样,不过这不重要。

同样,在 BUILD 文件中,属于该包的文件可以直接使用文件名进行引用。

generate.cc
testdata/input.txt
复制代码

不过在其他包中,或者在命令行,你必须将其标签写全,比如 //my/app:generate.cc。即使这个文件在子包中。

命名规范

为了不和 shell 一些保留字符冲突,因此标签名有意做了严格限制。

包名是一个包含 BUILD 文件的目录的名称,相对于顶级的源文件树。

项目名中不能使用 -

Rules

规则指定输入和输出之间的关系,以及构建输出的步骤。规则可以是许多不同的 kinds 或 classes 的一种。

每个规则都有一个名称,通过 name 属性指定。名称必须是一个有效的 target 名称,如命名规范中所述。

有些场景中,名称意义不大;而在另外一些场景中,名称是有意义的,比如在 *_binary*_test 规则中,下面例子中的 name 就确定了编译后二进制文件的名称。

cc_binary(
    name = "my_app",
    srcs = ["my_app.cc"],
    deps = [
        "//absl/base",
        "//absl/strings",
    ],
)
复制代码

每个规则都有很多属性,每个属性也有各自的类型。

BUILD 文件

每个包下面必须存在 BUILD 文件,文件中的内容被 Starlark 语言按顺序解释。

为了鼓励将代码和数据进行分离,BUILD 文件中不能定义函数、for 和 if(列表推导中可以使用 if),函数应该定义在 .bzl 文件中。另外,*args**kwargs 也不允许出现在 BUILD 文件中,你需要明确的给出参数列表。

BUILD 文件使用 Latin-1 字符集进行解释,因此不要在其中使用中文。

load

Bazel 的扩展是以 .bzl 结尾的文件,使用 load 语句用来从一个扩展中导入 symbol(这个 symbol 是扩展中的一个结构)。

load("//foo/bar:file.bzl", "some_library")
复制代码

这个代码会加载 foo/bar/file.bzl 文件,然后将 some_library 添加到当前环境。它可以用来加载新的规则、函数或者常量。多个 symbols 可以使用参数同时导入,参数不能是变量,必须是字符串,且 load 必须顶格写,也就是它不能出现在函数中。

load 的第一个参数是 .bzl 文件的标签名,如果是一个相对标签(使用 : 开头),那么它会被相对于包含当前 bzl 文件的包(而不是目录)进行解析。

load 还支持别名:

load("//foo/bar:file.bzl", library_alias = "some_library")
复制代码

您可以在一个 load 语句中定义多个别名。此外,参数列表可以包含别名和常规符号名称。以下示例完全合法(请注意何时使用引号)。

load(":my_rules.bzl", "some_rule", nice_alias = "some_other_rule")
复制代码

.bzl 文件中,以 _ 开头的 symbol 不会导出,也无法从其他文件加载。

外部依赖

外部依赖定义在 workspace 目录下的 WORKSPACE 文件中。只要你在 WORKSPACE 文件中定义好你想使用的外部依赖,那么你就可以在该 workspace 下面包的 BUILD 文件中通过外部依赖的名称来引用它。

比如,有下面两个项目在文件系统上:

/
  home/
    user/
      project1/
        WORKSPACE
        BUILD
        srcs/
          ...
      project2/
        WORKSPACE
        BUILD
        my-libs/
复制代码

假如 project1 想要依赖一个定义在 /home/user/project2/BUILD 中的 target :foo,那么可以在 project1/BUILD 中添加 @project2//:foo

外部依赖除了可以是其他的 workspace,还可以是其他文件系统上的文件,以及可以是从互联网上下载的文件,你甚至可以自定义 Repository Rules 来提供更复杂的行为。

WORKSPACE 文件的语法和 BUILD 文件相同,不过允许更复杂的规则集。

Bazel 支持以下的外部依赖:

  • 其他 Bazel 项目
  • 非 Bazel 项目
  • 外部包

依赖其他 Bazel 项目

可以使用 local_repository、 git_repositoryhttp_archive

比如引用当前文件系统中的其他 Bazel 项目:

local_repository(
    name = "coworkers_project",
    path = "/path/to/coworkers-project",
)
复制代码

如果 coworkers-project 项目中有一个 target //foo:bar,你可以在 BUILD 中通过 @coworkers_project//foo:bar 来引用。

依赖非 Bazel 项目

使用 new_ 开头的规则,比如 new_local_repository 可以让你从非 Bazel 项目中创建 target。

假设你正在定义 my-project/ 项目,但是这个项目会用到 coworkers-project/ 项目生成的一个 .so 文件,你可以在 my_project/WORKSPACE 文件中这么定义:

new_local_repository(
    name = "coworkers_project",
    path = "/path/to/coworkers-project",
    build_file = "coworker.BUILD",
)
复制代码

build_file 指定一个 BUILD 文件去覆盖已存在的项目,coworker.BUILD 文件内容如下:

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

然后你就可以在你项目的 BUILD 文件中通过 @coworkers_project//:some-lib 进行引用了。

Python社区是高质量的Python/Django开发社区
本文地址:http://www.python88.com/topic/33959
 
337 次点击