blob: 179d03acf028b00368314232b318fd87ecd6db1b [file] [log] [blame]
# Copyright 2022 The Fuchsia Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
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",
"C_COMPILE_ACTION_NAME",
)
_CC_RULES = [
"cc_library",
"cc_binary",
"cc_test",
"cc_inc_library",
"cc_proto_library",
]
_CPP_HEADER_EXT = [
"hpp",
"hxx",
"hh",
"ipp",
]
_HEADER_EXT = _CPP_HEADER_EXT + ["h"]
_CPP_SRC_EXT = [
"cpp",
"cxx",
"cc",
]
_CPP_EXT = _CPP_SRC_EXT + _CPP_HEADER_EXT
# Placeholder for exec root directory.
_EXEC_ROOT_MARKER = "__EXEC_ROOT__"
# Assume C++ if all files are headers or there is a c++ file.
def _is_cpp_target(srcs):
if any([src.extension in _CPP_EXT for src in srcs]):
return True
if all([src.extension in _HEADER_EXT for src in srcs]):
return True
return False
def _get_sources(ctx, target):
srcs = []
if hasattr(ctx.rule.attr, "srcs"):
srcs += [f for src in ctx.rule.attr.srcs for f in src.files.to_list()]
if hasattr(ctx.rule.attr, "hdrs"):
srcs += [f for src in ctx.rule.attr.hdrs for f in src.files.to_list()]
return srcs
def _add_compile_flags(options, flag, args):
for arg in args.to_list():
if len(arg) == 0:
arg = "."
options.append("{} {}".format(flag, arg))
# Get compilation options from the CompilationContext from CcInfo
# https://docs.bazel.build/versions/main/skylark/lib/CcInfo.html
def _get_compile_flags(dep):
options = []
compilation_context = dep[CcInfo].compilation_context
# ignore headers and validation_artifacts.
_add_compile_flags(options, "-I", compilation_context.includes)
_add_compile_flags(options, "-isystem", compilation_context.system_includes)
_add_compile_flags(options, "-iquote", compilation_context.quote_includes)
_add_compile_flags(options, "-F", compilation_context.framework_includes)
_add_compile_flags(options, "-D", compilation_context.defines)
_add_compile_flags(options, "-D", compilation_context.local_defines)
return options
CompilationAspectInfo = provider(
doc = "A private provider to pass information from a package build to archive.",
fields = {
"compilation_db": "compilation database",
},
)
# Returns the command lines to compile all the c/c++ source files for the given target.
def _cc_compile_commands(ctx, target, feature_configuration, cc_toolchain):
compile_flags = _get_compile_flags(target)
#Assume C++ code
srcs = _get_sources(ctx, target)
if _is_cpp_target(srcs):
compile_variables = cc_common.create_compile_variables(
feature_configuration = feature_configuration,
cc_toolchain = cc_toolchain,
user_compile_flags = ctx.fragments.cpp.cxxopts +
ctx.fragments.cpp.copts,
add_legacy_cxx_options = True,
)
action_name = CPP_COMPILE_ACTION_NAME
else:
compile_variables = cc_common.create_compile_variables(
feature_configuration = feature_configuration,
cc_toolchain = cc_toolchain,
user_compile_flags = ctx.fragments.cpp.copts,
)
action_name = C_COMPILE_ACTION_NAME
compiler_options = cc_common.get_memory_inefficient_command_line(
feature_configuration = feature_configuration,
action_name = action_name,
variables = compile_variables,
)
if hasattr(ctx.rule.attr, "copts"):
compile_flags.extend(ctx.rule.attr.copts)
compiler_info = str(
cc_common.get_tool_for_action(
feature_configuration = feature_configuration,
action_name = action_name,
),
)
cmdline_list = [compiler_info]
cmdline_list.extend(compiler_options)
cmdline_list.extend(compile_flags)
cmdline = " ".join(cmdline_list)
compile_commands = []
for src in srcs:
compile_commands.append(struct(
cmdline = cmdline + " -c " + src.path,
src = src,
))
return compile_commands
# Returns a list of compilation_db object entries for the given target.
def _build_compilation_db(target, ctx):
compilation_db = []
cc_toolchain = find_cpp_toolchain(ctx)
feature_configuration = cc_common.configure_features(
ctx = ctx,
cc_toolchain = cc_toolchain,
requested_features = ctx.features,
unsupported_features = ctx.disabled_features,
)
if ctx.rule.kind in _CC_RULES:
compile_commands = _cc_compile_commands(ctx, target, feature_configuration, cc_toolchain)
else:
return []
for compile_command in compile_commands:
compilation_db.append(
struct(
command = compile_command.cmdline,
directory = _EXEC_ROOT_MARKER,
file = compile_command.src.path,
),
)
return compilation_db
def _compilation_db_aspect_impl(target, ctx):
transitive_compilation_db = []
# Applies the aspect to the targets in the deps attribute, if present.
if hasattr(ctx.rule.attr, "deps"):
for dep in ctx.rule.attr.deps:
transitive_compilation_db.append(dep[CompilationAspectInfo].compilation_db)
if hasattr(ctx.rule.attr, "content"):
for dep in ctx.rule.attr.content:
if CompilationAspectInfo in dep:
transitive_compilation_db.append(dep[CompilationAspectInfo].compilation_db)
compilation_db = depset(
_build_compilation_db(target, ctx),
transitive = transitive_compilation_db,
)
return [CompilationAspectInfo(compilation_db = compilation_db)]
compilation_db_aspect = aspect(
implementation = _compilation_db_aspect_impl,
attr_aspects = ["deps", "content"],
attrs = {
"_cc_toolchain": attr.label(
default = Label("@bazel_tools//tools/cpp:current_cc_toolchain"),
),
},
fragments = ["cpp"],
toolchains = ["@bazel_tools//tools/cpp:toolchain_type"],
)
def _compilation_db_rule_impl(ctx):
compilation_db = []
for dep in ctx.attr.deps:
compilation_db.append(dep[CompilationAspectInfo].compilation_db)
compilation_db = depset(transitive = compilation_db)
exec_root = ctx.attr.output_base + "/execroot/" + ctx.workspace_name
content = json.encode(compilation_db.to_list())
content = content.replace(_EXEC_ROOT_MARKER, exec_root)
out = ctx.actions.declare_file(ctx.attr.filename.name)
ctx.actions.write(output = ctx.outputs.filename, content = content)
_clangd_compilation_database = rule(
implementation = _compilation_db_rule_impl,
toolchains = ["@rules_fuchsia//fuchsia:toolchain"],
attrs = {
"output_base": attr.string(
default = "__OUTPUT_BASE__",
doc = ("Output Bazel directory to store the compilation database"),
),
"deps": attr.label_list(aspects = [compilation_db_aspect]),
"filename": attr.output(),
},
)
# TODO(fxbug.dev/92380): Revisit the compilation database implementation after a review of alternatives
def clangd_compilation_database(**kwargs):
_clangd_compilation_database(
filename = kwargs.pop("filename", "compile_commands.json"),
**kwargs
)