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