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