blob: ef6fd0e2f2821cba0228618b2678389d13aff3ba [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.
"""Fuchsia cc primitives.
Drop in replacements for cc_binary and cc_test:
- fuchsia_cc_binary
- fuchsia_cc_test
"""
load("//fuchsia/constraints:target_compatibility.bzl", "COMPATIBILITY")
load("//fuchsia/private:fuchsia_toolchains.bzl", "FUCHSIA_TOOLCHAIN_DEFINITION", "get_fuchsia_sdk_toolchain")
load(":fuchsia_component.bzl", "fuchsia_test_component")
load(
":providers.bzl",
"FuchsiaDebugSymbolInfo",
"FuchsiaPackageResourcesInfo",
"FuchsiaUnstrippedBinaryInfo",
)
load(":utils.bzl", "find_cc_toolchain", "forward_providers")
KNOWN_PROVIDERS = [
CcInfo,
# This provider is generated by cc_binary/cc_test, but there's no way to
# load it.
# CcLauncherInfo,
DebugPackageInfo,
FuchsiaDebugSymbolInfo,
FuchsiaPackageResourcesInfo,
InstrumentedFilesInfo,
OutputGroupInfo,
]
def _fuchsia_cc_impl(ctx):
# Expect exactly one binary to be generated by the native cc_* rule.
native_outputs = ctx.attr.native_target.files.to_list()
if len(native_outputs) != 1:
fail("Expected exactly 1 native output for %s, got %s" % (ctx.attr.native_target, native_outputs))
target_in = native_outputs[0]
# Make sure we have a trailing "/"
install_root = ctx.attr.install_root and ctx.attr.install_root.removesuffix("/") + "/"
# Do not list the generated unstripped binary as a resource here.
# Instead it is exposed through a FuchsiaUnstrippedBinaryInfo provider
# which will later be processed to generate the corresponding resource
# entry, referencing its stripped version.
resources = []
# Check the restricted symbols
if ctx.attr.restricted_symbols:
cc_toolchain = find_cc_toolchain(ctx)
# Create a copy of the input so that we ensure that this action runs.
# We do not want to rely on links here since they may not play well with
# remote builds.
target_out = ctx.actions.declare_file("_" + target_in.basename)
ctx.actions.run(
executable = ctx.executable._check_restricted_symbols,
arguments = [
"--binary",
target_in.path,
"--objdump",
cc_toolchain.objdump_executable,
"--output",
target_out.path,
"--restricted_symbols_file",
ctx.file.restricted_symbols.path,
],
inputs = [target_in, ctx.file.restricted_symbols],
outputs = [target_out],
tools = cc_toolchain.all_files,
progress_message = "Checking that binary does not have restricted symbols %s" % target_in,
mnemonic = "CheckRestrictedSymbols",
)
else:
target_out = target_in
# Forward CC providers along with metadata for packaging.
return forward_providers(
ctx,
ctx.attr.native_target,
rename_executable = ctx.attr.bin_name,
*KNOWN_PROVIDERS
) + [
ctx.attr.clang_debug_symbols[FuchsiaDebugSymbolInfo],
FuchsiaPackageResourcesInfo(resources = resources),
FuchsiaUnstrippedBinaryInfo(
dest = install_root + ctx.attr.bin_name,
unstripped_file = target_out,
),
]
fuchsia_cc = rule(
implementation = _fuchsia_cc_impl,
toolchains = ["@bazel_tools//tools/cpp:toolchain_type"],
doc = """Attaches fuchsia-specific metadata to native cc_* targets.
This allows them to be directly included in fuchsia_component.
""",
attrs = {
"bin_name": attr.string(
doc = "The name of the executable to place under install_root.",
mandatory = True,
),
"install_root": attr.string(
doc = "The path to install the built binary, defaults to bin/",
default = "bin/",
mandatory = False,
),
"native_target": attr.label(
doc = "The underlying cc_* target.",
mandatory = True,
providers = [[CcInfo], [CcSharedLibraryInfo]],
),
"clang_debug_symbols": attr.label(
doc = "Clang debug symbols.",
default = "@fuchsia_clang//:debug_symbols",
providers = [FuchsiaDebugSymbolInfo],
),
"deps": attr.label_list(
doc = """The exact list of dependencies dep-ed on by native_target.
We need these because we can't rely on `cc_binary`'s DefaultInfo
[run]files (Bazel does not handle static libraries correctly.)
See https://github.com/bazelbuild/bazel/issues/1920.
Failure to provide the *exact list* of dependencies may result in a
runtime crash.
""",
providers = [CcInfo],
),
"implicit_deps": attr.label_list(
doc = """Implicit resources/libraries to include within the resulting package.""",
default = ["@fuchsia_sdk//pkg/fdio"],
),
"data": attr.label_list(
doc = "Packaged files needed by this target at runtime.",
providers = [FuchsiaPackageResourcesInfo],
),
"restricted_symbols": attr.label(
doc = """A file containing a list of restricted symbols.
If provided, this list will be checked against the symbols in the binary.
If any of the restricted symbols are present in the binary then this
rule will fail.
""",
allow_single_file = True,
mandatory = False,
),
"_check_restricted_symbols": attr.label(
default = "//fuchsia/tools:check_restricted_symbols",
executable = True,
cfg = "exec",
),
"_cc_toolchain": attr.label(
default = Label("@bazel_tools//tools/cpp:current_cc_toolchain"),
),
} | COMPATIBILITY.FUCHSIA_ATTRS,
)
def data_for_features(features):
data = [
"@fuchsia_sdk//pkg/sysroot:dist",
"@fuchsia_clang//:runtime",
]
# Check to see if the user is requesting a static cpp compilation via our
# provided feature. If they do not make this request we need to add the
# dist target to include the libcxx package resources.
# It would be tempting to use a feature_flag and add this as a select but
# this does not work because the feature_flag will only check if the feature
# is enabled in the context of the action which is not set yet.
#
# Additionally, this mechanism only works if a user specifies this feature
# on the target they are compiling and not at a higher level since features
# will not be known if they are set on the command line. This is not a
# problem because we are not globally controlling this feature. If we want
# to add that support in the future we can.
#
# A future optimization would be to move the select into the dist target itself.
if "static_cpp_standard_library" not in features:
data.append("@fuchsia_clang//:dist")
return data
def fuchsia_cc_binary(
*,
name,
bin_name = None,
tags = ["manual"],
visibility = None,
features = [],
deps = [],
**cc_binary_kwargs):
"""A fuchsia-specific cc_binary drop-in replacement.
The resulting target can be used as a dep in fuchsia_component.
Args:
name: The target name.
bin_name: The filename to place under bin/. Defaults to name.
tags: Tags to set for all generated targets. This type of target is marked "manual" by default.
visibility: The visibility of all generated targets.
features: The normal bazel meaning.
deps: Forwarded to the underlying `cc_binary`.
**cc_binary_kwargs: Arguments to forward to `cc_binary`.
"""
# TODO(https://fxbug.dev/385351779): Switch to the version from `@rules_cc`
# once the `@rules_cc` dependency in-tree is version-bumped.
native.cc_binary(
name = "%s.binary" % name,
tags = tags + ["manual"],
visibility = visibility,
features = features,
deps = deps,
**cc_binary_kwargs
)
fuchsia_cc(
name = name,
bin_name = bin_name or name,
native_target = "%s.binary" % name,
deps = deps,
data = data_for_features(features),
visibility = visibility,
tags = tags,
)
def fuchsia_cc_test(
*,
name,
death_unittest = False,
tags = ["manual"],
visibility = None,
deps = [],
features = [],
**cc_test_kwargs):
"""A fuchsia-specific cc_test drop-in replacement.
The resulting target can be used as a dep in fuchsia_component.
Args:
name: The target name.
death_unittest: Whether this test is a gtest unittest that uses ASSERT_DEATH.
tags: Tags to set for all generated targets. This type of target is marked "manual" by default.
visibility: The visibility of all generated targets.
deps: Typical bazel meaning.
features: The normal bazel meaning.
**cc_test_kwargs: Arguments to forward to `cc_test`.
"""
native.cc_test(
name = "%s.binary" % name,
tags = tags + ["manual"],
deps = deps,
features = features,
visibility = visibility,
**cc_test_kwargs
)
fuchsia_cc(
name = name,
bin_name = name,
native_target = "%s.binary" % name,
deps = deps,
data = data_for_features(features),
tags = tags,
testonly = True,
visibility = visibility,
)
_fuchsia_cc_test_manifest(
name = "%s_autogen_cml" % name,
test_binary_name = name,
deps = deps,
death_unittest = death_unittest,
testonly = True,
tags = tags + ["manual"],
visibility = visibility,
)
# Generate the default component.
fuchsia_test_component(
name = "%s.unittest_component" % name,
component_name = name,
manifest = ":%s_autogen_cml" % name,
deps = [name],
tags = tags + ["manual"],
visibility = visibility,
)
# fuchsia_cc_test build rules.
def _fuchsia_cc_test_manifest_impl(ctx):
sdk = get_fuchsia_sdk_toolchain(ctx)
# Detect googletest.
is_gtest = False
for dep in ctx.attr.deps:
if dep.label.workspace_name == ctx.attr._googletest.label.workspace_name:
is_gtest = True
break
# Write cml.
generated_cml = ctx.actions.declare_file("%s.cml" % ctx.attr.test_binary_name)
ctx.actions.expand_template(
template = ctx.attr._template_file.files.to_list()[0],
output = generated_cml,
substitutions = {
"{{RUNNER_SHARD}}": sdk.gtest_runner_shard if is_gtest else sdk.elf_test_runner_shard,
"{{BINARY}}": ctx.attr.test_binary_name,
"{{LAUNCHER_PROTOCOL}}": """{
// Needed for ASSERT_DEATH, which is common across many unit tests.
protocol: [ "fuchsia.process.Launcher" ],
},""" if ctx.attr.death_unittest else "",
},
)
return [
DefaultInfo(files = depset([generated_cml])),
]
_fuchsia_cc_test_manifest = rule(
implementation = _fuchsia_cc_test_manifest_impl,
doc = """Generates a stub cml file for a given cc_test-backed _fuchsia_cc.
Detects whether gtest is included as a dependency. If it is, the cml file
will use gtest_runner. Otherwise it will use the elf_test_runner.
""",
toolchains = [FUCHSIA_TOOLCHAIN_DEFINITION],
attrs = {
"test_binary_name": attr.string(
doc = "The test binary's name.",
mandatory = True,
),
"deps": attr.label_list(
doc = "The same deps passed into _fuchsia_cc. Used for determining whether a gtest runner is suitable for this test.",
mandatory = True,
providers = [CcInfo],
),
"death_unittest": attr.bool(
doc = "Whether the test is a gtest unit test and uses ASSERT_DEATH.",
mandatory = True,
),
"_googletest": attr.label(
doc = "Any googletest label.",
allow_single_file = True,
default = "@com_google_googletest//:BUILD.bazel",
),
"_template_file": attr.label(
doc = "The template cml file.",
default = "//fuchsia/private:templates/cc_test_manifest.cml.tmpl",
allow_single_file = True,
),
} | COMPATIBILITY.HOST_ATTRS,
)