github.com/bazelbuild/rules_go@v0.47.2-0.20240515105122-e7ddb9ea474e/go/private/actions/link.bzl (about)

     1  # Copyright 2014 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:collections.bzl",
    17      "collections",
    18  )
    19  load(
    20      "//go/private:common.bzl",
    21      "GO_TOOLCHAIN_LABEL",
    22      "as_set",
    23      "count_group_matches",
    24      "has_shared_lib_extension",
    25  )
    26  load(
    27      "//go/private:mode.bzl",
    28      "LINKMODE_NORMAL",
    29      "LINKMODE_PLUGIN",
    30      "extld_from_cc_toolchain",
    31      "extldflags_from_cc_toolchain",
    32  )
    33  load(
    34      "//go/private:rpath.bzl",
    35      "rpath",
    36  )
    37  
    38  def _format_archive(d):
    39      return "{}={}={}".format(d.label, d.importmap, d.file.path)
    40  
    41  def _transitive_archives_without_test_archives(archive, test_archives):
    42      # Build the set of transitive dependencies. Currently, we tolerate multiple
    43      # archives with the same importmap (though this will be an error in the
    44      # future), but there is a special case which is difficult to avoid:
    45      # If a go_test has internal and external archives, and the external test
    46      # transitively depends on the library under test, we need to exclude the
    47      # library under test and use the internal test archive instead.
    48      deps = depset(transitive = [d.transitive for d in archive.direct])
    49      result = {}
    50  
    51      # Unfortunately, Starlark doesn't support set()
    52      test_imports = {}
    53      for t in test_archives:
    54          test_imports[t.importmap] = True
    55      for d in deps.to_list():
    56          if d.importmap in test_imports:
    57              continue
    58          if d.importmap in result:
    59              print("Multiple copies of {} passed to the linker. Ignoring {} in favor of {}".format(d.importmap, d.file.path, result[d.importmap].file.path))
    60              continue
    61          result[d.importmap] = d
    62      return result.values()
    63  
    64  def emit_link(
    65          go,
    66          archive = None,
    67          test_archives = [],
    68          executable = None,
    69          gc_linkopts = [],
    70          version_file = None,
    71          info_file = None):
    72      """See go/toolchains.rst#link for full documentation."""
    73  
    74      if archive == None:
    75          fail("archive is a required parameter")
    76      if executable == None:
    77          fail("executable is a required parameter")
    78  
    79      # Exclude -lstdc++ from link options. We don't want to link against it
    80      # unless we actually have some C++ code. _cgo_codegen will include it
    81      # in archives via CGO_LDFLAGS if it's needed.
    82      extldflags = [f for f in extldflags_from_cc_toolchain(go) if f not in ("-lstdc++", "-lc++", "-static")]
    83  
    84      if go.coverage_enabled:
    85          extldflags.append("--coverage")
    86      gc_linkopts = list(gc_linkopts)
    87      gc_linkopts.extend(go.mode.gc_linkopts)
    88      gc_linkopts, extldflags = _extract_extldflags(gc_linkopts, extldflags)
    89      builder_args = go.builder_args(go, "link")
    90      tool_args = go.tool_args(go)
    91  
    92      # use ar tool from cc toolchain if cc toolchain provides it
    93      if go.cgo_tools and go.cgo_tools.ar_path and go.cgo_tools.ar_path.endswith("ar"):
    94          tool_args.add_all(["-extar", go.cgo_tools.ar_path])
    95  
    96      # Add in any mode specific behaviours
    97      if go.mode.race:
    98          tool_args.add("-race")
    99      if go.mode.msan:
   100          tool_args.add("-msan")
   101  
   102      if go.mode.pure:
   103          tool_args.add("-linkmode", "internal")
   104      else:
   105          extld = extld_from_cc_toolchain(go)
   106          tool_args.add_all(extld)
   107          if extld and (go.mode.static or
   108                        go.mode.race or
   109                        go.mode.link != LINKMODE_NORMAL or
   110                        go.mode.goos == "windows" and go.mode.msan):
   111              # Force external linking for the following conditions:
   112              # * Mode is static but not pure: -static must be passed to the C
   113              #   linker if the binary contains cgo code. See #2168, #2216.
   114              # * Non-normal build mode: may not be strictly necessary, especially
   115              #   for modes like "pie".
   116              # * Race or msan build for Windows: Go linker has pairwise
   117              #   incompatibilities with mingw, and we get link errors in race mode.
   118              #   Using the C linker avoids that. Race and msan always require a
   119              #   a C toolchain. See #2614.
   120              # * Linux race builds: we get linker errors during build with Go's
   121              #   internal linker. For example, when using zig cc v0.10
   122              #   (clang-15.0.3):
   123              #
   124              #       runtime/cgo(.text): relocation target memset not defined
   125              tool_args.add("-linkmode", "external")
   126  
   127      if go.mode.static:
   128          extldflags.append("-static")
   129      if go.mode.link != LINKMODE_NORMAL:
   130          builder_args.add("-buildmode", go.mode.link)
   131      if go.mode.link == LINKMODE_PLUGIN:
   132          tool_args.add("-pluginpath", archive.data.importpath)
   133  
   134      arcs = _transitive_archives_without_test_archives(archive, test_archives)
   135      arcs.extend(test_archives)
   136      if (go.coverage_enabled and go.coverdata and
   137          not any([arc.importmap == go.coverdata.data.importmap for arc in arcs])):
   138          arcs.append(go.coverdata.data)
   139      builder_args.add_all(arcs, before_each = "-arc", map_each = _format_archive)
   140      builder_args.add("-package_list", go.package_list)
   141  
   142      # Build a list of rpaths for dynamic libraries we need to find.
   143      # rpaths are relative paths from the binary to directories where libraries
   144      # are stored. Binaries that require these will only work when installed in
   145      # the bazel execroot. Most binaries are only dynamically linked against
   146      # system libraries though.
   147      cgo_rpaths = sorted(collections.uniq([
   148          f
   149          for d in archive.cgo_deps.to_list()
   150          if has_shared_lib_extension(d.basename)
   151          for f in rpath.flags(go, d, executable = executable)
   152      ]))
   153      extldflags.extend(cgo_rpaths)
   154  
   155      # Process x_defs, and record whether stamping is used.
   156      stamp_x_defs_volatile = False
   157      stamp_x_defs_stable = False
   158      for k, v in archive.x_defs.items():
   159          builder_args.add("-X", "%s=%s" % (k, v))
   160          if go.stamp:
   161              stable_vars_count = (count_group_matches(v, "{STABLE_", "}") +
   162                                   v.count("{BUILD_EMBED_LABEL}") +
   163                                   v.count("{BUILD_USER}") +
   164                                   v.count("{BUILD_HOST}"))
   165              if stable_vars_count > 0:
   166                  stamp_x_defs_stable = True
   167              if count_group_matches(v, "{", "}") != stable_vars_count:
   168                  stamp_x_defs_volatile = True
   169  
   170      # Stamping support
   171      stamp_inputs = []
   172      if stamp_x_defs_stable:
   173          stamp_inputs.append(info_file)
   174      if stamp_x_defs_volatile:
   175          stamp_inputs.append(version_file)
   176      if stamp_inputs:
   177          builder_args.add_all(stamp_inputs, before_each = "-stamp")
   178  
   179      builder_args.add("-o", executable)
   180      builder_args.add("-main", archive.data.file)
   181      builder_args.add("-p", archive.data.importmap)
   182      tool_args.add_all(gc_linkopts)
   183      tool_args.add_all(go.toolchain.flags.link)
   184  
   185      # Do not remove, somehow this is needed when building for darwin/arm only.
   186      tool_args.add("-buildid=redacted")
   187      if go.mode.strip:
   188          tool_args.add("-s", "-w")
   189      tool_args.add_joined("-extldflags", extldflags, join_with = " ")
   190  
   191      conflict_err = _check_conflicts(arcs)
   192      if conflict_err:
   193          # Report package conflict errors in execution instead of analysis.
   194          # We could call fail() with this message, but Bazel prints a stack
   195          # that doesn't give useful information.
   196          builder_args.add("-conflict_err", conflict_err)
   197  
   198      inputs_direct = stamp_inputs + [go.sdk.package_list]
   199      if go.coverage_enabled and go.coverdata:
   200          inputs_direct.append(go.coverdata.data.file)
   201      inputs_transitive = [
   202          archive.libs,
   203          archive.cgo_deps,
   204          as_set(go.crosstool),
   205          as_set(go.sdk.tools),
   206          as_set(go.stdlib.libs),
   207      ]
   208      inputs = depset(direct = inputs_direct, transitive = inputs_transitive)
   209  
   210      go.actions.run(
   211          inputs = inputs,
   212          outputs = [executable],
   213          mnemonic = "GoLink",
   214          executable = go.toolchain._builder,
   215          arguments = [builder_args, "--", tool_args],
   216          env = go.env,
   217          toolchain = GO_TOOLCHAIN_LABEL,
   218      )
   219  
   220  def _extract_extldflags(gc_linkopts, extldflags):
   221      """Extracts -extldflags from gc_linkopts and combines them into a single list.
   222  
   223      Args:
   224        gc_linkopts: a list of flags passed in through the gc_linkopts attributes.
   225          ctx.expand_make_variables should have already been applied. -extldflags
   226          may appear multiple times in this list.
   227        extldflags: a list of flags to be passed to the external linker.
   228  
   229      Return:
   230        A tuple containing the filtered gc_linkopts with external flags removed,
   231        and a combined list of external flags. Each string in the returned
   232        extldflags list may contain multiple flags, separated by whitespace.
   233      """
   234      filtered_gc_linkopts = []
   235      is_extldflags = False
   236      for opt in gc_linkopts:
   237          if is_extldflags:
   238              is_extldflags = False
   239              extldflags.append(opt)
   240          elif opt == "-extldflags":
   241              is_extldflags = True
   242          else:
   243              filtered_gc_linkopts.append(opt)
   244      return filtered_gc_linkopts, extldflags
   245  
   246  def _check_conflicts(arcs):
   247      importmap_to_label = {}
   248      for arc in arcs:
   249          if arc.importmap in importmap_to_label:
   250              return """package conflict error: {}: multiple copies of package passed to linker:
   251  	{}
   252  	{}
   253  Set "importmap" to different paths or use 'bazel cquery' to ensure only one
   254  package with this path is linked.""".format(
   255                  arc.importmap,
   256                  importmap_to_label[arc.importmap],
   257                  arc.label,
   258              )
   259          importmap_to_label[arc.importmap] = arc.label
   260      for arc in arcs:
   261          for dep_importmap, dep_label in zip(arc._dep_importmaps, arc._dep_labels):
   262              if dep_importmap not in importmap_to_label:
   263                  return "package conflict error: {}: package needed by {} was not passed to linker".format(
   264                      dep_importmap,
   265                      arc.label,
   266                  )
   267              if importmap_to_label[dep_importmap] != dep_label:
   268                  err = """package conflict error: {}: package imports {}
   269  	  was compiled with: {}
   270  	but was linked with: {}""".format(
   271                      arc.importmap,
   272                      dep_importmap,
   273                      dep_label,
   274                      importmap_to_label[dep_importmap],
   275                  )
   276                  if importmap_to_label[dep_importmap].name.endswith("_test"):
   277                      err += """
   278  This sometimes happens when an external test (package ending with _test)
   279  imports a package that imports the library being tested. This is not supported."""
   280                  err += "\nSee https://github.com/bazelbuild/rules_go/issues/1877."
   281                  return err
   282      return None