| # Copyright 2014 The Bazel Authors. All rights reserved. |
| # |
| # Licensed under the Apache License, Version 2.0 (the "License"); |
| # you may not use this file except in compliance with the License. |
| # You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| # See the License for the specific language governing permissions and |
| # limitations under the License. |
| |
| # (From https://github.com/bazelbuild/rules_go/blob/master/go/def.bzl@a6f9d0c) |
| |
| load("//go/private:repositories.bzl", "go_repositories") |
| load("//go/private:go_repository.bzl", "go_repository", "new_go_repository") |
| load("//go/private:go_prefix.bzl", "go_prefix") |
| load("//go/private:json.bzl", "json_marshal") |
| |
| """These are bare-bones Go rules. |
| |
| In order of priority: |
| |
| - BUILD file must be written by hand. |
| |
| - No support for SWIG |
| |
| - No test sharding or test XML. |
| |
| """ |
| |
| _DEFAULT_LIB = "go_default_library" |
| |
| _VENDOR_PREFIX = "/vendor/" |
| |
| go_filetype = FileType([ |
| ".go", |
| ".s", |
| ".S", |
| ".h", # may be included by .s |
| ]) |
| |
| # be consistent to cc_library. |
| hdr_exts = [ |
| ".h", |
| ".hh", |
| ".hpp", |
| ".hxx", |
| ".inc", |
| ] |
| |
| cc_hdr_filetype = FileType(hdr_exts) |
| |
| # Extensions of files we can build with the Go compiler or with cc_library. |
| # This is a subset of the extensions recognized by go/build. |
| cgo_filetype = FileType([ |
| ".go", |
| ".c", |
| ".cc", |
| ".cxx", |
| ".cpp", |
| ".s", |
| ".S", |
| ".h", |
| ".hh", |
| ".hpp", |
| ".hxx", |
| ]) |
| |
| ################ |
| |
| 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 |
| |
| def _is_darwin_cpu(ctx): |
| 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 |
| |
| 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)], |
| ) |
| |
| 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 != "": |
| 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 |
| |
| 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), |
| ) |
| |
| 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, 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 |
| |
| 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 |
| |
| 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), |
| ) |
| |
| 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, |
| ) |
| |
| 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_env_attrs = { |
| "toolchain": attr.label( |
| default = Label("//go/toolchain:toolchain"), |
| allow_files = True, |
| cfg = "host", |
| ), |
| "go_tool": attr.label( |
| default = Label("//go/toolchain:go_tool"), |
| single_file = True, |
| allow_files = True, |
| cfg = "host", |
| ), |
| "go_prefix": attr.label( |
| providers = ["go_prefix"], |
| default = Label( |
| "//:go_prefix", |
| relative_to_caller_repository = True, |
| ), |
| allow_files = False, |
| cfg = "host", |
| ), |
| "go_src": attr.label( |
| default = Label("//go/toolchain:go_src"), |
| allow_files = True, |
| cfg = "host", |
| ), |
| "go_include": attr.label( |
| default = Label("//go/toolchain:go_include"), |
| single_file = True, |
| allow_files = True, |
| cfg = "host", |
| ), |
| "go_root": attr.label( |
| providers = ["go_root"], |
| default = Label( |
| "//go/toolchain:go_root", |
| ), |
| allow_files = False, |
| cfg = "host", |
| ), |
| "_filter_tags": attr.label( |
| default = Label("//go/tools/filter_tags"), |
| cfg = "host", |
| executable = True, |
| single_file = True, |
| ), |
| "_filter_exec": attr.label( |
| default = Label("//go/tools/filter_exec"), |
| cfg = "host", |
| executable = True, |
| single_file = True, |
| ), |
| "_asm": attr.label( |
| default = Label("//go/tools/builders:asm"), |
| cfg = "host", |
| executable = True, |
| single_file = True, |
| ), |
| } |
| |
| go_library_attrs = go_env_attrs + { |
| "data": attr.label_list( |
| allow_files = True, |
| cfg = "data", |
| ), |
| "srcs": attr.label_list(allow_files = go_filetype), |
| "deps": attr.label_list( |
| providers = [ |
| "transitive_go_library_paths", |
| "transitive_go_libraries", |
| "transitive_cgo_deps", |
| ], |
| ), |
| "importpath": attr.string(), |
| "library": attr.label( |
| providers = [ |
| "direct_deps", |
| "go_sources", |
| "asm_sources", |
| "cgo_object", |
| "gc_goopts", |
| ], |
| ), |
| "gc_goopts": attr.string_list(), |
| } |
| |
| _crosstool_attrs = { |
| "_crosstool": attr.label( |
| default = Label("//tools/defaults:crosstool"), |
| ), |
| } |
| |
| go_link_attrs = go_library_attrs + _crosstool_attrs + { |
| "gc_linkopts": attr.string_list(), |
| "linkstamp": attr.string(), |
| "x_defs": attr.string_dict(), |
| } |
| |
| go_library = rule( |
| go_library_impl, |
| attrs = go_library_attrs + { |
| "cgo_object": attr.label( |
| providers = [ |
| "cgo_obj", |
| "cgo_deps", |
| ], |
| ), |
| }, |
| fragments = ["cpp"], |
| ) |
| |
| go_binary = rule( |
| go_binary_impl, |
| attrs = go_library_attrs + _crosstool_attrs + go_link_attrs, |
| executable = True, |
| fragments = ["cpp"], |
| ) |
| |
| go_test = rule( |
| go_test_impl, |
| attrs = go_library_attrs + _crosstool_attrs + go_link_attrs + { |
| "test_generator": attr.label( |
| executable = True, |
| default = Label( |
| "//go/tools:generate_test_main", |
| ), |
| cfg = "host", |
| ), |
| }, |
| executable = True, |
| fragments = ["cpp"], |
| test = True, |
| ) |
| |
| 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 "." |
| |
| def _exec_path(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" % |
| (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), |
| ) |
| |
| _cgo_filter_srcs = rule( |
| implementation = _cgo_filter_srcs_impl, |
| attrs = { |
| "srcs": attr.label_list( |
| allow_files = cgo_filetype, |
| ), |
| "_filter_tags": attr.label( |
| default = Label("//go/tools/filter_tags"), |
| cfg = "host", |
| executable = True, |
| single_file = True, |
| ), |
| }, |
| fragments = ["cpp"], |
| ) |
| |
| 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", |
| ] |
| |
| 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, |
| attrs = go_env_attrs + _crosstool_attrs + { |
| "srcs": attr.label_list( |
| allow_files = go_filetype, |
| non_empty = True, |
| ), |
| "c_hdrs": attr.label_list( |
| allow_files = cc_hdr_filetype, |
| ), |
| "deps": attr.label_list( |
| allow_files = False, |
| providers = ["cc"], |
| ), |
| "copts": attr.string_list(), |
| "linkopts": attr.string_list(), |
| "outdir": attr.string(mandatory = True), |
| "outs": attr.output_list( |
| mandatory = True, |
| non_empty = True, |
| ), |
| }, |
| fragments = ["cpp"], |
| 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_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]), |
| ) |
| |
| _cgo_import = rule( |
| _cgo_import_impl, |
| attrs = go_env_attrs + { |
| "cgo_o": attr.label( |
| allow_files = True, |
| single_file = True, |
| ), |
| "sample_go_src": attr.label( |
| allow_files = True, |
| single_file = True, |
| ), |
| "out": attr.output( |
| mandatory = True, |
| ), |
| "_extract_package": attr.label( |
| default = Label("//go/tools/extract_package"), |
| executable = True, |
| cfg = "host", |
| ), |
| }, |
| fragments = ["cpp"], |
| ) |
| |
| 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 = [], |
| ) |
| |
| _cgo_genrule = rule( |
| _cgo_genrule_impl, |
| attrs = { |
| "srcs": attr.label_list(allow_files = FileType([".go"])), |
| "cgo_object": attr.label( |
| providers = [ |
| "cgo_obj", |
| "cgo_deps", |
| ], |
| ), |
| "deps": attr.label_list( |
| providers = [ |
| "direct_deps", |
| "transitive_go_library_paths", |
| "transitive_go_libraries", |
| "transitive_cgo_deps", |
| ], |
| ), |
| }, |
| fragments = ["cpp"], |
| ) |
| |
| """Generates symbol-import directives for cgo |
| |
| Args: |
| cgo_o: The loadable object to extract dynamic symbols from. |
| sample_go_src: A go source which is compiled together with the generated file. |
| The generated file will have the same Go package name as this file. |
| out: Destination of the generated codes. |
| """ |
| |
| def _cgo_object_impl(ctx): |
| 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, |
| attrs = _crosstool_attrs + { |
| "src": attr.label( |
| mandatory = True, |
| providers = ["cc"], |
| ), |
| "cgogen": attr.label( |
| mandatory = True, |
| providers = ["cgo_deps"], |
| ), |
| "out": attr.output( |
| mandatory = True, |
| ), |
| }, |
| fragments = ["cpp"], |
| ) |
| |
| """Generates _all.o to be archived together with Go objects. |
| |
| Args: |
| src: source static library which contains objects |
| cgogen: _cgo_codegen rule which knows the dependency cc_library() rules |
| to be linked together with src when we generate the final go binary. |
| """ |
| |
| 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, |
| ) |
| 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"], |
| ) |
| |
| 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 = 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 |
| ) |