Scala 规则教程的回归:执行

一则或许对你有用的小广告

欢迎加入小哈的星球 ,你将获得:专属的项目实战 / Java 学习路线 / 一对一提问 / 学习打卡/ 赠书活动

目前,正在 星球 内带小伙伴们做第一个项目:全栈前后端分离博客项目,采用技术栈 Spring Boot + Mybatis Plus + Vue 3.x + Vite 4手把手,前端 + 后端全栈开发,从 0 到 1 讲解每个功能点开发步骤,1v1 答疑,陪伴式直到项目上线,目前已更新了 204 小节,累计 32w+ 字,讲解图:1416 张,还在持续爆肝中,后续还会上新更多项目,目标是将 Java 领域典型的项目都整上,如秒杀系统、在线商城、IM 即时通讯、权限管理等等,已有 870+ 小伙伴加入,欢迎点击围观

这建立在本教程的 第一部分 之上。在这篇文章中,我们将使规则实际生成一个可执行文件。

scalac 捕获输出

上次教程结束时,我们调用了 scalac,但忽略了结果:


 (cd /private/var/tmp/_bazel_kchodorow/92df5f72e3c78c053575a1a42537d8c3/blerg && \
  exec env - \
  /bin/bash -c 'external/scala/bin/scalac HelloWorld.scala; echo '\''blah'\'' > bazel-out/local_darwin-fastbuild/bin/hello-world.sh')

如果您查看操作运行的目录(在我的例子中为 /private/var/tmp/_bazel_kchodorow/92df5f72e3c78c053575a1a42537d8c3/blerg ),您可以看到创建了 HelloWorld.class HelloWorld$.class 。这个目录称为 执行根目录 ,它是 bazel 执行构建操作的地方。 Bazel 为源代码、执行构建操作和输出文件 ( bazel-out/ ) 使用单独的目录树。文件不会从执行根移动到输出树,除非我们告诉 Bazel 我们需要它们。

我们希望我们编译的 scala 程序以 bazel-out/ 结束,但是有一个小问题。对于像 Java(和 Scala)这样的语言,单个源文件可能包含内部类,这些内部类会导致多个 .class 文件通过单个编译操作生成。 Bazel 在运行操作之前无法知道将生成多少类文件。但是,Bazel 要求每个操作都提前声明其输出。解决这个问题的方法是打包 .class 文件并将生成的存档作为构建输出。

在此示例中,我们将把 .class 文件添加到 .jar 中。让我们将其添加到输出中,现在应该如下所示:


 (cd /private/var/tmp/_bazel_kchodorow/92df5f72e3c78c053575a1a42537d8c3/blerg && \
  exec env - \
  /bin/bash -c 'external/scala/bin/scalac HelloWorld.scala; echo '\''blah'\'' > bazel-out/local_darwin-fastbuild/bin/hello-world.sh')

impl 函数中,我们的命令变得有点复杂,因此我将其更改为一个命令数组,然后将它们加入操作中的“\n”:


 (cd /private/var/tmp/_bazel_kchodorow/92df5f72e3c78c053575a1a42537d8c3/blerg && \
  exec env - \
  /bin/bash -c 'external/scala/bin/scalac HelloWorld.scala; echo '\''blah'\'' > bazel-out/local_darwin-fastbuild/bin/hello-world.sh')

这将编译 src,找到所有的 .class 文件,并将它们添加到输出 jar。如果我们运行它,我们会得到:


 (cd /private/var/tmp/_bazel_kchodorow/92df5f72e3c78c053575a1a42537d8c3/blerg && \
  exec env - \
  /bin/bash -c 'external/scala/bin/scalac HelloWorld.scala; echo '\''blah'\'' > bazel-out/local_darwin-fastbuild/bin/hello-world.sh')

我们先看一下 hello-world.jar 包含的内容:


 (cd /private/var/tmp/_bazel_kchodorow/92df5f72e3c78c053575a1a42537d8c3/blerg && \
  exec env - \
  /bin/bash -c 'external/scala/bin/scalac HelloWorld.scala; echo '\''blah'\'' > bazel-out/local_darwin-fastbuild/bin/hello-world.sh')

看起来不错!但是,我们实际上无法运行这个 jar,因为 java 不知道主类应该是什么:


 (cd /private/var/tmp/_bazel_kchodorow/92df5f72e3c78c053575a1a42537d8c3/blerg && \
  exec env - \
  /bin/bash -c 'external/scala/bin/scalac HelloWorld.scala; echo '\''blah'\'' > bazel-out/local_darwin-fastbuild/bin/hello-world.sh')

java_binary 规则 类似,让我们向 scala_binary 添加一个 main_class 属性并将其放入 jar 的清单中。将 'main_class' : attr.string(), 添加到 scala_binary attrs 并将 cmd 更改为以下内容:


 (cd /private/var/tmp/_bazel_kchodorow/92df5f72e3c78c053575a1a42537d8c3/blerg && \
  exec env - \
  /bin/bash -c 'external/scala/bin/scalac HelloWorld.scala; echo '\''blah'\'' > bazel-out/local_darwin-fastbuild/bin/hello-world.sh')

请记住更新您的实际 BUILD 文件以添加 main_class 属性:


 (cd /private/var/tmp/_bazel_kchodorow/92df5f72e3c78c053575a1a42537d8c3/blerg && \
  exec env - \
  /bin/bash -c 'external/scala/bin/scalac HelloWorld.scala; echo '\''blah'\'' > bazel-out/local_darwin-fastbuild/bin/hello-world.sh')

现在构建和运行为您提供:


 (cd /private/var/tmp/_bazel_kchodorow/92df5f72e3c78c053575a1a42537d8c3/blerg && \
  exec env - \
  /bin/bash -c 'external/scala/bin/scalac HelloWorld.scala; echo '\''blah'\'' > bazel-out/local_darwin-fastbuild/bin/hello-world.sh')

更近!现在它找不到它需要的一些 Scala 库。如果我们也指定了 scala 库 jar,您可以在命令行上手动添加它以查看我们的 jar 确实有效:


 (cd /private/var/tmp/_bazel_kchodorow/92df5f72e3c78c053575a1a42537d8c3/blerg && \
  exec env - \
  /bin/bash -c 'external/scala/bin/scalac HelloWorld.scala; echo '\''blah'\'' > bazel-out/local_darwin-fastbuild/bin/hello-world.sh')

所以我们需要我们的规则来生成一个基本上运行这个命令的可执行文件,这可以通过在我们的构建中添加另一个操作来完成。首先,我们将通过将其添加为隐藏属性来添加对 scala-library.jar 的依赖:


 (cd /private/var/tmp/_bazel_kchodorow/92df5f72e3c78c053575a1a42537d8c3/blerg && \
  exec env - \
  /bin/bash -c 'external/scala/bin/scalac HelloWorld.scala; echo '\''blah'\'' > bazel-out/local_darwin-fastbuild/bin/hello-world.sh')

使 scala_binary s 可执行

让我们在这里暂停片刻并换档:我们将告诉 bazel scala_binary 是二进制文件。为此,我们将 executable = True 添加到属性中,并在输出中删除对 hello-world.sh 的引用:


 (cd /private/var/tmp/_bazel_kchodorow/92df5f72e3c78c053575a1a42537d8c3/blerg && \
  exec env - \
  /bin/bash -c 'external/scala/bin/scalac HelloWorld.scala; echo '\''blah'\'' > bazel-out/local_darwin-fastbuild/bin/hello-world.sh')

这表示 scala_binary(name = "foo", ...) 应该有一个创建名为 foo 的二进制文件的操作,可以通过实现函数中的 ctx.outputs.executable 引用它。我们现在可以使用 bazel run :hello-world (而不是 bazel build :hello-world; ./bazel-bin/hello-world.sh )。

我们要创建的可执行文件是上面的 java 命令,所以我们将第二个操作添加到 impl ,这是一个 文件操作 (因为我们只是生成一个具有特定内容的文件,而不是执行一系列命令来生成一个 .罐):


 (cd /private/var/tmp/_bazel_kchodorow/92df5f72e3c78c053575a1a42537d8c3/blerg && \
  exec env - \
  /bin/bash -c 'external/scala/bin/scalac HelloWorld.scala; echo '\''blah'\'' > bazel-out/local_darwin-fastbuild/bin/hello-world.sh')

请注意,我还在文件中添加了一行以回显它的运行位置。如果我们现在使用 bazel run ,你会看到:


 (cd /private/var/tmp/_bazel_kchodorow/92df5f72e3c78c053575a1a42537d8c3/blerg && \
  exec env - \
  /bin/bash -c 'external/scala/bin/scalac HelloWorld.scala; echo '\''blah'\'' > bazel-out/local_darwin-fastbuild/bin/hello-world.sh')

糟糕,它找不到罐子!那条路径是什么, hello-world.runfiles ,它从哪里运行二进制文件?

运行文件目录

bazel run runfiles 目录运行二进制文件,该目录不同于上面提到的源根目录、执行根目录和输出树。 runfiles 目录应包含可执行文件在执行期间所需的所有资源。请注意,这不是在 bazel build 步骤中使用的 执行根目录 。当你实际执行由 bazel 创建的东西时,它的资源需要在 runfiles 目录中。

在这种情况下,我们的可执行文件需要访问 hello-world.jar scala-library.jar 。要添加这些文件,API 有点奇怪。您必须从规则实现中返回一个包含 runfiles 对象的结构。因此,添加以下内容作为 impl 函数的最后一行:


 (cd /private/var/tmp/_bazel_kchodorow/92df5f72e3c78c053575a1a42537d8c3/blerg && \
  exec env - \
  /bin/bash -c 'external/scala/bin/scalac HelloWorld.scala; echo '\''blah'\'' > bazel-out/local_darwin-fastbuild/bin/hello-world.sh')

现在如果你再次运行它,它会打印:


 (cd /private/var/tmp/_bazel_kchodorow/92df5f72e3c78c053575a1a42537d8c3/blerg && \
  exec env - \
  /bin/bash -c 'external/scala/bin/scalac HelloWorld.scala; echo '\''blah'\'' > bazel-out/local_darwin-fastbuild/bin/hello-world.sh')

万岁!

然而!如果我们将它作为 bazel-bin/hello-world 运行,它将无法找到 jars(因为我们不在 runfiles 目录中)。要查找 runfiles 目录,而不管二进制文件从何处运行,请将您的 content 变量更改为以下内容:


 (cd /private/var/tmp/_bazel_kchodorow/92df5f72e3c78c053575a1a42537d8c3/blerg && \
  exec env - \
  /bin/bash -c 'external/scala/bin/scalac HelloWorld.scala; echo '\''blah'\'' > bazel-out/local_darwin-fastbuild/bin/hello-world.sh')

这样,如果它是从 bazel run 运行的, $0 将是二进制文件的绝对路径(在我的例子中, /private/var/tmp/_bazel_kchodorow/92df5f72e3c78c053575a1a42537d8c3/blerg/bazel-out/local_darwin-fastbuild/bin/hello-world ).如果它通过 bazel-bin/hello-world 运行, $0 就是: bazel-bin/hello-world 。无论哪种方式,我们都会在执行命令之前进入 runfiles 目录。

现在我们的规则成功生成了一个二进制文件。您可以 在 GitHub 上 查看此示例的完整代码。

在本教程的最后一部分,我们将解决剩余的问题:

  • 不支持多个源文件,更不用说依赖性了。
  • [action 'Unknown hello-world.jar'] 非常难看。

直到下一次!