...

Text file src/github.com/bazelbuild/rules_go/go/private/actions/link.bzl

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

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

View as plain text