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