1# Copyright (C) 2021 The Android Open Source Project
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7#     http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
15load("@bazel_skylib//lib:paths.bzl", "paths")
16load("@bazel_tools//tools/cpp:toolchain_utils.bzl", "find_cpp_toolchain")
17load("@soong_injection//android:constants.bzl", android_constants = "constants")
18load("@soong_injection//api_levels:platform_versions.bzl", "platform_versions")
19load("@soong_injection//cc_toolchain:config_constants.bzl", cc_constants = "constants")
20load("//build/bazel/rules:common.bzl", "strip_bp2build_label_suffix")
21load("//build/bazel/rules/common:api.bzl", "api")
22
23_static_bionic_targets = ["//bionic/libc:libc_bp2build_cc_library_static", "//bionic/libdl:libdl_bp2build_cc_library_static", "//bionic/libm:libm_bp2build_cc_library_static"]
24
25# When building a APEX, stub libraries of libc, libdl, libm should be used in linking.
26_bionic_stub_targets = [
27    "//bionic/libc:libc_stub_libs_current",
28    "//bionic/libdl:libdl_stub_libs_current",
29    "//bionic/libm:libm_stub_libs_current",
30]
31
32# When building an android_app/android_test that set an sdk_version, NDK variant of stub libraries of libc, libdl, libm should be used in linking.
33_bionic_ndk_stub_targets = [
34    "//bionic/libc:libc.ndk_stub_libs_current",
35    "//bionic/libdl:libdl.ndk_stub_libs_current",
36    "//bionic/libm:libm.ndk_stub_libs_current",
37]
38
39# The default system_dynamic_deps value for cc libraries. This value should be
40# used if no value for system_dynamic_deps is specified.
41system_dynamic_deps_defaults = select({
42    "//build/bazel/rules/apex:android-in_apex": _bionic_stub_targets,
43    "//build/bazel/rules/apex:android-non_apex": _bionic_stub_targets,
44    "//build/bazel/rules/apex:linux_bionic-in_apex": _bionic_stub_targets,
45    "//build/bazel/rules/apex:linux_bionic-non_apex": _bionic_stub_targets,
46    "//build/bazel/rules/apex:unbundled_app": _bionic_ndk_stub_targets,
47    "//conditions:default": [],
48})
49
50system_static_deps_defaults = select({
51    "//build/bazel/rules/apex:android-in_apex": _bionic_stub_targets,
52    "//build/bazel/rules/apex:android-non_apex": _static_bionic_targets,
53    "//build/bazel/rules/apex:linux_bionic-in_apex": _bionic_stub_targets,
54    "//build/bazel/rules/apex:linux_bionic-non_apex": _static_bionic_targets,
55    "//build/bazel/rules/apex:unbundled_app": _bionic_ndk_stub_targets,
56    "//conditions:default": [],
57})
58
59# List comes from here:
60# https://cs.android.com/android/platform/superproject/+/master:build/soong/cc/cc.go;l=1441;drc=9fd9129b5728602a4768e8e8e695660b683c405e
61_bionic_libs = ["libc", "libm", "libdl", "libdl_android", "linker", "linkerconfig"]
62
63# Comes from here:
64# https://cs.android.com/android/platform/superproject/+/master:build/soong/cc/cc.go;l=1450;drc=9fd9129b5728602a4768e8e8e695660b683c405e
65_bootstrap_libs = ["libclang_rt.hwasan"]
66
67future_version = 10000
68
69CcSanitizerLibraryInfo = provider(
70    "Denotes which sanitizer libraries to include",
71    fields = {
72        "propagate_ubsan_deps": ("True if any ubsan sanitizers are " +
73                                 "enabled on any transitive deps, or " +
74                                 "the current target. False otherwise"),
75    },
76)
77
78# Must be called from within a rule (not a macro) so that the features select
79# has been resolved.
80def get_sanitizer_lib_info(features, deps):
81    propagate_ubsan_deps = False
82    for feature in features:
83        if feature.startswith("ubsan_"):
84            propagate_ubsan_deps = True
85            break
86    if not propagate_ubsan_deps:
87        for dep in deps:
88            if (CcSanitizerLibraryInfo in dep and
89                dep[CcSanitizerLibraryInfo].propagate_ubsan_deps):
90                propagate_ubsan_deps = True
91                break
92    return CcSanitizerLibraryInfo(
93        propagate_ubsan_deps = propagate_ubsan_deps,
94    )
95
96def _sanitizer_deps_impl(ctx):
97    if (CcSanitizerLibraryInfo in ctx.attr.dep and
98        ctx.attr.dep[CcSanitizerLibraryInfo].propagate_ubsan_deps):
99        # To operate correctly with native cc_binary and cc_sharedLibrary,
100        # copy the linker inputs and ensure that this target is marked as the
101        # "owner". Otherwise, upstream targets may drop these linker inputs.
102        # See b/264894507.
103        libraries = [
104            lib
105            for input in ctx.attr._ubsan_library[CcInfo].linking_context.linker_inputs.to_list()
106            for lib in input.libraries
107        ]
108        new_linker_input = cc_common.create_linker_input(
109            owner = ctx.label,
110            libraries = depset(direct = libraries),
111        )
112        linking_context = cc_common.create_linking_context(
113            linker_inputs = depset(direct = [new_linker_input]),
114        )
115        return [CcInfo(linking_context = linking_context)]
116    return [CcInfo()]
117
118# This rule is essentially a workaround to be able to add dependencies
119# conditionally based on provider values
120sanitizer_deps = rule(
121    implementation = _sanitizer_deps_impl,
122    doc = "A rule that propagates given sanitizer dependencies if the " +
123          "proper conditions are met",
124    attrs = {
125        "dep": attr.label(
126            mandatory = True,
127            doc = "library to check for sanitizer dependency propagation",
128        ),
129        "_ubsan_library": attr.label(
130            default = "//prebuilts/clang/host/linux-x86:libclang_rt.ubsan_minimal",
131            doc = "The library target corresponding to the undefined " +
132                  "behavior sanitizer library to be used",
133        ),
134    },
135    provides = [CcInfo],
136)
137
138def sdk_version_feature_from_parsed_version(version):
139    return "sdk_version_" + str(version)
140
141def _create_sdk_version_features_map():
142    version_feature_map = {}
143    for level in api.api_levels.values():
144        version_feature_map["//build/bazel/rules/apex:min_sdk_version_" + str(level)] = [sdk_version_feature_from_parsed_version(level)]
145    version_feature_map["//conditions:default"] = [sdk_version_feature_from_parsed_version(future_version)]
146
147    return version_feature_map
148
149sdk_version_features = select(_create_sdk_version_features_map())
150
151def add_lists_defaulting_to_none(*args):
152    """Adds multiple lists, but is well behaved with a `None` default."""
153    combined = None
154    for arg in args:
155        if arg != None:
156            if combined == None:
157                combined = []
158            combined += arg
159
160    return combined
161
162# get_includes_paths expects a rule context, a list of directories, and
163# whether the directories are package-relative and returns a list of exec
164# root-relative paths. This handles the need to search for files both in the
165# source tree and generated files.
166def get_includes_paths(ctx, dirs, package_relative = True):
167    execution_relative_dirs = []
168    for rel_dir in dirs:
169        if rel_dir == ".":
170            rel_dir = ""
171        execution_rel_dir = rel_dir
172        if package_relative:
173            execution_rel_dir = ctx.label.package
174            if len(rel_dir) > 0:
175                execution_rel_dir = execution_rel_dir + "/" + rel_dir
176
177        # To allow this repo to be used as an external one.
178        repo_prefix_dir = execution_rel_dir
179        if ctx.label.workspace_root != "":
180            repo_prefix_dir = ctx.label.workspace_root + "/" + execution_rel_dir
181        execution_relative_dirs.append(repo_prefix_dir)
182
183        # to support generated files, we also need to export includes relatives to the bin directory
184        if not execution_rel_dir.startswith("/"):
185            execution_relative_dirs.append(ctx.bin_dir.path + "/" + execution_rel_dir)
186    return execution_relative_dirs
187
188def create_ccinfo_for_includes(
189        ctx,
190        hdrs = [],
191        includes = [],
192        absolute_includes = [],
193        system_includes = [],
194        deps = []):
195    # Create a compilation context using the string includes of this target.
196    compilation_context = cc_common.create_compilation_context(
197        headers = depset(hdrs),
198        includes = depset(
199            get_includes_paths(ctx, includes) +
200            get_includes_paths(ctx, absolute_includes, package_relative = False),
201        ),
202        system_includes = depset(get_includes_paths(ctx, system_includes)),
203    )
204
205    # Combine this target's compilation context with those of the deps; use only
206    # the compilation context of the combined CcInfo.
207    cc_infos = [dep[CcInfo] for dep in deps]
208    cc_infos.append(CcInfo(compilation_context = compilation_context))
209    combined_info = cc_common.merge_cc_infos(cc_infos = cc_infos)
210
211    return CcInfo(compilation_context = combined_info.compilation_context)
212
213def is_external_directory(package_name):
214    if package_name.startswith("external"):
215        return True
216    if package_name.startswith("hardware"):
217        paths = package_name.split("/")
218        if len(paths) < 2:
219            return True
220        secondary_path = paths[1]
221        if secondary_path in ["google", "interfaces", "ril"]:
222            return False
223        return not secondary_path.startswith("libhardware")
224    if package_name.startswith("vendor"):
225        paths = package_name.split("/")
226        if len(paths) < 2:
227            return True
228        secondary_path = paths[1]
229        return "google" not in secondary_path
230    return False
231
232# TODO: Move this to a common rule dir, instead of a cc rule dir. Nothing here
233# should be cc specific, except that the current callers are (only) cc rules.
234def parse_sdk_version(version):
235    if version == "apex_inherit":
236        # use the version determined by the transition value.
237        return sdk_version_features + [sdk_version_feature_from_parsed_version("apex_inherit")]
238
239    return [sdk_version_feature_from_parsed_version(parse_apex_sdk_version(version))]
240
241def parse_apex_sdk_version(version):
242    if version == "" or version == "current" or version == "10000":
243        return future_version
244    elif version in api.api_levels.keys():
245        return api.api_levels[version]
246    elif version.isdigit():
247        version = int(version)
248        if version in api.api_levels.values():
249            return version
250        elif version == platform_versions.platform_sdk_version:
251            # For internal branch states, support parsing a finalized version number
252            # that's also still in
253            # platform_versions.platform_version_active_codenames, but not api.api_levels.
254            #
255            # This happens a few months each year on internal branches where the
256            # internal master branch has a finalized API, but is not released yet,
257            # therefore the Platform_sdk_version is usually latest AOSP dessert
258            # version + 1. The generated api.api_levels map sets these to 9000 + i,
259            # where i is the index of the current/future version, so version is not
260            # in the api.api_levels.values() list, but it is a valid sdk version.
261            #
262            # See also b/234321488#comment2
263            return version
264    fail("Unknown sdk version: %s, could not be parsed as " % version +
265         "an integer and/or is not a recognized codename. Valid api levels are:" +
266         str(api.api_levels))
267
268CPP_EXTENSIONS = ["cc", "cpp", "c++"]
269
270C_EXTENSIONS = ["c"]
271
272_HEADER_EXTENSIONS = ["h", "hh", "hpp", "hxx", "h++", "inl", "inc", "ipp", "h.generic"]
273
274def get_non_header_srcs(input_srcs, exclude_srcs = [], source_extensions = None, header_extensions = _HEADER_EXTENSIONS):
275    """get_non_header_srcs returns a list of srcs that do not have header extensions and aren't in the exclude srcs list
276
277    Args:
278        input_srcs (list[File]): list of files to filter
279        exclude_srcs (list[File]): list of files that should be excluded from the returned list
280        source_extensions (list[str]): list of extensions that designate sources.
281            If None, all extensions are valid. Otherwise only source with these extensions are returned
282        header_extensions (list[str]): list of extensions that designate headers
283    Returns:
284        srcs, hdrs (list[File], list[File]): tuple of lists of files; srcs have non-header extension and are not excluded,
285            and hdrs are files with header extensions
286    """
287    srcs = []
288    hdrs = []
289    for s in input_srcs:
290        is_source = not source_extensions or s.extension in source_extensions
291        if s.extension in header_extensions:
292            hdrs.append(s)
293        elif is_source and s not in exclude_srcs:
294            srcs.append(s)
295    return srcs, hdrs
296
297def prefix_in_list(str, prefixes):
298    """returns the prefix if any element of prefixes is a prefix of path
299
300    Args:
301        str (str): the string to compare prefixes against
302        prefixes (list[str]): a list of prefixes to check against str
303    Returns:
304        prefix (str or None): the prefix (if any) that str starts with
305    """
306    for prefix in prefixes:
307        if str.startswith(prefix):
308            return prefix
309    return None
310
311_DISALLOWED_INCLUDE_DIRS = android_constants.NeverAllowNotInIncludeDir
312_PACKAGES_DISALLOWED_TO_SPECIFY_INCLUDE_DIRS = android_constants.NeverAllowNoUseIncludeDir
313
314def check_absolute_include_dirs_disabled(target_package, absolute_includes):
315    """checks that absolute include dirs are disabled for some directories
316
317    Args:
318        target_package (str): package of current target
319        absolute_includes (list[str]): list of absolute include directories
320    """
321    if len(absolute_includes) > 0:
322        disallowed_prefix = prefix_in_list(
323            target_package,
324            _PACKAGES_DISALLOWED_TO_SPECIFY_INCLUDE_DIRS,
325        )
326        if disallowed_prefix != None:
327            fail("include_dirs is deprecated, all usages of them in '" +
328                 disallowed_prefix + "' have been migrated to use alternate" +
329                 " mechanisms and so can no longer be used.")
330
331    for path in absolute_includes:
332        if path in _DISALLOWED_INCLUDE_DIRS:
333            fail("include_dirs is deprecated, all usages of '" + path + "' have" +
334                 " been migrated to use alternate mechanisms and so can no longer" +
335                 " be used.")
336
337def get_compilation_args(toolchain, feature_config, flags, compilation_ctx, action_name):
338    compilation_vars = cc_common.create_compile_variables(
339        cc_toolchain = toolchain,
340        feature_configuration = feature_config,
341        user_compile_flags = flags,
342        include_directories = compilation_ctx.includes,
343        quote_include_directories = compilation_ctx.quote_includes,
344        system_include_directories = compilation_ctx.system_includes,
345        framework_include_directories = compilation_ctx.framework_includes,
346    )
347
348    return cc_common.get_memory_inefficient_command_line(
349        feature_configuration = feature_config,
350        action_name = action_name,
351        variables = compilation_vars,
352    )
353
354def build_compilation_flags(ctx, deps, user_flags, action_name):
355    cc_toolchain = find_cpp_toolchain(ctx)
356
357    feature_config = cc_common.configure_features(
358        ctx = ctx,
359        cc_toolchain = cc_toolchain,
360        language = "c++",
361        requested_features = ctx.features,
362        unsupported_features = ctx.disabled_features,
363    )
364
365    cc_info = cc_common.merge_cc_infos(direct_cc_infos = [d[CcInfo] for d in deps])
366
367    compilation_flags = get_compilation_args(
368        toolchain = cc_toolchain,
369        feature_config = feature_config,
370        flags = user_flags,
371        compilation_ctx = cc_info.compilation_context,
372        action_name = action_name,
373    )
374
375    return cc_info.compilation_context, compilation_flags
376
377def is_bionic_lib(name):
378    return name in _bionic_libs
379
380def is_bootstrap_lib(name):
381    return name in _bootstrap_libs
382
383CcAndroidMkInfo = provider(
384    "Provides information to be passed to AndroidMk in Soong",
385    fields = {
386        "local_static_libs": "list of target names passed to LOCAL_STATIC_LIBRARIES AndroidMk variable",
387        "local_whole_static_libs": "list of target names passed to LOCAL_WHOLE_STATIC_LIBRARIES AndroidMk variable",
388        "local_shared_libs": "list of target names passed to LOCAL_SHARED_LIBRARIES AndroidMk variable",
389    },
390)
391
392def create_cc_androidmk_provider(*, static_deps, whole_archive_deps, dynamic_deps):
393    # Since this information is provided to Soong for mixed builds,
394    # we are just taking the Soong module name rather than the Bazel
395    # label.
396    # TODO(b/266197834) consider moving this logic to the mixed builds
397    # handler in Soong
398    local_static_libs = [
399        strip_bp2build_label_suffix(d.label.name)
400        for d in static_deps
401    ]
402    local_whole_static_libs = [
403        strip_bp2build_label_suffix(d.label.name)
404        for d in whole_archive_deps
405    ]
406    local_shared_libs = [
407        strip_bp2build_label_suffix(d.label.name)
408        for d in dynamic_deps
409    ]
410    return CcAndroidMkInfo(
411        local_static_libs = local_static_libs,
412        local_whole_static_libs = local_whole_static_libs,
413        local_shared_libs = local_shared_libs,
414    )
415
416def create_cc_prebuilt_library_info(ctx, lib_to_link):
417    "Create the CcInfo for a prebuilt_library_{shared,static}"
418
419    compilation_context = cc_common.create_compilation_context(
420        includes = depset(get_includes_paths(ctx, ctx.attr.export_includes)),
421        system_includes = depset(get_includes_paths(ctx, ctx.attr.export_system_includes)),
422    )
423    linker_input = cc_common.create_linker_input(
424        owner = ctx.label,
425        libraries = depset(direct = [lib_to_link] if lib_to_link != None else []),
426    )
427    linking_context = cc_common.create_linking_context(
428        linker_inputs = depset(direct = [linker_input]),
429    )
430    return [
431        CcInfo(
432            compilation_context = compilation_context,
433            linking_context = linking_context,
434        ),
435        linker_input,
436    ]
437
438# Check that -l<lib> requested via linkopts is supported by the toolchain.
439def check_valid_ldlibs(ctx, linkopts):
440    libs_in_linkopts = [lo for lo in linkopts if lo.startswith("-l")]
441    if not libs_in_linkopts:
442        return
443
444    # Android
445    if ctx.target_platform_has_constraint(ctx.attr._android_constraint[platform_common.ConstraintValueInfo]):
446        fail("Library requested via -l is not supported for device builds. Use implementation_deps instead.")
447
448    libs_available = []
449
450    # linux
451    if ctx.target_platform_has_constraint(ctx.attr._linux_constraint[platform_common.ConstraintValueInfo]):
452        libs_available = cc_constants.LinuxAvailableLibraries
453
454    # darwin
455    if ctx.target_platform_has_constraint(ctx.attr._darwin_constraint[platform_common.ConstraintValueInfo]):
456        libs_available = cc_constants.DarwinAvailableLibraries
457
458    # windows
459    if ctx.target_platform_has_constraint(ctx.attr._windows_constraint[platform_common.ConstraintValueInfo]):
460        libs_available = cc_constants.WindowsAvailableLibraries
461
462    bad_libs = [lib for lib in libs_in_linkopts if lib not in libs_available]
463    if bad_libs:
464        fail("Host library(s) requested via -l is not available in the toolchain. Got: %s, Supported: %s" % (bad_libs, libs_available))
465
466def path_in_list(path, list):
467    path_parts = paths.normalize(path).split("/")
468    found = False
469    for value in list:
470        value_parts = paths.normalize(value).split("/")
471        if len(value_parts) > len(path_parts):
472            continue
473        match = True
474        for i in range(len(value_parts)):
475            if path_parts[i] != value_parts[i]:
476                match = False
477                break
478        if match == True:
479            found = True
480    return found
481