...

Text file src/github.com/bazelbuild/rules_go/go/private/extensions.bzl

Documentation: github.com/bazelbuild/rules_go/go/private

     1# Copyright 2023 The Bazel Authors. All rights reserved.
     2#
     3# Licensed under the Apache License, Version 2.0 (the "License");
     4# you may not use this file except in compliance with the License.
     5# You may obtain a copy of the License at
     6#
     7#    http://www.apache.org/licenses/LICENSE-2.0
     8#
     9# Unless required by applicable law or agreed to in writing, software
    10# distributed under the License is distributed on an "AS IS" BASIS,
    11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12# See the License for the specific language governing permissions and
    13# limitations under the License.
    14
    15load("@io_bazel_rules_go_bazel_features//:features.bzl", "bazel_features")
    16load("//go/private:sdk.bzl", "detect_host_platform", "go_download_sdk_rule", "go_host_sdk_rule", "go_multiple_toolchains")
    17load("//go/private:nogo.bzl", "DEFAULT_NOGO", "NOGO_DEFAULT_EXCLUDES", "NOGO_DEFAULT_INCLUDES", "go_register_nogo")
    18
    19def host_compatible_toolchain_impl(ctx):
    20    ctx.file("BUILD.bazel")
    21    ctx.file("defs.bzl", content = """
    22HOST_COMPATIBLE_SDK = Label({})
    23""".format(repr(ctx.attr.toolchain)))
    24
    25host_compatible_toolchain = repository_rule(
    26    implementation = host_compatible_toolchain_impl,
    27    attrs = {
    28        # We cannot use attr.label for the `toolchain` attribute since the module extension cannot
    29        # refer to the repositories it creates by their apparent repository names.
    30        "toolchain": attr.string(
    31            doc = "The apparent label of a `ROOT` file in the repository of a host compatible toolchain created by the `go_sdk` extension",
    32            mandatory = True,
    33        ),
    34    },
    35    doc = "An external repository to expose the first host compatible toolchain",
    36)
    37
    38_download_tag = tag_class(
    39    attrs = {
    40        "name": attr.string(),
    41        "goos": attr.string(),
    42        "goarch": attr.string(),
    43        "sdks": attr.string_list_dict(),
    44        "experiments": attr.string_list(
    45            doc = "Go experiments to enable via GOEXPERIMENT",
    46        ),
    47        "urls": attr.string_list(default = ["https://dl.google.com/go/{}"]),
    48        "version": attr.string(),
    49        "patches": attr.label_list(
    50            doc = "A list of patches to apply to the SDK after downloading it",
    51        ),
    52        "patch_strip": attr.int(
    53            default = 0,
    54            doc = "The number of leading path segments to be stripped from the file name in the patches.",
    55        ),
    56        "strip_prefix": attr.string(default = "go"),
    57    },
    58)
    59
    60_host_tag = tag_class(
    61    attrs = {
    62        "name": attr.string(),
    63        "version": attr.string(),
    64        "experiments": attr.string_list(
    65            doc = "Go experiments to enable via GOEXPERIMENT",
    66        ),
    67    },
    68)
    69
    70_nogo_tag = tag_class(
    71    attrs = {
    72        "nogo": attr.label(
    73            doc = "The nogo target to use when this module is the root module.",
    74        ),
    75        "includes": attr.label_list(
    76            default = NOGO_DEFAULT_INCLUDES,
    77            # The special include "all" is undocumented on purpose: With it, adding a new transitive
    78            # dependency to a Go module can cause a build failure if the new dependency has lint
    79            # issues.
    80            doc = """
    81A Go target is checked with nogo if its package matches at least one of the entries in 'includes'
    82and none of the entries in 'excludes'. By default, nogo is applied to all targets in the main
    83repository.
    84
    85Uses the same format as 'visibility', i.e., every entry must be a label that ends with ':__pkg__' or
    86':__subpackages__'.
    87""",
    88        ),
    89        "excludes": attr.label_list(
    90            default = NOGO_DEFAULT_EXCLUDES,
    91            doc = "See 'includes'.",
    92        ),
    93    },
    94)
    95
    96# A list of (goos, goarch) pairs that are commonly used for remote executors in cross-platform
    97# builds (where host != exec platform). By default, we register toolchains for all of these
    98# platforms in addition to the host platform.
    99_COMMON_EXEC_PLATFORMS = [
   100    ("darwin", "amd64"),
   101    ("darwin", "arm64"),
   102    ("linux", "amd64"),
   103    ("linux", "arm64"),
   104    ("windows", "amd64"),
   105    ("windows", "arm64"),
   106]
   107
   108# This limit can be increased essentially arbitrarily, but doing so will cause a rebuild of all
   109# targets using any of these toolchains due to the changed repository name.
   110_MAX_NUM_TOOLCHAINS = 9999
   111_TOOLCHAIN_INDEX_PAD_LENGTH = len(str(_MAX_NUM_TOOLCHAINS))
   112
   113def _go_sdk_impl(ctx):
   114    nogo_tag = struct(
   115        nogo = DEFAULT_NOGO,
   116        includes = NOGO_DEFAULT_INCLUDES,
   117        excludes = NOGO_DEFAULT_EXCLUDES,
   118    )
   119    for module in ctx.modules:
   120        if not module.is_root or not module.tags.nogo:
   121            continue
   122        if len(module.tags.nogo) > 1:
   123            # Make use of the special formatting applied to tags by fail.
   124            fail(
   125                "go_sdk.nogo: only one tag can be specified per module, got:\n",
   126                *[t for p in zip(module.tags.nogo, len(module.tags.nogo) * ["\n"]) for t in p]
   127            )
   128        nogo_tag = module.tags.nogo[0]
   129        for scope in nogo_tag.includes + nogo_tag.excludes:
   130            # Validate that the scope references a valid, visible repository.
   131            # buildifier: disable=no-effect
   132            scope.workspace_name
   133            if scope.name != "__pkg__" and scope.name != "__subpackages__":
   134                fail(
   135                    "go_sdk.nogo: all entries in includes and excludes must end with ':__pkg__' or ':__subpackages__', got '{}' in".format(scope.name),
   136                    nogo_tag,
   137                )
   138    go_register_nogo(
   139        name = "io_bazel_rules_nogo",
   140        nogo = str(nogo_tag.nogo),
   141        # Go through canonical label literals to avoid a dependency edge on the packages in the
   142        # scope.
   143        includes = [str(l) for l in nogo_tag.includes],
   144        excludes = [str(l) for l in nogo_tag.excludes],
   145    )
   146
   147    multi_version_module = {}
   148    for module in ctx.modules:
   149        if module.name in multi_version_module:
   150            multi_version_module[module.name] = True
   151        else:
   152            multi_version_module[module.name] = False
   153
   154    # We remember the first host compatible toolchain declared by the download and host tags.
   155    # The order follows bazel's iteration over modules (the toolchains declared by the root module are considered first).
   156    # We know that at least `go_default_sdk` (which is declared by the `rules_go` module itself) is host compatible.
   157    first_host_compatible_toolchain = None
   158    host_detected_goos, host_detected_goarch = detect_host_platform(ctx)
   159    toolchains = []
   160    for module in ctx.modules:
   161        for index, download_tag in enumerate(module.tags.download):
   162            # SDKs without an explicit version are fetched even when not selected by toolchain
   163            # resolution. This is acceptable if brought in by the root module, but transitive
   164            # dependencies should not slow down the build in this way.
   165            if not module.is_root and not download_tag.version:
   166                fail("go_sdk.download: version must be specified in non-root module " + module.name)
   167
   168            # SDKs with an explicit name are at risk of colliding with those from other modules.
   169            # This is acceptable if brought in by the root module as the user is responsible for any
   170            # conflicts that arise. rules_go itself provides "go_default_sdk".
   171            # TODO: Now that Gazelle relies on the go_host_compatible_sdk_label repo, remove the
   172            #       special case for "go_default_sdk". Users should migrate to @rules_go//go.
   173            if (not module.is_root and not module.name == "rules_go") and download_tag.name:
   174                fail("go_sdk.download: name must not be specified in non-root module " + module.name)
   175
   176            name = download_tag.name or _default_go_sdk_name(
   177                module = module,
   178                multi_version = multi_version_module[module.name],
   179                tag_type = "download",
   180                index = index,
   181            )
   182            go_download_sdk_rule(
   183                name = name,
   184                goos = download_tag.goos,
   185                goarch = download_tag.goarch,
   186                sdks = download_tag.sdks,
   187                experiments = download_tag.experiments,
   188                patches = download_tag.patches,
   189                patch_strip = download_tag.patch_strip,
   190                urls = download_tag.urls,
   191                version = download_tag.version,
   192                strip_prefix = download_tag.strip_prefix,
   193            )
   194
   195            if (not download_tag.goos or download_tag.goos == host_detected_goos) and (not download_tag.goarch or download_tag.goarch == host_detected_goarch):
   196                first_host_compatible_toolchain = first_host_compatible_toolchain or "@{}//:ROOT".format(name)
   197
   198            toolchains.append(struct(
   199                goos = download_tag.goos,
   200                goarch = download_tag.goarch,
   201                sdk_repo = name,
   202                sdk_type = "remote",
   203                sdk_version = download_tag.version,
   204            ))
   205
   206            # Additionally register SDKs for all common execution platforms, but only if the user
   207            # specified a version to prevent eager fetches.
   208            if download_tag.version and not download_tag.goos and not download_tag.goarch:
   209                for goos, goarch in _COMMON_EXEC_PLATFORMS:
   210                    if goos == host_detected_goos and goarch == host_detected_goarch:
   211                        # We already added the host-compatible toolchain above.
   212                        continue
   213
   214                    if download_tag.sdks and not "{}_{}".format(goos, goarch) in download_tag.sdks:
   215                        # The user supplied custom download links, but not for this tuple.
   216                        continue
   217
   218                    default_name = _default_go_sdk_name(
   219                        module = module,
   220                        multi_version = multi_version_module[module.name],
   221                        tag_type = "download",
   222                        index = index,
   223                        suffix = "_{}_{}".format(goos, goarch),
   224                    )
   225                    go_download_sdk_rule(
   226                        name = default_name,
   227                        goos = download_tag.goos,
   228                        goarch = download_tag.goarch,
   229                        sdks = download_tag.sdks,
   230                        urls = download_tag.urls,
   231                        version = download_tag.version,
   232                    )
   233
   234                    toolchains.append(struct(
   235                        goos = goos,
   236                        goarch = goarch,
   237                        sdk_repo = default_name,
   238                        sdk_type = "remote",
   239                        sdk_version = download_tag.version,
   240                    ))
   241
   242        for index, host_tag in enumerate(module.tags.host):
   243            # Dependencies can rely on rules_go providing a default remote SDK. They can also
   244            # configure a specific version of the SDK to use. However, they should not add a
   245            # dependency on the host's Go SDK.
   246            if not module.is_root:
   247                fail("go_sdk.host: cannot be used in non-root module " + module.name)
   248
   249            name = host_tag.name or _default_go_sdk_name(
   250                module = module,
   251                multi_version = multi_version_module[module.name],
   252                tag_type = "host",
   253                index = index,
   254            )
   255            go_host_sdk_rule(
   256                name = name,
   257                version = host_tag.version,
   258                experiments = host_tag.experiments,
   259            )
   260
   261            toolchains.append(struct(
   262                goos = "",
   263                goarch = "",
   264                sdk_repo = name,
   265                sdk_type = "host",
   266                sdk_version = host_tag.version,
   267            ))
   268            first_host_compatible_toolchain = first_host_compatible_toolchain or "@{}//:ROOT".format(name)
   269
   270    host_compatible_toolchain(name = "go_host_compatible_sdk_label", toolchain = first_host_compatible_toolchain)
   271    if len(toolchains) > _MAX_NUM_TOOLCHAINS:
   272        fail("more than {} go_sdk tags are not supported".format(_MAX_NUM_TOOLCHAINS))
   273
   274    # Toolchains in a BUILD file are registered in the order given by name, not in the order they
   275    # are declared:
   276    # https://cs.opensource.google/bazel/bazel/+/master:src/main/java/com/google/devtools/build/lib/packages/Package.java;drc=8e41dce65b97a3d466d6b1e65005abc52a07b90b;l=156
   277    # We pad with an index that lexicographically sorts in the same order as if these toolchains
   278    # were registered using register_toolchains in their MODULE.bazel files.
   279    go_multiple_toolchains(
   280        name = "go_toolchains",
   281        prefixes = [
   282            _toolchain_prefix(index, toolchain.sdk_repo)
   283            for index, toolchain in enumerate(toolchains)
   284        ],
   285        geese = [toolchain.goos for toolchain in toolchains],
   286        goarchs = [toolchain.goarch for toolchain in toolchains],
   287        sdk_repos = [toolchain.sdk_repo for toolchain in toolchains],
   288        sdk_types = [toolchain.sdk_type for toolchain in toolchains],
   289        sdk_versions = [toolchain.sdk_version for toolchain in toolchains],
   290    )
   291
   292def _default_go_sdk_name(*, module, multi_version, tag_type, index, suffix = ""):
   293    # Keep the version out of the repository name if possible to prevent unnecessary rebuilds when
   294    # it changes.
   295    return "{name}_{version}_{tag_type}_{index}{suffix}".format(
   296        # "main_" is not a valid module name and thus can't collide.
   297        name = module.name or "main_",
   298        version = module.version if multi_version else "",
   299        tag_type = tag_type,
   300        index = index,
   301        suffix = suffix,
   302    )
   303
   304def _toolchain_prefix(index, name):
   305    """Prefixes the given name with the index, padded with zeros to ensure lexicographic sorting.
   306
   307    Examples:
   308      _toolchain_prefix(   2, "foo") == "_0002_foo_"
   309      _toolchain_prefix(2000, "foo") == "_2000_foo_"
   310    """
   311    return "_{}_{}_".format(_left_pad_zero(index, _TOOLCHAIN_INDEX_PAD_LENGTH), name)
   312
   313def _left_pad_zero(index, length):
   314    if index < 0:
   315        fail("index must be non-negative")
   316    return ("0" * length + str(index))[-length:]
   317
   318go_sdk_extra_kwargs = {
   319    # The choice of a host-compatible SDK is expressed in repository rule attribute values and
   320    # depends on host OS and architecture.
   321    "os_dependent": True,
   322    "arch_dependent": True,
   323} if bazel_features.external_deps.module_extension_has_os_arch_dependent else {}
   324
   325go_sdk = module_extension(
   326    implementation = _go_sdk_impl,
   327    tag_classes = {
   328        "download": _download_tag,
   329        "host": _host_tag,
   330        "nogo": _nogo_tag,
   331    },
   332    **go_sdk_extra_kwargs
   333)

View as plain text