From 35bae07323b7ac4a9ddeab60909d9c411970fab2 Mon Sep 17 00:00:00 2001 From: Matt Clarkson Date: Thu, 23 Nov 2023 13:33:09 +0000 Subject: [PATCH] feat: add `export` extension The extension can be use to "export" local repositories into the extension. This is especially useful for exposing repositories that provide hermetic, executable entrypoints. --- e2e/MODULE.bazel | 7 +++ e2e/export/BUILD.bazel | 8 +++ toolchain/export/BUILD.bazel | 0 toolchain/export/defs.bzl | 5 ++ toolchain/export/extension.bzl | 84 +++++++++++++++++++++++++ toolchain/export/symlink/BUILD.bazel | 0 toolchain/export/symlink/repository.bzl | 28 +++++++++ 7 files changed, 132 insertions(+) create mode 100644 e2e/export/BUILD.bazel create mode 100644 toolchain/export/BUILD.bazel create mode 100644 toolchain/export/defs.bzl create mode 100644 toolchain/export/extension.bzl create mode 100644 toolchain/export/symlink/BUILD.bazel create mode 100644 toolchain/export/symlink/repository.bzl diff --git a/e2e/MODULE.bazel b/e2e/MODULE.bazel index 2a4835b..4759a27 100644 --- a/e2e/MODULE.bazel +++ b/e2e/MODULE.bazel @@ -52,3 +52,10 @@ select( "arm-linux-musl": "@fixture-arm-linux-musl", }, ) + +export = use_extension("@rules_toolchain//toolchain/export:defs.bzl", "export") +export.symlink( + name = "export", + target = "@fixture", +) +use_repo(export, "export") diff --git a/e2e/export/BUILD.bazel b/e2e/export/BUILD.bazel new file mode 100644 index 0000000..9b01712 --- /dev/null +++ b/e2e/export/BUILD.bazel @@ -0,0 +1,8 @@ +load("@bazel_skylib//rules:diff_test.bzl", "diff_test") + +diff_test( + name = "hello-world", + size = "small", + file1 = "@export//:test/fixture/hello-world.txt", + file2 = "//test/fixture:hello-world.txt", +) diff --git a/toolchain/export/BUILD.bazel b/toolchain/export/BUILD.bazel new file mode 100644 index 0000000..e69de29 diff --git a/toolchain/export/defs.bzl b/toolchain/export/defs.bzl new file mode 100644 index 0000000..60954c6 --- /dev/null +++ b/toolchain/export/defs.bzl @@ -0,0 +1,5 @@ +load("//toolchain/export:extension.bzl", _export = "export") + +visibility("public") + +export = _export diff --git a/toolchain/export/extension.bzl b/toolchain/export/extension.bzl new file mode 100644 index 0000000..42175cf --- /dev/null +++ b/toolchain/export/extension.bzl @@ -0,0 +1,84 @@ +load("//toolchain/export/symlink:repository.bzl", _ATTRS = "ATTRS", _DOC = "DOC", _symlink = "symlink") + +visibility("//toolchain/export/...") + +DOC = """An extension to export local repositories to other Bazel modules. + +A common pattern for Bazel modules is to download hermetic tooling, often with `rules_download`. + +The downloaded repositories are local in scope to the current Bazel module. + +The locally downloaded repositories can provide hermetic, executable files that are useful for repository rules. + +The hermetic, executable files are often useful for other Bazel modules to perform hermetic repositories. + +This module provides a way to "export" those locally visible repositories to other Bazel modules. + +For example, a `@python` Bazel module could provide: + +```py +download_archive = use_repo_rule("@rules_download//download:defs.bzl", "download_archive") +download_archive( + name = "python-amd64-linux-gnu", + ..., +) + +toolchain_select = use_extension("@rules_toolchain//toolchain:defs.bzl", "toolchain_select") +toolchain_select( + name = "python-local", + map = { + "amd64-linux-gnu": "@python-amd64-linux-gnu", + } +) + +export = use_extension("@rules_toolchain//toolchain/export:defs.bzl", "export") +export.symlink( + name = "python", + target = "@python-local", +) +use_repo(export, "python") + +some_hermetic_repository_rule( + name = "placeholder", + python = "@python//:entrypoint", +) +``` + +The _same_ downloaded Python interpreter can be used in another Bazel module: + +```py +export = use_extension("@rules_toolchain//toolchain/export:defs.bzl", "export") +use_repo(export, "python") + +some_other_hermetic_repository_rule( + name = "placeholder", + python = "@python//:entrypoint", +) +``` + +The de-facto `:entrypoint` label is often used for the executable file entrypoint that can be used with `rctx.execute`. + +The exported repository names are global across the module extension. +""" + +symlink = tag_class( + doc = _DOC, + attrs = { + "name": attr.string(doc = "Name of the generated repository.", mandatory = True), + } | _ATTRS, +) + +TAGS = { + "symlink": symlink, +} + +def implementation(mctx): + for mod in mctx.modules: + for d in mod.tags.symlink: + _symlink(name = d.name, **{a: getattr(d, a) for a in _ATTRS}) + +export = module_extension( + doc = DOC, + implementation = implementation, + tag_classes = TAGS, +) diff --git a/toolchain/export/symlink/BUILD.bazel b/toolchain/export/symlink/BUILD.bazel new file mode 100644 index 0000000..e69de29 diff --git a/toolchain/export/symlink/repository.bzl b/toolchain/export/symlink/repository.bzl new file mode 100644 index 0000000..3f5f669 --- /dev/null +++ b/toolchain/export/symlink/repository.bzl @@ -0,0 +1,28 @@ +visibility("//toolchain/export/...") + +DOC = "Symlinks a repository to another." + +ATTRS = { + "target": attr.label( + doc = "The repository to symlink to.", + mandatory = True, + ), +} + +def implementation(rctx): + label = rctx.attr.target + workspace = label.relative(":WORKSPACE") + path = rctx.path(workspace) + if not path.exists: + fail("Failed to find `{}`, can only symlink repository labels.".format(path, label)) + target = path.dirname + rctx.delete(".") + rctx.symlink(target, ".") + +symlink = repository_rule( + doc = DOC, + attrs = ATTRS, + implementation = implementation, + configure = True, + local = True, +) -- GitLab