1load("//go/private:sdk.bzl", "detect_host_platform", "go_download_sdk_rule", "go_host_sdk_rule", "go_multiple_toolchains") 2load("//go/private:repositories.bzl", "go_rules_dependencies") 3 4def host_compatible_toolchain_impl(ctx): 5 ctx.file("BUILD.bazel") 6 ctx.file("defs.bzl", content = """ 7HOST_COMPATIBLE_SDK = Label({}) 8""".format(repr(ctx.attr.toolchain))) 9 10host_compatible_toolchain = repository_rule( 11 implementation = host_compatible_toolchain_impl, 12 attrs = { 13 # We cannot use attr.label for the `toolchain` attribute since the module extension cannot 14 # refer to the repositories it creates by their apparent repository names. 15 "toolchain": attr.string( 16 doc = "The apparent label of a `ROOT` file in the repository of a host compatible toolchain created by the `go_sdk` extension", 17 mandatory = True, 18 ), 19 }, 20 doc = "An external repository to expose the first host compatible toolchain", 21) 22 23_download_tag = tag_class( 24 attrs = { 25 "name": attr.string(), 26 "goos": attr.string(), 27 "goarch": attr.string(), 28 "sdks": attr.string_list_dict(), 29 "urls": attr.string_list(default = ["https://dl.google.com/go/{}"]), 30 "version": attr.string(), 31 "strip_prefix": attr.string(default = "go"), 32 }, 33) 34 35_host_tag = tag_class( 36 attrs = { 37 "name": attr.string(), 38 "version": attr.string(), 39 }, 40) 41 42# This limit can be increased essentially arbitrarily, but doing so will cause a rebuild of all 43# targets using any of these toolchains due to the changed repository name. 44_MAX_NUM_TOOLCHAINS = 9999 45_TOOLCHAIN_INDEX_PAD_LENGTH = len(str(_MAX_NUM_TOOLCHAINS)) 46 47def _go_sdk_impl(ctx): 48 multi_version_module = {} 49 for module in ctx.modules: 50 if module.name in multi_version_module: 51 multi_version_module[module.name] = True 52 else: 53 multi_version_module[module.name] = False 54 55 # We remember the first host compatible toolchain declared by the download and host tags. 56 # The order follows bazel's iteration over modules (the toolchains declared by the root module are considered first). 57 # We know that at least `go_default_sdk` (which is declared by the `rules_go` module itself) is host compatible. 58 first_host_compatible_toolchain = None 59 host_detected_goos, host_detected_goarch = detect_host_platform(ctx) 60 toolchains = [] 61 for module in ctx.modules: 62 for index, download_tag in enumerate(module.tags.download): 63 # SDKs without an explicit version are fetched even when not selected by toolchain 64 # resolution. This is acceptable if brought in by the root module, but transitive 65 # dependencies should not slow down the build in this way. 66 if not module.is_root and not download_tag.version: 67 fail("go_sdk.download: version must be specified in non-root module " + module.name) 68 69 # SDKs with an explicit name are at risk of colliding with those from other modules. 70 # This is acceptable if brought in by the root module as the user is responsible for any 71 # conflicts that arise. rules_go itself provides "go_default_sdk", which is used by 72 # Gazelle to bootstrap itself. 73 # TODO(https://github.com/bazelbuild/bazel-gazelle/issues/1469): Investigate whether 74 # Gazelle can use the first user-defined SDK instead to prevent unnecessary downloads. 75 if (not module.is_root and not module.name == "rules_go") and download_tag.name: 76 fail("go_sdk.download: name must not be specified in non-root module " + module.name) 77 78 name = download_tag.name or _default_go_sdk_name( 79 module = module, 80 multi_version = multi_version_module[module.name], 81 tag_type = "download", 82 index = index, 83 ) 84 go_download_sdk_rule( 85 name = name, 86 goos = download_tag.goos, 87 goarch = download_tag.goarch, 88 sdks = download_tag.sdks, 89 urls = download_tag.urls, 90 version = download_tag.version, 91 ) 92 93 if (not download_tag.goos or download_tag.goos == host_detected_goos) and (not download_tag.goarch or download_tag.goarch == host_detected_goarch): 94 first_host_compatible_toolchain = first_host_compatible_toolchain or "@{}//:ROOT".format(name) 95 96 toolchains.append(struct( 97 goos = download_tag.goos, 98 goarch = download_tag.goarch, 99 sdk_repo = name, 100 sdk_type = "remote", 101 sdk_version = download_tag.version, 102 )) 103 104 for index, host_tag in enumerate(module.tags.host): 105 # Dependencies can rely on rules_go providing a default remote SDK. They can also 106 # configure a specific version of the SDK to use. However, they should not add a 107 # dependency on the host's Go SDK. 108 if not module.is_root: 109 fail("go_sdk.host: cannot be used in non-root module " + module.name) 110 111 name = host_tag.name or _default_go_sdk_name( 112 module = module, 113 multi_version = multi_version_module[module.name], 114 tag_type = "host", 115 index = index, 116 ) 117 go_host_sdk_rule( 118 name = name, 119 version = host_tag.version, 120 ) 121 122 toolchains.append(struct( 123 goos = "", 124 goarch = "", 125 sdk_repo = name, 126 sdk_type = "host", 127 sdk_version = host_tag.version, 128 )) 129 first_host_compatible_toolchain = first_host_compatible_toolchain or "@{}//:ROOT".format(name) 130 131 host_compatible_toolchain(name = "go_host_compatible_sdk_label", toolchain = first_host_compatible_toolchain) 132 if len(toolchains) > _MAX_NUM_TOOLCHAINS: 133 fail("more than {} go_sdk tags are not supported".format(_MAX_NUM_TOOLCHAINS)) 134 135 # Toolchains in a BUILD file are registered in the order given by name, not in the order they 136 # are declared: 137 # https://cs.opensource.google/bazel/bazel/+/master:src/main/java/com/google/devtools/build/lib/packages/Package.java;drc=8e41dce65b97a3d466d6b1e65005abc52a07b90b;l=156 138 # We pad with an index that lexicographically sorts in the same order as if these toolchains 139 # were registered using register_toolchains in their MODULE.bazel files. 140 go_multiple_toolchains( 141 name = "go_toolchains", 142 prefixes = [ 143 _toolchain_prefix(index, toolchain.sdk_repo) 144 for index, toolchain in enumerate(toolchains) 145 ], 146 geese = [toolchain.goos for toolchain in toolchains], 147 goarchs = [toolchain.goarch for toolchain in toolchains], 148 sdk_repos = [toolchain.sdk_repo for toolchain in toolchains], 149 sdk_types = [toolchain.sdk_type for toolchain in toolchains], 150 sdk_versions = [toolchain.sdk_version for toolchain in toolchains], 151 ) 152 153def _default_go_sdk_name(*, module, multi_version, tag_type, index): 154 # Keep the version out of the repository name if possible to prevent unnecessary rebuilds when 155 # it changes. 156 return "{name}_{version}_{tag_type}_{index}".format( 157 name = module.name, 158 version = module.version if multi_version else "", 159 tag_type = tag_type, 160 index = index, 161 ) 162 163def _toolchain_prefix(index, name): 164 """Prefixes the given name with the index, padded with zeros to ensure lexicographic sorting. 165 166 Examples: 167 _toolchain_prefix( 2, "foo") == "_0002_foo_" 168 _toolchain_prefix(2000, "foo") == "_2000_foo_" 169 """ 170 return "_{}_{}_".format(_left_pad_zero(index, _TOOLCHAIN_INDEX_PAD_LENGTH), name) 171 172def _left_pad_zero(index, length): 173 if index < 0: 174 fail("index must be non-negative") 175 return ("0" * length + str(index))[-length:] 176 177go_sdk = module_extension( 178 implementation = _go_sdk_impl, 179 tag_classes = { 180 "download": _download_tag, 181 "host": _host_tag, 182 }, 183) 184 185def _non_module_dependencies_impl(_ctx): 186 go_rules_dependencies(force = True) 187 188non_module_dependencies = module_extension( 189 implementation = _non_module_dependencies_impl, 190) 191