diff --git a/syntax/testdata/scan.star b/syntax/testdata/scan.star index 3b87c63b..4f62ba94 100644 --- a/syntax/testdata/scan.star +++ b/syntax/testdata/scan.star @@ -72,502 +72,532 @@ cgo_filetype = FileType([ ################ def go_environment_vars(ctx): - """Return a map of environment variables for use with actions, based on - the arguments. Uses the ctx.fragments.cpp.cpu attribute, if present, - and picks a default of target_os="linux" and target_arch="amd64" - otherwise. - - Args: - The starlark Context. - - Returns: - A dict of environment variables for running Go tool commands that build for - the target OS and architecture. - """ - default_toolchain = {"GOOS": "linux", "GOARCH": "amd64"} - bazel_to_go_toolchain = { - "k8": {"GOOS": "linux", "GOARCH": "amd64"}, - "piii": {"GOOS": "linux", "GOARCH": "386"}, - "darwin": {"GOOS": "darwin", "GOARCH": "amd64"}, - "darwin_x86_64": {"GOOS": "darwin", "GOARCH": "amd64"}, - "freebsd": {"GOOS": "freebsd", "GOARCH": "amd64"}, - "armeabi-v7a": {"GOOS": "linux", "GOARCH": "arm"}, - "arm": {"GOOS": "linux", "GOARCH": "arm"} - } - env = {} - if hasattr(ctx.file, "go_tool"): - env["GOROOT"] = ctx.file.go_tool.dirname + "/.." - env.update(bazel_to_go_toolchain.get(ctx.fragments.cpp.cpu, default_toolchain)) - return env + """Return a map of environment variables for use with actions, based on + the arguments. Uses the ctx.fragments.cpp.cpu attribute, if present, + and picks a default of target_os="linux" and target_arch="amd64" + otherwise. + + Args: + The starlark Context. + + Returns: + A dict of environment variables for running Go tool commands that build for + the target OS and architecture. + """ + default_toolchain = {"GOOS": "linux", "GOARCH": "amd64"} + bazel_to_go_toolchain = { + "k8": {"GOOS": "linux", "GOARCH": "amd64"}, + "piii": {"GOOS": "linux", "GOARCH": "386"}, + "darwin": {"GOOS": "darwin", "GOARCH": "amd64"}, + "darwin_x86_64": {"GOOS": "darwin", "GOARCH": "amd64"}, + "freebsd": {"GOOS": "freebsd", "GOARCH": "amd64"}, + "armeabi-v7a": {"GOOS": "linux", "GOARCH": "arm"}, + "arm": {"GOOS": "linux", "GOARCH": "arm"}, + } + env = {} + if hasattr(ctx.file, "go_tool"): + env["GOROOT"] = ctx.file.go_tool.dirname + "/.." + env.update(bazel_to_go_toolchain.get(ctx.fragments.cpp.cpu, default_toolchain)) + return env def _is_darwin_cpu(ctx): - cpu = ctx.fragments.cpp.cpu - return cpu == "darwin" or cpu == "darwin_x86_64" + cpu = ctx.fragments.cpp.cpu + return cpu == "darwin" or cpu == "darwin_x86_64" def _emit_generate_params_action(cmds, ctx, fn): - cmds_all = [ - # Use bash explicitly. /bin/sh is default, and it may be linked to a - # different shell, e.g., /bin/dash on Ubuntu. - "#!/bin/bash", - "set -e", - ] - cmds_all += cmds - cmds_all_str = "\n".join(cmds_all) + "\n" - f = ctx.new_file(ctx.configuration.bin_dir, fn) - ctx.file_action( - output = f, - content = cmds_all_str, - executable = True) - return f + cmds_all = [ + # Use bash explicitly. /bin/sh is default, and it may be linked to a + # different shell, e.g., /bin/dash on Ubuntu. + "#!/bin/bash", + "set -e", + ] + cmds_all += cmds + cmds_all_str = "\n".join(cmds_all) + "\n" + f = ctx.new_file(ctx.configuration.bin_dir, fn) + ctx.file_action( + output = f, + content = cmds_all_str, + executable = True, + ) + return f def _emit_go_asm_action(ctx, source, hdrs, out_obj): - """Construct the command line for compiling Go Assembly code. - Constructs a symlink tree to accomodate for workspace name. - Args: - ctx: The starlark Context. - source: a source code artifact - hdrs: list of .h files that may be included - out_obj: the artifact (configured target?) that should be produced - """ - params = { - "go_tool": ctx.file.go_tool.path, - "includes": [f.dirname for f in hdrs] + [ctx.file.go_include.path], - "source": source.path, - "out": out_obj.path, - } - - inputs = hdrs + ctx.files.toolchain + [source] - ctx.action( - inputs = inputs, - outputs = [out_obj], - mnemonic = "GoAsmCompile", - executable = ctx.executable._asm, - arguments = [json_marshal(params)], - ) + """Construct the command line for compiling Go Assembly code. + Constructs a symlink tree to accomodate for workspace name. + Args: + ctx: The starlark Context. + source: a source code artifact + hdrs: list of .h files that may be included + out_obj: the artifact (configured target?) that should be produced + """ + params = { + "go_tool": ctx.file.go_tool.path, + "includes": [f.dirname for f in hdrs] + [ctx.file.go_include.path], + "source": source.path, + "out": out_obj.path, + } + + inputs = hdrs + ctx.files.toolchain + [source] + ctx.action( + inputs = inputs, + outputs = [out_obj], + mnemonic = "GoAsmCompile", + executable = ctx.executable._asm, + arguments = [json_marshal(params)], + ) def _go_importpath(ctx): - """Returns the expected importpath of the go_library being built. - - Args: - ctx: The starlark Context - - Returns: - Go importpath of the library - """ - path = ctx.attr.importpath - if path != "": + """Returns the expected importpath of the go_library being built. + + Args: + ctx: The starlark Context + + Returns: + Go importpath of the library + """ + path = ctx.attr.importpath + if path != "": + return path + path = ctx.attr.go_prefix.go_prefix + if path.endswith("/"): + path = path[:-1] + if ctx.label.package: + path += "/" + ctx.label.package + if ctx.label.name != _DEFAULT_LIB: + path += "/" + ctx.label.name + if path.rfind(_VENDOR_PREFIX) != -1: + path = path[len(_VENDOR_PREFIX) + path.rfind(_VENDOR_PREFIX):] + if path[0] == "/": + path = path[1:] return path - path = ctx.attr.go_prefix.go_prefix - if path.endswith("/"): - path = path[:-1] - if ctx.label.package: - path += "/" + ctx.label.package - if ctx.label.name != _DEFAULT_LIB: - path += "/" + ctx.label.name - if path.rfind(_VENDOR_PREFIX) != -1: - path = path[len(_VENDOR_PREFIX) + path.rfind(_VENDOR_PREFIX):] - if path[0] == "/": - path = path[1:] - return path def _emit_go_compile_action(ctx, sources, deps, libpaths, out_object, gc_goopts): - """Construct the command line for compiling Go code. - - Args: - ctx: The starlark Context. - sources: an iterable of source code artifacts (or CTs? or labels?) - deps: an iterable of dependencies. Each dependency d should have an - artifact in d.transitive_go_libraries representing all imported libraries. - libpaths: the set of paths to search for imported libraries. - out_object: the object file that should be produced - gc_goopts: additional flags to pass to the compiler. - """ - if ctx.coverage_instrumented(): - sources = _emit_go_cover_action(ctx, sources) - - # Compile filtered files. - args = [ - "-cgo", - ctx.file.go_tool.path, - "tool", "compile", - "-o", out_object.path, - "-trimpath", "-abs-.", - "-I", "-abs-.", - ] - inputs = depset(sources + ctx.files.toolchain) - for dep in deps: - inputs += dep.transitive_go_libraries - for path in libpaths: - args += ["-I", path] - args += gc_goopts + [("" if i.basename.startswith("_cgo") else "-filter-") + i.path for i in sources] - ctx.action( - inputs = list(inputs), - outputs = [out_object], - mnemonic = "GoCompile", - executable = ctx.executable._filter_exec, - arguments = args, - env = go_environment_vars(ctx), - ) - - return sources + """Construct the command line for compiling Go code. + + Args: + ctx: The starlark Context. + sources: an iterable of source code artifacts (or CTs? or labels?) + deps: an iterable of dependencies. Each dependency d should have an + artifact in d.transitive_go_libraries representing all imported libraries. + libpaths: the set of paths to search for imported libraries. + out_object: the object file that should be produced + gc_goopts: additional flags to pass to the compiler. + """ + if ctx.coverage_instrumented(): + sources = _emit_go_cover_action(ctx, sources) + + # Compile filtered files. + args = [ + "-cgo", + ctx.file.go_tool.path, + "tool", + "compile", + "-o", + out_object.path, + "-trimpath", + "-abs-.", + "-I", + "-abs-.", + ] + inputs = depset(sources + ctx.files.toolchain) + for dep in deps: + inputs += dep.transitive_go_libraries + for path in libpaths: + args += ["-I", path] + args += gc_goopts + [("" if i.basename.startswith("_cgo") else "-filter-") + i.path for i in sources] + ctx.action( + inputs = list(inputs), + outputs = [out_object], + mnemonic = "GoCompile", + executable = ctx.executable._filter_exec, + arguments = args, + env = go_environment_vars(ctx), + ) + + return sources def _emit_go_pack_action(ctx, out_lib, objects): - """Construct the command line for packing objects together. - - Args: - ctx: The starlark Context. - out_lib: the archive that should be produced - objects: an iterable of object files to be added to the output archive file. - """ - ctx.action( - inputs = objects + ctx.files.toolchain, - outputs = [out_lib], - mnemonic = "GoPack", - executable = ctx.file.go_tool, - arguments = ["tool", "pack", "c", out_lib.path] + [a.path for a in objects], - env = go_environment_vars(ctx), - ) + """Construct the command line for packing objects together. -def _emit_go_cover_action(ctx, sources): - """Construct the command line for test coverage instrument. - - Args: - ctx: The starlark Context. - sources: an iterable of Go source files. - - Returns: - A list of Go source code files which might be coverage instrumented. - """ - outputs = [] - # TODO(linuxerwang): make the mode configurable. - count = 0 - - for src in sources: - if not src.path.endswith(".go") or src.path.endswith("_test.go"): - outputs += [src] - continue - - cover_var = "GoCover_%d" % count - out = ctx.new_file(src, src.basename[:-3] + '_' + cover_var + '.cover.go') - outputs += [out] + Args: + ctx: The starlark Context. + out_lib: the archive that should be produced + objects: an iterable of object files to be added to the output archive file. + """ ctx.action( - inputs = [src] + ctx.files.toolchain, - outputs = [out], - mnemonic = "GoCover", + inputs = objects + ctx.files.toolchain, + outputs = [out_lib], + mnemonic = "GoPack", executable = ctx.file.go_tool, - arguments = ["tool", "cover", "--mode=set", "-var=%s" % cover_var, "-o", out.path, src.path], + arguments = ["tool", "pack", "c", out_lib.path] + [a.path for a in objects], env = go_environment_vars(ctx), ) - count += 1 - return outputs +def _emit_go_cover_action(ctx, sources): + """Construct the command line for test coverage instrument. + + Args: + ctx: The starlark Context. + sources: an iterable of Go source files. + + Returns: + A list of Go source code files which might be coverage instrumented. + """ + outputs = [] + + # TODO(linuxerwang): make the mode configurable. + count = 0 + + for src in sources: + if not src.path.endswith(".go") or src.path.endswith("_test.go"): + outputs += [src] + continue + + cover_var = "GoCover_%d" % count + out = ctx.new_file(src, src.basename[:-3] + "_" + cover_var + ".cover.go") + outputs += [out] + ctx.action( + inputs = [src] + ctx.files.toolchain, + outputs = [out], + mnemonic = "GoCover", + executable = ctx.file.go_tool, + arguments = ["tool", "cover", "--mode=set", "-var=%s" % cover_var, "-o", out.path, src.path], + env = go_environment_vars(ctx), + ) + count += 1 + + return outputs def go_library_impl(ctx): - """Implements the go_library() rule.""" - - sources = depset(ctx.files.srcs) - go_srcs = depset([s for s in sources if s.basename.endswith('.go')]) - asm_srcs = [s for s in sources if s.basename.endswith('.s') or s.basename.endswith('.S')] - asm_hdrs = [s for s in sources if s.basename.endswith('.h')] - deps = ctx.attr.deps - dep_runfiles = [d.data_runfiles for d in deps] - - cgo_object = None - if hasattr(ctx.attr, "cgo_object"): - cgo_object = ctx.attr.cgo_object - - if ctx.attr.library: - go_srcs += ctx.attr.library.go_sources - asm_srcs += ctx.attr.library.asm_sources - asm_hdrs += ctx.attr.library.asm_headers - deps += ctx.attr.library.direct_deps - dep_runfiles += [ctx.attr.library.data_runfiles] - if ctx.attr.library.cgo_object: - if cgo_object: - fail("go_library %s cannot have cgo_object because the package " + - "already has cgo_object in %s" % (ctx.label.name, - ctx.attr.library.name)) - cgo_object = ctx.attr.library.cgo_object - if not go_srcs: - fail("may not be empty", "srcs") - - transitive_cgo_deps = depset([], order="topological") - if cgo_object: - dep_runfiles += [cgo_object.data_runfiles] - transitive_cgo_deps += cgo_object.cgo_deps - - extra_objects = [cgo_object.cgo_obj] if cgo_object else [] - for src in asm_srcs: - obj = ctx.new_file(src, "%s.dir/%s.o" % (ctx.label.name, src.basename[:-2])) - _emit_go_asm_action(ctx, src, asm_hdrs, obj) - extra_objects += [obj] - - lib_name = _go_importpath(ctx) + ".a" - out_lib = ctx.new_file(lib_name) - out_object = ctx.new_file(ctx.label.name + ".o") - search_path = out_lib.path[:-len(lib_name)] - gc_goopts = _gc_goopts(ctx) - transitive_go_libraries = depset([out_lib]) - transitive_go_library_paths = depset([search_path]) - for dep in deps: - transitive_go_libraries += dep.transitive_go_libraries - transitive_cgo_deps += dep.transitive_cgo_deps - transitive_go_library_paths += dep.transitive_go_library_paths - - go_srcs = _emit_go_compile_action(ctx, - sources = go_srcs, - deps = deps, - libpaths = transitive_go_library_paths, - out_object = out_object, - gc_goopts = gc_goopts, - ) - _emit_go_pack_action(ctx, out_lib, [out_object] + extra_objects) - - dylibs = [] - if cgo_object: - dylibs += [d for d in cgo_object.cgo_deps if d.path.endswith(".so")] - - runfiles = ctx.runfiles(files = dylibs, collect_data = True) - for d in dep_runfiles: - runfiles = runfiles.merge(d) - - return struct( - label = ctx.label, - files = depset([out_lib]), - runfiles = runfiles, - go_sources = go_srcs, - asm_sources = asm_srcs, - asm_headers = asm_hdrs, - cgo_object = cgo_object, - direct_deps = ctx.attr.deps, - transitive_cgo_deps = transitive_cgo_deps, - transitive_go_libraries = transitive_go_libraries, - transitive_go_library_paths = transitive_go_library_paths, - gc_goopts = gc_goopts, - ) - -def _c_linker_options(ctx, blacklist=[]): - """Extracts flags to pass to $(CC) on link from the current context - - Args: - ctx: the current context - blacklist: Any flags starts with any of these prefixes are filtered out from - the return value. - - Returns: - A list of command line flags - """ - cpp = ctx.fragments.cpp - features = ctx.features - options = cpp.compiler_options(features) - options += cpp.unfiltered_compiler_options(features) - options += cpp.link_options - options += cpp.mostly_static_link_options(ctx.features, False) - filtered = [] - for opt in options: - if any([opt.startswith(prefix) for prefix in blacklist]): - continue - filtered.append(opt) - return filtered + """Implements the go_library() rule.""" + + sources = depset(ctx.files.srcs) + go_srcs = depset([s for s in sources if s.basename.endswith(".go")]) + asm_srcs = [s for s in sources if s.basename.endswith(".s") or s.basename.endswith(".S")] + asm_hdrs = [s for s in sources if s.basename.endswith(".h")] + deps = ctx.attr.deps + dep_runfiles = [d.data_runfiles for d in deps] + + cgo_object = None + if hasattr(ctx.attr, "cgo_object"): + cgo_object = ctx.attr.cgo_object + + if ctx.attr.library: + go_srcs += ctx.attr.library.go_sources + asm_srcs += ctx.attr.library.asm_sources + asm_hdrs += ctx.attr.library.asm_headers + deps += ctx.attr.library.direct_deps + dep_runfiles += [ctx.attr.library.data_runfiles] + if ctx.attr.library.cgo_object: + if cgo_object: + fail("go_library %s cannot have cgo_object because the package " + + "already has cgo_object in %s" % ( + ctx.label.name, + ctx.attr.library.name, + )) + cgo_object = ctx.attr.library.cgo_object + if not go_srcs: + fail("may not be empty", "srcs") + + transitive_cgo_deps = depset([], order = "topological") + if cgo_object: + dep_runfiles += [cgo_object.data_runfiles] + transitive_cgo_deps += cgo_object.cgo_deps + + extra_objects = [cgo_object.cgo_obj] if cgo_object else [] + for src in asm_srcs: + obj = ctx.new_file(src, "%s.dir/%s.o" % (ctx.label.name, src.basename[:-2])) + _emit_go_asm_action(ctx, src, asm_hdrs, obj) + extra_objects += [obj] + + lib_name = _go_importpath(ctx) + ".a" + out_lib = ctx.new_file(lib_name) + out_object = ctx.new_file(ctx.label.name + ".o") + search_path = out_lib.path[:-len(lib_name)] + gc_goopts = _gc_goopts(ctx) + transitive_go_libraries = depset([out_lib]) + transitive_go_library_paths = depset([search_path]) + for dep in deps: + transitive_go_libraries += dep.transitive_go_libraries + transitive_cgo_deps += dep.transitive_cgo_deps + transitive_go_library_paths += dep.transitive_go_library_paths + + go_srcs = _emit_go_compile_action( + ctx, + sources = go_srcs, + deps = deps, + libpaths = transitive_go_library_paths, + out_object = out_object, + gc_goopts = gc_goopts, + ) + _emit_go_pack_action(ctx, out_lib, [out_object] + extra_objects) + + dylibs = [] + if cgo_object: + dylibs += [d for d in cgo_object.cgo_deps if d.path.endswith(".so")] + + runfiles = ctx.runfiles(files = dylibs, collect_data = True) + for d in dep_runfiles: + runfiles = runfiles.merge(d) + + return struct( + label = ctx.label, + files = depset([out_lib]), + runfiles = runfiles, + go_sources = go_srcs, + asm_sources = asm_srcs, + asm_headers = asm_hdrs, + cgo_object = cgo_object, + direct_deps = ctx.attr.deps, + transitive_cgo_deps = transitive_cgo_deps, + transitive_go_libraries = transitive_go_libraries, + transitive_go_library_paths = transitive_go_library_paths, + gc_goopts = gc_goopts, + ) + +def _c_linker_options(ctx, blocklist = []): + """Extracts flags to pass to $(CC) on link from the current context + + Args: + ctx: the current context + blocklist: Any flags starts with any of these prefixes are filtered out from + the return value. + + Returns: + A list of command line flags + """ + cpp = ctx.fragments.cpp + features = ctx.features + options = cpp.compiler_options(features) + options += cpp.unfiltered_compiler_options(features) + options += cpp.link_options + options += cpp.mostly_static_link_options(ctx.features, False) + filtered = [] + for opt in options: + if any([opt.startswith(prefix) for prefix in blocklist]): + continue + filtered.append(opt) + return filtered def _gc_goopts(ctx): - gc_goopts = [ctx.expand_make_variables("gc_goopts", f, {}) - for f in ctx.attr.gc_goopts] - if ctx.attr.library: - gc_goopts += ctx.attr.library.gc_goopts - return gc_goopts + gc_goopts = [ + ctx.expand_make_variables("gc_goopts", f, {}) + for f in ctx.attr.gc_goopts + ] + if ctx.attr.library: + gc_goopts += ctx.attr.library.gc_goopts + return gc_goopts def _gc_linkopts(ctx): - gc_linkopts = [ctx.expand_make_variables("gc_linkopts", f, {}) - for f in ctx.attr.gc_linkopts] - for k, v in ctx.attr.x_defs.items(): - gc_linkopts += ["-X", "%s='%s'" % (k, v)] - return gc_linkopts + gc_linkopts = [ + ctx.expand_make_variables("gc_linkopts", f, {}) + for f in ctx.attr.gc_linkopts + ] + for k, v in ctx.attr.x_defs.items(): + gc_linkopts += ["-X", "%s='%s'" % (k, v)] + return gc_linkopts def _extract_extldflags(gc_linkopts, extldflags): - """Extracts -extldflags from gc_linkopts and combines them into a single list. - - Args: - gc_linkopts: a list of flags passed in through the gc_linkopts attributes. - ctx.expand_make_variables should have already been applied. - extldflags: a list of flags to be passed to the external linker. - - Return: - A tuple containing the filtered gc_linkopts with external flags removed, - and a combined list of external flags. - """ - filtered_gc_linkopts = [] - is_extldflags = False - for opt in gc_linkopts: - if is_extldflags: - is_extldflags = False - extldflags += [opt] - elif opt == "-extldflags": - is_extldflags = True - else: - filtered_gc_linkopts += [opt] - return filtered_gc_linkopts, extldflags - -def _emit_go_link_action(ctx, transitive_go_library_paths, transitive_go_libraries, cgo_deps, libs, - executable, gc_linkopts): - """Sets up a symlink tree to libraries to link together.""" - config_strip = len(ctx.configuration.bin_dir.path) + 1 - pkg_depth = executable.dirname[config_strip:].count('/') + 1 - - ld = "%s" % ctx.fragments.cpp.compiler_executable - extldflags = _c_linker_options(ctx) + [ - "-Wl,-rpath,$ORIGIN/" + ("../" * pkg_depth), - ] - for d in cgo_deps: - if d.basename.endswith('.so'): - short_dir = d.dirname[len(d.root.path):] - extldflags += ["-Wl,-rpath,$ORIGIN/" + ("../" * pkg_depth) + short_dir] - gc_linkopts, extldflags = _extract_extldflags(gc_linkopts, extldflags) - - link_cmd = [ - ctx.file.go_tool.path, - "tool", "link", - "-L", "." - ] - for path in transitive_go_library_paths: - link_cmd += ["-L", path] - link_cmd += [ - "-o", executable.path, - ] + gc_linkopts + ['"${STAMP_XDEFS[@]}"'] - - # workaround for a bug in ld(1) on Mac OS X. - # http://lists.apple.com/archives/Darwin-dev/2006/Sep/msg00084.html - # TODO(yugui) Remove this workaround once rules_go stops supporting XCode 7.2 - # or earlier. - if not _is_darwin_cpu(ctx): - link_cmd += ["-s"] - - link_cmd += [ - "-extld", ld, - "-extldflags", "'%s'" % " ".join(extldflags), - ] + [lib.path for lib in libs] - - # Avoided -s on OSX but but it requires dsymutil to be on $PATH. - # TODO(yugui) Remove this workaround once rules_go stops supporting XCode 7.2 - # or earlier. - cmds = ["export PATH=$PATH:/usr/bin"] - - cmds += [ - "STAMP_XDEFS=()", - ] - - stamp_inputs = [] - if ctx.attr.linkstamp: - # read workspace status files, converting "KEY value" lines - # to "-X $linkstamp.KEY=value" arguments to the go linker. - stamp_inputs = [ctx.info_file, ctx.version_file] - for f in stamp_inputs: - cmds += [ - "while read -r key value || [[ -n $key ]]; do", - " STAMP_XDEFS+=(-X \"%s.$key=$value\")" % ctx.attr.linkstamp, - "done < " + f.path, - ] - - cmds += [' '.join(link_cmd)] - - f = _emit_generate_params_action(cmds, ctx, lib.basename + ".GoLinkFile.params") - - ctx.action( - inputs = [f] + (list(transitive_go_libraries) + [lib] + list(cgo_deps) + - ctx.files.toolchain + ctx.files._crosstool) + stamp_inputs, - outputs = [executable], - command = f.path, - mnemonic = "GoLink", - env = go_environment_vars(ctx), - ) + """Extracts -extldflags from gc_linkopts and combines them into a single list. + + Args: + gc_linkopts: a list of flags passed in through the gc_linkopts attributes. + ctx.expand_make_variables should have already been applied. + extldflags: a list of flags to be passed to the external linker. + + Return: + A tuple containing the filtered gc_linkopts with external flags removed, + and a combined list of external flags. + """ + filtered_gc_linkopts = [] + is_extldflags = False + for opt in gc_linkopts: + if is_extldflags: + is_extldflags = False + extldflags += [opt] + elif opt == "-extldflags": + is_extldflags = True + else: + filtered_gc_linkopts += [opt] + return filtered_gc_linkopts, extldflags + +def _emit_go_link_action( + ctx, + transitive_go_library_paths, + transitive_go_libraries, + cgo_deps, + libs, + executable, + gc_linkopts): + """Sets up a symlink tree to libraries to link together.""" + config_strip = len(ctx.configuration.bin_dir.path) + 1 + pkg_depth = executable.dirname[config_strip:].count("/") + 1 + + ld = "%s" % ctx.fragments.cpp.compiler_executable + extldflags = _c_linker_options(ctx) + [ + "-Wl,-rpath,$ORIGIN/" + ("../" * pkg_depth), + ] + for d in cgo_deps: + if d.basename.endswith(".so"): + short_dir = d.dirname[len(d.root.path):] + extldflags += ["-Wl,-rpath,$ORIGIN/" + ("../" * pkg_depth) + short_dir] + gc_linkopts, extldflags = _extract_extldflags(gc_linkopts, extldflags) + + link_cmd = [ + ctx.file.go_tool.path, + "tool", + "link", + "-L", + ".", + ] + for path in transitive_go_library_paths: + link_cmd += ["-L", path] + link_cmd += [ + "-o", + executable.path, + ] + gc_linkopts + ['"${STAMP_XDEFS[@]}"'] + + # workaround for a bug in ld(1) on Mac OS X. + # http://lists.apple.com/archives/Darwin-dev/2006/Sep/msg00084.html + # TODO(yugui) Remove this workaround once rules_go stops supporting XCode 7.2 + # or earlier. + if not _is_darwin_cpu(ctx): + link_cmd += ["-s"] + + link_cmd += [ + "-extld", + ld, + "-extldflags", + "'%s'" % " ".join(extldflags), + ] + [lib.path for lib in libs] + + # Avoided -s on OSX but but it requires dsymutil to be on $PATH. + # TODO(yugui) Remove this workaround once rules_go stops supporting XCode 7.2 + # or earlier. + cmds = ["export PATH=$PATH:/usr/bin"] + + cmds += [ + "STAMP_XDEFS=()", + ] + + stamp_inputs = [] + if ctx.attr.linkstamp: + # read workspace status files, converting "KEY value" lines + # to "-X $linkstamp.KEY=value" arguments to the go linker. + stamp_inputs = [ctx.info_file, ctx.version_file] + for f in stamp_inputs: + cmds += [ + "while read -r key value || [[ -n $key ]]; do", + " STAMP_XDEFS+=(-X \"%s.$key=$value\")" % ctx.attr.linkstamp, + "done < " + f.path, + ] + + cmds += [" ".join(link_cmd)] + + f = _emit_generate_params_action(cmds, ctx, lib.basename + ".GoLinkFile.params") + + ctx.action( + inputs = [f] + (list(transitive_go_libraries) + [lib] + list(cgo_deps) + + ctx.files.toolchain + ctx.files._crosstool) + stamp_inputs, + outputs = [executable], + command = f.path, + mnemonic = "GoLink", + env = go_environment_vars(ctx), + ) def go_binary_impl(ctx): - """go_binary_impl emits actions for compiling and linking a go executable.""" - lib_result = go_library_impl(ctx) - _emit_go_link_action( - ctx, - transitive_go_libraries=lib_result.transitive_go_libraries, - transitive_go_library_paths=lib_result.transitive_go_library_paths, - cgo_deps=lib_result.transitive_cgo_deps, - libs=lib_result.files, - executable=ctx.outputs.executable, - gc_linkopts=_gc_linkopts(ctx)) - - return struct( - files = depset([ctx.outputs.executable]), - runfiles = lib_result.runfiles, - cgo_object = lib_result.cgo_object, - ) + """go_binary_impl emits actions for compiling and linking a go executable.""" + lib_result = go_library_impl(ctx) + _emit_go_link_action( + ctx, + transitive_go_libraries = lib_result.transitive_go_libraries, + transitive_go_library_paths = lib_result.transitive_go_library_paths, + cgo_deps = lib_result.transitive_cgo_deps, + libs = lib_result.files, + executable = ctx.outputs.executable, + gc_linkopts = _gc_linkopts(ctx), + ) + + return struct( + files = depset([ctx.outputs.executable]), + runfiles = lib_result.runfiles, + cgo_object = lib_result.cgo_object, + ) def go_test_impl(ctx): - """go_test_impl implements go testing. - - It emits an action to run the test generator, and then compiles the - test into a binary.""" - - lib_result = go_library_impl(ctx) - main_go = ctx.new_file(ctx.label.name + "_main_test.go") - main_object = ctx.new_file(ctx.label.name + "_main_test.o") - main_lib = ctx.new_file(ctx.label.name + "_main_test.a") - go_import = _go_importpath(ctx) - - cmds = [ - 'UNFILTERED_TEST_FILES=(%s)' % - ' '.join(["'%s'" % f.path for f in lib_result.go_sources]), - 'FILTERED_TEST_FILES=()', - 'while read -r line; do', - ' if [ -n "$line" ]; then', - ' FILTERED_TEST_FILES+=("$line")', - ' fi', - 'done < <(\'%s\' -cgo "${UNFILTERED_TEST_FILES[@]}")' % - ctx.executable._filter_tags.path, - ' '.join([ - "'%s'" % ctx.executable.test_generator.path, - '--package', - go_import, - '--output', - "'%s'" % main_go.path, - '"${FILTERED_TEST_FILES[@]}"', - ]), - ] - f = _emit_generate_params_action( - cmds, ctx, ctx.label.name + ".GoTestGenTest.params") - inputs = (list(lib_result.go_sources) + list(ctx.files.toolchain) + - [f, ctx.executable._filter_tags, ctx.executable.test_generator]) - ctx.action( - inputs = inputs, - outputs = [main_go], - command = f.path, - mnemonic = "GoTestGenTest", - env = dict(go_environment_vars(ctx), RUNDIR=ctx.label.package)) - - _emit_go_compile_action( - ctx, - sources=depset([main_go]), - deps=ctx.attr.deps + [lib_result], - libpaths=lib_result.transitive_go_library_paths, - out_object=main_object, - gc_goopts=_gc_goopts(ctx), - ) - _emit_go_pack_action(ctx, main_lib, [main_object]) - _emit_go_link_action( - ctx, - transitive_go_library_paths=lib_result.transitive_go_library_paths, - transitive_go_libraries=lib_result.transitive_go_libraries, - cgo_deps=lib_result.transitive_cgo_deps, - libs=[main_lib], - executable=ctx.outputs.executable, - gc_linkopts=_gc_linkopts(ctx)) - - # TODO(bazel-team): the Go tests should do a chdir to the directory - # holding the data files, so open-source go tests continue to work - # without code changes. - runfiles = ctx.runfiles(files = [ctx.outputs.executable]) - runfiles = runfiles.merge(lib_result.runfiles) - return struct( - files = depset([ctx.outputs.executable]), - runfiles = runfiles, - ) + """go_test_impl implements go testing. + + It emits an action to run the test generator, and then compiles the + test into a binary.""" + + lib_result = go_library_impl(ctx) + main_go = ctx.new_file(ctx.label.name + "_main_test.go") + main_object = ctx.new_file(ctx.label.name + "_main_test.o") + main_lib = ctx.new_file(ctx.label.name + "_main_test.a") + go_import = _go_importpath(ctx) + + cmds = [ + "UNFILTERED_TEST_FILES=(%s)" % + " ".join(["'%s'" % f.path for f in lib_result.go_sources]), + "FILTERED_TEST_FILES=()", + "while read -r line; do", + ' if [ -n "$line" ]; then', + ' FILTERED_TEST_FILES+=("$line")', + " fi", + 'done < <(\'%s\' -cgo "${UNFILTERED_TEST_FILES[@]}")' % + ctx.executable._filter_tags.path, + " ".join([ + "'%s'" % ctx.executable.test_generator.path, + "--package", + go_import, + "--output", + "'%s'" % main_go.path, + '"${FILTERED_TEST_FILES[@]}"', + ]), + ] + f = _emit_generate_params_action( + cmds, + ctx, + ctx.label.name + ".GoTestGenTest.params", + ) + inputs = (list(lib_result.go_sources) + list(ctx.files.toolchain) + + [f, ctx.executable._filter_tags, ctx.executable.test_generator]) + ctx.action( + inputs = inputs, + outputs = [main_go], + command = f.path, + mnemonic = "GoTestGenTest", + env = dict(go_environment_vars(ctx), RUNDIR = ctx.label.package), + ) + + _emit_go_compile_action( + ctx, + sources = depset([main_go]), + deps = ctx.attr.deps + [lib_result], + libpaths = lib_result.transitive_go_library_paths, + out_object = main_object, + gc_goopts = _gc_goopts(ctx), + ) + _emit_go_pack_action(ctx, main_lib, [main_object]) + _emit_go_link_action( + ctx, + transitive_go_library_paths = lib_result.transitive_go_library_paths, + transitive_go_libraries = lib_result.transitive_go_libraries, + cgo_deps = lib_result.transitive_cgo_deps, + libs = [main_lib], + executable = ctx.outputs.executable, + gc_linkopts = _gc_linkopts(ctx), + ) + + # TODO(bazel-team): the Go tests should do a chdir to the directory + # holding the data files, so open-source go tests continue to work + # without code changes. + runfiles = ctx.runfiles(files = [ctx.outputs.executable]) + runfiles = runfiles.merge(lib_result.runfiles) + return struct( + files = depset([ctx.outputs.executable]), + runfiles = runfiles, + ) go_env_attrs = { "toolchain": attr.label( @@ -704,51 +734,51 @@ go_test = rule( ) def _pkg_dir(workspace_root, package_name): - if workspace_root and package_name: - return workspace_root + "/" + package_name - if workspace_root: - return workspace_root - if package_name: - return package_name - return "." + if workspace_root and package_name: + return workspace_root + "/" + package_name + if workspace_root: + return workspace_root + if package_name: + return package_name + return "." def _exec_path(path): - if path.startswith('/'): - return path - return '${execroot}/' + path + if path.startswith("/"): + return path + return "${execroot}/" + path def _cgo_filter_srcs_impl(ctx): - srcs = ctx.files.srcs - dsts = [] - cmds = [] - for src in srcs: - stem, _, ext = src.path.rpartition('.') - dst_basename = "%s.filtered.%s" % (stem, ext) - dst = ctx.new_file(src, dst_basename) - cmds += [ - "if '%s' -cgo -quiet '%s'; then" % + srcs = ctx.files.srcs + dsts = [] + cmds = [] + for src in srcs: + stem, _, ext = src.path.rpartition(".") + dst_basename = "%s.filtered.%s" % (stem, ext) + dst = ctx.new_file(src, dst_basename) + cmds += [ + "if '%s' -cgo -quiet '%s'; then" % (ctx.executable._filter_tags.path, src.path), - " cp '%s' '%s'" % (src.path, dst.path), - "else", - " echo -n >'%s'" % dst.path, - "fi", - ] - dsts.append(dst) - - if ctx.label.package == "": - script_name = ctx.label.name + ".CGoFilterSrcs.params" - else: - script_name = ctx.label.package + "/" + ctx.label.name + ".CGoFilterSrcs.params" - f = _emit_generate_params_action(cmds, ctx, script_name) - ctx.action( - inputs = [f, ctx.executable._filter_tags] + srcs, - outputs = dsts, - command = f.path, - mnemonic = "CgoFilterSrcs", - ) - return struct( - files = depset(dsts), - ) + " cp '%s' '%s'" % (src.path, dst.path), + "else", + " echo -n >'%s'" % dst.path, + "fi", + ] + dsts.append(dst) + + if ctx.label.package == "": + script_name = ctx.label.name + ".CGoFilterSrcs.params" + else: + script_name = ctx.label.package + "/" + ctx.label.name + ".CGoFilterSrcs.params" + f = _emit_generate_params_action(cmds, ctx, script_name) + ctx.action( + inputs = [f, ctx.executable._filter_tags] + srcs, + outputs = dsts, + command = f.path, + mnemonic = "CgoFilterSrcs", + ) + return struct( + files = depset(dsts), + ) _cgo_filter_srcs = rule( implementation = _cgo_filter_srcs_impl, @@ -767,90 +797,90 @@ _cgo_filter_srcs = rule( ) def _cgo_codegen_impl(ctx): - go_srcs = ctx.files.srcs - srcs = go_srcs + ctx.files.c_hdrs - linkopts = ctx.attr.linkopts - copts = ctx.fragments.cpp.c_options + ctx.attr.copts - deps = depset([], order="topological") - for d in ctx.attr.deps: - srcs += list(d.cc.transitive_headers) - deps += d.cc.libs - copts += ['-D' + define for define in d.cc.defines] - for inc in d.cc.include_directories: - copts += ['-I', _exec_path(inc)] - for hdr in ctx.files.c_hdrs: - copts += ['-iquote', hdr.dirname] - for inc in d.cc.quote_include_directories: - copts += ['-iquote', _exec_path(inc)] - for inc in d.cc.system_include_directories: - copts += ['-isystem', _exec_path(inc)] - for lib in d.cc.libs: - if lib.basename.startswith('lib') and lib.basename.endswith('.so'): - linkopts += ['-L', lib.dirname, '-l', lib.basename[3:-3]] - else: - linkopts += [lib.path] - linkopts += d.cc.link_flags - - p = _pkg_dir(ctx.label.workspace_root, ctx.label.package) + "/" - if p == "./": - p = "" # workaround when cgo_library in repository root - out_dir = (ctx.configuration.genfiles_dir.path + '/' + - p + ctx.attr.outdir) - cc = ctx.fragments.cpp.compiler_executable - cmds = [ - # We cannot use env for CC because $(CC) on OSX is relative - # and '../' does not work fine due to symlinks. - 'export CC=$(cd $(dirname {cc}); pwd)/$(basename {cc})'.format(cc=cc), - 'export CXX=$CC', - 'objdir="%s/gen"' % out_dir, - 'execroot=$(pwd)', - 'mkdir -p "$objdir"', - 'unfiltered_go_files=(%s)' % ' '.join(["'%s'" % f.path for f in go_srcs]), - 'filtered_go_files=()', - 'for file in "${unfiltered_go_files[@]}"; do', - ' stem=$(basename "$file" .go)', - ' if %s -cgo -quiet "$file"; then' % ctx.executable._filter_tags.path, - ' filtered_go_files+=("$file")', - ' else', - ' grep --max-count 1 "^package " "$file" >"$objdir/$stem.go"', - ' echo -n >"$objdir/$stem.c"', - ' fi', - 'done', - 'if [ ${#filtered_go_files[@]} -eq 0 ]; then', - ' echo no buildable Go source files in %s >&1' % str(ctx.label), - ' exit 1', - 'fi', - '"$GOROOT/bin/go" tool cgo -objdir "$objdir" -- %s "${filtered_go_files[@]}"' % - ' '.join(['"%s"' % copt for copt in copts]), - # Rename the outputs using glob so we don't have to understand cgo's mangling - # TODO(#350): might be fixed by this?. - 'for file in "${filtered_go_files[@]}"; do', - ' stem=$(basename "$file" .go)', - ' mv "$objdir/"*"$stem.cgo1.go" "$objdir/$stem.go"', - ' mv "$objdir/"*"$stem.cgo2.c" "$objdir/$stem.c"', - 'done', - 'rm -f $objdir/_cgo_.o $objdir/_cgo_flags', + go_srcs = ctx.files.srcs + srcs = go_srcs + ctx.files.c_hdrs + linkopts = ctx.attr.linkopts + copts = ctx.fragments.cpp.c_options + ctx.attr.copts + deps = depset([], order = "topological") + for d in ctx.attr.deps: + srcs += list(d.cc.transitive_headers) + deps += d.cc.libs + copts += ["-D" + define for define in d.cc.defines] + for inc in d.cc.include_directories: + copts += ["-I", _exec_path(inc)] + for hdr in ctx.files.c_hdrs: + copts += ["-iquote", hdr.dirname] + for inc in d.cc.quote_include_directories: + copts += ["-iquote", _exec_path(inc)] + for inc in d.cc.system_include_directories: + copts += ["-isystem", _exec_path(inc)] + for lib in d.cc.libs: + if lib.basename.startswith("lib") and lib.basename.endswith(".so"): + linkopts += ["-L", lib.dirname, "-l", lib.basename[3:-3]] + else: + linkopts += [lib.path] + linkopts += d.cc.link_flags + + p = _pkg_dir(ctx.label.workspace_root, ctx.label.package) + "/" + if p == "./": + p = "" # workaround when cgo_library in repository root + out_dir = (ctx.configuration.genfiles_dir.path + "/" + + p + ctx.attr.outdir) + cc = ctx.fragments.cpp.compiler_executable + cmds = [ + # We cannot use env for CC because $(CC) on OSX is relative + # and '../' does not work fine due to symlinks. + "export CC=$(cd $(dirname {cc}); pwd)/$(basename {cc})".format(cc = cc), + "export CXX=$CC", + 'objdir="%s/gen"' % out_dir, + "execroot=$(pwd)", + 'mkdir -p "$objdir"', + "unfiltered_go_files=(%s)" % " ".join(["'%s'" % f.path for f in go_srcs]), + "filtered_go_files=()", + 'for file in "${unfiltered_go_files[@]}"; do', + ' stem=$(basename "$file" .go)', + ' if %s -cgo -quiet "$file"; then' % ctx.executable._filter_tags.path, + ' filtered_go_files+=("$file")', + " else", + ' grep --max-count 1 "^package " "$file" >"$objdir/$stem.go"', + ' echo -n >"$objdir/$stem.c"', + " fi", + "done", + "if [ ${#filtered_go_files[@]} -eq 0 ]; then", + " echo no buildable Go source files in %s >&1" % str(ctx.label), + " exit 1", + "fi", + '"$GOROOT/bin/go" tool cgo -objdir "$objdir" -- %s "${filtered_go_files[@]}"' % + " ".join(['"%s"' % copt for copt in copts]), + # Rename the outputs using glob so we don't have to understand cgo's mangling + # TODO(#350): might be fixed by this?. + 'for file in "${filtered_go_files[@]}"; do', + ' stem=$(basename "$file" .go)', + ' mv "$objdir/"*"$stem.cgo1.go" "$objdir/$stem.go"', + ' mv "$objdir/"*"$stem.cgo2.c" "$objdir/$stem.c"', + "done", + "rm -f $objdir/_cgo_.o $objdir/_cgo_flags", ] - f = _emit_generate_params_action(cmds, ctx, out_dir + ".CGoCodeGenFile.params") - - inputs = (srcs + ctx.files.toolchain + ctx.files._crosstool + - [f, ctx.executable._filter_tags]) - ctx.action( - inputs = inputs, - outputs = ctx.outputs.outs, - mnemonic = "CGoCodeGen", - progress_message = "CGoCodeGen %s" % ctx.label, - command = f.path, - env = go_environment_vars(ctx) + { - "CGO_LDFLAGS": " ".join(linkopts), - }, - ) - return struct( - label = ctx.label, - files = depset(ctx.outputs.outs), - cgo_deps = deps, - ) + f = _emit_generate_params_action(cmds, ctx, out_dir + ".CGoCodeGenFile.params") + + inputs = (srcs + ctx.files.toolchain + ctx.files._crosstool + + [f, ctx.executable._filter_tags]) + ctx.action( + inputs = inputs, + outputs = ctx.outputs.outs, + mnemonic = "CGoCodeGen", + progress_message = "CGoCodeGen %s" % ctx.label, + command = f.path, + env = go_environment_vars(ctx) + { + "CGO_LDFLAGS": " ".join(linkopts), + }, + ) + return struct( + label = ctx.label, + files = depset(ctx.outputs.outs), + cgo_deps = deps, + ) _cgo_codegen_rule = rule( _cgo_codegen_impl, @@ -878,91 +908,102 @@ _cgo_codegen_rule = rule( output_to_genfiles = True, ) -def _cgo_codegen(name, srcs, c_hdrs=[], deps=[], copts=[], linkopts=[], - go_tool=None, toolchain=None): - """Generates glue codes for interop between C and Go - - Args: - name: A unique name of the rule - srcs: list of Go source files. - Each of them must contain `import "C"`. - c_hdrs: C/C++ header files necessary to determine kinds of - C/C++ identifiers in srcs. - deps: A list of cc_library rules. - The generated codes are expected to be linked with these deps. - linkopts: A list of linker options, - These flags are passed to the linker when the generated codes - are linked into the target binary. - """ - outdir = name + ".dir" - outgen = outdir + "/gen" - - go_thunks = [] - c_thunks = [] - for s in srcs: - if not s.endswith('.go'): - fail("not a .go file: %s" % s) - basename = s[:-3] - if basename.rfind("/") >= 0: - basename = basename[basename.rfind("/")+1:] - go_thunks.append(outgen + "/" + basename + ".go") - c_thunks.append(outgen + "/" + basename + ".c") - - outs = struct( - name = name, - - outdir = outgen, - go_thunks = go_thunks, - c_thunks = c_thunks, - c_exports = [ - outgen + "/_cgo_export.c", - outgen + "/_cgo_export.h", - ], - c_dummy = outgen + "/_cgo_main.c", - gotypes = outgen + "/_cgo_gotypes.go", - ) - - _cgo_codegen_rule( - name = name, - srcs = srcs, - c_hdrs = c_hdrs, - deps = deps, - copts = copts, - linkopts = linkopts, - - go_tool = go_tool, - toolchain = toolchain, - - outdir = outdir, - outs = outs.go_thunks + outs.c_thunks + outs.c_exports + [ - outs.c_dummy, outs.gotypes, - ], - - visibility = ["//visibility:private"], - ) - return outs +def _cgo_codegen( + name, + srcs, + c_hdrs = [], + deps = [], + copts = [], + linkopts = [], + go_tool = None, + toolchain = None): + """Generates glue codes for interop between C and Go + + Args: + name: A unique name of the rule + srcs: list of Go source files. + Each of them must contain `import "C"`. + c_hdrs: C/C++ header files necessary to determine kinds of + C/C++ identifiers in srcs. + deps: A list of cc_library rules. + The generated codes are expected to be linked with these deps. + linkopts: A list of linker options, + These flags are passed to the linker when the generated codes + are linked into the target binary. + """ + outdir = name + ".dir" + outgen = outdir + "/gen" + + go_thunks = [] + c_thunks = [] + for s in srcs: + if not s.endswith(".go"): + fail("not a .go file: %s" % s) + basename = s[:-3] + if basename.rfind("/") >= 0: + basename = basename[basename.rfind("/") + 1:] + go_thunks.append(outgen + "/" + basename + ".go") + c_thunks.append(outgen + "/" + basename + ".c") + + outs = struct( + name = name, + outdir = outgen, + go_thunks = go_thunks, + c_thunks = c_thunks, + c_exports = [ + outgen + "/_cgo_export.c", + outgen + "/_cgo_export.h", + ], + c_dummy = outgen + "/_cgo_main.c", + gotypes = outgen + "/_cgo_gotypes.go", + ) + + _cgo_codegen_rule( + name = name, + srcs = srcs, + c_hdrs = c_hdrs, + deps = deps, + copts = copts, + linkopts = linkopts, + go_tool = go_tool, + toolchain = toolchain, + outdir = outdir, + outs = outs.go_thunks + outs.c_thunks + outs.c_exports + [ + outs.c_dummy, + outs.gotypes, + ], + visibility = ["//visibility:private"], + ) + return outs def _cgo_import_impl(ctx): - cmds = [ - (ctx.file.go_tool.path + " tool cgo" + - " -dynout " + ctx.outputs.out.path + - " -dynimport " + ctx.file.cgo_o.path + - " -dynpackage $(%s %s)" % (ctx.executable._extract_package.path, - ctx.file.sample_go_src.path)), - ] - f = _emit_generate_params_action(cmds, ctx, ctx.outputs.out.path + ".CGoImportGenFile.params") - ctx.action( - inputs = (ctx.files.toolchain + - [f, ctx.file.go_tool, ctx.executable._extract_package, - ctx.file.cgo_o, ctx.file.sample_go_src]), - outputs = [ctx.outputs.out], - command = f.path, - mnemonic = "CGoImportGen", - env = go_environment_vars(ctx), - ) - return struct( - files = depset([ctx.outputs.out]), - ) + cmds = [ + (ctx.file.go_tool.path + " tool cgo" + + " -dynout " + ctx.outputs.out.path + + " -dynimport " + ctx.file.cgo_o.path + + " -dynpackage $(%s %s)" % ( + ctx.executable._extract_package.path, + ctx.file.sample_go_src.path, + )), + ] + f = _emit_generate_params_action(cmds, ctx, ctx.outputs.out.path + ".CGoImportGenFile.params") + ctx.action( + inputs = (ctx.files.toolchain + + [ + f, + ctx.file.go_tool, + ctx.executable._extract_package, + ctx.file.cgo_o, + ctx.file.sample_go_src, + ]), + outputs = [ctx.outputs.out], + command = f.path, + mnemonic = "CGoImportGen", + env = go_environment_vars(ctx), + ) + return struct( + files = depset([ctx.outputs.out]), + ) _cgo_import = rule( _cgo_import_impl, @@ -988,15 +1029,15 @@ _cgo_import = rule( ) def _cgo_genrule_impl(ctx): - return struct( - label = ctx.label, - go_sources = ctx.files.srcs, - asm_sources = [], - asm_headers = [], - cgo_object = ctx.attr.cgo_object, - direct_deps = ctx.attr.deps, - gc_goopts = [], - ) + return struct( + label = ctx.label, + go_sources = ctx.files.srcs, + asm_sources = [], + asm_headers = [], + cgo_object = ctx.attr.cgo_object, + direct_deps = ctx.attr.deps, + gc_goopts = [], + ) _cgo_genrule = rule( _cgo_genrule_impl, @@ -1030,40 +1071,43 @@ Args: """ def _cgo_object_impl(ctx): - arguments = _c_linker_options(ctx, blacklist=[ - # never link any dependency libraries - "-l", "-L", - # manage flags to ld(1) by ourselves - "-Wl,"]) - arguments += [ - "-o", ctx.outputs.out.path, - "-nostdlib", - "-Wl,-r", - ] - if _is_darwin_cpu(ctx): - arguments += ["-shared", "-Wl,-all_load"] - else: - arguments += ["-Wl,-whole-archive"] - - lo = ctx.files.src[-1] - arguments += [lo.path] - - ctx.action( - inputs = [lo] + ctx.files._crosstool, - outputs = [ctx.outputs.out], - mnemonic = "CGoObject", - progress_message = "Linking %s" % ctx.outputs.out.short_path, - executable = ctx.fragments.cpp.compiler_executable, - arguments = arguments, - ) - runfiles = ctx.runfiles(collect_data = True) - runfiles = runfiles.merge(ctx.attr.src.data_runfiles) - return struct( - files = depset([ctx.outputs.out]), - cgo_obj = ctx.outputs.out, - cgo_deps = ctx.attr.cgogen.cgo_deps, - runfiles = runfiles, - ) + arguments = _c_linker_options(ctx, blocklist = [ + # never link any dependency libraries + "-l", + "-L", + # manage flags to ld(1) by ourselves + "-Wl,", + ]) + arguments += [ + "-o", + ctx.outputs.out.path, + "-nostdlib", + "-Wl,-r", + ] + if _is_darwin_cpu(ctx): + arguments += ["-shared", "-Wl,-all_load"] + else: + arguments += ["-Wl,-whole-archive"] + + lo = ctx.files.src[-1] + arguments += [lo.path] + + ctx.action( + inputs = [lo] + ctx.files._crosstool, + outputs = [ctx.outputs.out], + mnemonic = "CGoObject", + progress_message = "Linking %s" % ctx.outputs.out.short_path, + executable = ctx.fragments.cpp.compiler_executable, + arguments = arguments, + ) + runfiles = ctx.runfiles(collect_data = True) + runfiles = runfiles.merge(ctx.attr.src.data_runfiles) + return struct( + files = depset([ctx.outputs.out]), + cgo_obj = ctx.outputs.out, + cgo_deps = ctx.attr.cgogen.cgo_deps, + runfiles = runfiles, + ) _cgo_object = rule( _cgo_object_impl, @@ -1092,182 +1136,189 @@ Args: """ def _setup_cgo_library(name, srcs, cdeps, copts, clinkopts, go_tool, toolchain): - go_srcs = [s for s in srcs if s.endswith('.go')] - c_hdrs = [s for s in srcs if any([s.endswith(ext) for ext in hdr_exts])] - c_srcs = [s for s in srcs if not s in (go_srcs + c_hdrs)] - - # Split cgo files into .go parts and .c parts (plus some other files). - cgogen = _cgo_codegen( - name = name + ".cgo", - srcs = go_srcs, - c_hdrs = c_hdrs, - deps = cdeps, - copts = copts, - linkopts = clinkopts, - go_tool = go_tool, - toolchain = toolchain, - ) - - # Filter c_srcs with build constraints. - c_filtered_srcs = [] - if len(c_srcs) > 0: - c_filtered_srcs_name = name + "_filter_cgo_srcs" - _cgo_filter_srcs( - name = c_filtered_srcs_name, - srcs = c_srcs, + go_srcs = [s for s in srcs if s.endswith(".go")] + c_hdrs = [s for s in srcs if any([s.endswith(ext) for ext in hdr_exts])] + c_srcs = [s for s in srcs if not s in (go_srcs + c_hdrs)] + + # Split cgo files into .go parts and .c parts (plus some other files). + cgogen = _cgo_codegen( + name = name + ".cgo", + srcs = go_srcs, + c_hdrs = c_hdrs, + deps = cdeps, + copts = copts, + linkopts = clinkopts, + go_tool = go_tool, + toolchain = toolchain, + ) + + # Filter c_srcs with build constraints. + c_filtered_srcs = [] + if len(c_srcs) > 0: + c_filtered_srcs_name = name + "_filter_cgo_srcs" + _cgo_filter_srcs( + name = c_filtered_srcs_name, + srcs = c_srcs, + ) + c_filtered_srcs.append(":" + c_filtered_srcs_name) + + pkg_dir = _pkg_dir( + "external/" + REPOSITORY_NAME[1:] if len(REPOSITORY_NAME) > 1 else "", + PACKAGE_NAME, + ) + + # Platform-specific settings + native.config_setting( + name = name + "_windows_setting", + values = { + "cpu": "x64_windows_msvc", + }, + ) + platform_copts = select({ + ":" + name + "_windows_setting": ["-mthreads"], + "//conditions:default": ["-pthread"], + }) + platform_linkopts = select({ + ":" + name + "_windows_setting": ["-mthreads"], + "//conditions:default": ["-pthread"], + }) + + # Bundles objects into an archive so that _cgo_.o and _all.o can share them. + native.cc_library( + name = cgogen.outdir + "/_cgo_lib", + srcs = cgogen.c_thunks + cgogen.c_exports + c_filtered_srcs + c_hdrs, + deps = cdeps, + copts = copts + platform_copts + [ + "-I", + pkg_dir, + "-I", + "$(GENDIR)/" + pkg_dir + "/" + cgogen.outdir, + # The generated thunks often contain unused variables. + "-Wno-unused-variable", + ], + linkopts = clinkopts + platform_linkopts, + linkstatic = 1, + # _cgo_.o and _all.o keep all objects in this archive. + # But it should not be very annoying in the final binary target + # because _cgo_object rule does not propagate alwayslink=1 + alwayslink = 1, + visibility = ["//visibility:private"], + ) + + # Loadable object which cgo reads when it generates _cgo_import.go + native.cc_binary( + name = cgogen.outdir + "/_cgo_.o", + srcs = [cgogen.c_dummy], + deps = cdeps + [cgogen.outdir + "/_cgo_lib"], + copts = copts, + linkopts = clinkopts, + visibility = ["//visibility:private"], ) - c_filtered_srcs.append(":" + c_filtered_srcs_name) - - pkg_dir = _pkg_dir( - "external/" + REPOSITORY_NAME[1:] if len(REPOSITORY_NAME) > 1 else "", - PACKAGE_NAME) - - # Platform-specific settings - native.config_setting( - name = name + "_windows_setting", - values = { - "cpu": "x64_windows_msvc", - }, - ) - platform_copts = select({ - ":" + name + "_windows_setting": ["-mthreads"], - "//conditions:default": ["-pthread"], - }) - platform_linkopts = select({ - ":" + name + "_windows_setting": ["-mthreads"], - "//conditions:default": ["-pthread"], - }) - - # Bundles objects into an archive so that _cgo_.o and _all.o can share them. - native.cc_library( - name = cgogen.outdir + "/_cgo_lib", - srcs = cgogen.c_thunks + cgogen.c_exports + c_filtered_srcs + c_hdrs, - deps = cdeps, - copts = copts + platform_copts + [ - "-I", pkg_dir, - "-I", "$(GENDIR)/" + pkg_dir + "/" + cgogen.outdir, - # The generated thunks often contain unused variables. - "-Wno-unused-variable", - ], - linkopts = clinkopts + platform_linkopts, - linkstatic = 1, - # _cgo_.o and _all.o keep all objects in this archive. - # But it should not be very annoying in the final binary target - # because _cgo_object rule does not propagate alwayslink=1 - alwayslink = 1, - visibility = ["//visibility:private"], - ) - - # Loadable object which cgo reads when it generates _cgo_import.go - native.cc_binary( - name = cgogen.outdir + "/_cgo_.o", - srcs = [cgogen.c_dummy], - deps = cdeps + [cgogen.outdir + "/_cgo_lib"], - copts = copts, - linkopts = clinkopts, - visibility = ["//visibility:private"], - ) - _cgo_import( - name = "%s.cgo.importgen" % name, - cgo_o = cgogen.outdir + "/_cgo_.o", - out = cgogen.outdir + "/_cgo_import.go", - sample_go_src = go_srcs[0], - go_tool = go_tool, - toolchain = toolchain, - visibility = ["//visibility:private"], - ) - - _cgo_object( - name = cgogen.outdir + "/_cgo_object", - src = cgogen.outdir + "/_cgo_lib", - out = cgogen.outdir + "/_all.o", - cgogen = cgogen.name, - visibility = ["//visibility:private"], - ) - return cgogen - -def cgo_genrule(name, srcs, - copts=[], - clinkopts=[], - cdeps=[], - **kwargs): - cgogen = _setup_cgo_library( - name = name, - srcs = srcs, - cdeps = cdeps, - copts = copts, - clinkopts = clinkopts, - toolchain = None, - go_tool = None, - ) - _cgo_genrule( - name = name, - srcs = cgogen.go_thunks + [ - cgogen.gotypes, - cgogen.outdir + "/_cgo_import.go", - ], - cgo_object = cgogen.outdir + "/_cgo_object", - **kwargs - ) - -def cgo_library(name, srcs, - toolchain=None, - go_tool=None, - copts=[], - clinkopts=[], - cdeps=[], - **kwargs): - """Builds a cgo-enabled go library. - - Args: - name: A unique name for this rule. - srcs: List of Go, C and C++ files that are processed to build a Go library. - Those Go files must contain `import "C"`. - C and C++ files can be anything allowed in `srcs` attribute of - `cc_library`. - copts: Add these flags to the C++ compiler. - clinkopts: Add these flags to the C++ linker. - cdeps: List of C/C++ libraries to be linked into the binary target. - They must be `cc_library` rules. - deps: List of other libraries to be linked to this library target. - data: List of files needed by this rule at runtime. - - NOTE: - `srcs` cannot contain pure-Go files, which do not have `import "C"`. - So you need to define another `go_library` when you build a go package with - both cgo-enabled and pure-Go sources. - - ``` - cgo_library( - name = "cgo_enabled", - srcs = ["cgo-enabled.go", "foo.cc", "bar.S", "baz.a"], + _cgo_import( + name = "%s.cgo.importgen" % name, + cgo_o = cgogen.outdir + "/_cgo_.o", + out = cgogen.outdir + "/_cgo_import.go", + sample_go_src = go_srcs[0], + go_tool = go_tool, + toolchain = toolchain, + visibility = ["//visibility:private"], + ) + + _cgo_object( + name = cgogen.outdir + "/_cgo_object", + src = cgogen.outdir + "/_cgo_lib", + out = cgogen.outdir + "/_all.o", + cgogen = cgogen.name, + visibility = ["//visibility:private"], + ) + return cgogen + +def cgo_genrule( + name, + srcs, + copts = [], + clinkopts = [], + cdeps = [], + **kwargs): + cgogen = _setup_cgo_library( + name = name, + srcs = srcs, + cdeps = cdeps, + copts = copts, + clinkopts = clinkopts, + toolchain = None, + go_tool = None, + ) + _cgo_genrule( + name = name, + srcs = cgogen.go_thunks + [ + cgogen.gotypes, + cgogen.outdir + "/_cgo_import.go", + ], + cgo_object = cgogen.outdir + "/_cgo_object", + **kwargs + ) + +def cgo_library( + name, + srcs, + toolchain = None, + go_tool = None, + copts = [], + clinkopts = [], + cdeps = [], + **kwargs): + """Builds a cgo-enabled go library. + + Args: + name: A unique name for this rule. + srcs: List of Go, C and C++ files that are processed to build a Go library. + Those Go files must contain `import "C"`. + C and C++ files can be anything allowed in `srcs` attribute of + `cc_library`. + copts: Add these flags to the C++ compiler. + clinkopts: Add these flags to the C++ linker. + cdeps: List of C/C++ libraries to be linked into the binary target. + They must be `cc_library` rules. + deps: List of other libraries to be linked to this library target. + data: List of files needed by this rule at runtime. + + NOTE: + `srcs` cannot contain pure-Go files, which do not have `import "C"`. + So you need to define another `go_library` when you build a go package with + both cgo-enabled and pure-Go sources. + + ``` + cgo_library( + name = "cgo_enabled", + srcs = ["cgo-enabled.go", "foo.cc", "bar.S", "baz.a"], + ) + + go_library( + name = "go_default_library", + srcs = ["pure-go.go"], + library = ":cgo_enabled", + ) + ``` + """ + cgogen = _setup_cgo_library( + name = name, + srcs = srcs, + cdeps = cdeps, + copts = copts, + clinkopts = clinkopts, + go_tool = go_tool, + toolchain = toolchain, ) go_library( - name = "go_default_library", - srcs = ["pure-go.go"], - library = ":cgo_enabled", + name = name, + srcs = cgogen.go_thunks + [ + cgogen.gotypes, + cgogen.outdir + "/_cgo_import.go", + ], + cgo_object = cgogen.outdir + "/_cgo_object", + go_tool = go_tool, + toolchain = toolchain, + **kwargs ) - ``` - """ - cgogen = _setup_cgo_library( - name = name, - srcs = srcs, - cdeps = cdeps, - copts = copts, - clinkopts = clinkopts, - go_tool = go_tool, - toolchain = toolchain, - ) - - go_library( - name = name, - srcs = cgogen.go_thunks + [ - cgogen.gotypes, - cgogen.outdir + "/_cgo_import.go", - ], - cgo_object = cgogen.outdir + "/_cgo_object", - go_tool = go_tool, - toolchain = toolchain, - **kwargs - )