diff --git a/e2e/MODULE.bazel b/e2e/MODULE.bazel index 2a4835b1b4ce4dacbcecfe59ce0d71da1edc31d9..4759a27f2a5e03e4defb527bfe2c11c09f7bc9f9 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 0000000000000000000000000000000000000000..9b01712f9b50a683f57157a6469f546c25e8e64b --- /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 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/toolchain/export/defs.bzl b/toolchain/export/defs.bzl new file mode 100644 index 0000000000000000000000000000000000000000..60954c68965b11cc818829f5a6f47be0a87cefab --- /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 0000000000000000000000000000000000000000..42175cf2ba71f84f3ae242e539ae29307bf38981 --- /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 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/toolchain/export/symlink/repository.bzl b/toolchain/export/symlink/repository.bzl new file mode 100644 index 0000000000000000000000000000000000000000..3f5f6690f64764e6ca4b86bad5a81c66549fdc5e --- /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, +)