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