diff options
Diffstat (limited to 'go/private/extensions.bzl')
-rw-r--r-- | go/private/extensions.bzl | 190 |
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, +) |