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