1# Copyright 2020 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 "@bazel_skylib//lib:paths.bzl", 17 "paths", 18) 19load( 20 "//go/private:mode.bzl", 21 "LINKMODES", 22 "LINKMODE_NORMAL", 23) 24load( 25 "//go/private:platforms.bzl", 26 "CGO_GOOS_GOARCH", 27 "GOOS_GOARCH", 28) 29load( 30 "//go/private:providers.bzl", 31 "GoArchive", 32 "GoLibrary", 33 "GoSource", 34) 35 36# A list of rules_go settings that are possibly set by go_transition. 37# Keep their package name in sync with the implementation of 38# _original_setting_key. 39TRANSITIONED_GO_SETTING_KEYS = [ 40 "//go/config:static", 41 "//go/config:msan", 42 "//go/config:race", 43 "//go/config:pure", 44 "//go/config:linkmode", 45 "//go/config:tags", 46] 47 48def _deduped_and_sorted(strs): 49 return sorted({s: None for s in strs}.keys()) 50 51def _original_setting_key(key): 52 if not "//go/config:" in key: 53 fail("_original_setting_key currently assumes that all Go settings live under //go/config, got: " + key) 54 name = key.split(":")[1] 55 return "//go/private/rules:original_" + name 56 57_SETTING_KEY_TO_ORIGINAL_SETTING_KEY = { 58 setting: _original_setting_key(setting) 59 for setting in TRANSITIONED_GO_SETTING_KEYS 60} 61 62def _go_transition_impl(settings, attr): 63 # NOTE: Keep the list of rules_go settings set by this transition in sync 64 # with POTENTIALLY_TRANSITIONED_SETTINGS. 65 # 66 # NOTE(bazelbuild/bazel#11409): Calling fail here for invalid combinations 67 # of flags reports an error but does not stop the build. 68 # In any case, get_mode should mainly be responsible for reporting 69 # invalid modes, since it also takes --features flags into account. 70 71 original_settings = settings 72 settings = dict(settings) 73 74 _set_ternary(settings, attr, "static") 75 race = _set_ternary(settings, attr, "race") 76 msan = _set_ternary(settings, attr, "msan") 77 pure = _set_ternary(settings, attr, "pure") 78 if race == "on": 79 if pure == "on": 80 fail('race = "on" cannot be set when pure = "on" is set. race requires cgo.') 81 pure = "off" 82 settings["//go/config:pure"] = False 83 if msan == "on": 84 if pure == "on": 85 fail('msan = "on" cannot be set when msan = "on" is set. msan requires cgo.') 86 pure = "off" 87 settings["//go/config:pure"] = False 88 if pure == "on": 89 race = "off" 90 settings["//go/config:race"] = False 91 msan = "off" 92 settings["//go/config:msan"] = False 93 cgo = pure == "off" 94 95 goos = getattr(attr, "goos", "auto") 96 goarch = getattr(attr, "goarch", "auto") 97 _check_ternary("pure", pure) 98 if goos != "auto" or goarch != "auto": 99 if goos == "auto": 100 fail("goos must be set if goarch is set") 101 if goarch == "auto": 102 fail("goarch must be set if goos is set") 103 if (goos, goarch) not in GOOS_GOARCH: 104 fail("invalid goos, goarch pair: {}, {}".format(goos, goarch)) 105 if cgo and (goos, goarch) not in CGO_GOOS_GOARCH: 106 fail('pure is "off" but cgo is not supported on {} {}'.format(goos, goarch)) 107 platform = "@io_bazel_rules_go//go/toolchain:{}_{}{}".format(goos, goarch, "_cgo" if cgo else "") 108 settings["//command_line_option:platforms"] = platform 109 110 tags = getattr(attr, "gotags", []) 111 if tags: 112 settings["//go/config:tags"] = _deduped_and_sorted(tags) 113 114 linkmode = getattr(attr, "linkmode", "auto") 115 if linkmode != "auto": 116 if linkmode not in LINKMODES: 117 fail("linkmode: invalid mode {}; want one of {}".format(linkmode, ", ".join(LINKMODES))) 118 settings["//go/config:linkmode"] = linkmode 119 120 for key, original_key in _SETTING_KEY_TO_ORIGINAL_SETTING_KEY.items(): 121 old_value = original_settings[key] 122 value = settings[key] 123 124 # If the outgoing configuration would differ from the incoming one in a 125 # value, record the old value in the special original_* key so that the 126 # real setting can be reset to this value before the new configuration 127 # would cross a non-deps dependency edge. 128 if value != old_value: 129 # Encoding as JSON makes it possible to embed settings of arbitrary 130 # types (currently bool, string and string_list) into a single type 131 # of setting (string) with the information preserved whether the 132 # original setting wasn't set explicitly (empty string) or was set 133 # explicitly to its default (always a non-empty string with JSON 134 # encoding, e.g. "\"\"" or "[]"). 135 settings[original_key] = json.encode(old_value) 136 else: 137 settings[original_key] = "" 138 139 return settings 140 141def _request_nogo_transition(settings, _attr): 142 """Indicates that we want the project configured nogo instead of a noop. 143 144 This does not guarantee that the project configured nogo will be used (if 145 bootstrap is true we are currently building nogo so that is a cyclic 146 dependency). 147 148 The config setting nogo_active requires bootstrap to be false and 149 request_nogo to be true to provide the project configured nogo. 150 """ 151 settings = dict(settings) 152 settings["//go/private:request_nogo"] = True 153 return settings 154 155request_nogo_transition = transition( 156 implementation = _request_nogo_transition, 157 inputs = [], 158 outputs = ["//go/private:request_nogo"], 159) 160 161go_transition = transition( 162 implementation = _go_transition_impl, 163 inputs = [ 164 "//command_line_option:platforms", 165 ] + TRANSITIONED_GO_SETTING_KEYS, 166 outputs = [ 167 "//command_line_option:platforms", 168 ] + TRANSITIONED_GO_SETTING_KEYS + _SETTING_KEY_TO_ORIGINAL_SETTING_KEY.values(), 169) 170 171_common_reset_transition_dict = dict({ 172 "//go/private:request_nogo": False, 173 "//go/config:static": False, 174 "//go/config:msan": False, 175 "//go/config:race": False, 176 "//go/config:pure": False, 177 "//go/config:debug": False, 178 "//go/config:linkmode": LINKMODE_NORMAL, 179 "//go/config:tags": [], 180}, **{setting: "" for setting in _SETTING_KEY_TO_ORIGINAL_SETTING_KEY.values()}) 181 182_reset_transition_dict = dict(_common_reset_transition_dict, **{ 183 "//go/private:bootstrap_nogo": True, 184}) 185 186_reset_transition_keys = sorted(_reset_transition_dict.keys()) 187 188_stdlib_keep_keys = sorted([ 189 "//go/config:msan", 190 "//go/config:race", 191 "//go/config:pure", 192 "//go/config:linkmode", 193 "//go/config:tags", 194]) 195 196def _go_tool_transition_impl(settings, _attr): 197 """Sets most Go settings to default values (use for external Go tools). 198 199 go_tool_transition sets all of the //go/config settings to their default 200 values and disables nogo. This is used for Go tool binaries like nogo 201 itself. Tool binaries shouldn't depend on the link mode or tags of the 202 target configuration and neither the tools nor the code they potentially 203 generate should be subject to nogo's static analysis. This transition 204 doesn't change the platform (goos, goarch), but tool binaries should also 205 have `cfg = "exec"` so tool binaries should be built for the execution 206 platform. 207 """ 208 return dict(settings, **_reset_transition_dict) 209 210go_tool_transition = transition( 211 implementation = _go_tool_transition_impl, 212 inputs = _reset_transition_keys, 213 outputs = _reset_transition_keys, 214) 215 216def _non_go_tool_transition_impl(settings, _attr): 217 """Sets all Go settings to default values (use for external non-Go tools). 218 219 non_go_tool_transition sets all of the //go/config settings as well as the 220 nogo settings to their default values. This is used for all tools that are 221 not themselves targets created from rules_go rules and thus do not read 222 these settings. Resetting all of them to defaults prevents unnecessary 223 configuration changes for these targets that could cause rebuilds. 224 225 Examples: This transition is applied to attributes referencing proto_library 226 targets or protoc directly. 227 """ 228 settings = dict(settings, **_reset_transition_dict) 229 settings["//go/private:bootstrap_nogo"] = False 230 return settings 231 232non_go_tool_transition = transition( 233 implementation = _non_go_tool_transition_impl, 234 inputs = _reset_transition_keys, 235 outputs = _reset_transition_keys, 236) 237 238def _go_stdlib_transition_impl(settings, _attr): 239 """Sets all Go settings to their default values, except for those affecting the Go SDK. 240 241 This transition is similar to _non_go_tool_transition except that it keeps the 242 parts of the configuration that determine how to build the standard library. 243 It's used to consolidate the configurations used to build the standard library to limit 244 the number built. 245 """ 246 settings = dict(settings) 247 for label, value in _reset_transition_dict.items(): 248 if label not in _stdlib_keep_keys: 249 settings[label] = value 250 settings["//go/config:tags"] = [t for t in settings["//go/config:tags"] if t in _TAG_AFFECTS_STDLIB] 251 settings["//go/private:bootstrap_nogo"] = False 252 return settings 253 254go_stdlib_transition = transition( 255 implementation = _go_stdlib_transition_impl, 256 inputs = _reset_transition_keys, 257 outputs = _reset_transition_keys, 258) 259 260def _go_reset_target_impl(ctx): 261 t = ctx.attr.dep[0] # [0] seems to be necessary with the transition 262 providers = [t[p] for p in [GoLibrary, GoSource, GoArchive] if p in t] 263 264 # We can't pass DefaultInfo through as-is, since Bazel forbids executable 265 # if it's a file declared in a different target. To emulate that, symlink 266 # to the original executable, if there is one. 267 default_info = t[DefaultInfo] 268 269 new_executable = None 270 original_executable = default_info.files_to_run.executable 271 default_runfiles = default_info.default_runfiles 272 if original_executable: 273 # In order for the symlink to have the same basename as the original 274 # executable (important in the case of proto plugins), put it in a 275 # subdirectory named after the label to prevent collisions. 276 new_executable = ctx.actions.declare_file(paths.join(ctx.label.name, original_executable.basename)) 277 ctx.actions.symlink( 278 output = new_executable, 279 target_file = original_executable, 280 is_executable = True, 281 ) 282 default_runfiles = default_runfiles.merge(ctx.runfiles([new_executable])) 283 284 providers.append( 285 DefaultInfo( 286 files = default_info.files, 287 data_runfiles = default_info.data_runfiles, 288 default_runfiles = default_runfiles, 289 executable = new_executable, 290 ), 291 ) 292 return providers 293 294go_reset_target = rule( 295 implementation = _go_reset_target_impl, 296 attrs = { 297 "dep": attr.label( 298 mandatory = True, 299 cfg = go_tool_transition, 300 ), 301 "_allowlist_function_transition": attr.label( 302 default = "@bazel_tools//tools/allowlists/function_transition_allowlist", 303 ), 304 }, 305 doc = """Forwards providers from a target and applies go_tool_transition. 306 307go_reset_target depends on a single target, built using go_tool_transition. It 308forwards Go providers and DefaultInfo. 309 310This is used to work around a problem with building tools: Go tools should be 311built with 'cfg = "exec"' so they work on the execution platform, but we also 312need to apply go_tool_transition so that e.g. a tool isn't built as a shared 313library with race instrumentation. This acts as an intermediate rule that allows 314to apply both both transitions. 315""", 316) 317 318non_go_reset_target = rule( 319 implementation = _go_reset_target_impl, 320 attrs = { 321 "dep": attr.label( 322 mandatory = True, 323 cfg = non_go_tool_transition, 324 ), 325 "_allowlist_function_transition": attr.label( 326 default = "@bazel_tools//tools/allowlists/function_transition_allowlist", 327 ), 328 }, 329 doc = """Forwards providers from a target and applies non_go_tool_transition. 330 331non_go_reset_target depends on a single target, built using 332non_go_tool_transition. It forwards Go providers and DefaultInfo. 333 334This is used to work around a problem with building tools: Non-Go tools should 335be built with 'cfg = "exec"' so they work on the execution platform, but they 336also shouldn't be affected by Go-specific config changes applied by 337go_transition. 338""", 339) 340 341def _non_go_transition_impl(settings, _attr): 342 """Sets all Go settings to the values they had before the last go_transition. 343 344 non_go_transition sets all of the //go/config settings to the value they had 345 before the last go_transition. This should be used on all attributes of 346 go_library/go_binary/go_test that are built in the target configuration and 347 do not constitute advertise any Go providers. 348 349 Examples: This transition is applied to the 'data' attribute of go_binary so 350 that other Go binaries used at runtime aren't affected by a non-standard 351 link mode set on the go_binary target, but still use the same top-level 352 settings such as e.g. race instrumentation. 353 """ 354 new_settings = {} 355 for key, original_key in _SETTING_KEY_TO_ORIGINAL_SETTING_KEY.items(): 356 original_value = settings[original_key] 357 if original_value: 358 # Reset to the original value of the setting before go_transition. 359 new_settings[key] = json.decode(original_value) 360 else: 361 new_settings[key] = settings[key] 362 363 # Reset the value of the helper setting to its default for two reasons: 364 # 1. Performance: This ensures that the Go settings of non-Go 365 # dependencies have the same values as before the go_transition, 366 # which can prevent unnecessary rebuilds caused by configuration 367 # changes. 368 # 2. Correctness in edge cases: If there is a path in the build graph 369 # from a go_binary's non-Go dependency to a go_library that does not 370 # pass through another go_binary (e.g., through a custom rule 371 # replacement for go_binary), this transition could be applied again 372 # and cause incorrect Go setting values. 373 new_settings[original_key] = "" 374 375 return new_settings 376 377non_go_transition = transition( 378 implementation = _non_go_transition_impl, 379 inputs = TRANSITIONED_GO_SETTING_KEYS + _SETTING_KEY_TO_ORIGINAL_SETTING_KEY.values(), 380 outputs = TRANSITIONED_GO_SETTING_KEYS + _SETTING_KEY_TO_ORIGINAL_SETTING_KEY.values(), 381) 382 383def _check_ternary(name, value): 384 if value not in ("on", "off", "auto"): 385 fail('{}: must be "on", "off", or "auto"'.format(name)) 386 387def _set_ternary(settings, attr, name): 388 value = getattr(attr, name, "auto") 389 _check_ternary(name, value) 390 if value != "auto": 391 label = "//go/config:{}".format(name) 392 settings[label] = value == "on" 393 return value 394 395_SDK_VERSION_BUILD_SETTING = "//go/toolchain:sdk_version" 396TRANSITIONED_GO_CROSS_SETTING_KEYS = [ 397 _SDK_VERSION_BUILD_SETTING, 398 "//command_line_option:platforms", 399] 400 401def _go_cross_transition_impl(settings, attr): 402 settings = dict(settings) 403 if attr.sdk_version != None: 404 settings[_SDK_VERSION_BUILD_SETTING] = attr.sdk_version 405 406 if attr.platform != None: 407 settings["//command_line_option:platforms"] = str(attr.platform) 408 409 return settings 410 411go_cross_transition = transition( 412 implementation = _go_cross_transition_impl, 413 inputs = TRANSITIONED_GO_CROSS_SETTING_KEYS, 414 outputs = TRANSITIONED_GO_CROSS_SETTING_KEYS, 415) 416 417# A list of Go build tags that potentially affect the build of the standard 418# library. 419# 420# This should be updated to contain the union of all tags relevant for all 421# versions of Go that are still relevant. 422# 423# Currently supported versions: 1.18, 1.19, 1.20 424# 425# To regenerate, run and paste the output of 426# bazel run //go/tools/internal/stdlib_tags:stdlib_tags -- path/to/go_sdk_1/src ... 427_TAG_AFFECTS_STDLIB = { 428 "alpha": None, 429 "appengine": None, 430 "asan": None, 431 "boringcrypto": None, 432 "cmd_go_bootstrap": None, 433 "compiler_bootstrap": None, 434 "debuglog": None, 435 "faketime": None, 436 "gc": None, 437 "gccgo": None, 438 "gen": None, 439 "generate": None, 440 "gofuzz": None, 441 "ignore": None, 442 "libfuzzer": None, 443 "m68k": None, 444 "math_big_pure_go": None, 445 "msan": None, 446 "netcgo": None, 447 "netgo": None, 448 "nethttpomithttp2": None, 449 "nios2": None, 450 "noopt": None, 451 "osusergo": None, 452 "purego": None, 453 "race": None, 454 "sh": None, 455 "shbe": None, 456 "tablegen": None, 457 "testgo": None, 458 "timetzdata": None, 459} 460