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