aboutsummaryrefslogtreecommitdiff
path: root/go/private/extensions.bzl
diff options
context:
space:
mode:
Diffstat (limited to 'go/private/extensions.bzl')
-rw-r--r--go/private/extensions.bzl190
1 files changed, 190 insertions, 0 deletions
diff --git a/go/private/extensions.bzl b/go/private/extensions.bzl
new file mode 100644
index 00000000..03d3fea2
--- /dev/null
+++ b/go/private/extensions.bzl
@@ -0,0 +1,190 @@
+load("//go/private:sdk.bzl", "detect_host_platform", "go_download_sdk_rule", "go_host_sdk_rule", "go_multiple_toolchains")
+load("//go/private:repositories.bzl", "go_rules_dependencies")
+
+def host_compatible_toolchain_impl(ctx):
+ ctx.file("BUILD.bazel")
+ ctx.file("defs.bzl", content = """
+HOST_COMPATIBLE_SDK = Label({})
+""".format(repr(ctx.attr.toolchain)))
+
+host_compatible_toolchain = repository_rule(
+ implementation = host_compatible_toolchain_impl,
+ attrs = {
+ # We cannot use attr.label for the `toolchain` attribute since the module extension cannot
+ # refer to the repositories it creates by their apparent repository names.
+ "toolchain": attr.string(
+ doc = "The apparent label of a `ROOT` file in the repository of a host compatible toolchain created by the `go_sdk` extension",
+ mandatory = True,
+ ),
+ },
+ doc = "An external repository to expose the first host compatible toolchain",
+)
+
+_download_tag = tag_class(
+ attrs = {
+ "name": attr.string(),
+ "goos": attr.string(),
+ "goarch": attr.string(),
+ "sdks": attr.string_list_dict(),
+ "urls": attr.string_list(default = ["https://dl.google.com/go/{}"]),
+ "version": attr.string(),
+ "strip_prefix": attr.string(default = "go"),
+ },
+)
+
+_host_tag = tag_class(
+ attrs = {
+ "name": attr.string(),
+ "version": attr.string(),
+ },
+)
+
+# This limit can be increased essentially arbitrarily, but doing so will cause a rebuild of all
+# targets using any of these toolchains due to the changed repository name.
+_MAX_NUM_TOOLCHAINS = 9999
+_TOOLCHAIN_INDEX_PAD_LENGTH = len(str(_MAX_NUM_TOOLCHAINS))
+
+def _go_sdk_impl(ctx):
+ multi_version_module = {}
+ for module in ctx.modules:
+ if module.name in multi_version_module:
+ multi_version_module[module.name] = True
+ else:
+ multi_version_module[module.name] = False
+
+ # We remember the first host compatible toolchain declared by the download and host tags.
+ # The order follows bazel's iteration over modules (the toolchains declared by the root module are considered first).
+ # We know that at least `go_default_sdk` (which is declared by the `rules_go` module itself) is host compatible.
+ first_host_compatible_toolchain = None
+ host_detected_goos, host_detected_goarch = detect_host_platform(ctx)
+ toolchains = []
+ for module in ctx.modules:
+ for index, download_tag in enumerate(module.tags.download):
+ # SDKs without an explicit version are fetched even when not selected by toolchain
+ # resolution. This is acceptable if brought in by the root module, but transitive
+ # dependencies should not slow down the build in this way.
+ if not module.is_root and not download_tag.version:
+ fail("go_sdk.download: version must be specified in non-root module " + module.name)
+
+ # SDKs with an explicit name are at risk of colliding with those from other modules.
+ # This is acceptable if brought in by the root module as the user is responsible for any
+ # conflicts that arise. rules_go itself provides "go_default_sdk", which is used by
+ # Gazelle to bootstrap itself.
+ # TODO(https://github.com/bazelbuild/bazel-gazelle/issues/1469): Investigate whether
+ # Gazelle can use the first user-defined SDK instead to prevent unnecessary downloads.
+ if (not module.is_root and not module.name == "rules_go") and download_tag.name:
+ fail("go_sdk.download: name must not be specified in non-root module " + module.name)
+
+ name = download_tag.name or _default_go_sdk_name(
+ module = module,
+ multi_version = multi_version_module[module.name],
+ tag_type = "download",
+ index = index,
+ )
+ go_download_sdk_rule(
+ name = name,
+ goos = download_tag.goos,
+ goarch = download_tag.goarch,
+ sdks = download_tag.sdks,
+ urls = download_tag.urls,
+ version = download_tag.version,
+ )
+
+ if (not download_tag.goos or download_tag.goos == host_detected_goos) and (not download_tag.goarch or download_tag.goarch == host_detected_goarch):
+ first_host_compatible_toolchain = first_host_compatible_toolchain or "@{}//:ROOT".format(name)
+
+ toolchains.append(struct(
+ goos = download_tag.goos,
+ goarch = download_tag.goarch,
+ sdk_repo = name,
+ sdk_type = "remote",
+ sdk_version = download_tag.version,
+ ))
+
+ for index, host_tag in enumerate(module.tags.host):
+ # Dependencies can rely on rules_go providing a default remote SDK. They can also
+ # configure a specific version of the SDK to use. However, they should not add a
+ # dependency on the host's Go SDK.
+ if not module.is_root:
+ fail("go_sdk.host: cannot be used in non-root module " + module.name)
+
+ name = host_tag.name or _default_go_sdk_name(
+ module = module,
+ multi_version = multi_version_module[module.name],
+ tag_type = "host",
+ index = index,
+ )
+ go_host_sdk_rule(
+ name = name,
+ version = host_tag.version,
+ )
+
+ toolchains.append(struct(
+ goos = "",
+ goarch = "",
+ sdk_repo = name,
+ sdk_type = "host",
+ sdk_version = host_tag.version,
+ ))
+ first_host_compatible_toolchain = first_host_compatible_toolchain or "@{}//:ROOT".format(name)
+
+ host_compatible_toolchain(name = "go_host_compatible_sdk_label", toolchain = first_host_compatible_toolchain)
+ if len(toolchains) > _MAX_NUM_TOOLCHAINS:
+ fail("more than {} go_sdk tags are not supported".format(_MAX_NUM_TOOLCHAINS))
+
+ # Toolchains in a BUILD file are registered in the order given by name, not in the order they
+ # are declared:
+ # https://cs.opensource.google/bazel/bazel/+/master:src/main/java/com/google/devtools/build/lib/packages/Package.java;drc=8e41dce65b97a3d466d6b1e65005abc52a07b90b;l=156
+ # We pad with an index that lexicographically sorts in the same order as if these toolchains
+ # were registered using register_toolchains in their MODULE.bazel files.
+ go_multiple_toolchains(
+ name = "go_toolchains",
+ prefixes = [
+ _toolchain_prefix(index, toolchain.sdk_repo)
+ for index, toolchain in enumerate(toolchains)
+ ],
+ geese = [toolchain.goos for toolchain in toolchains],
+ goarchs = [toolchain.goarch for toolchain in toolchains],
+ sdk_repos = [toolchain.sdk_repo for toolchain in toolchains],
+ sdk_types = [toolchain.sdk_type for toolchain in toolchains],
+ sdk_versions = [toolchain.sdk_version for toolchain in toolchains],
+ )
+
+def _default_go_sdk_name(*, module, multi_version, tag_type, index):
+ # Keep the version out of the repository name if possible to prevent unnecessary rebuilds when
+ # it changes.
+ return "{name}_{version}_{tag_type}_{index}".format(
+ name = module.name,
+ version = module.version if multi_version else "",
+ tag_type = tag_type,
+ index = index,
+ )
+
+def _toolchain_prefix(index, name):
+ """Prefixes the given name with the index, padded with zeros to ensure lexicographic sorting.
+
+ Examples:
+ _toolchain_prefix( 2, "foo") == "_0002_foo_"
+ _toolchain_prefix(2000, "foo") == "_2000_foo_"
+ """
+ return "_{}_{}_".format(_left_pad_zero(index, _TOOLCHAIN_INDEX_PAD_LENGTH), name)
+
+def _left_pad_zero(index, length):
+ if index < 0:
+ fail("index must be non-negative")
+ return ("0" * length + str(index))[-length:]
+
+go_sdk = module_extension(
+ implementation = _go_sdk_impl,
+ tag_classes = {
+ "download": _download_tag,
+ "host": _host_tag,
+ },
+)
+
+def _non_module_dependencies_impl(_ctx):
+ go_rules_dependencies(force = True)
+
+non_module_dependencies = module_extension(
+ implementation = _non_module_dependencies_impl,
+)