diff options
Diffstat (limited to 'go/private/context.bzl')
-rw-r--r-- | go/private/context.bzl | 912 |
1 files changed, 912 insertions, 0 deletions
diff --git a/go/private/context.bzl b/go/private/context.bzl new file mode 100644 index 00000000..db4fe09d --- /dev/null +++ b/go/private/context.bzl @@ -0,0 +1,912 @@ +# Copyright 2017 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. + +load( + "@bazel_tools//tools/cpp:toolchain_utils.bzl", + "find_cpp_toolchain", +) +load( + "@bazel_tools//tools/build_defs/cc:action_names.bzl", + "CPP_COMPILE_ACTION_NAME", + "CPP_LINK_DYNAMIC_LIBRARY_ACTION_NAME", + "CPP_LINK_EXECUTABLE_ACTION_NAME", + "CPP_LINK_STATIC_LIBRARY_ACTION_NAME", + "C_COMPILE_ACTION_NAME", + "OBJCPP_COMPILE_ACTION_NAME", + "OBJC_COMPILE_ACTION_NAME", +) +load( + ":go_toolchain.bzl", + "GO_TOOLCHAIN", +) +load( + ":providers.bzl", + "CgoContextInfo", + "EXPLICIT_PATH", + "EXPORT_PATH", + "GoArchive", + "GoConfigInfo", + "GoContextInfo", + "GoLibrary", + "GoSource", + "GoStdLib", + "INFERRED_PATH", + "get_source", +) +load( + ":mode.bzl", + "get_mode", + "installsuffix", +) +load( + ":common.bzl", + "COVERAGE_OPTIONS_DENYLIST", + "as_iterable", + "goos_to_extension", + "goos_to_shared_extension", + "is_struct", +) +load( + "//go/platform:apple.bzl", + "apple_ensure_options", +) +load( + "@bazel_skylib//rules:common_settings.bzl", + "BuildSettingInfo", +) +load( + "//go/private/rules:transition.bzl", + "request_nogo_transition", +) + +# cgo requires a gcc/clang style compiler. +# We use a denylist instead of an allowlist: +# - Bazel's auto-detected toolchains used to set the compiler name to "compiler" +# for gcc (fixed in 6.0.0), which defeats the purpose of an allowlist. +# - The compiler name field is free-form and user-defined, so we would have to +# provide a way to override this list. +# TODO: Convert to a denylist once we can assume Bazel 6.0.0 or later and have a +# way for users to extend the list. +_UNSUPPORTED_C_COMPILERS = { + "msvc-cl": None, + "clang-cl": None, +} + +_COMPILER_OPTIONS_DENYLIST = dict({ + # cgo parses the error messages from the compiler. It can't handle colors. + # Ignore both variants of the diagnostics color flag. + "-fcolor-diagnostics": None, + "-fdiagnostics-color": None, + + # cgo also wants to see all the errors when it is testing the compiler. + # fmax-errors limits that and causes build failures. + "-fmax-errors=": None, + "-Wall": None, + + # Symbols are needed by Go, so keep them + "-g0": None, + + # Don't compile generated cgo code with coverage. If we do an internal + # link, we may have undefined references to coverage functions. + "--coverage": None, + "-ftest-coverage": None, + "-fprofile-arcs": None, + "-fprofile-instr-generate": None, + "-fcoverage-mapping": None, +}, **COVERAGE_OPTIONS_DENYLIST) + +_LINKER_OPTIONS_DENYLIST = { + "-Wl,--gc-sections": None, +} + +_UNSUPPORTED_FEATURES = [ + # These toolchain features require special rule support and will thus break + # with CGo. + # Taken from https://github.com/bazelbuild/rules_rust/blob/521e649ff44e9711fe3c45b0ec1e792f7e1d361e/rust/private/utils.bzl#L20. + "thin_lto", + "module_maps", + "use_header_modules", + "fdo_instrument", + "fdo_optimize", +] + +def _match_option(option, pattern): + if pattern.endswith("="): + return option.startswith(pattern) + else: + return option == pattern + +def _filter_options(options, denylist): + return [ + option + for option in options + if not any([_match_option(option, pattern) for pattern in denylist]) + ] + +def _child_name(go, path, ext, name): + if not name: + name = go.label.name + if path or not ext: + # The '_' avoids collisions with another file matching the label name. + # For example, hello and hello/testmain.go. + name += "_" + if path: + name += "/" + path + if ext: + name += ext + return name + +def _declare_file(go, path = "", ext = "", name = ""): + return go.actions.declare_file(_child_name(go, path, ext, name)) + +def _declare_directory(go, path = "", ext = "", name = ""): + return go.actions.declare_directory(_child_name(go, path, ext, name)) + +def _new_args(go): + # TODO(jayconrod): print warning. + return go.builder_args(go) + +def _builder_args(go, command = None): + args = go.actions.args() + args.use_param_file("-param=%s") + args.set_param_file_format("shell") + if command: + args.add(command) + args.add("-sdk", go.sdk.root_file.dirname) + args.add("-installsuffix", installsuffix(go.mode)) + args.add_joined("-tags", go.tags, join_with = ",") + return args + +def _tool_args(go): + args = go.actions.args() + args.use_param_file("-param=%s") + args.set_param_file_format("shell") + return args + +def _new_library(go, name = None, importpath = None, resolver = None, importable = True, testfilter = None, is_main = False, **kwargs): + if not importpath: + importpath = go.importpath + importmap = go.importmap + else: + importmap = importpath + pathtype = go.pathtype + if not importable and pathtype == EXPLICIT_PATH: + pathtype = EXPORT_PATH + + return GoLibrary( + name = go.label.name if not name else name, + label = go.label, + importpath = importpath, + importmap = importmap, + importpath_aliases = go.importpath_aliases, + pathtype = pathtype, + resolve = resolver, + testfilter = testfilter, + is_main = is_main, + **kwargs + ) + +def _merge_embed(source, embed): + s = get_source(embed) + source["srcs"] = s.srcs + source["srcs"] + source["orig_srcs"] = s.orig_srcs + source["orig_srcs"] + source["orig_src_map"].update(s.orig_src_map) + source["embedsrcs"] = source["embedsrcs"] + s.embedsrcs + source["cover"] = source["cover"] + s.cover + source["deps"] = source["deps"] + s.deps + source["x_defs"].update(s.x_defs) + source["gc_goopts"] = source["gc_goopts"] + s.gc_goopts + source["runfiles"] = source["runfiles"].merge(s.runfiles) + if s.cgo and source["cgo"]: + fail("multiple libraries with cgo enabled") + source["cgo"] = source["cgo"] or s.cgo + source["cdeps"] = source["cdeps"] or s.cdeps + source["cppopts"] = source["cppopts"] or s.cppopts + source["copts"] = source["copts"] or s.copts + source["cxxopts"] = source["cxxopts"] or s.cxxopts + source["clinkopts"] = source["clinkopts"] or s.clinkopts + source["cgo_deps"] = source["cgo_deps"] + s.cgo_deps + source["cgo_exports"] = source["cgo_exports"] + s.cgo_exports + +def _dedup_deps(deps): + """Returns a list of deps without duplicate import paths. + + Earlier targets take precedence over later targets. This is intended to + allow an embedding library to override the dependencies of its + embedded libraries. + + Args: + deps: an iterable containing either Targets or GoArchives. + """ + deduped_deps = [] + importpaths = {} + for dep in deps: + if hasattr(dep, "data") and hasattr(dep.data, "importpath"): + importpath = dep.data.importpath + else: + importpath = dep[GoLibrary].importpath + if importpath in importpaths: + continue + importpaths[importpath] = None + deduped_deps.append(dep) + return deduped_deps + +def _library_to_source(go, attr, library, coverage_instrumented): + #TODO: stop collapsing a depset in this line... + attr_srcs = [f for t in getattr(attr, "srcs", []) for f in as_iterable(t.files)] + generated_srcs = getattr(library, "srcs", []) + srcs = attr_srcs + generated_srcs + embedsrcs = [f for t in getattr(attr, "embedsrcs", []) for f in as_iterable(t.files)] + source = { + "library": library, + "mode": go.mode, + "srcs": srcs, + "orig_srcs": srcs, + "orig_src_map": {}, + "cover": [], + "embedsrcs": embedsrcs, + "x_defs": {}, + "deps": getattr(attr, "deps", []), + "gc_goopts": _expand_opts(go, "gc_goopts", getattr(attr, "gc_goopts", [])), + "runfiles": _collect_runfiles(go, getattr(attr, "data", []), getattr(attr, "deps", [])), + "cgo": getattr(attr, "cgo", False), + "cdeps": getattr(attr, "cdeps", []), + "cppopts": _expand_opts(go, "cppopts", getattr(attr, "cppopts", [])), + "copts": _expand_opts(go, "copts", getattr(attr, "copts", [])), + "cxxopts": _expand_opts(go, "cxxopts", getattr(attr, "cxxopts", [])), + "clinkopts": _expand_opts(go, "clinkopts", getattr(attr, "clinkopts", [])), + "cgo_deps": [], + "cgo_exports": [], + "cc_info": None, + } + if coverage_instrumented: + source["cover"] = attr_srcs + for dep in source["deps"]: + _check_binary_dep(go, dep, "deps") + for e in getattr(attr, "embed", []): + _check_binary_dep(go, e, "embed") + _merge_embed(source, e) + source["deps"] = _dedup_deps(source["deps"]) + x_defs = source["x_defs"] + for k, v in getattr(attr, "x_defs", {}).items(): + v = _expand_location(go, attr, v) + if "." not in k: + k = "{}.{}".format(library.importmap, k) + x_defs[k] = v + source["x_defs"] = x_defs + if not source["cgo"]: + for k in ("cdeps", "cppopts", "copts", "cxxopts", "clinkopts"): + if getattr(attr, k, None): + fail(k + " set without cgo = True") + for f in source["srcs"]: + # This check won't report directory sources that contain C/C++ + # sources. compilepkg will catch these instead. + if f.extension in ("c", "cc", "cxx", "cpp", "hh", "hpp", "hxx"): + fail("source {} has C/C++ extension, but cgo was not enabled (set 'cgo = True')".format(f.path)) + if library.resolve: + library.resolve(go, attr, source, _merge_embed) + source["cc_info"] = _collect_cc_infos(source["deps"], source["cdeps"]) + return GoSource(**source) + +def _collect_runfiles(go, data, deps): + """Builds a set of runfiles from the deps and data attributes. + + srcs and their runfiles are not included.""" + files = depset(transitive = [t[DefaultInfo].files for t in data]) + runfiles = go._ctx.runfiles(transitive_files = files) + for t in data: + runfiles = runfiles.merge(t[DefaultInfo].data_runfiles) + for t in deps: + runfiles = runfiles.merge(get_source(t).runfiles) + return runfiles + +def _collect_cc_infos(deps, cdeps): + cc_infos = [] + for dep in cdeps: + if CcInfo in dep: + cc_infos.append(dep[CcInfo]) + for dep in deps: + # dep may be a struct, which doesn't support indexing by providers. + if is_struct(dep): + continue + if GoSource in dep: + cc_infos.append(dep[GoSource].cc_info) + return cc_common.merge_cc_infos(cc_infos = cc_infos) + +def _check_binary_dep(go, dep, edge): + """Checks that this rule doesn't depend on a go_binary or go_test. + + go_binary and go_test may return provides with useful information for other + rules (like go_path), but go_binary and go_test may not depend on other + go_binary and go_binary targets. Their dependencies may be built in + different modes, resulting in conflicts and opaque errors. + """ + if (type(dep) == "Target" and + DefaultInfo in dep and + getattr(dep[DefaultInfo], "files_to_run", None) and + dep[DefaultInfo].files_to_run.executable): + fail("rule {rule} depends on executable {dep} via {edge}. This is not safe for cross-compilation. Depend on go_library instead.".format( + rule = str(go.label), + dep = str(dep.label), + edge = edge, + )) + +def _check_importpaths(ctx): + paths = [] + p = getattr(ctx.attr, "importpath", "") + if p: + paths.append(p) + p = getattr(ctx.attr, "importmap", "") + if p: + paths.append(p) + paths.extend(getattr(ctx.attr, "importpath_aliases", ())) + + for p in paths: + if ":" in p: + fail("import path '%s' contains invalid character :" % p) + +def _infer_importpath(ctx): + DEFAULT_LIB = "go_default_library" + VENDOR_PREFIX = "/vendor/" + + # Check if paths were explicitly set, either in this rule or in an + # embedded rule. + attr_importpath = getattr(ctx.attr, "importpath", "") + attr_importmap = getattr(ctx.attr, "importmap", "") + embed_importpath = "" + embed_importmap = "" + for embed in getattr(ctx.attr, "embed", []): + if GoLibrary not in embed: + continue + lib = embed[GoLibrary] + if lib.pathtype == EXPLICIT_PATH: + embed_importpath = lib.importpath + embed_importmap = lib.importmap + break + + importpath = attr_importpath or embed_importpath + importmap = attr_importmap or embed_importmap or importpath + if importpath: + return importpath, importmap, EXPLICIT_PATH + + # Guess an import path based on the directory structure + # This should only really be relied on for binaries + importpath = ctx.label.package + if ctx.label.name != DEFAULT_LIB and not importpath.endswith(ctx.label.name): + importpath += "/" + ctx.label.name + if importpath.rfind(VENDOR_PREFIX) != -1: + importpath = importpath[len(VENDOR_PREFIX) + importpath.rfind(VENDOR_PREFIX):] + if importpath.startswith("/"): + importpath = importpath[1:] + return importpath, importpath, INFERRED_PATH + +def go_context(ctx, attr = None): + """Returns an API used to build Go code. + + See /go/toolchains.rst#go-context + """ + if not attr: + attr = ctx.attr + toolchain = ctx.toolchains[GO_TOOLCHAIN] + cgo_context_info = None + go_config_info = None + stdlib = None + coverdata = None + nogo = None + if hasattr(attr, "_go_context_data"): + go_context_data = _flatten_possibly_transitioned_attr(attr._go_context_data) + if CgoContextInfo in go_context_data: + cgo_context_info = go_context_data[CgoContextInfo] + go_config_info = go_context_data[GoConfigInfo] + stdlib = go_context_data[GoStdLib] + coverdata = go_context_data[GoContextInfo].coverdata + nogo = go_context_data[GoContextInfo].nogo + if getattr(attr, "_cgo_context_data", None) and CgoContextInfo in attr._cgo_context_data: + cgo_context_info = attr._cgo_context_data[CgoContextInfo] + if getattr(attr, "cgo_context_data", None) and CgoContextInfo in attr.cgo_context_data: + cgo_context_info = attr.cgo_context_data[CgoContextInfo] + if hasattr(attr, "_go_config"): + go_config_info = attr._go_config[GoConfigInfo] + if hasattr(attr, "_stdlib"): + stdlib = _flatten_possibly_transitioned_attr(attr._stdlib)[GoStdLib] + + mode = get_mode(ctx, toolchain, cgo_context_info, go_config_info) + tags = mode.tags + binary = toolchain.sdk.go + + if stdlib: + goroot = stdlib.root_file.dirname + else: + goroot = toolchain.sdk.root_file.dirname + + env = { + "GOARCH": mode.goarch, + "GOOS": mode.goos, + "GOEXPERIMENT": ",".join(toolchain.sdk.experiments), + "GOROOT": goroot, + "GOROOT_FINAL": "GOROOT", + "CGO_ENABLED": "0" if mode.pure else "1", + + # If we use --action_env=GOPATH, or in other cases where environment + # variables are passed through to this builder, the SDK build will try + # to write to that GOPATH (e.g. for x/net/nettest). This will fail if + # the GOPATH is on a read-only mount, and is generally a bad idea. + # Explicitly clear this environment variable to ensure that doesn't + # happen. See #2291 for more information. + "GOPATH": "", + } + + # The level of support is determined by the platform constraints in + # //go/constraints/amd64. + # See https://github.com/golang/go/wiki/MinimumRequirements#amd64 + if mode.amd64: + env["GOAMD64"] = mode.amd64 + if not cgo_context_info: + crosstool = [] + cgo_tools = None + else: + env.update(cgo_context_info.env) + crosstool = cgo_context_info.crosstool + + # Add C toolchain directories to PATH. + # On ARM, go tool link uses some features of gcc to complete its work, + # so PATH is needed on ARM. + path_set = {} + if "PATH" in env: + for p in env["PATH"].split(ctx.configuration.host_path_separator): + path_set[p] = None + cgo_tools = cgo_context_info.cgo_tools + tool_paths = [ + cgo_tools.c_compiler_path, + cgo_tools.ld_executable_path, + cgo_tools.ld_static_lib_path, + cgo_tools.ld_dynamic_lib_path, + ] + for tool_path in tool_paths: + tool_dir, _, _ = tool_path.rpartition("/") + path_set[tool_dir] = None + paths = sorted(path_set.keys()) + if ctx.configuration.host_path_separator == ":": + # HACK: ":" is a proxy for a UNIX-like host. + # The tools returned above may be bash scripts that reference commands + # in directories we might not otherwise include. For example, + # on macOS, wrapped_ar calls dirname. + if "/bin" not in path_set: + paths.append("/bin") + if "/usr/bin" not in path_set: + paths.append("/usr/bin") + env["PATH"] = ctx.configuration.host_path_separator.join(paths) + + # TODO(jayconrod): remove this. It's way too broad. Everything should + # depend on more specific lists. + sdk_files = ([toolchain.sdk.go] + + toolchain.sdk.srcs + + toolchain.sdk.headers + + toolchain.sdk.libs + + toolchain.sdk.tools) + + _check_importpaths(ctx) + importpath, importmap, pathtype = _infer_importpath(ctx) + importpath_aliases = tuple(getattr(attr, "importpath_aliases", ())) + + return struct( + # Fields + toolchain = toolchain, + sdk = toolchain.sdk, + mode = mode, + root = goroot, + go = binary, + stdlib = stdlib, + sdk_root = toolchain.sdk.root_file, + sdk_files = sdk_files, + sdk_tools = toolchain.sdk.tools, + actions = ctx.actions, + exe_extension = goos_to_extension(mode.goos), + shared_extension = goos_to_shared_extension(mode.goos), + crosstool = crosstool, + package_list = toolchain.sdk.package_list, + importpath = importpath, + importmap = importmap, + importpath_aliases = importpath_aliases, + pathtype = pathtype, + cgo_tools = cgo_tools, + nogo = nogo, + coverdata = coverdata, + coverage_enabled = ctx.configuration.coverage_enabled, + coverage_instrumented = ctx.coverage_instrumented(), + env = env, + tags = tags, + stamp = mode.stamp, + label = ctx.label, + cover_format = mode.cover_format, + # Action generators + archive = toolchain.actions.archive, + binary = toolchain.actions.binary, + link = toolchain.actions.link, + + # Helpers + args = _new_args, # deprecated + builder_args = _builder_args, + tool_args = _tool_args, + new_library = _new_library, + library_to_source = _library_to_source, + declare_file = _declare_file, + declare_directory = _declare_directory, + + # Private + # TODO: All uses of this should be removed + _ctx = ctx, + ) + +def _go_context_data_impl(ctx): + if "race" in ctx.features: + print("WARNING: --features=race is no longer supported. Use --@io_bazel_rules_go//go/config:race instead.") + if "msan" in ctx.features: + print("WARNING: --features=msan is no longer supported. Use --@io_bazel_rules_go//go/config:msan instead.") + nogo = ctx.files.nogo[0] if ctx.files.nogo else None + providers = [ + GoContextInfo( + coverdata = ctx.attr.coverdata[GoArchive], + nogo = nogo, + ), + ctx.attr.stdlib[GoStdLib], + ctx.attr.go_config[GoConfigInfo], + ] + if ctx.attr.cgo_context_data and CgoContextInfo in ctx.attr.cgo_context_data: + providers.append(ctx.attr.cgo_context_data[CgoContextInfo]) + return providers + +go_context_data = rule( + _go_context_data_impl, + attrs = { + "cgo_context_data": attr.label(), + "coverdata": attr.label( + mandatory = True, + providers = [GoArchive], + ), + "go_config": attr.label( + mandatory = True, + providers = [GoConfigInfo], + ), + "nogo": attr.label( + mandatory = True, + cfg = "exec", + ), + "stdlib": attr.label( + mandatory = True, + providers = [GoStdLib], + ), + "_allowlist_function_transition": attr.label( + default = "@bazel_tools//tools/allowlists/function_transition_allowlist", + ), + }, + doc = """go_context_data gathers information about the build configuration. + It is a common dependency of all Go targets.""", + toolchains = [GO_TOOLCHAIN], + cfg = request_nogo_transition, +) + +def _cgo_context_data_impl(ctx): + # TODO(jayconrod): find a way to get a list of files that comprise the + # toolchain (to be inputs into actions that need it). + # ctx.files._cc_toolchain won't work when cc toolchain resolution + # is switched on. + cc_toolchain = find_cpp_toolchain(ctx) + if cc_toolchain.compiler in _UNSUPPORTED_C_COMPILERS: + return [] + + feature_configuration = cc_common.configure_features( + ctx = ctx, + cc_toolchain = cc_toolchain, + requested_features = ctx.features, + unsupported_features = ctx.disabled_features + _UNSUPPORTED_FEATURES, + ) + + # TODO(jayconrod): keep the environment separate for different actions. + env = {} + + c_compile_variables = cc_common.create_compile_variables( + feature_configuration = feature_configuration, + cc_toolchain = cc_toolchain, + ) + c_compiler_path = cc_common.get_tool_for_action( + feature_configuration = feature_configuration, + action_name = C_COMPILE_ACTION_NAME, + ) + c_compile_options = _filter_options( + cc_common.get_memory_inefficient_command_line( + feature_configuration = feature_configuration, + action_name = C_COMPILE_ACTION_NAME, + variables = c_compile_variables, + ) + ctx.fragments.cpp.copts + ctx.fragments.cpp.conlyopts, + _COMPILER_OPTIONS_DENYLIST, + ) + env.update(cc_common.get_environment_variables( + feature_configuration = feature_configuration, + action_name = C_COMPILE_ACTION_NAME, + variables = c_compile_variables, + )) + + cxx_compile_variables = cc_common.create_compile_variables( + feature_configuration = feature_configuration, + cc_toolchain = cc_toolchain, + ) + cxx_compile_options = _filter_options( + cc_common.get_memory_inefficient_command_line( + feature_configuration = feature_configuration, + action_name = CPP_COMPILE_ACTION_NAME, + variables = cxx_compile_variables, + ) + ctx.fragments.cpp.copts + ctx.fragments.cpp.cxxopts, + _COMPILER_OPTIONS_DENYLIST, + ) + env.update(cc_common.get_environment_variables( + feature_configuration = feature_configuration, + action_name = CPP_COMPILE_ACTION_NAME, + variables = cxx_compile_variables, + )) + + objc_compile_variables = cc_common.create_compile_variables( + feature_configuration = feature_configuration, + cc_toolchain = cc_toolchain, + ) + objc_compile_options = _filter_options( + cc_common.get_memory_inefficient_command_line( + feature_configuration = feature_configuration, + action_name = OBJC_COMPILE_ACTION_NAME, + variables = objc_compile_variables, + ), + _COMPILER_OPTIONS_DENYLIST, + ) + env.update(cc_common.get_environment_variables( + feature_configuration = feature_configuration, + action_name = OBJC_COMPILE_ACTION_NAME, + variables = objc_compile_variables, + )) + + objcxx_compile_variables = cc_common.create_compile_variables( + feature_configuration = feature_configuration, + cc_toolchain = cc_toolchain, + ) + objcxx_compile_options = _filter_options( + cc_common.get_memory_inefficient_command_line( + feature_configuration = feature_configuration, + action_name = OBJCPP_COMPILE_ACTION_NAME, + variables = objcxx_compile_variables, + ), + _COMPILER_OPTIONS_DENYLIST, + ) + env.update(cc_common.get_environment_variables( + feature_configuration = feature_configuration, + action_name = OBJCPP_COMPILE_ACTION_NAME, + variables = objcxx_compile_variables, + )) + + ld_executable_variables = cc_common.create_link_variables( + feature_configuration = feature_configuration, + cc_toolchain = cc_toolchain, + is_linking_dynamic_library = False, + ) + ld_executable_path = cc_common.get_tool_for_action( + feature_configuration = feature_configuration, + action_name = CPP_LINK_EXECUTABLE_ACTION_NAME, + ) + ld_executable_options = _filter_options( + cc_common.get_memory_inefficient_command_line( + feature_configuration = feature_configuration, + action_name = CPP_LINK_EXECUTABLE_ACTION_NAME, + variables = ld_executable_variables, + ) + ctx.fragments.cpp.linkopts, + _LINKER_OPTIONS_DENYLIST, + ) + env.update(cc_common.get_environment_variables( + feature_configuration = feature_configuration, + action_name = CPP_LINK_EXECUTABLE_ACTION_NAME, + variables = ld_executable_variables, + )) + + # We don't collect options for static libraries. Go always links with + # "ar" in "c-archive" mode. We can set the ar executable path with + # -extar, but the options are hard-coded to something like -q -c -s. + ld_static_lib_variables = cc_common.create_link_variables( + feature_configuration = feature_configuration, + cc_toolchain = cc_toolchain, + is_linking_dynamic_library = False, + ) + ld_static_lib_path = cc_common.get_tool_for_action( + feature_configuration = feature_configuration, + action_name = CPP_LINK_STATIC_LIBRARY_ACTION_NAME, + ) + env.update(cc_common.get_environment_variables( + feature_configuration = feature_configuration, + action_name = CPP_LINK_STATIC_LIBRARY_ACTION_NAME, + variables = ld_static_lib_variables, + )) + + ld_dynamic_lib_variables = cc_common.create_link_variables( + feature_configuration = feature_configuration, + cc_toolchain = cc_toolchain, + is_linking_dynamic_library = True, + ) + ld_dynamic_lib_path = cc_common.get_tool_for_action( + feature_configuration = feature_configuration, + action_name = CPP_LINK_DYNAMIC_LIBRARY_ACTION_NAME, + ) + ld_dynamic_lib_options = _filter_options( + cc_common.get_memory_inefficient_command_line( + feature_configuration = feature_configuration, + action_name = CPP_LINK_DYNAMIC_LIBRARY_ACTION_NAME, + variables = ld_dynamic_lib_variables, + ) + ctx.fragments.cpp.linkopts, + _LINKER_OPTIONS_DENYLIST, + ) + + env.update(cc_common.get_environment_variables( + feature_configuration = feature_configuration, + action_name = CPP_LINK_DYNAMIC_LIBRARY_ACTION_NAME, + variables = ld_dynamic_lib_variables, + )) + + tags = [] + if "gotags" in ctx.var: + tags = ctx.var["gotags"].split(",") + apple_ensure_options( + ctx, + env, + tags, + (c_compile_options, cxx_compile_options, objc_compile_options, objcxx_compile_options), + (ld_executable_options, ld_dynamic_lib_options), + cc_toolchain.target_gnu_system_name, + ) + + return [CgoContextInfo( + crosstool = cc_toolchain.all_files.to_list(), + tags = tags, + env = env, + cgo_tools = struct( + cc_toolchain = cc_toolchain, + feature_configuration = feature_configuration, + c_compiler_path = c_compiler_path, + c_compile_options = c_compile_options, + cxx_compile_options = cxx_compile_options, + objc_compile_options = objc_compile_options, + objcxx_compile_options = objcxx_compile_options, + ld_executable_path = ld_executable_path, + ld_executable_options = ld_executable_options, + ld_static_lib_path = ld_static_lib_path, + ld_dynamic_lib_path = ld_dynamic_lib_path, + ld_dynamic_lib_options = ld_dynamic_lib_options, + ), + )] + +cgo_context_data = rule( + implementation = _cgo_context_data_impl, + attrs = { + "_cc_toolchain": attr.label(default = "@bazel_tools//tools/cpp:current_cc_toolchain"), + "_xcode_config": attr.label( + default = "@bazel_tools//tools/osx:current_xcode_config", + ), + }, + toolchains = ["@bazel_tools//tools/cpp:toolchain_type"], + fragments = ["apple", "cpp"], + doc = """Collects information about the C/C++ toolchain. The C/C++ toolchain + is needed to build cgo code, but is generally optional. Rules can't have + optional toolchains, so instead, we have an optional dependency on this + rule.""", +) + +def _cgo_context_data_proxy_impl(ctx): + if ctx.attr.actual and CgoContextInfo in ctx.attr.actual: + return [ctx.attr.actual[CgoContextInfo]] + return [] + +cgo_context_data_proxy = rule( + implementation = _cgo_context_data_proxy_impl, + attrs = { + "actual": attr.label(), + }, + doc = """Conditionally depends on cgo_context_data and forwards it provider. + + Useful in situations where select cannot be used, like attribute defaults. + """, +) + +def _go_config_impl(ctx): + return [GoConfigInfo( + static = ctx.attr.static[BuildSettingInfo].value, + race = ctx.attr.race[BuildSettingInfo].value, + msan = ctx.attr.msan[BuildSettingInfo].value, + pure = ctx.attr.pure[BuildSettingInfo].value, + strip = ctx.attr.strip, + debug = ctx.attr.debug[BuildSettingInfo].value, + linkmode = ctx.attr.linkmode[BuildSettingInfo].value, + gc_linkopts = ctx.attr.gc_linkopts[BuildSettingInfo].value, + tags = ctx.attr.gotags[BuildSettingInfo].value, + stamp = ctx.attr.stamp, + cover_format = ctx.attr.cover_format[BuildSettingInfo].value, + gc_goopts = ctx.attr.gc_goopts[BuildSettingInfo].value, + amd64 = ctx.attr.amd64, + )] + +go_config = rule( + implementation = _go_config_impl, + attrs = { + "static": attr.label( + mandatory = True, + providers = [BuildSettingInfo], + ), + "race": attr.label( + mandatory = True, + providers = [BuildSettingInfo], + ), + "msan": attr.label( + mandatory = True, + providers = [BuildSettingInfo], + ), + "pure": attr.label( + mandatory = True, + providers = [BuildSettingInfo], + ), + "strip": attr.bool(mandatory = True), + "debug": attr.label( + mandatory = True, + providers = [BuildSettingInfo], + ), + "linkmode": attr.label( + mandatory = True, + providers = [BuildSettingInfo], + ), + "gc_linkopts": attr.label( + mandatory = True, + providers = [BuildSettingInfo], + ), + "gotags": attr.label( + mandatory = True, + providers = [BuildSettingInfo], + ), + "stamp": attr.bool(mandatory = True), + "cover_format": attr.label( + mandatory = True, + providers = [BuildSettingInfo], + ), + "gc_goopts": attr.label( + mandatory = True, + providers = [BuildSettingInfo], + ), + "amd64": attr.string(), + }, + provides = [GoConfigInfo], + doc = """Collects information about build settings in the current + configuration. Rules may depend on this instead of depending on all + the build settings directly.""", +) + +def _expand_opts(go, attribute_name, opts): + return [go._ctx.expand_make_variables(attribute_name, opt, {}) for opt in opts] + +def _expand_location(go, attr, s): + return go._ctx.expand_location(s, getattr(attr, "data", [])) + +_LIST_TYPE = type([]) + +# Used to get attribute values which may have been transitioned. +# Transitioned attributes end up as lists. +# We never use split-transitions, so we always expect exactly one element in those lists. +# But if the attribute wasn't transitioned, it won't be a list. +def _flatten_possibly_transitioned_attr(maybe_list): + if type(maybe_list) == _LIST_TYPE: + if len(maybe_list) == 1: + return maybe_list[0] + else: + fail("Expected exactly one element in list but got {}".format(maybe_list)) + return maybe_list |