# Copyright (C) 2023 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_skylib//lib:paths.bzl", "paths") load("@bazel_skylib//rules:common_settings.bzl", "BuildSettingInfo") load( "@bazel_tools//tools/cpp:cc_toolchain_config_lib.bzl", "action_config", "tool", "tool_path", ) load("//build/bazel/platforms/arch/variants:constants.bzl", _arch_constants = "constants") load( ":cc_toolchain_constants.bzl", "arch_to_variants", "variant_constraints", "variant_name", "x86_64_host_toolchains", "x86_64_musl_host_toolchains", "x86_host_toolchains", "x86_musl_host_toolchains", _actions = "actions", _enabled_features = "enabled_features", _generated_config_constants = "generated_config_constants", _generated_sanitizer_constants = "generated_sanitizer_constants", ) load( ":cc_toolchain_features.bzl", "get_features", "int_overflow_ignorelist_filename", "int_overflow_ignorelist_path", ) # Clang-specific configuration. _ClangVersionInfo = provider(fields = ["clang_version", "includes"]) CLANG_TOOLS = [ "llvm-ar", "llvm-dwarfdump", "llvm-nm", "llvm-objcopy", "llvm-objdump", "llvm-readelf", "llvm-strip", "clang-tidy", "clang-tidy.sh", "clang-tidy.real", ] CLANG_TEST_TOOLS = [ "llvm-readelf", "llvm-nm", ] def clang_tool_output_group(tool_name): return tool_name.replace("-", "_").replace(".", "_") _libclang_rt_prebuilt_map = { "android_arm": "libclang_rt.builtins-arm-android.a", "android_arm64": "libclang_rt.builtins-aarch64-android.a", "android_x86": "libclang_rt.builtins-i686-android.a", "android_x86_64": "libclang_rt.builtins-x86_64-android.a", "linux_bionic_x86_64": "libclang_rt.builtins-x86_64-android.a", "linux_glibc_x86": "libclang_rt.builtins-i386.a", "linux_glibc_x86_64": "libclang_rt.builtins-x86_64.a", "linux_musl_x86": "i686-unknown-linux-musl/lib/linux/libclang_rt.builtins-i386.a", "linux_musl_x86_64": "x86_64-unknown-linux-musl/lib/linux/libclang_rt.builtins-x86_64.a", } _libclang_rt_ubsan_minimal_prebuilt_map = { "android_arm": "libclang_rt.ubsan_minimal-arm-android.a", "android_arm64": "libclang_rt.ubsan_minimal-aarch64-android.a", "android_x86": "libclang_rt.ubsan_minimal-i686-android.a", "android_x86_64": "libclang_rt.ubsan_minimal-x86_64-android.a", "linux_bionic_x86_64": "libclang_rt.ubsan_minimal-x86_64-android.a", "linux_glibc_x86": "libclang_rt.ubsan_minimal-i386.a", "linux_glibc_x86_64": "libclang_rt.ubsan_minimal-x86_64.a", "linux_musl_x86": "i686-unknown-linux-musl/lib/linux/libclang_rt.ubsan_minimal-i386.a", "linux_musl_x86_64": "x86_64-unknown-linux-musl/lib/linux/libclang_rt.ubsan_minimal-x86_64.a", } def _clang_version_info_impl(ctx): clang_version = ctx.attr.clang_version[BuildSettingInfo].value clang_version_directory = paths.join(ctx.label.package, clang_version) clang_version_dir_prefix = clang_version_directory + "/" clang_short_version = ctx.attr.clang_short_version[BuildSettingInfo].value all_files = {} # a set to do fast prebuilt lookups later output_groups = { "compiler_clang_includes": [], "compiler_binaries": [], "linker_binaries": [], "ar_files": [], "clang_test_tools": [], } for file in ctx.files.clang_files: if not file.short_path.startswith(clang_version_dir_prefix): continue file_path = file.short_path.removeprefix(clang_version_dir_prefix) all_files[file_path] = file file_path_parts = file_path.split("/") if file_path_parts[:2] == ["lib", "clang"] and file_path_parts[4] == "include": output_groups["compiler_clang_includes"].append(file) # /lib/clang/*/include/** if file_path_parts[0] == "bin" and len(file_path_parts) == 2: output_groups["linker_binaries"].append(file) # /bin/* if file.basename.startswith("clang"): output_groups["compiler_binaries"].append(file) # /bin/clang* if file.basename == "llvm-ar": output_groups["ar_files"].append(file) # /bin/llvm-ar if file.basename in CLANG_TEST_TOOLS: output_groups["clang_test_tools"].append(file) if file.basename in CLANG_TOOLS: output_groups[clang_tool_output_group(file.basename)] = [file] libclang_rt_prefix = "lib/clang/%s/lib/linux" % clang_short_version for arch, path in _libclang_rt_prebuilt_map.items(): file_path = paths.join(libclang_rt_prefix, path) if file_path in all_files: output_groups["libclang_rt_builtins_" + arch] = [all_files[file_path]] else: fail("could not find libclang_rt_builtin for `%s` at path `%s`" % (arch, file_path)) for arch, path in _libclang_rt_ubsan_minimal_prebuilt_map.items(): file_path = paths.join(libclang_rt_prefix, path) if file_path in all_files: output_groups["libclang_rt_ubsan_minimal_" + arch] = [all_files[file_path]] else: fail("could not find libclang_rt_ubsan_minimal for `%s` at path `%s`" % (arch, file_path)) return [ _ClangVersionInfo( clang_version = clang_version, includes = [ paths.join(clang_version_directory, "lib", "clang", clang_short_version, "include"), ], ), OutputGroupInfo( **output_groups ), ] clang_version_info = rule( implementation = _clang_version_info_impl, attrs = { "clang_version": attr.label(mandatory = True), "clang_short_version": attr.label(mandatory = True), "clang_files": attr.label_list( allow_files = True, ), }, ) def _tool_paths(clang_version_info): return [ tool_path( name = "gcc", path = clang_version_info.clang_version + "/bin/clang", ), tool_path( name = "ld", path = clang_version_info.clang_version + "/bin/ld.lld", ), tool_path( name = "ar", path = clang_version_info.clang_version + "/bin/llvm-ar", ), tool_path( name = "cpp", path = "/bin/false", ), tool_path( name = "gcov", path = clang_version_info.clang_version + "/bin/llvm-profdata", ), tool_path( name = "llvm-cov", path = clang_version_info.clang_version + "/bin/llvm-cov", ), tool_path( name = "nm", path = clang_version_info.clang_version + "/bin/llvm-nm", ), tool_path( name = "objcopy", path = clang_version_info.clang_version + "/bin/llvm-objcopy", ), tool_path( name = "objdump", path = clang_version_info.clang_version + "/bin/llvm-objdump", ), # Soong has a wrapper around strip. # https://cs.android.com/android/platform/superproject/+/master:build/soong/cc/strip.go;l=62;drc=master # https://cs.android.com/android/platform/superproject/+/master:build/soong/cc/builder.go;l=991-1025;drc=master tool_path( name = "strip", path = clang_version_info.clang_version + "/bin/llvm-strip", ), tool_path( name = "clang++", path = clang_version_info.clang_version + "/bin/clang++", ), ] # Set tools used for all actions in Android's C++ builds. def _create_action_configs(tool_paths, target_os): action_configs = [] tool_name_to_tool = {} for tool_path in tool_paths: tool_name_to_tool[tool_path.name] = tool(path = tool_path.path) # use clang for assembler actions # https://cs.android.com/android/_/android/platform/build/soong/+/a14b18fb31eada7b8b58ecd469691c20dcb371b3:cc/builder.go;l=616;drc=769a51cc6aa9402c1c55e978e72f528c26b6a48f for action_name in _actions.assemble: action_configs.append(action_config( action_name = action_name, enabled = True, tools = [tool_name_to_tool["gcc"]], implies = [ "user_compile_flags", "compiler_input_flags", "compiler_output_flags", ], )) # use clang++ for compiling C++ # https://cs.android.com/android/_/android/platform/build/soong/+/a14b18fb31eada7b8b58ecd469691c20dcb371b3:cc/builder.go;l=627;drc=769a51cc6aa9402c1c55e978e72f528c26b6a48f action_configs.append(action_config( action_name = _actions.cpp_compile, enabled = True, tools = [tool_name_to_tool["clang++"]], implies = [ "user_compile_flags", "compiler_input_flags", "compiler_output_flags", ], )) # use clang for compiling C # https://cs.android.com/android/_/android/platform/build/soong/+/a14b18fb31eada7b8b58ecd469691c20dcb371b3:cc/builder.go;l=623;drc=769a51cc6aa9402c1c55e978e72f528c26b6a48f action_configs.append(action_config( action_name = _actions.c_compile, enabled = True, # this is clang, but needs to be called gcc for legacy reasons. # to avoid this, we need to set `no_legacy_features`: b/201257475 # http://google3/third_party/bazel/src/main/java/com/google/devtools/build/lib/rules/cpp/CcModule.java;l=1106-1122;rcl=398974497 # http://google3/third_party/bazel/src/main/java/com/google/devtools/build/lib/rules/cpp/CcModule.java;l=1185-1187;rcl=398974497 tools = [tool_name_to_tool["gcc"]], implies = [ "user_compile_flags", "compiler_input_flags", "compiler_output_flags", ], )) rpath_features = [] if target_os not in ("android", "windows"): rpath_features.append("runtime_library_search_directories") # use clang++ for dynamic linking # https://cs.android.com/android/_/android/platform/build/soong/+/a14b18fb31eada7b8b58ecd469691c20dcb371b3:cc/builder.go;l=790;drc=769a51cc6aa9402c1c55e978e72f528c26b6a48f for action_name in [_actions.cpp_link_dynamic_library, _actions.cpp_link_nodeps_dynamic_library]: action_configs.append(action_config( action_name = action_name, enabled = True, tools = [tool_name_to_tool["clang++"]], implies = [ "shared_flag", "linkstamps", "output_execpath_flags", "library_search_directories", "libraries_to_link", "user_link_flags", "linker_param_file", ] + rpath_features, )) # use clang++ for linking cc executables action_configs.append(action_config( action_name = _actions.cpp_link_executable, enabled = True, tools = [tool_name_to_tool["clang++"]], implies = [ "linkstamps", "output_execpath_flags", "library_search_directories", "libraries_to_link", "user_link_flags", "linker_param_file", ] + rpath_features, )) # use llvm-ar for creating static archives action_configs.append(action_config( action_name = _actions.cpp_link_static_library, enabled = True, tools = [tool_name_to_tool["ar"]], implies = [ "linker_param_file", ], )) # unused, but Bazel complains if there isn't an action config for strip action_configs.append(action_config( action_name = _actions.strip, enabled = True, tools = [tool_name_to_tool["strip"]], # This doesn't imply any feature, because Bazel currently mimics # Soong by running strip actions in a rule (stripped_shared_library). )) # use llvm-objcopy to remove addrsig sections from partially linked objects action_configs.append(action_config( action_name = "objcopy", enabled = True, tools = [tool_name_to_tool["objcopy"]], )) return action_configs def _cc_toolchain_config_impl(ctx): clang_version_info = ctx.attr.clang_version[_ClangVersionInfo] tool_paths = _tool_paths(clang_version_info) action_configs = _create_action_configs(tool_paths, ctx.attr.target_os) builtin_include_dirs = [] builtin_include_dirs.extend(clang_version_info.includes) # b/186035856: Do not add anything to this list. builtin_include_dirs.extend(_generated_config_constants.CommonGlobalIncludes) crt_files = struct( shared_library_crtbegin = ctx.file.shared_library_crtbegin, shared_library_crtend = ctx.file.shared_library_crtend, shared_binary_crtbegin = ctx.file.shared_binary_crtbegin, static_binary_crtbegin = ctx.file.static_binary_crtbegin, binary_crtend = ctx.file.binary_crtend, ) features = get_features( ctx, builtin_include_dirs, crt_files, ) # This is so that Bazel doesn't validate .d files against the set of headers # declared in BUILD files (Blueprint files don't contain that data). %workspace%/ # will be replaced with the empty string by Bazel (essentially making it relative # to the build directory), so Bazel will skip the validations of all the headers # in .d files. For details please see CppCompileAction.validateInclude. We add # %workspace% after calling get_features so it won't be exposed as an -isystem flag. builtin_include_dirs.append("%workspace%/") return cc_common.create_cc_toolchain_config_info( ctx = ctx, toolchain_identifier = ctx.attr.toolchain_identifier, tool_paths = tool_paths, features = features, action_configs = action_configs, cxx_builtin_include_directories = builtin_include_dirs, target_cpu = "_".join([ctx.attr.target_os, ctx.attr.target_arch]), # The attributes below are required by the constructor, but don't # affect actions at all. host_system_name = "__toolchain_host_system_name__", target_system_name = "__toolchain_target_system_name__", target_libc = "__toolchain_target_libc__", compiler = "__toolchain_compiler__", abi_version = "__toolchain_abi_version__", abi_libc_version = "__toolchain_abi_libc_version__", ) _cc_toolchain_config = rule( implementation = _cc_toolchain_config_impl, attrs = { "target_os": attr.string(mandatory = True), "target_arch": attr.string(mandatory = True), "toolchain_identifier": attr.string(mandatory = True), "clang_version": attr.label(mandatory = True, providers = [_ClangVersionInfo]), "target_flags": attr.string_list(default = []), "compiler_flags": attr.string_list(default = []), "linker_flags": attr.string_list(default = []), "libclang_rt_builtin": attr.label(allow_single_file = True), "libclang_rt_ubsan_minimal": attr.label(allow_single_file = True), # crtbegin and crtend libraries for compiling cc_library_shared and # cc_binary against the Bionic runtime "shared_library_crtbegin": attr.label(allow_single_file = True, cfg = "target"), "shared_library_crtend": attr.label(allow_single_file = True, cfg = "target"), "shared_binary_crtbegin": attr.label(allow_single_file = True, cfg = "target"), "static_binary_crtbegin": attr.label(allow_single_file = True, cfg = "target"), "binary_crtend": attr.label(allow_single_file = True, cfg = "target"), "rtti_toggle": attr.bool(default = True), "_auto_zero_initialize": attr.label( default = "//build/bazel/toolchains/clang/host/linux-x86:auto_zero_initialize_env", ), "_auto_pattern_initialize": attr.label( default = "//build/bazel/toolchains/clang/host/linux-x86:auto_pattern_initialize_env", ), "_auto_uninitialize": attr.label( default = "//build/bazel/toolchains/clang/host/linux-x86:auto_uninitialize_env", ), "_use_ccache": attr.label( default = "//build/bazel/toolchains/clang/host/linux-x86:use_ccache_env", ), "_llvm_next": attr.label( default = "//build/bazel/toolchains/clang/host/linux-x86:llvm_next_env", ), "_allow_unknown_warning_option": attr.label( default = "//build/bazel/toolchains/clang/host/linux-x86:allow_unknown_warning_option_env", ), "_device_max_page_size_supported": attr.label( default = "//build/bazel/product_config:device_max_page_size_supported", ), "_device_no_bionic_page_size_macro": attr.label( default = "//build/bazel/product_config:device_no_bionic_page_size_macro", ), "_clang_default_debug_level": attr.label( default = "//build/bazel/toolchains/clang/host/linux-x86:clang_default_debug_level", ), }, provides = [CcToolchainConfigInfo], ) # macro to expand feature flags for a toolchain # we do not pass these directly to the toolchain so the order can # be specified per toolchain def expand_feature_flags(arch_variant, arch_variant_to_features = {}, flag_map = {}): flags = [] features = _enabled_features(arch_variant, arch_variant_to_features) for feature in features: flags.extend(flag_map.get(feature, [])) return flags # Macro to set up both the toolchain and the config. def android_cc_toolchain( name, target_os = None, target_arch = None, clang_version = None, gcc_toolchain = None, # If false, the crt version and "normal" version of this toolchain are identical. crt = None, libclang_rt_builtin = None, libclang_rt_ubsan_minimal = None, target_flags = [], compiler_flags = [], linker_flags = [], toolchain_identifier = None, rtti_toggle = True): extra_linker_paths = [] libclang_rt_path = None if libclang_rt_builtin: libclang_rt_path = libclang_rt_builtin extra_linker_paths.append(libclang_rt_path) libclang_rt_ubsan_minimal_path = None if libclang_rt_ubsan_minimal: libclang_rt_ubsan_minimal_path = libclang_rt_ubsan_minimal if gcc_toolchain: gcc_toolchain_path = "//%s:tools" % gcc_toolchain extra_linker_paths.append(gcc_toolchain_path) extra_linker_paths.append("//%s:%s" % ( _generated_sanitizer_constants.CfiExportsMapPath, _generated_sanitizer_constants.CfiExportsMapFilename, )) extra_linker_paths.append("//%s:%s" % ( _generated_sanitizer_constants.CfiBlocklistPath, _generated_sanitizer_constants.CfiBlocklistFilename, )) common_toolchain_config = dict( [ ("target_os", target_os), ("target_arch", target_arch), ("clang_version", clang_version), ("libclang_rt_builtin", libclang_rt_path), ("libclang_rt_ubsan_minimal", libclang_rt_ubsan_minimal_path), ("target_flags", target_flags), ("compiler_flags", compiler_flags), ("linker_flags", linker_flags), ("rtti_toggle", rtti_toggle), ], ) _cc_toolchain_config( name = "%s_nocrt_config" % name, toolchain_identifier = toolchain_identifier + "_nocrt", **common_toolchain_config ) # Create the filegroups needed for sandboxing toolchain inputs to C++ actions. native.filegroup( name = "%s_compiler_clang_includes" % name, srcs = [clang_version], output_group = "compiler_clang_includes", ) native.filegroup( name = "%s_compiler_binaries" % name, srcs = [clang_version], output_group = "compiler_binaries", ) native.filegroup( name = "%s_linker_binaries" % name, srcs = [clang_version], output_group = "linker_binaries", ) native.filegroup( name = "%s_ar_files" % name, srcs = [clang_version], output_group = "ar_files", ) native.filegroup( name = "%s_compiler_files" % name, srcs = [ "%s_compiler_binaries" % name, "%s_compiler_clang_includes" % name, "@//%s:%s" % (int_overflow_ignorelist_path, int_overflow_ignorelist_filename), ], ) native.filegroup( name = "%s_linker_files" % name, srcs = ["%s_linker_binaries" % name] + extra_linker_paths, ) native.filegroup( name = "%s_all_files" % name, srcs = [ "%s_compiler_files" % name, "%s_linker_files" % name, "%s_ar_files" % name, ], ) native.cc_toolchain( name = name + "_nocrt", all_files = "%s_all_files" % name, as_files = "//:empty", # Note the "//" prefix, see comment above ar_files = "%s_ar_files" % name, compiler_files = "%s_compiler_files" % name, dwp_files = ":empty", linker_files = "%s_linker_files" % name, objcopy_files = ":empty", strip_files = ":empty", supports_param_files = 0, toolchain_config = ":%s_nocrt_config" % name, toolchain_identifier = toolchain_identifier + "_nocrt", ) if crt: # Write the toolchain config. _cc_toolchain_config( name = "%s_config" % name, toolchain_identifier = toolchain_identifier, shared_library_crtbegin = crt.shared_library_crtbegin, shared_library_crtend = crt.shared_library_crtend, shared_binary_crtbegin = crt.shared_binary_crtbegin, static_binary_crtbegin = crt.static_binary_crtbegin, binary_crtend = crt.binary_crtend, **common_toolchain_config ) native.filegroup( name = "%s_crt_libs" % name, srcs = [ crt.shared_library_crtbegin, crt.shared_library_crtend, crt.shared_binary_crtbegin, crt.static_binary_crtbegin, crt.binary_crtend, ], ) native.filegroup( name = "%s_linker_files_with_crt" % name, srcs = [ "%s_linker_files" % name, "%s_crt_libs" % name, ], ) # Create the actual cc_toolchain. # The dependency on //:empty is intentional; it's necessary so that Bazel # can parse .d files correctly (see the comment in $TOP/BUILD) native.cc_toolchain( name = name, all_files = "%s_all_files" % name, as_files = "//:empty", # Note the "//" prefix, see comment above ar_files = "%s_ar_files" % name, compiler_files = "%s_compiler_files" % name, dwp_files = ":empty", linker_files = "%s_linker_files_with_crt" % name, objcopy_files = ":empty", strip_files = ":empty", supports_param_files = 0, toolchain_config = ":%s_config" % name, toolchain_identifier = toolchain_identifier, exec_transition_for_inputs = False, ) else: _cc_toolchain_config( name = "%s_config" % name, toolchain_identifier = toolchain_identifier, **common_toolchain_config ) native.alias( name = name, actual = name + "_nocrt", ) def _toolchain_name(arch, variant, nocrt = False): return "cc_toolchain_{arch}{variant}{nocrt}_def".format( arch = arch, variant = variant_name(variant), nocrt = "_nocrt" if nocrt else "", ) def toolchain_definition(arch, variant, nocrt = False): """Macro to create a toolchain with a standardized name The name used here must match that used in cc_register_toolchains. """ name = _toolchain_name(arch, variant, nocrt) native.toolchain( name = name, exec_compatible_with = [ "//build/bazel_common_rules/platforms/arch:x86_64", "//build/bazel_common_rules/platforms/os:linux", ], target_compatible_with = [ "//build/bazel_common_rules/platforms/arch:%s" % arch, "//build/bazel_common_rules/platforms/os:android", ] + variant_constraints( variant, _arch_constants.AndroidArchToVariantToFeatures[arch], ), toolchain = ":cc_toolchain_{arch}{variant}{nocrt}".format( arch = arch, variant = variant_name(variant), nocrt = "_nocrt" if nocrt else "", ), toolchain_type = ( ":nocrt_toolchain" if nocrt else "@bazel_tools//tools/cpp:toolchain_type" ), ) def cc_register_toolchains(): """Register cc_toolchains for device and host platforms. This function ensures that generic (non-variant) device toolchains are registered last. """ generic_toolchains = [] arch_variant_toolchains = [] cpu_variant_toolchains = [] for arch, variants in arch_to_variants.items(): for variant in variants: if not variant.arch_variant: generic_toolchains.append((arch, variant)) elif not variant.cpu_variant: arch_variant_toolchains.append((arch, variant)) else: cpu_variant_toolchains.append((arch, variant)) target_toolchains = [ _toolchain_name(arch, variant, nocrt = nocrt) for nocrt in [False, True] for arch, variant in ( # Ordering is important here: more specific toolchains must be # registered before more generic toolchains cpu_variant_toolchains + arch_variant_toolchains + generic_toolchains ) ] host_toolchains = [ tc[0] + "_def" for tc in x86_64_host_toolchains + x86_host_toolchains + x86_64_musl_host_toolchains + x86_musl_host_toolchains ] native.register_toolchains(*[ "//prebuilts/clang/host/linux-x86:" + tc for tc in host_toolchains + target_toolchains ])