如何在不使用相对路径的情况下从另一个规则访问 bazel 规则的输出?

How can I access the output of a bazel rule from another rule without using a relative path?

我正在尝试使用Bazel编译基于dhall-kubernetes的dhall程序生成Kubernetes YAML文件。

没有 dhall-kubernetes 使用简单的 bazel 宏的 basic dhall compile 工作正常。

我已经制作了一个使用 dhall 的依赖解析来下载 dhall-kubernetes 的示例 - 请参阅 here。这也有效,但速度很慢(我认为是因为 dhall 分别下载每个远程文件),并引入了对 bazel 规则执行的网络依赖性,我希望避免这种情况。

我的首选方法是使用 Bazel 下载 dhall-kubernetes 的归档发布版本,然后让规则在本地访问它(参见 here)。我的解决方案需要 Prelude.dhall 和 package.dhall 中的相对路径,以便 examples/k8s 包引用 dhall-kubernetes。虽然它有效,但我担心这会破坏 Bazel 沙箱,因为它需要 Bazel 内部使用的文件夹结构的特殊知识。有没有更好的方法?

Prelude.dhall:

../../external/dhall-kubernetes/1.17/Prelude.dhall 

工作空间:

workspace(name = "dhall")

load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")

DHALL_KUBERNETES_VERSION = "4.0.0"

http_archive(
    name = "dhall-kubernetes",
    sha256 = "0bc2b5d2735ca60ae26d388640a4790bd945abf326da52f7f28a66159e56220d",
    url = "https://github.com/dhall-lang/dhall-kubernetes/archive/v%s.zip" % DHALL_KUBERNETES_VERSION,
    strip_prefix = "dhall-kubernetes-4.0.0",
    build_file = "@//:BUILD.dhall-kubernetes",
)

BUILD.dhall-kubernetes:

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

filegroup(
    name = "dhall-k8s-1.17",
    srcs = glob([
        "1.17/**/*",
    ]),
)

examples/k8s/建造:

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

genrule(
    name = "special_ingress",
    srcs = ["ingress.dhall",
            "Prelude.dhall",
            "package.dhall",
        "@dhall-kubernetes//:dhall-k8s-1.17"
    ],
    outs = ["ingress.yaml"],
    cmd = "dhall-to-yaml --file $(location ingress.dhall) > $@",
    visibility = [
        "//visibility:public"
    ]
)

有一种方法可以检测 dhall 进行“离线”构建,这意味着包管理器获取所有 Dhall 依赖项而不是 Dhall 获取它们。

事实上,我为 Nixpkgs 实现了完全相同的功能,您可以将其翻译成 Bazel:

高级解释

基本技巧是利用 Dhall 导入系统的一个特性,即如果缓存了受语义完整性检查(即“语义哈希”)保护的包,那么 Dhall 将使用缓存而不是取包裹。您可以利用此技巧让包管理器通过以这种方式注入依赖项来绕过 Dhall 的远程导入。

您可以在这里找到与 Nix 相关的逻辑:

...但我将尝试以独立于包管理器的方式解释它是如何工作的。

包结构

首先,使用 Nix 构建的 Dhall“包”的最终产品是具有以下结构的目录:

$ nix-build --attr 'dhallPackages.Prelude'         
…

$ tree -a ./result
./result
├── .cache
│   └── dhall
│       └── 122026b0ef498663d269e4dc6a82b0ee289ec565d683ef4c00d0ebdd25333a5a3c98
└── binary.dhall

2 directories, 2 files

这个目录的内容是:

  • ./cache/dhall/1220XXX…XXX

    包含单个构建产品的 Dhall 的有效缓存目录:解释的 Dhall 表达式的二进制编码。

    您可以使用 dhall encode 创建这样的二进制文件,您可以通过将上面的 XXX…XXX 替换为表达式的 sha256 编码来计算文件名,您可以获得使用 dhall hash 命令。

  • ./binary.dhall

    一个方便的 Dhall 文件,包含表达式 missing sha256:XXX…XXX。仅当我们构建的匹配散列 sha256:XXX…XXX 的表达式已被缓存时,解释此表达式才会成功。

    文件被称为 binary.dhall 因为这是 Dhall 等同于“二进制”包分发,这意味着导入只能从二进制缓存中获取,不能从源中获取和解释。

  • 可选:./source.dhall

    这是一个包含与缓存表达式等效的完全 αβ 规范化表达式的文件。默认情况下,除了顶层包之外的所有包都应该省略它,因为它包含存储在 ./cache/1220XXX…XXX 中的相同表达式,尽管效率较低(因为二进制编码更紧凑)

    此文件被称为 ./source.dhall 因为这是 Dhall 等同于“源”包分发,它包含产生相同结果的有效源代码。

用户界面

构建包的函数有四个参数:

  • 包名

    这不是 material 构建。这只是为了命名,因为每个 Nix 包都必须有一个人类可读的名称。

  • 构建的依赖关系

    这些依赖项中的每一个都是生成目录树的构建产品,就像我上面描述的那样(即 ./cache 目录、./binary.dhall 文件和可选的 ./source.dhall 文件)

  • Dhall 表达式

    这可以是任意的 Dhall 源代码,只有一个警告:表达式传递引用的所有远程导入必须受到完整性检查的保护,并且这些导入必须与此 Dhall 包的依赖项之一匹配(以便可以通过缓存而不是 Dhall 运行时获取 URL)

    来满足导入
  • 布尔值选项,指定是否保留./source.dhall文件,默认为False

实施

Dhall 包生成器的工作方式是:

  • 首先,使用-f-with-http标志构建Haskell Dhall包

    这个标志编译出对 HTTP 远程导入的支持,这样如果用户忘记为远程导入提供依赖项,他们将收到一条错误消息说 Import resolution is disabled

    我们将在所有后续步骤中使用此可执行文件

  • 在当前工作目录中创建一个名为.cache/dhall

    的缓存目录

    ... 并使用存储在每个依赖项的 ./cache/ 目录

    中的二进制文件填充缓存目录
  • 配置解释器使用我们创建的缓存目录

    ...通过设置 XDG_CACHE_HOME 指向我们刚刚在当前工作目录中创建的 .cache 目录

  • 为我们的包解释和 α-规范化 Dhall 源代码

    ... 使用 dhall --alpha 命令。将结果保存到 $out/source.dhall,其中 $out 是将存储最终构建产品的目录

  • 获取表达式的哈希值

    ... 使用 dhall hash 命令。我们将需要此哈希来执行以下两个步骤。

  • 创建对应的二进制缓存文件

    ... 使用 dhall encode 命令并将文件保存到 $out/cache/dhall/1220${HASH}

  • 创建 ./binary.dhall 文件

    ...只需将包含 missing sha256:${HASH}

    的文本文件写入 $out/binary.dhall
  • 可选:删除 ./source.dhall 文件

    ...如果用户没有请求保留文件。默认情况下省略此文件有助于在包存储中保存 space,因为不会将相同的表达式存储两次(作为二进制文件和源代码)。

打包约定

拥有此功能后,有一些约定可以帮助简化“大范围”的操作

  • 默认情况下,包应该构建项目的 ./package.dhall 文件

  • 轻松覆盖包版本

  • 轻松覆盖包内构建的文件

    换句话说,如果用户更喜欢导入像 https://prelude.dhall-lang.org/List/map 这样的单个文件而不是顶级 ./package.dhall 文件,那么应该有一种方法可以让他们指定像 [=48] 这样的依赖项=] 以获得构建和缓存该单个文件的包。

结论

希望对您有所帮助!如果您对如何执行此操作有更多疑问,可以在这里提问,也可以在我们的 Discourse 论坛上进行更多讨论,尤其是在这个成语最初起源的线程上: