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