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  }