| # 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, |
| ) |