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