xref: /aosp_15_r20/external/bazelbuild-rules_go/go/private/extensions.bzl (revision 9bb1b549b6a84214c53be0924760be030e66b93a)
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