go.starlark.net@v0.0.0-20231101134539-556fd59b42f6/syntax/testdata/scan.star (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 # (From https://github.com/bazelbuild/rules_go/blob/master/go/def.bzl@a6f9d0c) 16 17 load("//go/private:repositories.bzl", "go_repositories") 18 load("//go/private:go_repository.bzl", "go_repository", "new_go_repository") 19 load("//go/private:go_prefix.bzl", "go_prefix") 20 load("//go/private:json.bzl", "json_marshal") 21 22 """These are bare-bones Go rules. 23 24 In order of priority: 25 26 - BUILD file must be written by hand. 27 28 - No support for SWIG 29 30 - No test sharding or test XML. 31 32 """ 33 34 _DEFAULT_LIB = "go_default_library" 35 36 _VENDOR_PREFIX = "/vendor/" 37 38 go_filetype = FileType([ 39 ".go", 40 ".s", 41 ".S", 42 ".h", # may be included by .s 43 ]) 44 45 # be consistent to cc_library. 46 hdr_exts = [ 47 ".h", 48 ".hh", 49 ".hpp", 50 ".hxx", 51 ".inc", 52 ] 53 54 cc_hdr_filetype = FileType(hdr_exts) 55 56 # Extensions of files we can build with the Go compiler or with cc_library. 57 # This is a subset of the extensions recognized by go/build. 58 cgo_filetype = FileType([ 59 ".go", 60 ".c", 61 ".cc", 62 ".cxx", 63 ".cpp", 64 ".s", 65 ".S", 66 ".h", 67 ".hh", 68 ".hpp", 69 ".hxx", 70 ]) 71 72 ################ 73 74 def go_environment_vars(ctx): 75 """Return a map of environment variables for use with actions, based on 76 the arguments. Uses the ctx.fragments.cpp.cpu attribute, if present, 77 and picks a default of target_os="linux" and target_arch="amd64" 78 otherwise. 79 80 Args: 81 The starlark Context. 82 83 Returns: 84 A dict of environment variables for running Go tool commands that build for 85 the target OS and architecture. 86 """ 87 default_toolchain = {"GOOS": "linux", "GOARCH": "amd64"} 88 bazel_to_go_toolchain = { 89 "k8": {"GOOS": "linux", "GOARCH": "amd64"}, 90 "piii": {"GOOS": "linux", "GOARCH": "386"}, 91 "darwin": {"GOOS": "darwin", "GOARCH": "amd64"}, 92 "darwin_x86_64": {"GOOS": "darwin", "GOARCH": "amd64"}, 93 "freebsd": {"GOOS": "freebsd", "GOARCH": "amd64"}, 94 "armeabi-v7a": {"GOOS": "linux", "GOARCH": "arm"}, 95 "arm": {"GOOS": "linux", "GOARCH": "arm"}, 96 } 97 env = {} 98 if hasattr(ctx.file, "go_tool"): 99 env["GOROOT"] = ctx.file.go_tool.dirname + "/.." 100 env.update(bazel_to_go_toolchain.get(ctx.fragments.cpp.cpu, default_toolchain)) 101 return env 102 103 def _is_darwin_cpu(ctx): 104 cpu = ctx.fragments.cpp.cpu 105 return cpu == "darwin" or cpu == "darwin_x86_64" 106 107 def _emit_generate_params_action(cmds, ctx, fn): 108 cmds_all = [ 109 # Use bash explicitly. /bin/sh is default, and it may be linked to a 110 # different shell, e.g., /bin/dash on Ubuntu. 111 "#!/bin/bash", 112 "set -e", 113 ] 114 cmds_all += cmds 115 cmds_all_str = "\n".join(cmds_all) + "\n" 116 f = ctx.actions.declare_file(fn) 117 ctx.file_action( 118 output = f, 119 content = cmds_all_str, 120 executable = True, 121 ) 122 return f 123 124 def _emit_go_asm_action(ctx, source, hdrs, out_obj): 125 """Construct the command line for compiling Go Assembly code. 126 Constructs a symlink tree to accommodate for workspace name. 127 Args: 128 ctx: The starlark Context. 129 source: a source code artifact 130 hdrs: list of .h files that may be included 131 out_obj: the artifact (configured target?) that should be produced 132 """ 133 params = { 134 "go_tool": ctx.file.go_tool.path, 135 "includes": [f.dirname for f in hdrs] + [ctx.file.go_include.path], 136 "source": source.path, 137 "out": out_obj.path, 138 } 139 140 inputs = hdrs + ctx.files.toolchain + [source] 141 ctx.action( 142 inputs = inputs, 143 outputs = [out_obj], 144 mnemonic = "GoAsmCompile", 145 executable = ctx.executable._asm, 146 arguments = [json_marshal(params)], 147 ) 148 149 def _go_importpath(ctx): 150 """Returns the expected importpath of the go_library being built. 151 152 Args: 153 ctx: The starlark Context 154 155 Returns: 156 Go importpath of the library 157 """ 158 path = ctx.attr.importpath 159 if path != "": 160 return path 161 path = ctx.attr.go_prefix.go_prefix 162 if path.endswith("/"): 163 path = path[:-1] 164 if ctx.label.package: 165 path += "/" + ctx.label.package 166 if ctx.label.name != _DEFAULT_LIB: 167 path += "/" + ctx.label.name 168 if path.rfind(_VENDOR_PREFIX) != -1: 169 path = path[len(_VENDOR_PREFIX) + path.rfind(_VENDOR_PREFIX):] 170 if path[0] == "/": 171 path = path[1:] 172 return path 173 174 def _emit_go_compile_action(ctx, sources, deps, libpaths, out_object, gc_goopts): 175 """Construct the command line for compiling Go code. 176 177 Args: 178 ctx: The starlark Context. 179 sources: an iterable of source code artifacts (or CTs? or labels?) 180 deps: an iterable of dependencies. Each dependency d should have an 181 artifact in d.transitive_go_libraries representing all imported libraries. 182 libpaths: the set of paths to search for imported libraries. 183 out_object: the object file that should be produced 184 gc_goopts: additional flags to pass to the compiler. 185 """ 186 if ctx.coverage_instrumented(): 187 sources = _emit_go_cover_action(ctx, sources) 188 189 # Compile filtered files. 190 args = [ 191 "-cgo", 192 ctx.file.go_tool.path, 193 "tool", 194 "compile", 195 "-o", 196 out_object.path, 197 "-trimpath", 198 "-abs-.", 199 "-I", 200 "-abs-.", 201 ] 202 inputs = depset(sources + ctx.files.toolchain) 203 for dep in deps: 204 inputs += dep.transitive_go_libraries 205 for path in libpaths: 206 args += ["-I", path] 207 args += gc_goopts + [("" if i.basename.startswith("_cgo") else "-filter-") + i.path for i in sources] 208 ctx.action( 209 inputs = list(inputs), 210 outputs = [out_object], 211 mnemonic = "GoCompile", 212 executable = ctx.executable._filter_exec, 213 arguments = args, 214 env = go_environment_vars(ctx), 215 ) 216 217 return sources 218 219 def _emit_go_pack_action(ctx, out_lib, objects): 220 """Construct the command line for packing objects together. 221 222 Args: 223 ctx: The starlark Context. 224 out_lib: the archive that should be produced 225 objects: an iterable of object files to be added to the output archive file. 226 """ 227 ctx.action( 228 inputs = objects + ctx.files.toolchain, 229 outputs = [out_lib], 230 mnemonic = "GoPack", 231 executable = ctx.file.go_tool, 232 arguments = ["tool", "pack", "c", out_lib.path] + [a.path for a in objects], 233 env = go_environment_vars(ctx), 234 ) 235 236 def _emit_go_cover_action(ctx, sources): 237 """Construct the command line for test coverage instrument. 238 239 Args: 240 ctx: The starlark Context. 241 sources: an iterable of Go source files. 242 243 Returns: 244 A list of Go source code files which might be coverage instrumented. 245 """ 246 outputs = [] 247 248 # TODO(linuxerwang): make the mode configurable. 249 count = 0 250 251 for src in sources: 252 if not src.path.endswith(".go") or src.path.endswith("_test.go"): 253 outputs += [src] 254 continue 255 256 cover_var = "GoCover_%d" % count 257 out = ctx.actions.declare_file( 258 src.basename[:-3] + "_" + cover_var + ".cover.go", 259 sibling = src, 260 ) 261 outputs += [out] 262 ctx.action( 263 inputs = [src] + ctx.files.toolchain, 264 outputs = [out], 265 mnemonic = "GoCover", 266 executable = ctx.file.go_tool, 267 arguments = ["tool", "cover", "--mode=set", "-var=%s" % cover_var, "-o", out.path, src.path], 268 env = go_environment_vars(ctx), 269 ) 270 count += 1 271 272 return outputs 273 274 def go_library_impl(ctx): 275 """Implements the go_library() rule.""" 276 277 sources = depset(ctx.files.srcs) 278 go_srcs = depset([s for s in sources if s.basename.endswith(".go")]) 279 asm_srcs = [s for s in sources if s.basename.endswith(".s") or s.basename.endswith(".S")] 280 asm_hdrs = [s for s in sources if s.basename.endswith(".h")] 281 deps = ctx.attr.deps 282 dep_runfiles = [d.data_runfiles for d in deps] 283 284 cgo_object = None 285 if hasattr(ctx.attr, "cgo_object"): 286 cgo_object = ctx.attr.cgo_object 287 288 if ctx.attr.library: 289 go_srcs += ctx.attr.library.go_sources 290 asm_srcs += ctx.attr.library.asm_sources 291 asm_hdrs += ctx.attr.library.asm_headers 292 deps += ctx.attr.library.direct_deps 293 dep_runfiles += [ctx.attr.library.data_runfiles] 294 if ctx.attr.library.cgo_object: 295 if cgo_object: 296 fail("go_library %s cannot have cgo_object because the package " + 297 "already has cgo_object in %s" % ( 298 ctx.label.name, 299 ctx.attr.library.name, 300 )) 301 cgo_object = ctx.attr.library.cgo_object 302 if not go_srcs: 303 fail("may not be empty", "srcs") 304 305 transitive_cgo_deps = depset([], order = "topological") 306 if cgo_object: 307 dep_runfiles += [cgo_object.data_runfiles] 308 transitive_cgo_deps += cgo_object.cgo_deps 309 310 extra_objects = [cgo_object.cgo_obj] if cgo_object else [] 311 for src in asm_srcs: 312 obj = ctx.actions.declare_file( 313 "%s.dir/%s.o" % (ctx.label.name, src.basename[:-2]), 314 sibling = src, 315 ) 316 _emit_go_asm_action(ctx, src, asm_hdrs, obj) 317 extra_objects += [obj] 318 319 lib_name = _go_importpath(ctx) + ".a" 320 out_lib = ctx.actions.declare_file(lib_name) 321 out_object = ctx.actions.declare_file(ctx.label.name + ".o") 322 search_path = out_lib.path[:-len(lib_name)] 323 gc_goopts = _gc_goopts(ctx) 324 transitive_go_libraries = depset([out_lib]) 325 transitive_go_library_paths = depset([search_path]) 326 for dep in deps: 327 transitive_go_libraries += dep.transitive_go_libraries 328 transitive_cgo_deps += dep.transitive_cgo_deps 329 transitive_go_library_paths += dep.transitive_go_library_paths 330 331 go_srcs = _emit_go_compile_action( 332 ctx, 333 sources = go_srcs, 334 deps = deps, 335 libpaths = transitive_go_library_paths, 336 out_object = out_object, 337 gc_goopts = gc_goopts, 338 ) 339 _emit_go_pack_action(ctx, out_lib, [out_object] + extra_objects) 340 341 dylibs = [] 342 if cgo_object: 343 dylibs += [d for d in cgo_object.cgo_deps if d.path.endswith(".so")] 344 345 runfiles = ctx.runfiles(files = dylibs, collect_data = True) 346 for d in dep_runfiles: 347 runfiles = runfiles.merge(d) 348 349 return struct( 350 label = ctx.label, 351 files = depset([out_lib]), 352 runfiles = runfiles, 353 go_sources = go_srcs, 354 asm_sources = asm_srcs, 355 asm_headers = asm_hdrs, 356 cgo_object = cgo_object, 357 direct_deps = ctx.attr.deps, 358 transitive_cgo_deps = transitive_cgo_deps, 359 transitive_go_libraries = transitive_go_libraries, 360 transitive_go_library_paths = transitive_go_library_paths, 361 gc_goopts = gc_goopts, 362 ) 363 364 def _c_linker_options(ctx, blocklist = []): 365 """Extracts flags to pass to $(CC) on link from the current context 366 367 Args: 368 ctx: the current context 369 blocklist: Any flags starts with any of these prefixes are filtered out from 370 the return value. 371 372 Returns: 373 A list of command line flags 374 """ 375 cpp = ctx.fragments.cpp 376 features = ctx.features 377 options = cpp.compiler_options(features) 378 options += cpp.unfiltered_compiler_options(features) 379 options += cpp.link_options 380 options += cpp.mostly_static_link_options(ctx.features, False) 381 filtered = [] 382 for opt in options: 383 if any([opt.startswith(prefix) for prefix in blocklist]): 384 continue 385 filtered.append(opt) 386 return filtered 387 388 def _gc_goopts(ctx): 389 gc_goopts = [ 390 ctx.expand_make_variables("gc_goopts", f, {}) 391 for f in ctx.attr.gc_goopts 392 ] 393 if ctx.attr.library: 394 gc_goopts += ctx.attr.library.gc_goopts 395 return gc_goopts 396 397 def _gc_linkopts(ctx): 398 gc_linkopts = [ 399 ctx.expand_make_variables("gc_linkopts", f, {}) 400 for f in ctx.attr.gc_linkopts 401 ] 402 for k, v in ctx.attr.x_defs.items(): 403 gc_linkopts += ["-X", "%s='%s'" % (k, v)] 404 return gc_linkopts 405 406 def _extract_extldflags(gc_linkopts, extldflags): 407 """Extracts -extldflags from gc_linkopts and combines them into a single list. 408 409 Args: 410 gc_linkopts: a list of flags passed in through the gc_linkopts attributes. 411 ctx.expand_make_variables should have already been applied. 412 extldflags: a list of flags to be passed to the external linker. 413 414 Return: 415 A tuple containing the filtered gc_linkopts with external flags removed, 416 and a combined list of external flags. 417 """ 418 filtered_gc_linkopts = [] 419 is_extldflags = False 420 for opt in gc_linkopts: 421 if is_extldflags: 422 is_extldflags = False 423 extldflags += [opt] 424 elif opt == "-extldflags": 425 is_extldflags = True 426 else: 427 filtered_gc_linkopts += [opt] 428 return filtered_gc_linkopts, extldflags 429 430 def _emit_go_link_action( 431 ctx, 432 transitive_go_library_paths, 433 transitive_go_libraries, 434 cgo_deps, 435 libs, 436 executable, 437 gc_linkopts): 438 """Sets up a symlink tree to libraries to link together.""" 439 config_strip = len(ctx.configuration.bin_dir.path) + 1 440 pkg_depth = executable.dirname[config_strip:].count("/") + 1 441 442 ld = "%s" % ctx.fragments.cpp.compiler_executable 443 extldflags = _c_linker_options(ctx) + [ 444 "-Wl,-rpath,$ORIGIN/" + ("../" * pkg_depth), 445 ] 446 for d in cgo_deps: 447 if d.basename.endswith(".so"): 448 short_dir = d.dirname[len(d.root.path):] 449 extldflags += ["-Wl,-rpath,$ORIGIN/" + ("../" * pkg_depth) + short_dir] 450 gc_linkopts, extldflags = _extract_extldflags(gc_linkopts, extldflags) 451 452 link_cmd = [ 453 ctx.file.go_tool.path, 454 "tool", 455 "link", 456 "-L", 457 ".", 458 ] 459 for path in transitive_go_library_paths: 460 link_cmd += ["-L", path] 461 link_cmd += [ 462 "-o", 463 executable.path, 464 ] + gc_linkopts + ['"${STAMP_XDEFS[@]}"'] 465 466 # workaround for a bug in ld(1) on Mac OS X. 467 # http://lists.apple.com/archives/Darwin-dev/2006/Sep/msg00084.html 468 # TODO(yugui) Remove this workaround once rules_go stops supporting XCode 7.2 469 # or earlier. 470 if not _is_darwin_cpu(ctx): 471 link_cmd += ["-s"] 472 473 link_cmd += [ 474 "-extld", 475 ld, 476 "-extldflags", 477 "'%s'" % " ".join(extldflags), 478 ] + [lib.path for lib in libs] 479 480 # Avoided -s on OSX but but it requires dsymutil to be on $PATH. 481 # TODO(yugui) Remove this workaround once rules_go stops supporting XCode 7.2 482 # or earlier. 483 cmds = ["export PATH=$PATH:/usr/bin"] 484 485 cmds += [ 486 "STAMP_XDEFS=()", 487 ] 488 489 stamp_inputs = [] 490 if ctx.attr.linkstamp: 491 # read workspace status files, converting "KEY value" lines 492 # to "-X $linkstamp.KEY=value" arguments to the go linker. 493 stamp_inputs = [ctx.info_file, ctx.version_file] 494 for f in stamp_inputs: 495 cmds += [ 496 "while read -r key value || [[ -n $key ]]; do", 497 " STAMP_XDEFS+=(-X \"%s.$key=$value\")" % ctx.attr.linkstamp, 498 "done < " + f.path, 499 ] 500 501 cmds += [" ".join(link_cmd)] 502 503 f = _emit_generate_params_action(cmds, ctx, lib.basename + ".GoLinkFile.params") 504 505 ctx.action( 506 inputs = [f] + (list(transitive_go_libraries) + [lib] + list(cgo_deps) + 507 ctx.files.toolchain + ctx.files._crosstool) + stamp_inputs, 508 outputs = [executable], 509 command = f.path, 510 mnemonic = "GoLink", 511 env = go_environment_vars(ctx), 512 ) 513 514 def go_binary_impl(ctx): 515 """go_binary_impl emits actions for compiling and linking a go executable.""" 516 lib_result = go_library_impl(ctx) 517 _emit_go_link_action( 518 ctx, 519 transitive_go_libraries = lib_result.transitive_go_libraries, 520 transitive_go_library_paths = lib_result.transitive_go_library_paths, 521 cgo_deps = lib_result.transitive_cgo_deps, 522 libs = lib_result.files, 523 executable = ctx.outputs.executable, 524 gc_linkopts = _gc_linkopts(ctx), 525 ) 526 527 return struct( 528 files = depset([ctx.outputs.executable]), 529 runfiles = lib_result.runfiles, 530 cgo_object = lib_result.cgo_object, 531 ) 532 533 def go_test_impl(ctx): 534 """go_test_impl implements go testing. 535 536 It emits an action to run the test generator, and then compiles the 537 test into a binary.""" 538 539 lib_result = go_library_impl(ctx) 540 main_go = ctx.actions.declare_file(ctx.label.name + "_main_test.go") 541 main_object = ctx.actions.declare_file(ctx.label.name + "_main_test.o") 542 main_lib = ctx.actions.declare_file(ctx.label.name + "_main_test.a") 543 go_import = _go_importpath(ctx) 544 545 cmds = [ 546 "UNFILTERED_TEST_FILES=(%s)" % 547 " ".join(["'%s'" % f.path for f in lib_result.go_sources]), 548 "FILTERED_TEST_FILES=()", 549 "while read -r line; do", 550 ' if [ -n "$line" ]; then', 551 ' FILTERED_TEST_FILES+=("$line")', 552 " fi", 553 'done < <(\'%s\' -cgo "${UNFILTERED_TEST_FILES[@]}")' % 554 ctx.executable._filter_tags.path, 555 " ".join([ 556 "'%s'" % ctx.executable.test_generator.path, 557 "--package", 558 go_import, 559 "--output", 560 "'%s'" % main_go.path, 561 '"${FILTERED_TEST_FILES[@]}"', 562 ]), 563 ] 564 f = _emit_generate_params_action( 565 cmds, 566 ctx, 567 ctx.label.name + ".GoTestGenTest.params", 568 ) 569 inputs = (list(lib_result.go_sources) + list(ctx.files.toolchain) + 570 [f, ctx.executable._filter_tags, ctx.executable.test_generator]) 571 ctx.action( 572 inputs = inputs, 573 outputs = [main_go], 574 command = f.path, 575 mnemonic = "GoTestGenTest", 576 env = dict(go_environment_vars(ctx), RUNDIR = ctx.label.package), 577 ) 578 579 _emit_go_compile_action( 580 ctx, 581 sources = depset([main_go]), 582 deps = ctx.attr.deps + [lib_result], 583 libpaths = lib_result.transitive_go_library_paths, 584 out_object = main_object, 585 gc_goopts = _gc_goopts(ctx), 586 ) 587 _emit_go_pack_action(ctx, main_lib, [main_object]) 588 _emit_go_link_action( 589 ctx, 590 transitive_go_library_paths = lib_result.transitive_go_library_paths, 591 transitive_go_libraries = lib_result.transitive_go_libraries, 592 cgo_deps = lib_result.transitive_cgo_deps, 593 libs = [main_lib], 594 executable = ctx.outputs.executable, 595 gc_linkopts = _gc_linkopts(ctx), 596 ) 597 598 # TODO(bazel-team): the Go tests should do a chdir to the directory 599 # holding the data files, so open-source go tests continue to work 600 # without code changes. 601 runfiles = ctx.runfiles(files = [ctx.outputs.executable]) 602 runfiles = runfiles.merge(lib_result.runfiles) 603 return struct( 604 files = depset([ctx.outputs.executable]), 605 runfiles = runfiles, 606 ) 607 608 go_env_attrs = { 609 "toolchain": attr.label( 610 default = Label("//go/toolchain:toolchain"), 611 allow_files = True, 612 cfg = "host", 613 ), 614 "go_tool": attr.label( 615 default = Label("//go/toolchain:go_tool"), 616 single_file = True, 617 allow_files = True, 618 cfg = "host", 619 ), 620 "go_prefix": attr.label( 621 providers = ["go_prefix"], 622 default = Label( 623 "//:go_prefix", 624 relative_to_caller_repository = True, 625 ), 626 allow_files = False, 627 cfg = "host", 628 ), 629 "go_src": attr.label( 630 default = Label("//go/toolchain:go_src"), 631 allow_files = True, 632 cfg = "host", 633 ), 634 "go_include": attr.label( 635 default = Label("//go/toolchain:go_include"), 636 single_file = True, 637 allow_files = True, 638 cfg = "host", 639 ), 640 "go_root": attr.label( 641 providers = ["go_root"], 642 default = Label( 643 "//go/toolchain:go_root", 644 ), 645 allow_files = False, 646 cfg = "host", 647 ), 648 "_filter_tags": attr.label( 649 default = Label("//go/tools/filter_tags"), 650 cfg = "host", 651 executable = True, 652 single_file = True, 653 ), 654 "_filter_exec": attr.label( 655 default = Label("//go/tools/filter_exec"), 656 cfg = "host", 657 executable = True, 658 single_file = True, 659 ), 660 "_asm": attr.label( 661 default = Label("//go/tools/builders:asm"), 662 cfg = "host", 663 executable = True, 664 single_file = True, 665 ), 666 } 667 668 go_library_attrs = go_env_attrs + { 669 "data": attr.label_list( 670 allow_files = True, 671 cfg = "data", 672 ), 673 "srcs": attr.label_list(allow_files = go_filetype), 674 "deps": attr.label_list( 675 providers = [ 676 "transitive_go_library_paths", 677 "transitive_go_libraries", 678 "transitive_cgo_deps", 679 ], 680 ), 681 "importpath": attr.string(), 682 "library": attr.label( 683 providers = [ 684 "direct_deps", 685 "go_sources", 686 "asm_sources", 687 "cgo_object", 688 "gc_goopts", 689 ], 690 ), 691 "gc_goopts": attr.string_list(), 692 } 693 694 _crosstool_attrs = { 695 "_crosstool": attr.label( 696 default = Label("//tools/defaults:crosstool"), 697 ), 698 } 699 700 go_link_attrs = go_library_attrs + _crosstool_attrs + { 701 "gc_linkopts": attr.string_list(), 702 "linkstamp": attr.string(), 703 "x_defs": attr.string_dict(), 704 } 705 706 go_library = rule( 707 go_library_impl, 708 attrs = go_library_attrs + { 709 "cgo_object": attr.label( 710 providers = [ 711 "cgo_obj", 712 "cgo_deps", 713 ], 714 ), 715 }, 716 fragments = ["cpp"], 717 ) 718 719 go_binary = rule( 720 go_binary_impl, 721 attrs = go_library_attrs + _crosstool_attrs + go_link_attrs, 722 executable = True, 723 fragments = ["cpp"], 724 ) 725 726 go_test = rule( 727 go_test_impl, 728 attrs = go_library_attrs + _crosstool_attrs + go_link_attrs + { 729 "test_generator": attr.label( 730 executable = True, 731 default = Label( 732 "//go/tools:generate_test_main", 733 ), 734 cfg = "host", 735 ), 736 }, 737 executable = True, 738 fragments = ["cpp"], 739 test = True, 740 ) 741 742 def _pkg_dir(workspace_root, package_name): 743 if workspace_root and package_name: 744 return workspace_root + "/" + package_name 745 if workspace_root: 746 return workspace_root 747 if package_name: 748 return package_name 749 return "." 750 751 def _exec_path(path): 752 if path.startswith("/"): 753 return path 754 return "${execroot}/" + path 755 756 def _cgo_filter_srcs_impl(ctx): 757 srcs = ctx.files.srcs 758 dsts = [] 759 cmds = [] 760 for src in srcs: 761 stem, _, ext = src.path.rpartition(".") 762 dst_basename = "%s.filtered.%s" % (stem, ext) 763 dst = ctx.actions.declare_file(dst_basename, sibling = src) 764 cmds += [ 765 "if '%s' -cgo -quiet '%s'; then" % 766 (ctx.executable._filter_tags.path, src.path), 767 " cp '%s' '%s'" % (src.path, dst.path), 768 "else", 769 " echo -n >'%s'" % dst.path, 770 "fi", 771 ] 772 dsts.append(dst) 773 774 if ctx.label.package == "": 775 script_name = ctx.label.name + ".CGoFilterSrcs.params" 776 else: 777 script_name = ctx.label.package + "/" + ctx.label.name + ".CGoFilterSrcs.params" 778 f = _emit_generate_params_action(cmds, ctx, script_name) 779 ctx.action( 780 inputs = [f, ctx.executable._filter_tags] + srcs, 781 outputs = dsts, 782 command = f.path, 783 mnemonic = "CgoFilterSrcs", 784 ) 785 return struct( 786 files = depset(dsts), 787 ) 788 789 _cgo_filter_srcs = rule( 790 implementation = _cgo_filter_srcs_impl, 791 attrs = { 792 "srcs": attr.label_list( 793 allow_files = cgo_filetype, 794 ), 795 "_filter_tags": attr.label( 796 default = Label("//go/tools/filter_tags"), 797 cfg = "host", 798 executable = True, 799 single_file = True, 800 ), 801 }, 802 fragments = ["cpp"], 803 ) 804 805 def _cgo_codegen_impl(ctx): 806 go_srcs = ctx.files.srcs 807 srcs = go_srcs + ctx.files.c_hdrs 808 linkopts = ctx.attr.linkopts 809 copts = ctx.fragments.cpp.c_options + ctx.attr.copts 810 deps = depset([], order = "topological") 811 for d in ctx.attr.deps: 812 srcs += list(d.cc.transitive_headers) 813 deps += d.cc.libs 814 copts += ["-D" + define for define in d.cc.defines] 815 for inc in d.cc.include_directories: 816 copts += ["-I", _exec_path(inc)] 817 for hdr in ctx.files.c_hdrs: 818 copts += ["-iquote", hdr.dirname] 819 for inc in d.cc.quote_include_directories: 820 copts += ["-iquote", _exec_path(inc)] 821 for inc in d.cc.system_include_directories: 822 copts += ["-isystem", _exec_path(inc)] 823 for lib in d.cc.libs: 824 if lib.basename.startswith("lib") and lib.basename.endswith(".so"): 825 linkopts += ["-L", lib.dirname, "-l", lib.basename[3:-3]] 826 else: 827 linkopts += [lib.path] 828 linkopts += d.cc.link_flags 829 830 p = _pkg_dir(ctx.label.workspace_root, ctx.label.package) + "/" 831 if p == "./": 832 p = "" # workaround when cgo_library in repository root 833 out_dir = (ctx.configuration.bin_dir.path + "/" + 834 p + ctx.attr.outdir) 835 cc = ctx.fragments.cpp.compiler_executable 836 cmds = [ 837 # We cannot use env for CC because $(CC) on OSX is relative 838 # and '../' does not work fine due to symlinks. 839 "export CC=$(cd $(dirname {cc}); pwd)/$(basename {cc})".format(cc = cc), 840 "export CXX=$CC", 841 'objdir="%s/gen"' % out_dir, 842 "execroot=$(pwd)", 843 'mkdir -p "$objdir"', 844 "unfiltered_go_files=(%s)" % " ".join(["'%s'" % f.path for f in go_srcs]), 845 "filtered_go_files=()", 846 'for file in "${unfiltered_go_files[@]}"; do', 847 ' stem=$(basename "$file" .go)', 848 ' if %s -cgo -quiet "$file"; then' % ctx.executable._filter_tags.path, 849 ' filtered_go_files+=("$file")', 850 " else", 851 ' grep --max-count 1 "^package " "$file" >"$objdir/$stem.go"', 852 ' echo -n >"$objdir/$stem.c"', 853 " fi", 854 "done", 855 "if [ ${#filtered_go_files[@]} -eq 0 ]; then", 856 " echo no buildable Go source files in %s >&1" % str(ctx.label), 857 " exit 1", 858 "fi", 859 '"$GOROOT/bin/go" tool cgo -objdir "$objdir" -- %s "${filtered_go_files[@]}"' % 860 " ".join(['"%s"' % copt for copt in copts]), 861 # Rename the outputs using glob so we don't have to understand cgo's mangling 862 # TODO(#350): might be fixed by this?. 863 'for file in "${filtered_go_files[@]}"; do', 864 ' stem=$(basename "$file" .go)', 865 ' mv "$objdir/"*"$stem.cgo1.go" "$objdir/$stem.go"', 866 ' mv "$objdir/"*"$stem.cgo2.c" "$objdir/$stem.c"', 867 "done", 868 "rm -f $objdir/_cgo_.o $objdir/_cgo_flags", 869 ] 870 871 f = _emit_generate_params_action(cmds, ctx, out_dir + ".CGoCodeGenFile.params") 872 873 inputs = (srcs + ctx.files.toolchain + ctx.files._crosstool + 874 [f, ctx.executable._filter_tags]) 875 ctx.action( 876 inputs = inputs, 877 outputs = ctx.outputs.outs, 878 mnemonic = "CGoCodeGen", 879 progress_message = "CGoCodeGen %s" % ctx.label, 880 command = f.path, 881 env = go_environment_vars(ctx) + { 882 "CGO_LDFLAGS": " ".join(linkopts), 883 }, 884 ) 885 return struct( 886 label = ctx.label, 887 files = depset(ctx.outputs.outs), 888 cgo_deps = deps, 889 ) 890 891 _cgo_codegen_rule = rule( 892 _cgo_codegen_impl, 893 attrs = go_env_attrs + _crosstool_attrs + { 894 "srcs": attr.label_list( 895 allow_files = go_filetype, 896 non_empty = True, 897 ), 898 "c_hdrs": attr.label_list( 899 allow_files = cc_hdr_filetype, 900 ), 901 "deps": attr.label_list( 902 allow_files = False, 903 providers = ["cc"], 904 ), 905 "copts": attr.string_list(), 906 "linkopts": attr.string_list(), 907 "outdir": attr.string(mandatory = True), 908 "outs": attr.output_list( 909 mandatory = True, 910 non_empty = True, 911 ), 912 }, 913 fragments = ["cpp"], 914 ) 915 916 def _cgo_codegen( 917 name, 918 srcs, 919 c_hdrs = [], 920 deps = [], 921 copts = [], 922 linkopts = [], 923 go_tool = None, 924 toolchain = None): 925 """Generates glue codes for interop between C and Go 926 927 Args: 928 name: A unique name of the rule 929 srcs: list of Go source files. 930 Each of them must contain `import "C"`. 931 c_hdrs: C/C++ header files necessary to determine kinds of 932 C/C++ identifiers in srcs. 933 deps: A list of cc_library rules. 934 The generated codes are expected to be linked with these deps. 935 linkopts: A list of linker options, 936 These flags are passed to the linker when the generated codes 937 are linked into the target binary. 938 """ 939 outdir = name + ".dir" 940 outgen = outdir + "/gen" 941 942 go_thunks = [] 943 c_thunks = [] 944 for s in srcs: 945 if not s.endswith(".go"): 946 fail("not a .go file: %s" % s) 947 basename = s[:-3] 948 if basename.rfind("/") >= 0: 949 basename = basename[basename.rfind("/") + 1:] 950 go_thunks.append(outgen + "/" + basename + ".go") 951 c_thunks.append(outgen + "/" + basename + ".c") 952 953 outs = struct( 954 name = name, 955 outdir = outgen, 956 go_thunks = go_thunks, 957 c_thunks = c_thunks, 958 c_exports = [ 959 outgen + "/_cgo_export.c", 960 outgen + "/_cgo_export.h", 961 ], 962 c_dummy = outgen + "/_cgo_main.c", 963 gotypes = outgen + "/_cgo_gotypes.go", 964 ) 965 966 _cgo_codegen_rule( 967 name = name, 968 srcs = srcs, 969 c_hdrs = c_hdrs, 970 deps = deps, 971 copts = copts, 972 linkopts = linkopts, 973 go_tool = go_tool, 974 toolchain = toolchain, 975 outdir = outdir, 976 outs = outs.go_thunks + outs.c_thunks + outs.c_exports + [ 977 outs.c_dummy, 978 outs.gotypes, 979 ], 980 visibility = ["//visibility:private"], 981 ) 982 return outs 983 984 def _cgo_import_impl(ctx): 985 cmds = [ 986 (ctx.file.go_tool.path + " tool cgo" + 987 " -dynout " + ctx.outputs.out.path + 988 " -dynimport " + ctx.file.cgo_o.path + 989 " -dynpackage $(%s %s)" % ( 990 ctx.executable._extract_package.path, 991 ctx.file.sample_go_src.path, 992 )), 993 ] 994 f = _emit_generate_params_action(cmds, ctx, ctx.outputs.out.path + ".CGoImportGenFile.params") 995 ctx.action( 996 inputs = (ctx.files.toolchain + 997 [ 998 f, 999 ctx.file.go_tool, 1000 ctx.executable._extract_package, 1001 ctx.file.cgo_o, 1002 ctx.file.sample_go_src, 1003 ]), 1004 outputs = [ctx.outputs.out], 1005 command = f.path, 1006 mnemonic = "CGoImportGen", 1007 env = go_environment_vars(ctx), 1008 ) 1009 return struct( 1010 files = depset([ctx.outputs.out]), 1011 ) 1012 1013 _cgo_import = rule( 1014 _cgo_import_impl, 1015 attrs = go_env_attrs + { 1016 "cgo_o": attr.label( 1017 allow_files = True, 1018 single_file = True, 1019 ), 1020 "sample_go_src": attr.label( 1021 allow_files = True, 1022 single_file = True, 1023 ), 1024 "out": attr.output( 1025 mandatory = True, 1026 ), 1027 "_extract_package": attr.label( 1028 default = Label("//go/tools/extract_package"), 1029 executable = True, 1030 cfg = "host", 1031 ), 1032 }, 1033 fragments = ["cpp"], 1034 ) 1035 1036 def _cgo_genrule_impl(ctx): 1037 return struct( 1038 label = ctx.label, 1039 go_sources = ctx.files.srcs, 1040 asm_sources = [], 1041 asm_headers = [], 1042 cgo_object = ctx.attr.cgo_object, 1043 direct_deps = ctx.attr.deps, 1044 gc_goopts = [], 1045 ) 1046 1047 _cgo_genrule = rule( 1048 _cgo_genrule_impl, 1049 attrs = { 1050 "srcs": attr.label_list(allow_files = FileType([".go"])), 1051 "cgo_object": attr.label( 1052 providers = [ 1053 "cgo_obj", 1054 "cgo_deps", 1055 ], 1056 ), 1057 "deps": attr.label_list( 1058 providers = [ 1059 "direct_deps", 1060 "transitive_go_library_paths", 1061 "transitive_go_libraries", 1062 "transitive_cgo_deps", 1063 ], 1064 ), 1065 }, 1066 fragments = ["cpp"], 1067 ) 1068 1069 """Generates symbol-import directives for cgo 1070 1071 Args: 1072 cgo_o: The loadable object to extract dynamic symbols from. 1073 sample_go_src: A go source which is compiled together with the generated file. 1074 The generated file will have the same Go package name as this file. 1075 out: Destination of the generated codes. 1076 """ 1077 1078 def _cgo_object_impl(ctx): 1079 arguments = _c_linker_options(ctx, blocklist = [ 1080 # never link any dependency libraries 1081 "-l", 1082 "-L", 1083 # manage flags to ld(1) by ourselves 1084 "-Wl,", 1085 ]) 1086 arguments += [ 1087 "-o", 1088 ctx.outputs.out.path, 1089 "-nostdlib", 1090 "-Wl,-r", 1091 ] 1092 if _is_darwin_cpu(ctx): 1093 arguments += ["-shared", "-Wl,-all_load"] 1094 else: 1095 arguments += ["-Wl,-whole-archive"] 1096 1097 lo = ctx.files.src[-1] 1098 arguments += [lo.path] 1099 1100 ctx.action( 1101 inputs = [lo] + ctx.files._crosstool, 1102 outputs = [ctx.outputs.out], 1103 mnemonic = "CGoObject", 1104 progress_message = "Linking %s" % ctx.outputs.out.short_path, 1105 executable = ctx.fragments.cpp.compiler_executable, 1106 arguments = arguments, 1107 ) 1108 runfiles = ctx.runfiles(collect_data = True) 1109 runfiles = runfiles.merge(ctx.attr.src.data_runfiles) 1110 return struct( 1111 files = depset([ctx.outputs.out]), 1112 cgo_obj = ctx.outputs.out, 1113 cgo_deps = ctx.attr.cgogen.cgo_deps, 1114 runfiles = runfiles, 1115 ) 1116 1117 _cgo_object = rule( 1118 _cgo_object_impl, 1119 attrs = _crosstool_attrs + { 1120 "src": attr.label( 1121 mandatory = True, 1122 providers = ["cc"], 1123 ), 1124 "cgogen": attr.label( 1125 mandatory = True, 1126 providers = ["cgo_deps"], 1127 ), 1128 "out": attr.output( 1129 mandatory = True, 1130 ), 1131 }, 1132 fragments = ["cpp"], 1133 ) 1134 1135 """Generates _all.o to be archived together with Go objects. 1136 1137 Args: 1138 src: source static library which contains objects 1139 cgogen: _cgo_codegen rule which knows the dependency cc_library() rules 1140 to be linked together with src when we generate the final go binary. 1141 """ 1142 1143 def _setup_cgo_library(name, srcs, cdeps, copts, clinkopts, go_tool, toolchain): 1144 go_srcs = [s for s in srcs if s.endswith(".go")] 1145 c_hdrs = [s for s in srcs if any([s.endswith(ext) for ext in hdr_exts])] 1146 c_srcs = [s for s in srcs if not s in (go_srcs + c_hdrs)] 1147 1148 # Split cgo files into .go parts and .c parts (plus some other files). 1149 cgogen = _cgo_codegen( 1150 name = name + ".cgo", 1151 srcs = go_srcs, 1152 c_hdrs = c_hdrs, 1153 deps = cdeps, 1154 copts = copts, 1155 linkopts = clinkopts, 1156 go_tool = go_tool, 1157 toolchain = toolchain, 1158 ) 1159 1160 # Filter c_srcs with build constraints. 1161 c_filtered_srcs = [] 1162 if len(c_srcs) > 0: 1163 c_filtered_srcs_name = name + "_filter_cgo_srcs" 1164 _cgo_filter_srcs( 1165 name = c_filtered_srcs_name, 1166 srcs = c_srcs, 1167 ) 1168 c_filtered_srcs.append(":" + c_filtered_srcs_name) 1169 1170 pkg_dir = _pkg_dir( 1171 "external/" + REPOSITORY_NAME[1:] if len(REPOSITORY_NAME) > 1 else "", 1172 PACKAGE_NAME, 1173 ) 1174 1175 # Platform-specific settings 1176 native.config_setting( 1177 name = name + "_windows_setting", 1178 values = { 1179 "cpu": "x64_windows_msvc", 1180 }, 1181 ) 1182 platform_copts = select({ 1183 ":" + name + "_windows_setting": ["-mthreads"], 1184 "//conditions:default": ["-pthread"], 1185 }) 1186 platform_linkopts = select({ 1187 ":" + name + "_windows_setting": ["-mthreads"], 1188 "//conditions:default": ["-pthread"], 1189 }) 1190 1191 # Bundles objects into an archive so that _cgo_.o and _all.o can share them. 1192 native.cc_library( 1193 name = cgogen.outdir + "/_cgo_lib", 1194 srcs = cgogen.c_thunks + cgogen.c_exports + c_filtered_srcs + c_hdrs, 1195 deps = cdeps, 1196 copts = copts + platform_copts + [ 1197 "-I", 1198 pkg_dir, 1199 "-I", 1200 "$(GENDIR)/" + pkg_dir + "/" + cgogen.outdir, 1201 # The generated thunks often contain unused variables. 1202 "-Wno-unused-variable", 1203 ], 1204 linkopts = clinkopts + platform_linkopts, 1205 linkstatic = 1, 1206 # _cgo_.o and _all.o keep all objects in this archive. 1207 # But it should not be very annoying in the final binary target 1208 # because _cgo_object rule does not propagate alwayslink=1 1209 alwayslink = 1, 1210 visibility = ["//visibility:private"], 1211 ) 1212 1213 # Loadable object which cgo reads when it generates _cgo_import.go 1214 native.cc_binary( 1215 name = cgogen.outdir + "/_cgo_.o", 1216 srcs = [cgogen.c_dummy], 1217 deps = cdeps + [cgogen.outdir + "/_cgo_lib"], 1218 copts = copts, 1219 linkopts = clinkopts, 1220 visibility = ["//visibility:private"], 1221 ) 1222 _cgo_import( 1223 name = "%s.cgo.importgen" % name, 1224 cgo_o = cgogen.outdir + "/_cgo_.o", 1225 out = cgogen.outdir + "/_cgo_import.go", 1226 sample_go_src = go_srcs[0], 1227 go_tool = go_tool, 1228 toolchain = toolchain, 1229 visibility = ["//visibility:private"], 1230 ) 1231 1232 _cgo_object( 1233 name = cgogen.outdir + "/_cgo_object", 1234 src = cgogen.outdir + "/_cgo_lib", 1235 out = cgogen.outdir + "/_all.o", 1236 cgogen = cgogen.name, 1237 visibility = ["//visibility:private"], 1238 ) 1239 return cgogen 1240 1241 def cgo_genrule( 1242 name, 1243 srcs, 1244 copts = [], 1245 clinkopts = [], 1246 cdeps = [], 1247 **kwargs): 1248 cgogen = _setup_cgo_library( 1249 name = name, 1250 srcs = srcs, 1251 cdeps = cdeps, 1252 copts = copts, 1253 clinkopts = clinkopts, 1254 toolchain = None, 1255 go_tool = None, 1256 ) 1257 _cgo_genrule( 1258 name = name, 1259 srcs = cgogen.go_thunks + [ 1260 cgogen.gotypes, 1261 cgogen.outdir + "/_cgo_import.go", 1262 ], 1263 cgo_object = cgogen.outdir + "/_cgo_object", 1264 **kwargs 1265 ) 1266 1267 def cgo_library( 1268 name, 1269 srcs, 1270 toolchain = None, 1271 go_tool = None, 1272 copts = [], 1273 clinkopts = [], 1274 cdeps = [], 1275 **kwargs): 1276 """Builds a cgo-enabled go library. 1277 1278 Args: 1279 name: A unique name for this rule. 1280 srcs: List of Go, C and C++ files that are processed to build a Go library. 1281 Those Go files must contain `import "C"`. 1282 C and C++ files can be anything allowed in `srcs` attribute of 1283 `cc_library`. 1284 copts: Add these flags to the C++ compiler. 1285 clinkopts: Add these flags to the C++ linker. 1286 cdeps: List of C/C++ libraries to be linked into the binary target. 1287 They must be `cc_library` rules. 1288 deps: List of other libraries to be linked to this library target. 1289 data: List of files needed by this rule at runtime. 1290 1291 NOTE: 1292 `srcs` cannot contain pure-Go files, which do not have `import "C"`. 1293 So you need to define another `go_library` when you build a go package with 1294 both cgo-enabled and pure-Go sources. 1295 1296 ``` 1297 cgo_library( 1298 name = "cgo_enabled", 1299 srcs = ["cgo-enabled.go", "foo.cc", "bar.S", "baz.a"], 1300 ) 1301 1302 go_library( 1303 name = "go_default_library", 1304 srcs = ["pure-go.go"], 1305 library = ":cgo_enabled", 1306 ) 1307 ``` 1308 """ 1309 cgogen = _setup_cgo_library( 1310 name = name, 1311 srcs = srcs, 1312 cdeps = cdeps, 1313 copts = copts, 1314 clinkopts = clinkopts, 1315 go_tool = go_tool, 1316 toolchain = toolchain, 1317 ) 1318 1319 go_library( 1320 name = name, 1321 srcs = cgogen.go_thunks + [ 1322 cgogen.gotypes, 1323 cgogen.outdir + "/_cgo_import.go", 1324 ], 1325 cgo_object = cgogen.outdir + "/_cgo_object", 1326 go_tool = go_tool, 1327 toolchain = toolchain, 1328 **kwargs 1329 )