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