diff options
Diffstat (limited to 'go/private/rules/transition.bzl')
-rw-r--r-- | go/private/rules/transition.bzl | 459 |
1 files changed, 459 insertions, 0 deletions
diff --git a/go/private/rules/transition.bzl b/go/private/rules/transition.bzl new file mode 100644 index 00000000..4e87e30e --- /dev/null +++ b/go/private/rules/transition.bzl @@ -0,0 +1,459 @@ +# Copyright 2020 The Bazel Authors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +load( + "@bazel_skylib//lib:paths.bzl", + "paths", +) +load( + "//go/private:mode.bzl", + "LINKMODES", + "LINKMODE_NORMAL", +) +load( + "//go/private:platforms.bzl", + "CGO_GOOS_GOARCH", + "GOOS_GOARCH", +) +load( + "//go/private:providers.bzl", + "GoArchive", + "GoLibrary", + "GoSource", +) + +# A list of rules_go settings that are possibly set by go_transition. +# Keep their package name in sync with the implementation of +# _original_setting_key. +TRANSITIONED_GO_SETTING_KEYS = [ + "//go/config:static", + "//go/config:msan", + "//go/config:race", + "//go/config:pure", + "//go/config:linkmode", + "//go/config:tags", +] + +def _deduped_and_sorted(strs): + return sorted({s: None for s in strs}.keys()) + +def _original_setting_key(key): + if not "//go/config:" in key: + fail("_original_setting_key currently assumes that all Go settings live under //go/config, got: " + key) + name = key.split(":")[1] + return "//go/private/rules:original_" + name + +_SETTING_KEY_TO_ORIGINAL_SETTING_KEY = { + setting: _original_setting_key(setting) + for setting in TRANSITIONED_GO_SETTING_KEYS +} + +def _go_transition_impl(settings, attr): + # NOTE: Keep the list of rules_go settings set by this transition in sync + # with POTENTIALLY_TRANSITIONED_SETTINGS. + # + # NOTE(bazelbuild/bazel#11409): Calling fail here for invalid combinations + # of flags reports an error but does not stop the build. + # In any case, get_mode should mainly be responsible for reporting + # invalid modes, since it also takes --features flags into account. + + original_settings = settings + settings = dict(settings) + + _set_ternary(settings, attr, "static") + race = _set_ternary(settings, attr, "race") + msan = _set_ternary(settings, attr, "msan") + pure = _set_ternary(settings, attr, "pure") + if race == "on": + if pure == "on": + fail('race = "on" cannot be set when pure = "on" is set. race requires cgo.') + pure = "off" + settings["//go/config:pure"] = False + if msan == "on": + if pure == "on": + fail('msan = "on" cannot be set when msan = "on" is set. msan requires cgo.') + pure = "off" + settings["//go/config:pure"] = False + if pure == "on": + race = "off" + settings["//go/config:race"] = False + msan = "off" + settings["//go/config:msan"] = False + cgo = pure == "off" + + goos = getattr(attr, "goos", "auto") + goarch = getattr(attr, "goarch", "auto") + _check_ternary("pure", pure) + if goos != "auto" or goarch != "auto": + if goos == "auto": + fail("goos must be set if goarch is set") + if goarch == "auto": + fail("goarch must be set if goos is set") + if (goos, goarch) not in GOOS_GOARCH: + fail("invalid goos, goarch pair: {}, {}".format(goos, goarch)) + if cgo and (goos, goarch) not in CGO_GOOS_GOARCH: + fail('pure is "off" but cgo is not supported on {} {}'.format(goos, goarch)) + platform = "@io_bazel_rules_go//go/toolchain:{}_{}{}".format(goos, goarch, "_cgo" if cgo else "") + settings["//command_line_option:platforms"] = platform + + tags = getattr(attr, "gotags", []) + if tags: + settings["//go/config:tags"] = _deduped_and_sorted(tags) + + linkmode = getattr(attr, "linkmode", "auto") + if linkmode != "auto": + if linkmode not in LINKMODES: + fail("linkmode: invalid mode {}; want one of {}".format(linkmode, ", ".join(LINKMODES))) + settings["//go/config:linkmode"] = linkmode + + for key, original_key in _SETTING_KEY_TO_ORIGINAL_SETTING_KEY.items(): + old_value = original_settings[key] + value = settings[key] + + # If the outgoing configuration would differ from the incoming one in a + # value, record the old value in the special original_* key so that the + # real setting can be reset to this value before the new configuration + # would cross a non-deps dependency edge. + if value != old_value: + # Encoding as JSON makes it possible to embed settings of arbitrary + # types (currently bool, string and string_list) into a single type + # of setting (string) with the information preserved whether the + # original setting wasn't set explicitly (empty string) or was set + # explicitly to its default (always a non-empty string with JSON + # encoding, e.g. "\"\"" or "[]"). + settings[original_key] = json.encode(old_value) + else: + settings[original_key] = "" + + return settings + +def _request_nogo_transition(settings, _attr): + """Indicates that we want the project configured nogo instead of a noop. + + This does not guarantee that the project configured nogo will be used (if + bootstrap is true we are currently building nogo so that is a cyclic + dependency). + + The config setting nogo_active requires bootstrap to be false and + request_nogo to be true to provide the project configured nogo. + """ + settings = dict(settings) + settings["//go/private:request_nogo"] = True + return settings + +request_nogo_transition = transition( + implementation = _request_nogo_transition, + inputs = [], + outputs = ["//go/private:request_nogo"], +) + +go_transition = transition( + implementation = _go_transition_impl, + inputs = [ + "//command_line_option:platforms", + ] + TRANSITIONED_GO_SETTING_KEYS, + outputs = [ + "//command_line_option:platforms", + ] + TRANSITIONED_GO_SETTING_KEYS + _SETTING_KEY_TO_ORIGINAL_SETTING_KEY.values(), +) + +_common_reset_transition_dict = dict({ + "//go/private:request_nogo": False, + "//go/config:static": False, + "//go/config:msan": False, + "//go/config:race": False, + "//go/config:pure": False, + "//go/config:debug": False, + "//go/config:linkmode": LINKMODE_NORMAL, + "//go/config:tags": [], +}, **{setting: "" for setting in _SETTING_KEY_TO_ORIGINAL_SETTING_KEY.values()}) + +_reset_transition_dict = dict(_common_reset_transition_dict, **{ + "//go/private:bootstrap_nogo": True, +}) + +_reset_transition_keys = sorted(_reset_transition_dict.keys()) + +_stdlib_keep_keys = sorted([ + "//go/config:msan", + "//go/config:race", + "//go/config:pure", + "//go/config:linkmode", + "//go/config:tags", +]) + +def _go_tool_transition_impl(settings, _attr): + """Sets most Go settings to default values (use for external Go tools). + + go_tool_transition sets all of the //go/config settings to their default + values and disables nogo. This is used for Go tool binaries like nogo + itself. Tool binaries shouldn't depend on the link mode or tags of the + target configuration and neither the tools nor the code they potentially + generate should be subject to nogo's static analysis. This transition + doesn't change the platform (goos, goarch), but tool binaries should also + have `cfg = "exec"` so tool binaries should be built for the execution + platform. + """ + return dict(settings, **_reset_transition_dict) + +go_tool_transition = transition( + implementation = _go_tool_transition_impl, + inputs = _reset_transition_keys, + outputs = _reset_transition_keys, +) + +def _non_go_tool_transition_impl(settings, _attr): + """Sets all Go settings to default values (use for external non-Go tools). + + non_go_tool_transition sets all of the //go/config settings as well as the + nogo settings to their default values. This is used for all tools that are + not themselves targets created from rules_go rules and thus do not read + these settings. Resetting all of them to defaults prevents unnecessary + configuration changes for these targets that could cause rebuilds. + + Examples: This transition is applied to attributes referencing proto_library + targets or protoc directly. + """ + settings = dict(settings, **_reset_transition_dict) + settings["//go/private:bootstrap_nogo"] = False + return settings + +non_go_tool_transition = transition( + implementation = _non_go_tool_transition_impl, + inputs = _reset_transition_keys, + outputs = _reset_transition_keys, +) + +def _go_stdlib_transition_impl(settings, _attr): + """Sets all Go settings to their default values, except for those affecting the Go SDK. + + This transition is similar to _non_go_tool_transition except that it keeps the + parts of the configuration that determine how to build the standard library. + It's used to consolidate the configurations used to build the standard library to limit + the number built. + """ + settings = dict(settings) + for label, value in _reset_transition_dict.items(): + if label not in _stdlib_keep_keys: + settings[label] = value + settings["//go/config:tags"] = [t for t in settings["//go/config:tags"] if t in _TAG_AFFECTS_STDLIB] + settings["//go/private:bootstrap_nogo"] = False + return settings + +go_stdlib_transition = transition( + implementation = _go_stdlib_transition_impl, + inputs = _reset_transition_keys, + outputs = _reset_transition_keys, +) + +def _go_reset_target_impl(ctx): + t = ctx.attr.dep[0] # [0] seems to be necessary with the transition + providers = [t[p] for p in [GoLibrary, GoSource, GoArchive] if p in t] + + # We can't pass DefaultInfo through as-is, since Bazel forbids executable + # if it's a file declared in a different target. To emulate that, symlink + # to the original executable, if there is one. + default_info = t[DefaultInfo] + + new_executable = None + original_executable = default_info.files_to_run.executable + default_runfiles = default_info.default_runfiles + if original_executable: + # In order for the symlink to have the same basename as the original + # executable (important in the case of proto plugins), put it in a + # subdirectory named after the label to prevent collisions. + new_executable = ctx.actions.declare_file(paths.join(ctx.label.name, original_executable.basename)) + ctx.actions.symlink( + output = new_executable, + target_file = original_executable, + is_executable = True, + ) + default_runfiles = default_runfiles.merge(ctx.runfiles([new_executable])) + + providers.append( + DefaultInfo( + files = default_info.files, + data_runfiles = default_info.data_runfiles, + default_runfiles = default_runfiles, + executable = new_executable, + ), + ) + return providers + +go_reset_target = rule( + implementation = _go_reset_target_impl, + attrs = { + "dep": attr.label( + mandatory = True, + cfg = go_tool_transition, + ), + "_allowlist_function_transition": attr.label( + default = "@bazel_tools//tools/allowlists/function_transition_allowlist", + ), + }, + doc = """Forwards providers from a target and applies go_tool_transition. + +go_reset_target depends on a single target, built using go_tool_transition. It +forwards Go providers and DefaultInfo. + +This is used to work around a problem with building tools: Go tools should be +built with 'cfg = "exec"' so they work on the execution platform, but we also +need to apply go_tool_transition so that e.g. a tool isn't built as a shared +library with race instrumentation. This acts as an intermediate rule that allows +to apply both both transitions. +""", +) + +non_go_reset_target = rule( + implementation = _go_reset_target_impl, + attrs = { + "dep": attr.label( + mandatory = True, + cfg = non_go_tool_transition, + ), + "_allowlist_function_transition": attr.label( + default = "@bazel_tools//tools/allowlists/function_transition_allowlist", + ), + }, + doc = """Forwards providers from a target and applies non_go_tool_transition. + +non_go_reset_target depends on a single target, built using +non_go_tool_transition. It forwards Go providers and DefaultInfo. + +This is used to work around a problem with building tools: Non-Go tools should +be built with 'cfg = "exec"' so they work on the execution platform, but they +also shouldn't be affected by Go-specific config changes applied by +go_transition. +""", +) + +def _non_go_transition_impl(settings, _attr): + """Sets all Go settings to the values they had before the last go_transition. + + non_go_transition sets all of the //go/config settings to the value they had + before the last go_transition. This should be used on all attributes of + go_library/go_binary/go_test that are built in the target configuration and + do not constitute advertise any Go providers. + + Examples: This transition is applied to the 'data' attribute of go_binary so + that other Go binaries used at runtime aren't affected by a non-standard + link mode set on the go_binary target, but still use the same top-level + settings such as e.g. race instrumentation. + """ + new_settings = {} + for key, original_key in _SETTING_KEY_TO_ORIGINAL_SETTING_KEY.items(): + original_value = settings[original_key] + if original_value: + # Reset to the original value of the setting before go_transition. + new_settings[key] = json.decode(original_value) + else: + new_settings[key] = settings[key] + + # Reset the value of the helper setting to its default for two reasons: + # 1. Performance: This ensures that the Go settings of non-Go + # dependencies have the same values as before the go_transition, + # which can prevent unnecessary rebuilds caused by configuration + # changes. + # 2. Correctness in edge cases: If there is a path in the build graph + # from a go_binary's non-Go dependency to a go_library that does not + # pass through another go_binary (e.g., through a custom rule + # replacement for go_binary), this transition could be applied again + # and cause incorrect Go setting values. + new_settings[original_key] = "" + + return new_settings + +non_go_transition = transition( + implementation = _non_go_transition_impl, + inputs = TRANSITIONED_GO_SETTING_KEYS + _SETTING_KEY_TO_ORIGINAL_SETTING_KEY.values(), + outputs = TRANSITIONED_GO_SETTING_KEYS + _SETTING_KEY_TO_ORIGINAL_SETTING_KEY.values(), +) + +def _check_ternary(name, value): + if value not in ("on", "off", "auto"): + fail('{}: must be "on", "off", or "auto"'.format(name)) + +def _set_ternary(settings, attr, name): + value = getattr(attr, name, "auto") + _check_ternary(name, value) + if value != "auto": + label = "//go/config:{}".format(name) + settings[label] = value == "on" + return value + +_SDK_VERSION_BUILD_SETTING = "//go/toolchain:sdk_version" +TRANSITIONED_GO_CROSS_SETTING_KEYS = [ + _SDK_VERSION_BUILD_SETTING, + "//command_line_option:platforms", +] + +def _go_cross_transition_impl(settings, attr): + settings = dict(settings) + if attr.sdk_version != None: + settings[_SDK_VERSION_BUILD_SETTING] = attr.sdk_version + + if attr.platform != None: + settings["//command_line_option:platforms"] = str(attr.platform) + + return settings + +go_cross_transition = transition( + implementation = _go_cross_transition_impl, + inputs = TRANSITIONED_GO_CROSS_SETTING_KEYS, + outputs = TRANSITIONED_GO_CROSS_SETTING_KEYS, +) + +# A list of Go build tags that potentially affect the build of the standard +# library. +# +# This should be updated to contain the union of all tags relevant for all +# versions of Go that are still relevant. +# +# Currently supported versions: 1.18, 1.19, 1.20 +# +# To regenerate, run and paste the output of +# bazel run //go/tools/internal/stdlib_tags:stdlib_tags -- path/to/go_sdk_1/src ... +_TAG_AFFECTS_STDLIB = { + "alpha": None, + "appengine": None, + "asan": None, + "boringcrypto": None, + "cmd_go_bootstrap": None, + "compiler_bootstrap": None, + "debuglog": None, + "faketime": None, + "gc": None, + "gccgo": None, + "gen": None, + "generate": None, + "gofuzz": None, + "ignore": None, + "libfuzzer": None, + "m68k": None, + "math_big_pure_go": None, + "msan": None, + "netcgo": None, + "netgo": None, + "nethttpomithttp2": None, + "nios2": None, + "noopt": None, + "osusergo": None, + "purego": None, + "race": None, + "sh": None, + "shbe": None, + "tablegen": None, + "testgo": None, + "timetzdata": None, +} |