...

Text file src/github.com/bazelbuild/rules_go/go/private/rules/transition.bzl

Documentation: github.com/bazelbuild/rules_go/go/private/rules

     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    "//go/config:pgoprofile",
    47]
    48
    49def _deduped_and_sorted(strs):
    50    return sorted({s: None for s in strs}.keys())
    51
    52def _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
    63def _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
   149def _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
   163request_nogo_transition = transition(
   164    implementation = _request_nogo_transition,
   165    inputs = [],
   166    outputs = ["//go/private:request_nogo"],
   167)
   168
   169go_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
   206def _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
   220go_tool_transition = transition(
   221    implementation = _go_tool_transition_impl,
   222    inputs = _reset_transition_keys,
   223    outputs = _reset_transition_keys,
   224)
   225
   226def _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
   242non_go_tool_transition = transition(
   243    implementation = _non_go_tool_transition_impl,
   244    inputs = _reset_transition_keys,
   245    outputs = _reset_transition_keys,
   246)
   247
   248def _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
   264go_stdlib_transition = transition(
   265    implementation = _go_stdlib_transition_impl,
   266    inputs = _reset_transition_keys,
   267    outputs = _reset_transition_keys,
   268)
   269
   270def _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
   304go_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
   317go_reset_target depends on a single target, built using go_tool_transition. It
   318forwards Go providers and DefaultInfo.
   319
   320This is used to work around a problem with building tools: Go tools should be
   321built with 'cfg = "exec"' so they work on the execution platform, but we also
   322need to apply go_tool_transition so that e.g. a tool isn't built as a shared
   323library with race instrumentation. This acts as an intermediate rule that allows
   324to apply both both transitions.
   325""",
   326)
   327
   328non_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
   341non_go_reset_target depends on a single target, built using
   342non_go_tool_transition. It forwards Go providers and DefaultInfo.
   343
   344This is used to work around a problem with building tools: Non-Go tools should
   345be built with 'cfg = "exec"' so they work on the execution platform, but they
   346also shouldn't be affected by Go-specific config changes applied by
   347go_transition.
   348""",
   349)
   350
   351def _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
   387non_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
   393def _check_ternary(name, value):
   394    if value not in ("on", "off", "auto"):
   395        fail('{}: must be "on", "off", or "auto"'.format(name))
   396
   397def _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"
   406TRANSITIONED_GO_CROSS_SETTING_KEYS = [
   407    _SDK_VERSION_BUILD_SETTING,
   408    "//command_line_option:platforms",
   409]
   410
   411def _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
   421go_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}

View as plain text