...

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

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

     1# Copyright 2014 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("//go/private:common.bzl", "executable_path")
    16load("//go/private:nogo.bzl", "go_register_nogo")
    17load("//go/private/skylib/lib:versions.bzl", "versions")
    18load("@bazel_tools//tools/build_defs/repo:utils.bzl", "patch", "read_user_netrc", "use_netrc")
    19
    20MIN_SUPPORTED_VERSION = (1, 14, 0)
    21
    22def _go_host_sdk_impl(ctx):
    23    goroot = _detect_host_sdk(ctx)
    24    platform = _detect_sdk_platform(ctx, goroot)
    25    version = _detect_sdk_version(ctx, goroot)
    26    _sdk_build_file(ctx, platform, version, experiments = ctx.attr.experiments)
    27    _local_sdk(ctx, goroot)
    28
    29go_host_sdk_rule = repository_rule(
    30    implementation = _go_host_sdk_impl,
    31    environ = ["GOROOT"],
    32    attrs = {
    33        "version": attr.string(),
    34        "experiments": attr.string_list(
    35            doc = "Go experiments to enable via GOEXPERIMENT",
    36        ),
    37        "_sdk_build_file": attr.label(
    38            default = Label("//go/private:BUILD.sdk.bazel"),
    39        ),
    40    },
    41)
    42
    43def go_host_sdk(name, register_toolchains = True, **kwargs):
    44    go_host_sdk_rule(name = name, **kwargs)
    45    _go_toolchains(
    46        name = name + "_toolchains",
    47        sdk_repo = name,
    48        sdk_type = "host",
    49        sdk_version = kwargs.get("version"),
    50        goos = kwargs.get("goos"),
    51        goarch = kwargs.get("goarch"),
    52    )
    53    if register_toolchains:
    54        _register_toolchains(name)
    55
    56def _go_download_sdk_impl(ctx):
    57    if not ctx.attr.goos and not ctx.attr.goarch:
    58        goos, goarch = detect_host_platform(ctx)
    59    else:
    60        if not ctx.attr.goos:
    61            fail("goarch set but goos not set")
    62        if not ctx.attr.goarch:
    63            fail("goos set but goarch not set")
    64        goos, goarch = ctx.attr.goos, ctx.attr.goarch
    65    platform = goos + "_" + goarch
    66
    67    version = ctx.attr.version
    68    sdks = ctx.attr.sdks
    69
    70    if not version:
    71        if ctx.attr.patches:
    72            fail("a single version must be specified to apply patches")
    73
    74    if not sdks:
    75        # If sdks was unspecified, download a full list of files.
    76        # If version was unspecified, pick the latest version.
    77        # Even if version was specified, we need to download the file list
    78        # to find the SHA-256 sum. If we don't have it, Bazel won't cache
    79        # the downloaded archive.
    80        if not version:
    81            ctx.report_progress("Finding latest Go version")
    82        else:
    83            ctx.report_progress("Finding Go SHA-256 sums")
    84        ctx.download(
    85            url = [
    86                "https://go.dev/dl/?mode=json&include=all",
    87                "https://golang.google.cn/dl/?mode=json&include=all",
    88            ],
    89            output = "versions.json",
    90        )
    91
    92        data = ctx.read("versions.json")
    93        sdks_by_version = _parse_versions_json(data)
    94
    95        if not version:
    96            highest_version = None
    97            for v in sdks_by_version.keys():
    98                pv = parse_version(v)
    99                if not pv or _version_is_prerelease(pv):
   100                    # skip parse errors and pre-release versions
   101                    continue
   102                if not highest_version or _version_less(highest_version, pv):
   103                    highest_version = pv
   104            if not highest_version:
   105                fail("did not find any Go versions in https://go.dev/dl/?mode=json")
   106            version = _version_string(highest_version)
   107        if version not in sdks_by_version:
   108            fail("did not find version {} in https://go.dev/dl/?mode=json".format(version))
   109        sdks = sdks_by_version[version]
   110
   111    if platform not in sdks:
   112        fail("unsupported platform {}".format(platform))
   113    filename, sha256 = sdks[platform]
   114
   115    _remote_sdk(ctx, [url.format(filename) for url in ctx.attr.urls], ctx.attr.strip_prefix, sha256)
   116    patch(ctx, patch_args = _get_patch_args(ctx.attr.patch_strip))
   117
   118    detected_version = _detect_sdk_version(ctx, ".")
   119    _sdk_build_file(ctx, platform, detected_version, experiments = ctx.attr.experiments)
   120
   121    if not ctx.attr.sdks and not ctx.attr.version:
   122        # Returning this makes Bazel print a message that 'version' must be
   123        # specified for a reproducible build.
   124        return {
   125            "name": ctx.attr.name,
   126            "goos": ctx.attr.goos,
   127            "goarch": ctx.attr.goarch,
   128            "sdks": ctx.attr.sdks,
   129            "urls": ctx.attr.urls,
   130            "version": version,
   131            "strip_prefix": ctx.attr.strip_prefix,
   132        }
   133    return None
   134
   135go_download_sdk_rule = repository_rule(
   136    implementation = _go_download_sdk_impl,
   137    attrs = {
   138        "goos": attr.string(),
   139        "goarch": attr.string(),
   140        "sdks": attr.string_list_dict(),
   141        "experiments": attr.string_list(
   142            doc = "Go experiments to enable via GOEXPERIMENT",
   143        ),
   144        "urls": attr.string_list(default = ["https://dl.google.com/go/{}"]),
   145        "version": attr.string(),
   146        "strip_prefix": attr.string(default = "go"),
   147        "patches": attr.label_list(
   148            doc = "A list of patches to apply to the SDK after downloading it",
   149        ),
   150        "patch_strip": attr.int(
   151            default = 0,
   152            doc = "The number of leading path segments to be stripped from the file name in the patches.",
   153        ),
   154        "_sdk_build_file": attr.label(
   155            default = Label("//go/private:BUILD.sdk.bazel"),
   156        ),
   157    },
   158)
   159
   160def _define_version_constants(version, prefix = ""):
   161    pv = parse_version(version)
   162    if pv == None or len(pv) < 3:
   163        fail("error parsing sdk version: " + version)
   164    major, minor, patch = pv[0], pv[1], pv[2]
   165    prerelease = pv[3] if len(pv) > 3 else ""
   166    return """
   167{prefix}MAJOR_VERSION = "{major}"
   168{prefix}MINOR_VERSION = "{minor}"
   169{prefix}PATCH_VERSION = "{patch}"
   170{prefix}PRERELEASE_SUFFIX = "{prerelease}"
   171""".format(
   172        prefix = prefix,
   173        major = major,
   174        minor = minor,
   175        patch = patch,
   176        prerelease = prerelease,
   177    )
   178
   179def _to_constant_name(s):
   180    # Prefix with _ as identifiers are not allowed to start with numbers.
   181    return "_" + "".join([c if c.isalnum() else "_" for c in s.elems()]).upper()
   182
   183def _get_patch_args(patch_strip):
   184    if patch_strip:
   185        return ["-p{}".format(patch_strip)]
   186    return []
   187
   188def go_toolchains_single_definition(ctx, *, prefix, goos, goarch, sdk_repo, sdk_type, sdk_version):
   189    if not goos and not goarch:
   190        goos, goarch = detect_host_platform(ctx)
   191    else:
   192        if not goos:
   193            fail("goarch set but goos not set")
   194        if not goarch:
   195            fail("goos set but goarch not set")
   196
   197    chunks = []
   198    loads = []
   199    identifier_prefix = _to_constant_name(prefix)
   200
   201    # If a sdk_version attribute is provided, use that version. This avoids
   202    # eagerly fetching the SDK repository. But if it's not provided, we have
   203    # no choice and must load version constants from the version.bzl file that
   204    # _sdk_build_file creates. This will trigger an eager fetch.
   205    if sdk_version:
   206        chunks.append(_define_version_constants(sdk_version, prefix = identifier_prefix))
   207    else:
   208        loads.append("""load(
   209    "@{sdk_repo}//:version.bzl",
   210    {identifier_prefix}MAJOR_VERSION = "MAJOR_VERSION",
   211    {identifier_prefix}MINOR_VERSION = "MINOR_VERSION",
   212    {identifier_prefix}PATCH_VERSION = "PATCH_VERSION",
   213    {identifier_prefix}PRERELEASE_SUFFIX = "PRERELEASE_SUFFIX",
   214)
   215""".format(
   216            sdk_repo = sdk_repo,
   217            identifier_prefix = identifier_prefix,
   218        ))
   219
   220    chunks.append("""declare_bazel_toolchains(
   221    prefix = "{prefix}",
   222    go_toolchain_repo = "@{sdk_repo}",
   223    host_goarch = "{goarch}",
   224    host_goos = "{goos}",
   225    major = {identifier_prefix}MAJOR_VERSION,
   226    minor = {identifier_prefix}MINOR_VERSION,
   227    patch = {identifier_prefix}PATCH_VERSION,
   228    prerelease = {identifier_prefix}PRERELEASE_SUFFIX,
   229    sdk_type = "{sdk_type}",
   230)
   231""".format(
   232        prefix = prefix,
   233        identifier_prefix = identifier_prefix,
   234        sdk_repo = sdk_repo,
   235        goarch = goarch,
   236        goos = goos,
   237        sdk_type = sdk_type,
   238    ))
   239
   240    return struct(
   241        loads = loads,
   242        chunks = chunks,
   243    )
   244
   245def go_toolchains_build_file_content(
   246        ctx,
   247        prefixes,
   248        geese,
   249        goarchs,
   250        sdk_repos,
   251        sdk_types,
   252        sdk_versions):
   253    if not _have_same_length(prefixes, geese, goarchs, sdk_repos, sdk_types, sdk_versions):
   254        fail("all lists must have the same length")
   255
   256    loads = [
   257        """load("@io_bazel_rules_go//go/private:go_toolchain.bzl", "declare_bazel_toolchains")""",
   258    ]
   259    chunks = [
   260        """package(default_visibility = ["//visibility:public"])""",
   261    ]
   262
   263    for i in range(len(geese)):
   264        definition = go_toolchains_single_definition(
   265            ctx,
   266            prefix = prefixes[i],
   267            goos = geese[i],
   268            goarch = goarchs[i],
   269            sdk_repo = sdk_repos[i],
   270            sdk_type = sdk_types[i],
   271            sdk_version = sdk_versions[i],
   272        )
   273        loads.extend(definition.loads)
   274        chunks.extend(definition.chunks)
   275
   276    return "\n".join(loads + chunks)
   277
   278def _go_multiple_toolchains_impl(ctx):
   279    ctx.file(
   280        "BUILD.bazel",
   281        go_toolchains_build_file_content(
   282            ctx,
   283            prefixes = ctx.attr.prefixes,
   284            geese = ctx.attr.geese,
   285            goarchs = ctx.attr.goarchs,
   286            sdk_repos = ctx.attr.sdk_repos,
   287            sdk_types = ctx.attr.sdk_types,
   288            sdk_versions = ctx.attr.sdk_versions,
   289        ),
   290        executable = False,
   291    )
   292
   293go_multiple_toolchains = repository_rule(
   294    implementation = _go_multiple_toolchains_impl,
   295    attrs = {
   296        "prefixes": attr.string_list(mandatory = True),
   297        "sdk_repos": attr.string_list(mandatory = True),
   298        "sdk_types": attr.string_list(mandatory = True),
   299        "sdk_versions": attr.string_list(mandatory = True),
   300        "geese": attr.string_list(mandatory = True),
   301        "goarchs": attr.string_list(mandatory = True),
   302    },
   303)
   304
   305def _go_toolchains(name, sdk_repo, sdk_type, sdk_version = None, goos = None, goarch = None):
   306    go_multiple_toolchains(
   307        name = name,
   308        prefixes = [""],
   309        geese = [goos or ""],
   310        goarchs = [goarch or ""],
   311        sdk_repos = [sdk_repo],
   312        sdk_types = [sdk_type],
   313        sdk_versions = [sdk_version or ""],
   314    )
   315
   316def go_download_sdk(name, register_toolchains = True, **kwargs):
   317    go_download_sdk_rule(name = name, **kwargs)
   318    _go_toolchains(
   319        name = name + "_toolchains",
   320        sdk_repo = name,
   321        sdk_type = "remote",
   322        sdk_version = kwargs.get("version"),
   323        goos = kwargs.get("goos"),
   324        goarch = kwargs.get("goarch"),
   325    )
   326    if register_toolchains:
   327        _register_toolchains(name)
   328
   329def _go_local_sdk_impl(ctx):
   330    goroot = ctx.attr.path
   331    platform = _detect_sdk_platform(ctx, goroot)
   332    version = _detect_sdk_version(ctx, goroot)
   333    _sdk_build_file(ctx, platform, version, ctx.attr.experiments)
   334    _local_sdk(ctx, goroot)
   335
   336_go_local_sdk = repository_rule(
   337    implementation = _go_local_sdk_impl,
   338    attrs = {
   339        "path": attr.string(),
   340        "version": attr.string(),
   341        "experiments": attr.string_list(
   342            doc = "Go experiments to enable via GOEXPERIMENT",
   343        ),
   344        "_sdk_build_file": attr.label(
   345            default = Label("//go/private:BUILD.sdk.bazel"),
   346        ),
   347    },
   348)
   349
   350def go_local_sdk(name, register_toolchains = True, **kwargs):
   351    _go_local_sdk(name = name, **kwargs)
   352    _go_toolchains(
   353        name = name + "_toolchains",
   354        sdk_repo = name,
   355        sdk_type = "remote",
   356        sdk_version = kwargs.get("version"),
   357        goos = kwargs.get("goos"),
   358        goarch = kwargs.get("goarch"),
   359    )
   360    if register_toolchains:
   361        _register_toolchains(name)
   362
   363def _go_wrap_sdk_impl(ctx):
   364    if not ctx.attr.root_file and not ctx.attr.root_files:
   365        fail("either root_file or root_files must be provided")
   366    if ctx.attr.root_file and ctx.attr.root_files:
   367        fail("root_file and root_files cannot be both provided")
   368    if ctx.attr.root_file:
   369        root_file = ctx.attr.root_file
   370    else:
   371        goos, goarch = detect_host_platform(ctx)
   372        platform = goos + "_" + goarch
   373        if platform not in ctx.attr.root_files:
   374            fail("unsupported platform {}".format(platform))
   375        root_file = Label(ctx.attr.root_files[platform])
   376    goroot = str(ctx.path(root_file).dirname)
   377    platform = _detect_sdk_platform(ctx, goroot)
   378    version = _detect_sdk_version(ctx, goroot)
   379    _sdk_build_file(ctx, platform, version, ctx.attr.experiments)
   380    _local_sdk(ctx, goroot)
   381
   382_go_wrap_sdk = repository_rule(
   383    implementation = _go_wrap_sdk_impl,
   384    attrs = {
   385        "root_file": attr.label(
   386            mandatory = False,
   387            doc = "A file in the SDK root direcotry. Used to determine GOROOT.",
   388        ),
   389        "root_files": attr.string_dict(
   390            mandatory = False,
   391            doc = "A set of mappings from the host platform to a file in the SDK's root directory",
   392        ),
   393        "version": attr.string(),
   394        "experiments": attr.string_list(
   395            doc = "Go experiments to enable via GOEXPERIMENT",
   396        ),
   397        "_sdk_build_file": attr.label(
   398            default = Label("//go/private:BUILD.sdk.bazel"),
   399        ),
   400    },
   401)
   402
   403def go_wrap_sdk(name, register_toolchains = True, **kwargs):
   404    _go_wrap_sdk(name = name, **kwargs)
   405    _go_toolchains(
   406        name = name + "_toolchains",
   407        sdk_repo = name,
   408        sdk_type = "remote",
   409        sdk_version = kwargs.get("version"),
   410        goos = kwargs.get("goos"),
   411        goarch = kwargs.get("goarch"),
   412    )
   413    if register_toolchains:
   414        _register_toolchains(name)
   415
   416def _register_toolchains(repo):
   417    native.register_toolchains("@{}_toolchains//:all".format(repo))
   418
   419def _remote_sdk(ctx, urls, strip_prefix, sha256):
   420    if len(urls) == 0:
   421        fail("no urls specified")
   422    host_goos, _ = detect_host_platform(ctx)
   423
   424    ctx.report_progress("Downloading and extracting Go toolchain")
   425
   426    auth = use_netrc(read_user_netrc(ctx), urls, {})
   427
   428    # TODO(#2771): After bazelbuild/bazel#18448 is merged and available in
   429    # the minimum supported version of Bazel, remove the workarounds below.
   430    #
   431    # Go ships archives containing some non-ASCII file names, used in
   432    # test cases for Go's build system. Bazel has a bug extracting these
   433    # archives on certain file systems (macOS AFS at least, possibly also
   434    # Docker on macOS with a bind mount).
   435    #
   436    # For .tar.gz files (available for most platforms), we work around this bug
   437    # by using the system tar instead of ctx.download_and_extract.
   438    #
   439    # For .zip files, we use ctx.download_and_extract but with rename_files,
   440    # changing certain paths that trigger the bug. This is only available
   441    # in Bazel 6.0.0+ (bazelbuild/bazel#16052). The only situation where
   442    # .zip files are needed seems to be a macOS host using a Windows toolchain
   443    # for remote execution.
   444    if urls[0].endswith(".tar.gz"):
   445        if strip_prefix != "go":
   446            fail("strip_prefix not supported")
   447        ctx.download(
   448            url = urls,
   449            sha256 = sha256,
   450            output = "go_sdk.tar.gz",
   451            auth = auth,
   452        )
   453        res = ctx.execute(["tar", "-xf", "go_sdk.tar.gz", "--strip-components=1"])
   454        if res.return_code:
   455            fail("error extracting Go SDK:\n" + res.stdout + res.stderr)
   456        ctx.delete("go_sdk.tar.gz")
   457    elif (urls[0].endswith(".zip") and
   458          host_goos != "windows" and
   459          # Development versions of Bazel have an empty version string. We assume that they are
   460          # more recent than the version that introduced rename_files.
   461          versions.is_at_least("6.0.0", versions.get() or "6.0.0")):
   462        ctx.download_and_extract(
   463            url = urls,
   464            stripPrefix = strip_prefix,
   465            sha256 = sha256,
   466            rename_files = {
   467                "go/test/fixedbugs/issue27836.dir/\336foo.go": "go/test/fixedbugs/issue27836.dir/thfoo.go",
   468                "go/test/fixedbugs/issue27836.dir/\336main.go": "go/test/fixedbugs/issue27836.dir/thmain.go",
   469            },
   470            auth = auth,
   471        )
   472    else:
   473        ctx.download_and_extract(
   474            url = urls,
   475            stripPrefix = strip_prefix,
   476            sha256 = sha256,
   477            auth = auth,
   478        )
   479
   480def _local_sdk(ctx, path):
   481    for entry in ctx.path(path).readdir():
   482        if ctx.path(entry.basename).exists:
   483            continue
   484        ctx.symlink(entry, entry.basename)
   485
   486def _sdk_build_file(ctx, platform, version, experiments):
   487    ctx.file("ROOT")
   488    goos, _, goarch = platform.partition("_")
   489
   490    pv = parse_version(version)
   491    if pv != None and pv[1] >= 20:
   492        # Turn off coverageredesign GOEXPERIMENT on 1.20+
   493        # until rules_go is updated to work with the
   494        # coverage redesign.
   495        if not "nocoverageredesign" in experiments and not "coverageredesign" in experiments:
   496            experiments = experiments + ["nocoverageredesign"]
   497
   498    ctx.template(
   499        "BUILD.bazel",
   500        ctx.path(ctx.attr._sdk_build_file),
   501        executable = False,
   502        substitutions = {
   503            "{goos}": goos,
   504            "{goarch}": goarch,
   505            "{exe}": ".exe" if goos == "windows" else "",
   506            "{version}": version,
   507            "{experiments}": repr(experiments),
   508        },
   509    )
   510
   511    ctx.file(
   512        "version.bzl",
   513        executable = False,
   514        content = _define_version_constants(version),
   515    )
   516
   517def detect_host_platform(ctx):
   518    goos = ctx.os.name
   519    if goos == "mac os x":
   520        goos = "darwin"
   521    elif goos.startswith("windows"):
   522        goos = "windows"
   523
   524    goarch = ctx.os.arch
   525    if goarch == "aarch64":
   526        goarch = "arm64"
   527    elif goarch == "x86_64":
   528        goarch = "amd64"
   529
   530    return goos, goarch
   531
   532def _detect_host_sdk(ctx):
   533    root = "@invalid@"
   534    if "GOROOT" in ctx.os.environ:
   535        return ctx.os.environ["GOROOT"]
   536    res = ctx.execute([executable_path(ctx, "go"), "env", "GOROOT"])
   537    if res.return_code:
   538        fail("Could not detect host go version")
   539    root = res.stdout.strip()
   540    if not root:
   541        fail("host go version failed to report it's GOROOT")
   542    return root
   543
   544def _detect_sdk_platform(ctx, goroot):
   545    path = ctx.path(goroot + "/pkg/tool")
   546    if not path.exists:
   547        fail("Could not detect SDK platform: failed to find " + str(path))
   548    tool_entries = path.readdir()
   549
   550    platforms = []
   551    for f in tool_entries:
   552        if f.basename.find("_") >= 0:
   553            platforms.append(f.basename)
   554
   555    if len(platforms) == 0:
   556        fail("Could not detect SDK platform: found no platforms in %s" % path)
   557    if len(platforms) > 1:
   558        fail("Could not detect SDK platform: found multiple platforms %s in %s" % (platforms, path))
   559    return platforms[0]
   560
   561def _detect_sdk_version(ctx, goroot):
   562    version_file_path = goroot + "/VERSION"
   563    if ctx.path(version_file_path).exists:
   564        # VERSION file has version prefixed by go, eg. go1.18.3
   565        # 1.21: The version is the first line
   566        version_line = ctx.read(version_file_path).splitlines()[0]
   567        version = version_line[2:]
   568        if ctx.attr.version and ctx.attr.version != version:
   569            fail("SDK is version %s, but version %s was expected" % (version, ctx.attr.version))
   570        return version
   571
   572    # The top-level VERSION file does not exist in all Go SDK distributions, e.g. those shipped by Debian or Fedora.
   573    # Falling back to running "go version"
   574    go_binary_path = goroot + "/bin/go"
   575    result = ctx.execute([go_binary_path, "version"])
   576    if result.return_code != 0:
   577        fail("Could not detect SDK version: '%s version' exited with exit code %d" % (go_binary_path, result.return_code))
   578
   579    # go version output is of the form "go version go1.18.3 linux/amd64" or "go
   580    # version devel go1.19-fd1b5904ae Tue Mar 22 21:38:10 2022 +0000
   581    # linux/amd64". See the following links for how this output is generated:
   582    # - https://github.com/golang/go/blob/2bdb5c57f1efcbddab536028d053798e35de6226/src/cmd/go/internal/version/version.go#L75
   583    # - https://github.com/golang/go/blob/2bdb5c57f1efcbddab536028d053798e35de6226/src/cmd/dist/build.go#L333
   584    #
   585    # Read the third word, or the fourth word if the third word is "devel", to
   586    # find the version number.
   587    output_parts = result.stdout.split(" ")
   588    if len(output_parts) > 2 and output_parts[2].startswith("go"):
   589        version = output_parts[2][len("go"):]
   590    elif len(output_parts) > 3 and output_parts[2] == "devel" and output_parts[3].startswith("go"):
   591        version = output_parts[3][len("go"):]
   592    else:
   593        fail("Could not parse SDK version from '%s version' output: %s" % (go_binary_path, result.stdout))
   594    if parse_version(version) == None:
   595        fail("Could not parse SDK version from '%s version' output: %s" % (go_binary_path, result.stdout))
   596    if ctx.attr.version and ctx.attr.version != version:
   597        fail("SDK is version %s, but version %s was expected" % (version, ctx.attr.version))
   598    return version
   599
   600def _parse_versions_json(data):
   601    """Parses version metadata returned by go.dev.
   602
   603    Args:
   604        data: the contents of the file downloaded from
   605            https://go.dev/dl/?mode=json. We assume the file is valid
   606            JSON, is spaced and indented, and is in a particular format.
   607
   608    Return:
   609        A dict mapping version strings (like "1.15.5") to dicts mapping
   610        platform names (like "linux_amd64") to pairs of filenames
   611        (like "go1.15.5.linux-amd64.tar.gz") and hex-encoded SHA-256 sums.
   612    """
   613    sdks = json.decode(data)
   614    return {
   615        sdk["version"][len("go"):]: {
   616            "%s_%s" % (file["os"], file["arch"]): (
   617                file["filename"],
   618                file["sha256"],
   619            )
   620            for file in sdk["files"]
   621            if file["kind"] == "archive"
   622        }
   623        for sdk in sdks
   624    }
   625
   626def parse_version(version):
   627    """Parses a version string like "1.15.5" and returns a tuple of numbers or None"""
   628    l, r = 0, 0
   629    parsed = []
   630    for c in version.elems():
   631        if c == ".":
   632            if l == r:
   633                # empty component
   634                return None
   635            parsed.append(int(version[l:r]))
   636            r += 1
   637            l = r
   638            continue
   639
   640        if c.isdigit():
   641            r += 1
   642            continue
   643
   644        # pre-release suffix
   645        break
   646
   647    if l == r:
   648        # empty component
   649        return None
   650    parsed.append(int(version[l:r]))
   651    if len(parsed) == 2:
   652        # first minor version, like (1, 15)
   653        parsed.append(0)
   654    if len(parsed) != 3:
   655        # too many or too few components
   656        return None
   657    if r < len(version):
   658        # pre-release suffix
   659        parsed.append(version[r:])
   660    return tuple(parsed)
   661
   662def _version_is_prerelease(v):
   663    return len(v) > 3
   664
   665def _version_less(a, b):
   666    if a[:3] < b[:3]:
   667        return True
   668    if a[:3] > b[:3]:
   669        return False
   670    if len(a) > len(b):
   671        return True
   672    if len(a) < len(b) or len(a) == 3:
   673        return False
   674    return a[3:] < b[3:]
   675
   676def _version_string(v):
   677    suffix = v[3] if _version_is_prerelease(v) else ""
   678    if v[-1] == 0:
   679        v = v[:-1]
   680    return ".".join([str(n) for n in v]) + suffix
   681
   682def _have_same_length(*lists):
   683    if not lists:
   684        fail("expected at least one list")
   685    return len({len(l): None for l in lists}) == 1
   686
   687def go_register_toolchains(version = None, nogo = None, go_version = None, experiments = None):
   688    """See /go/toolchains.rst#go-register-toolchains for full documentation."""
   689    if not version:
   690        version = go_version  # old name
   691
   692    sdk_kinds = ("go_download_sdk_rule", "go_host_sdk_rule", "_go_local_sdk", "_go_wrap_sdk")
   693    existing_rules = native.existing_rules()
   694    sdk_rules = [r for r in existing_rules.values() if r["kind"] in sdk_kinds]
   695    if len(sdk_rules) == 0 and "go_sdk" in existing_rules:
   696        # may be local_repository in bazel_tests.
   697        sdk_rules.append(existing_rules["go_sdk"])
   698
   699    if version and len(sdk_rules) > 0:
   700        fail("go_register_toolchains: version set after go sdk rule declared ({})".format(", ".join([r["name"] for r in sdk_rules])))
   701    if len(sdk_rules) == 0:
   702        if not version:
   703            fail('go_register_toolchains: version must be a string like "1.15.5" or "host"')
   704        elif version == "host":
   705            go_host_sdk(name = "go_sdk", experiments = experiments)
   706        else:
   707            pv = parse_version(version)
   708            if not pv:
   709                fail('go_register_toolchains: version must be a string like "1.15.5" or "host"')
   710            if _version_less(pv, MIN_SUPPORTED_VERSION):
   711                print("DEPRECATED: Go versions before {} are not supported and may not work".format(_version_string(MIN_SUPPORTED_VERSION)))
   712            go_download_sdk(
   713                name = "go_sdk",
   714                version = version,
   715                experiments = experiments,
   716            )
   717
   718    if nogo:
   719        # Override default definition in go_rules_dependencies().
   720        go_register_nogo(
   721            name = "io_bazel_rules_nogo",
   722            nogo = nogo,
   723        )

View as plain text