unplugged-system/build/bazel/rules/cc/cc_library_common.bzl

424 lines
17 KiB
Python

# Copyright (C) 2021 The Android Open Source Project
#
# 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("@soong_injection//android:constants.bzl", android_constants = "constants")
load("@soong_injection//api_levels:platform_versions.bzl", "platform_versions")
load("//build/bazel/rules:common.bzl", "strip_bp2build_label_suffix")
load("//build/bazel/rules/common:api.bzl", "api")
_bionic_targets = ["//bionic/libc", "//bionic/libdl", "//bionic/libm"]
_static_bionic_targets = ["//bionic/libc:libc_bp2build_cc_library_static", "//bionic/libdl:libdl_bp2build_cc_library_static", "//bionic/libm:libm_bp2build_cc_library_static"]
# When building a APEX, stub libraries of libc, libdl, libm should be used in linking.
_bionic_stub_targets = [
"//bionic/libc:libc_stub_libs_current",
"//bionic/libdl:libdl_stub_libs_current",
"//bionic/libm:libm_stub_libs_current",
]
# The default system_dynamic_deps value for cc libraries. This value should be
# used if no value for system_dynamic_deps is specified.
system_dynamic_deps_defaults = select({
"//build/bazel/rules/apex:android-in_apex": _bionic_stub_targets,
"//build/bazel/rules/apex:android-non_apex": _bionic_targets,
"//build/bazel/rules/apex:linux_bionic-in_apex": _bionic_stub_targets,
"//build/bazel/rules/apex:linux_bionic-non_apex": _bionic_targets,
"//conditions:default": [],
})
system_static_deps_defaults = select({
"//build/bazel/rules/apex:android-in_apex": _bionic_stub_targets,
"//build/bazel/rules/apex:android-non_apex": _static_bionic_targets,
"//build/bazel/rules/apex:linux_bionic-in_apex": _bionic_stub_targets,
"//build/bazel/rules/apex:linux_bionic-non_apex": _static_bionic_targets,
"//conditions:default": [],
})
# List comes from here:
# https://cs.android.com/android/platform/superproject/+/master:build/soong/cc/cc.go;l=1441;drc=9fd9129b5728602a4768e8e8e695660b683c405e
_bionic_libs = ["libc", "libm", "libdl", "libdl_android", "linker", "linkerconfig"]
# Comes from here:
# https://cs.android.com/android/platform/superproject/+/master:build/soong/cc/cc.go;l=1450;drc=9fd9129b5728602a4768e8e8e695660b683c405e
_bootstrap_libs = ["libclang_rt.hwasan"]
future_version = 10000
CcSanitizerLibraryInfo = provider(
"Denotes which sanitizer libraries to include",
fields = {
"propagate_ubsan_deps": ("True if any ubsan sanitizers are " +
"enabled on any transitive deps, or " +
"the current target. False otherwise"),
},
)
# Must be called from within a rule (not a macro) so that the features select
# has been resolved.
def get_sanitizer_lib_info(features, deps):
propagate_ubsan_deps = False
for feature in features:
if feature.startswith("ubsan_"):
propagate_ubsan_deps = True
break
if not propagate_ubsan_deps:
for dep in deps:
if (CcSanitizerLibraryInfo in dep and
dep[CcSanitizerLibraryInfo].propagate_ubsan_deps):
propagate_ubsan_deps = True
break
return CcSanitizerLibraryInfo(
propagate_ubsan_deps = propagate_ubsan_deps,
)
def _sanitizer_deps_impl(ctx):
if (CcSanitizerLibraryInfo in ctx.attr.dep and
ctx.attr.dep[CcSanitizerLibraryInfo].propagate_ubsan_deps):
# To operate correctly with native cc_binary and cc_sharedLibrary,
# copy the linker inputs and ensure that this target is marked as the
# "owner". Otherwise, upstream targets may drop these linker inputs.
# See b/264894507.
libraries = [
lib
for input in ctx.attr._ubsan_library[CcInfo].linking_context.linker_inputs.to_list()
for lib in input.libraries
]
new_linker_input = cc_common.create_linker_input(
owner = ctx.label,
libraries = depset(direct = libraries),
)
linking_context = cc_common.create_linking_context(
linker_inputs = depset(direct = [new_linker_input]),
)
return [CcInfo(linking_context = linking_context)]
return [CcInfo()]
# This rule is essentially a workaround to be able to add dependencies
# conditionally based on provider values
sanitizer_deps = rule(
implementation = _sanitizer_deps_impl,
doc = "A rule that propagates given sanitizer dependencies if the " +
"proper conditions are met",
attrs = {
"dep": attr.label(
mandatory = True,
doc = "library to check for sanitizer dependency propagation",
),
"_ubsan_library": attr.label(
default = "//prebuilts/clang/host/linux-x86:libclang_rt.ubsan_minimal",
doc = "The library target corresponding to the undefined " +
"behavior sanitizer library to be used",
),
},
provides = [CcInfo],
)
def sdk_version_feature_from_parsed_version(version):
return "sdk_version_" + str(version)
def _create_sdk_version_features_map():
version_feature_map = {}
for level in api.api_levels.values():
version_feature_map["//build/bazel/rules/apex:min_sdk_version_" + str(level)] = [sdk_version_feature_from_parsed_version(level)]
version_feature_map["//conditions:default"] = [sdk_version_feature_from_parsed_version(future_version)]
return version_feature_map
sdk_version_features = select(_create_sdk_version_features_map())
def add_lists_defaulting_to_none(*args):
"""Adds multiple lists, but is well behaved with a `None` default."""
combined = None
for arg in args:
if arg != None:
if combined == None:
combined = []
combined += arg
return combined
# get_includes_paths expects a rule context, a list of directories, and
# whether the directories are package-relative and returns a list of exec
# root-relative paths. This handles the need to search for files both in the
# source tree and generated files.
def get_includes_paths(ctx, dirs, package_relative = True):
execution_relative_dirs = []
for rel_dir in dirs:
if rel_dir == ".":
rel_dir = ""
execution_rel_dir = rel_dir
if package_relative:
execution_rel_dir = ctx.label.package
if len(rel_dir) > 0:
execution_rel_dir = execution_rel_dir + "/" + rel_dir
# To allow this repo to be used as an external one.
repo_prefix_dir = execution_rel_dir
if ctx.label.workspace_root != "":
repo_prefix_dir = ctx.label.workspace_root + "/" + execution_rel_dir
execution_relative_dirs.append(repo_prefix_dir)
# to support generated files, we also need to export includes relatives to the bin directory
if not execution_rel_dir.startswith("/"):
execution_relative_dirs.append(ctx.bin_dir.path + "/" + execution_rel_dir)
return execution_relative_dirs
def create_ccinfo_for_includes(
ctx,
hdrs = [],
includes = [],
absolute_includes = [],
system_includes = [],
deps = []):
# Create a compilation context using the string includes of this target.
compilation_context = cc_common.create_compilation_context(
headers = depset(hdrs),
includes = depset(
get_includes_paths(ctx, includes) +
get_includes_paths(ctx, absolute_includes, package_relative = False),
),
system_includes = depset(get_includes_paths(ctx, system_includes)),
)
# Combine this target's compilation context with those of the deps; use only
# the compilation context of the combined CcInfo.
cc_infos = [dep[CcInfo] for dep in deps]
cc_infos.append(CcInfo(compilation_context = compilation_context))
combined_info = cc_common.merge_cc_infos(cc_infos = cc_infos)
return CcInfo(compilation_context = combined_info.compilation_context)
def is_external_directory(package_name):
if package_name.startswith("external"):
return True
if package_name.startswith("hardware"):
paths = package_name.split("/")
if len(paths) < 2:
return True
secondary_path = paths[1]
if secondary_path in ["google", "interfaces", "ril"]:
return False
return not secondary_path.startswith("libhardware")
if package_name.startswith("vendor"):
paths = package_name.split("/")
if len(paths) < 2:
return True
secondary_path = paths[1]
return "google" not in secondary_path
return False
# TODO: Move this to a common rule dir, instead of a cc rule dir. Nothing here
# should be cc specific, except that the current callers are (only) cc rules.
def parse_sdk_version(version):
if version == "apex_inherit":
# use the version determined by the transition value.
return sdk_version_features + [sdk_version_feature_from_parsed_version("apex_inherit")]
return [sdk_version_feature_from_parsed_version(parse_apex_sdk_version(version))]
def parse_apex_sdk_version(version):
if version == "" or version == "current" or version == "10000":
return future_version
elif version in api.api_levels.keys():
return api.api_levels[version]
elif version.isdigit():
version = int(version)
if version in api.api_levels.values():
return version
elif version == platform_versions.platform_sdk_version:
# For internal branch states, support parsing a finalized version number
# that's also still in
# platform_versions.platform_version_active_codenames, but not api.api_levels.
#
# This happens a few months each year on internal branches where the
# internal master branch has a finalized API, but is not released yet,
# therefore the Platform_sdk_version is usually latest AOSP dessert
# version + 1. The generated api.api_levels map sets these to 9000 + i,
# where i is the index of the current/future version, so version is not
# in the api.api_levels.values() list, but it is a valid sdk version.
#
# See also b/234321488#comment2
return version
fail("Unknown sdk version: %s, could not be parsed as " % version +
"an integer and/or is not a recognized codename. Valid api levels are:" +
str(api.api_levels))
CPP_EXTENSIONS = ["cc", "cpp", "c++"]
C_EXTENSIONS = ["c"]
_HEADER_EXTENSIONS = ["h", "hh", "hpp", "hxx", "h++", "inl", "inc", "ipp", "h.generic"]
def get_non_header_srcs(input_srcs, exclude_srcs = [], source_extensions = None, header_extensions = _HEADER_EXTENSIONS):
"""get_non_header_srcs returns a list of srcs that do not have header extensions and aren't in the exclude srcs list
Args:
input_srcs (list[File]): list of files to filter
exclude_srcs (list[File]): list of files that should be excluded from the returned list
source_extensions (list[str]): list of extensions that designate sources.
If None, all extensions are valid. Otherwise only source with these extensions are returned
header_extensions (list[str]): list of extensions that designate headers
Returns:
srcs, hdrs (list[File], list[File]): tuple of lists of files; srcs have non-header extension and are not excluded,
and hdrs are files with header extensions
"""
srcs = []
hdrs = []
for s in input_srcs:
is_source = not source_extensions or s.extension in source_extensions
if s.extension in header_extensions:
hdrs.append(s)
elif is_source and s not in exclude_srcs:
srcs.append(s)
return srcs, hdrs
def prefix_in_list(str, prefixes):
"""returns the prefix if any element of prefixes is a prefix of path
Args:
str (str): the string to compare prefixes against
prefixes (list[str]): a list of prefixes to check against str
Returns:
prefix (str or None): the prefix (if any) that str starts with
"""
for prefix in prefixes:
if str.startswith(prefix):
return prefix
return None
_DISALLOWED_INCLUDE_DIRS = android_constants.NeverAllowNotInIncludeDir
_PACKAGES_DISALLOWED_TO_SPECIFY_INCLUDE_DIRS = android_constants.NeverAllowNoUseIncludeDir
def check_absolute_include_dirs_disabled(target_package, absolute_includes):
"""checks that absolute include dirs are disabled for some directories
Args:
target_package (str): package of current target
absolute_includes (list[str]): list of absolute include directories
"""
if len(absolute_includes) > 0:
disallowed_prefix = prefix_in_list(
target_package,
_PACKAGES_DISALLOWED_TO_SPECIFY_INCLUDE_DIRS,
)
if disallowed_prefix != None:
fail("include_dirs is deprecated, all usages of them in '" +
disallowed_prefix + "' have been migrated to use alternate" +
" mechanisms and so can no longer be used.")
for path in absolute_includes:
if path in _DISALLOWED_INCLUDE_DIRS:
fail("include_dirs is deprecated, all usages of '" + path + "' have" +
" been migrated to use alternate mechanisms and so can no longer" +
" be used.")
def get_compilation_args(toolchain, feature_config, flags, compilation_ctx, action_name):
compilation_vars = cc_common.create_compile_variables(
cc_toolchain = toolchain,
feature_configuration = feature_config,
user_compile_flags = flags,
include_directories = compilation_ctx.includes,
quote_include_directories = compilation_ctx.quote_includes,
system_include_directories = compilation_ctx.system_includes,
framework_include_directories = compilation_ctx.framework_includes,
)
return cc_common.get_memory_inefficient_command_line(
feature_configuration = feature_config,
action_name = action_name,
variables = compilation_vars,
)
def build_compilation_flags(ctx, deps, user_flags, action_name):
cc_toolchain = find_cpp_toolchain(ctx)
feature_config = cc_common.configure_features(
ctx = ctx,
cc_toolchain = cc_toolchain,
language = "c++",
requested_features = ctx.features,
unsupported_features = ctx.disabled_features,
)
cc_info = cc_common.merge_cc_infos(direct_cc_infos = [d[CcInfo] for d in deps])
compilation_flags = get_compilation_args(
toolchain = cc_toolchain,
feature_config = feature_config,
flags = user_flags,
compilation_ctx = cc_info.compilation_context,
action_name = action_name,
)
return cc_info.compilation_context, compilation_flags
def is_bionic_lib(name):
return name in _bionic_libs
def is_bootstrap_lib(name):
return name in _bootstrap_libs
CcAndroidMkInfo = provider(
"Provides information to be passed to AndroidMk in Soong",
fields = {
"local_static_libs": "list of target names passed to LOCAL_STATIC_LIBRARIES AndroidMk variable",
"local_whole_static_libs": "list of target names passed to LOCAL_WHOLE_STATIC_LIBRARIES AndroidMk variable",
"local_shared_libs": "list of target names passed to LOCAL_SHARED_LIBRARIES AndroidMk variable",
},
)
def create_cc_androidmk_provider(*, static_deps, whole_archive_deps, dynamic_deps):
# Since this information is provided to Soong for mixed builds,
# we are just taking the Soong module name rather than the Bazel
# label.
# TODO(b/266197834) consider moving this logic to the mixed builds
# handler in Soong
local_static_libs = [
strip_bp2build_label_suffix(d.label.name)
for d in static_deps
]
local_whole_static_libs = [
strip_bp2build_label_suffix(d.label.name)
for d in whole_archive_deps
]
local_shared_libs = [
strip_bp2build_label_suffix(d.label.name)
for d in dynamic_deps
]
return CcAndroidMkInfo(
local_static_libs = local_static_libs,
local_whole_static_libs = local_whole_static_libs,
local_shared_libs = local_shared_libs,
)
def create_cc_prebuilt_library_info(ctx, lib_to_link):
"Create the CcInfo for a prebuilt_library_{shared,static}"
compilation_context = cc_common.create_compilation_context(
includes = depset(get_includes_paths(ctx, ctx.attr.export_includes)),
system_includes = depset(get_includes_paths(ctx, ctx.attr.export_system_includes)),
)
linker_input = cc_common.create_linker_input(
owner = ctx.label,
libraries = depset(direct = [lib_to_link] if lib_to_link != None else []),
)
linking_context = cc_common.create_linking_context(
linker_inputs = depset(direct = [linker_input]),
)
return CcInfo(
compilation_context = compilation_context,
linking_context = linking_context,
)