# Part of the Carbon Language project, under the Apache License v2.0 with LLVM # Exceptions. See /LICENSE for license information. # SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception """A Starlark cc_toolchain configuration rule""" load( "@bazel_tools//tools/cpp:cc_toolchain_config_lib.bzl", "action_config", "feature", "feature_set", "flag_group", "flag_set", "tool", "tool_path", "variable_with_value", "with_feature_set", ) load("@bazel_tools//tools/build_defs/cc:action_names.bzl", "ACTION_NAMES") load( ":clang_detected_variables.bzl", "clang_include_dirs_list", "clang_resource_dir", "llvm_bindir", "sysroot_dir", ) all_compile_actions = [ ACTION_NAMES.c_compile, ACTION_NAMES.cpp_compile, ACTION_NAMES.linkstamp_compile, ACTION_NAMES.assemble, ACTION_NAMES.preprocess_assemble, ACTION_NAMES.cpp_header_parsing, ACTION_NAMES.cpp_module_compile, ACTION_NAMES.cpp_module_codegen, ] all_cpp_compile_actions = [ ACTION_NAMES.cpp_compile, ACTION_NAMES.linkstamp_compile, ACTION_NAMES.cpp_header_parsing, ACTION_NAMES.cpp_module_compile, ACTION_NAMES.cpp_module_codegen, ] preprocessor_compile_actions = [ ACTION_NAMES.c_compile, ACTION_NAMES.cpp_compile, ACTION_NAMES.linkstamp_compile, ACTION_NAMES.preprocess_assemble, ACTION_NAMES.cpp_header_parsing, ACTION_NAMES.cpp_module_compile, ] codegen_compile_actions = [ ACTION_NAMES.c_compile, ACTION_NAMES.cpp_compile, ACTION_NAMES.linkstamp_compile, ACTION_NAMES.assemble, ACTION_NAMES.preprocess_assemble, ACTION_NAMES.cpp_module_codegen, ] all_link_actions = [ ACTION_NAMES.cpp_link_executable, ACTION_NAMES.cpp_link_dynamic_library, ACTION_NAMES.cpp_link_nodeps_dynamic_library, ] def _impl(ctx): tool_paths = [ tool_path(name = "ar", path = llvm_bindir + "/llvm-ar"), tool_path(name = "ld", path = llvm_bindir + "/ld.lld"), tool_path(name = "cpp", path = llvm_bindir + "/clang-cpp"), tool_path(name = "gcc", path = llvm_bindir + "/clang++"), tool_path(name = "dwp", path = llvm_bindir + "/llvm-dwp"), tool_path(name = "gcov", path = llvm_bindir + "/llvm-cov"), tool_path(name = "nm", path = llvm_bindir + "/llvm-nm"), tool_path(name = "objcopy", path = llvm_bindir + "/llvm-objcopy"), tool_path(name = "objdump", path = llvm_bindir + "/llvm-objdump"), tool_path(name = "strip", path = llvm_bindir + "/llvm-strip"), ] action_configs = [ action_config(action_name = name, enabled = True, tools = [tool(path = llvm_bindir + "/clang")]) for name in [ACTION_NAMES.c_compile] ] + [ action_config(action_name = name, enabled = True, tools = [tool(path = llvm_bindir + "/clang++")]) for name in all_cpp_compile_actions ] + [ action_config(action_name = name, enabled = True, tools = [tool(path = llvm_bindir + "/clang++")]) for name in all_link_actions ] + [ action_config(action_name = name, enabled = True, tools = [tool(path = llvm_bindir + "/llvm-ar")]) for name in [ACTION_NAMES.cpp_link_static_library] ] + [ action_config(action_name = name, enabled = True, tools = [tool(path = llvm_bindir + "/llvm-strip")]) for name in [ACTION_NAMES.strip] ] default_flags_feature = feature( name = "default_flags", enabled = True, flag_sets = [ flag_set( actions = all_compile_actions + all_link_actions, flag_groups = ([ flag_group( flags = [ "-no-canonical-prefixes", "-fcolor-diagnostics", ], ), ]), ), flag_set( actions = all_compile_actions, flag_groups = ([ flag_group( flags = [ "-Werror", "-Wall", "-Wextra", "-Wthread-safety", "-Wself-assign", "-Wimplicit-fallthrough", "-Wctad-maybe-unsupported", # Unfortunately, LLVM isn't clean for this warning. "-Wno-unused-parameter", # We use partial sets of designated initializers in # test code. "-Wno-missing-field-initializers", # Compile actions shouldn't link anything. "-c", ], ), flag_group( expand_if_available = "output_assembly_file", flags = ["-S"], ), flag_group( expand_if_available = "output_preprocess_file", flags = ["-E"], ), flag_group( flags = ["-MD", "-MF", "%{dependency_file}"], expand_if_available = "dependency_file", ), flag_group( flags = ["-frandom-seed=%{output_file}"], expand_if_available = "output_file", ), ]), ), flag_set( actions = all_cpp_compile_actions + all_link_actions, flag_groups = ([ flag_group( flags = [ "-std=c++17", "-stdlib=libc++", ], ), ]), ), flag_set( actions = codegen_compile_actions, flag_groups = ([ flag_group( flags = [ "-DNDEBUG", "-ffunction-sections", "-fdata-sections", ], ), ]), with_features = [with_feature_set(features = ["opt"])], ), flag_set( actions = codegen_compile_actions, flag_groups = [ flag_group(flags = ["-fPIC"], expand_if_available = "pic"), ], ), flag_set( actions = preprocessor_compile_actions, flag_groups = [ flag_group( flags = [ # Disable a warning and override builtin macros to # ensure a hermetic build. "-Wno-builtin-macro-redefined", "-D__DATE__=\"redacted\"", "-D__TIMESTAMP__=\"redacted\"", "-D__TIME__=\"redacted\"", ], ), flag_group( flags = ["-D%{preprocessor_defines}"], iterate_over = "preprocessor_defines", ), flag_group( flags = ["-include", "%{includes}"], iterate_over = "includes", expand_if_available = "includes", ), flag_group( flags = ["-iquote", "%{quote_include_paths}"], iterate_over = "quote_include_paths", ), flag_group( flags = ["-I%{include_paths}"], iterate_over = "include_paths", ), flag_group( flags = ["-isystem", "%{system_include_paths}"], iterate_over = "system_include_paths", ), ], ), flag_set( actions = [ ACTION_NAMES.cpp_link_dynamic_library, ACTION_NAMES.cpp_link_nodeps_dynamic_library, ], flag_groups = [flag_group(flags = ["-shared"])], ), flag_set( actions = [ ACTION_NAMES.cpp_link_executable, ], flag_groups = [ flag_group( flags = ["-pie"], expand_if_available = "force_pic", ), ], ), flag_set( actions = all_link_actions, flag_groups = [ flag_group( flags = ["-Wl,--gdb-index"], expand_if_available = "is_using_fission", ), flag_group( flags = ["-Wl,-S"], expand_if_available = "strip_debug_symbols", ), flag_group( flags = ["-L%{library_search_directories}"], iterate_over = "library_search_directories", expand_if_available = "library_search_directories", ), flag_group( iterate_over = "runtime_library_search_directories", flags = [ "-Wl,-rpath,$ORIGIN/%{runtime_library_search_directories}", ], expand_if_available = "runtime_library_search_directories", ), ], ), ], ) # Handle different levels of optimization with individual features so that # they can be ordered and the defaults can override the minimal settings if # both are enabled. minimal_optimization_flags = feature( name = "minimal_optimization_flags", flag_sets = [flag_set( actions = codegen_compile_actions, flag_groups = [flag_group(flags = [ "-O1", "-mllvm", "-fast-isel", ])], )], ) default_optimization_flags = feature( name = "default_optimization_flags", enabled = True, requires = [feature_set(["opt"])], flag_sets = [flag_set( actions = codegen_compile_actions, flag_groups = [flag_group(flags = [ "-O3", ])], )], ) # Handle different levels and forms of debug info emission with individual # features so that they can be ordered and the defaults can override the # minimal settings if both are enabled. minimal_debug_info_flags = feature( name = "minimal_debug_info_flags", flag_sets = [flag_set( actions = codegen_compile_actions, flag_groups = [flag_group(flags = [ "-gmlt", ])], )], ) default_debug_info_flags = feature( name = "default_debug_info_flags", enabled = True, flag_sets = [ flag_set( actions = codegen_compile_actions, flag_groups = ([ flag_group( flags = ["-g"], ), ]), with_features = [with_feature_set(features = ["dbg"])], ), flag_set( actions = codegen_compile_actions, flag_groups = [ flag_group( flags = ["-gsplit-dwarf", "-g"], expand_if_available = "per_object_debug_info_file", ), ], ), ], ) # This feature can be enabled in conjunction with any optimizations to # ensure accurate call stacks and backtraces for profilers or errors. preserve_call_stacks = feature( name = "preserve_call_stacks", flag_sets = [flag_set( actions = codegen_compile_actions, flag_groups = [flag_group(flags = [ # Ensure good backtraces by preserving frame pointers and # disabling tail call elimination. "-fno-omit-frame-pointer", "-mno-omit-leaf-frame-pointer", "-fno-optimize-sibling-calls", ])], )], ) sysroot_feature = feature( name = "sysroot", enabled = True, flag_sets = [ flag_set( actions = all_compile_actions + all_link_actions, flag_groups = [ flag_group( flags = ["--sysroot=%{sysroot}"], expand_if_available = "sysroot", ), ], ), ], ) use_module_maps = feature( name = "use_module_maps", requires = [feature_set(features = ["module_maps"])], flag_sets = [ flag_set( actions = [ ACTION_NAMES.c_compile, ACTION_NAMES.cpp_compile, ACTION_NAMES.cpp_header_parsing, ACTION_NAMES.cpp_module_compile, ], flag_groups = [ # These flag groups are separate so they do not expand to # the cross product of the variables. flag_group(flags = ["-fmodule-name=%{module_name}"]), flag_group( flags = ["-fmodule-map-file=%{module_map_file}"], ), ], ), ], ) # Tell bazel we support module maps in general, so they will be generated # for all c/c++ rules. # Note: not all C++ rules support module maps; thus, do not imply this # feature from other features - instead, require it. module_maps = feature( name = "module_maps", enabled = True, implies = [ # "module_map_home_cwd", # "module_map_without_extern_module", # "generate_submodules", ], ) layering_check = feature( name = "layering_check", implies = ["use_module_maps"], flag_sets = [ flag_set( actions = [ ACTION_NAMES.c_compile, ACTION_NAMES.cpp_compile, ACTION_NAMES.cpp_header_parsing, ACTION_NAMES.cpp_module_compile, ], flag_groups = [ flag_group(flags = [ "-fmodules-strict-decluse", "-Wprivate-header", ]), flag_group( iterate_over = "dependent_module_map_files", flags = [ "-fmodule-map-file=%{dependent_module_map_files}", ], ), ], ), ], ) sanitizer_common_flags = feature( name = "sanitizer_common_flags", requires = [feature_set(["nonhost"])], implies = ["minimal_optimization_flags", "minimal_debug_info_flags", "preserve_call_stacks"], flag_sets = [flag_set( actions = all_link_actions, flag_groups = [flag_group(flags = [ "-static-libsan", ])], )], ) asan = feature( name = "asan", requires = [feature_set(["nonhost"])], implies = ["sanitizer_common_flags"], flag_sets = [flag_set( actions = all_compile_actions + all_link_actions, flag_groups = [flag_group(flags = [ "-fsanitize=address,undefined", "-fsanitize-address-use-after-scope", # We don't need the recovery behavior of UBSan as we expect # builds to be clean. Not recoverying is a bit cheaper. "-fno-sanitize-recover=undefined", # Don't embed the full path name for files. This limits the size # and combined with line numbers is unlikely to result in many # ambiguities. "-fsanitize-undefined-strip-path-components=-1", # Force some expensive UBSan checks to the cheaper trap mode. # The dedicated debugging message is unlikely to be critical for # these. "-fsanitize-trap=alignment,bool,null,return,unreachable", # Needed due to clang AST issues, such as in # clang/AST/Redeclarable.h line 199. "-fno-sanitize=vptr", ])], )], ) enable_asan_in_fastbuild = feature( name = "enable_asan_in_fastbuild", enabled = True, requires = [feature_set(["nonhost", "fastbuild"])], implies = ["asan"], ) fuzzer = feature( name = "fuzzer", requires = [feature_set(["nonhost"])], implies = ["asan"], flag_sets = [flag_set( actions = all_compile_actions + all_link_actions, flag_groups = [flag_group(flags = [ "-fsanitize=fuzzer", ])], )], ) linux_flags_feature = feature( name = "linux_flags", enabled = True, flag_sets = [ flag_set( actions = all_link_actions, flag_groups = ([ flag_group( flags = [ "-fuse-ld=lld", "-stdlib=libc++", "-unwindlib=libunwind", # Force the C++ standard library and runtime # libraries to be statically linked. This works even # with libc++ and libunwind despite the names, # provided libc++ is built with two CMake options: # - `-DCMAKE_POSITION_INDEPENDENT_CODE=ON` # - `-DLIBCXX_STATICALLY_LINK_ABI_IN_STATIC_LIBRARY` # These are both required because of PR43604 # (impacting at least Debian packages of libc++) and # PR46321 (impacting most other packages). # We recommend using Homebrew's LLVM install on # Linux. "-static-libstdc++", "-static-libgcc", # Link with Clang's runtime library. This is always # linked statically. "-rtlib=compiler-rt", # Explicitly add LLVM libs to the search path to # preempt the detected GCC installation's library # paths. Those might have a system installed libc++ # and we want to find the one next to our Clang. "-L" + llvm_bindir + "/../lib", # Link with pthread. "-lpthread", ], ), ]), ), ], ) default_link_libraries_feature = feature( name = "default_link_libraries", enabled = True, flag_sets = [ flag_set( actions = all_link_actions, flag_groups = [ flag_group( flags = ["%{linkstamp_paths}"], iterate_over = "linkstamp_paths", expand_if_available = "linkstamp_paths", ), flag_group( iterate_over = "libraries_to_link", flag_groups = [ flag_group( flags = ["-Wl,--start-lib"], expand_if_equal = variable_with_value( name = "libraries_to_link.type", value = "object_file_group", ), ), flag_group( flags = ["-Wl,-whole-archive"], expand_if_true = "libraries_to_link.is_whole_archive", ), flag_group( flags = ["%{libraries_to_link.object_files}"], iterate_over = "libraries_to_link.object_files", expand_if_equal = variable_with_value( name = "libraries_to_link.type", value = "object_file_group", ), ), flag_group( flags = ["%{libraries_to_link.name}"], expand_if_equal = variable_with_value( name = "libraries_to_link.type", value = "object_file", ), ), flag_group( flags = ["%{libraries_to_link.name}"], expand_if_equal = variable_with_value( name = "libraries_to_link.type", value = "interface_library", ), ), flag_group( flags = ["%{libraries_to_link.name}"], expand_if_equal = variable_with_value( name = "libraries_to_link.type", value = "static_library", ), ), flag_group( flags = ["-l%{libraries_to_link.name}"], expand_if_equal = variable_with_value( name = "libraries_to_link.type", value = "dynamic_library", ), ), flag_group( flags = ["-l:%{libraries_to_link.name}"], expand_if_equal = variable_with_value( name = "libraries_to_link.type", value = "versioned_dynamic_library", ), ), flag_group( flags = ["-Wl,-no-whole-archive"], expand_if_true = "libraries_to_link.is_whole_archive", ), flag_group( flags = ["-Wl,--end-lib"], expand_if_equal = variable_with_value( name = "libraries_to_link.type", value = "object_file_group", ), ), ], expand_if_available = "libraries_to_link", ), # Note that the params file comes at the end, after the # libraries to link above. flag_group( expand_if_available = "linker_param_file", flags = ["@%{linker_param_file}"], ), ], ), ], ) # Place user provided compile flags after all the features so that these # flags can override or customize behavior. The only thing user flags # cannot override is the output file as Bazel depends on that. # # Finally, place the source file (if present) and output file last to make # reading the compile command lines easier for humans. final_flags_feature = feature( name = "final_flags", enabled = True, flag_sets = [ flag_set( actions = all_compile_actions, flag_groups = [ flag_group( flags = ["%{user_compile_flags}"], iterate_over = "user_compile_flags", expand_if_available = "user_compile_flags", ), flag_group( flags = ["%{source_file}"], expand_if_available = "source_file", ), flag_group( expand_if_available = "output_file", flags = ["-o", "%{output_file}"], ), ], ), flag_set( actions = all_link_actions, flag_groups = [ flag_group( flags = ["%{user_link_flags}"], iterate_over = "user_link_flags", expand_if_available = "user_link_flags", ), flag_group( flags = ["-o", "%{output_execpath}"], expand_if_available = "output_execpath", ), ], ), ], ) # Archive actions have an entirely independent set of flags and don't # interact with either compiler or link actions. default_archiver_flags_feature = feature( name = "default_archiver_flags", enabled = True, flag_sets = [ flag_set( actions = [ACTION_NAMES.cpp_link_static_library], flag_groups = [ flag_group(flags = ["rcsD"]), flag_group( flags = ["%{output_execpath}"], expand_if_available = "output_execpath", ), flag_group( iterate_over = "libraries_to_link", flag_groups = [ flag_group( flags = ["%{libraries_to_link.name}"], expand_if_equal = variable_with_value( name = "libraries_to_link.type", value = "object_file", ), ), flag_group( flags = ["%{libraries_to_link.object_files}"], iterate_over = "libraries_to_link.object_files", expand_if_equal = variable_with_value( name = "libraries_to_link.type", value = "object_file_group", ), ), ], expand_if_available = "libraries_to_link", ), flag_group( expand_if_available = "linker_param_file", flags = ["@%{linker_param_file}"], ), ], ), ], ) # Now that we have built up the constituent feature definitions, compose # them, including configuration based on the target platform. Currently, # the target platform is configured with the "cpu" attribute for legacy # reasons. Further, for legacy reasons the default is a Linux OS target and # the x88-64 CPU name is "k8". # First, define features that are simply used to configure others. features = [ feature(name = "dbg"), feature(name = "fastbuild"), feature(name = "host"), feature(name = "no_legacy_features"), feature(name = "nonhost"), feature(name = "opt"), feature(name = "supports_dynamic_linker", enabled = ctx.attr.target_cpu == "k8"), feature(name = "supports_pic", enabled = True), feature(name = "supports_start_end_lib", enabled = ctx.attr.target_cpu == "k8"), ] # The order of the features determines the relative order of flags used. # Start off adding the baseline features. features += [ default_flags_feature, minimal_optimization_flags, default_optimization_flags, minimal_debug_info_flags, default_debug_info_flags, preserve_call_stacks, sysroot_feature, sanitizer_common_flags, asan, enable_asan_in_fastbuild, fuzzer, layering_check, module_maps, use_module_maps, default_archiver_flags_feature, ] # Next, add the features based on the target platform. Here too the # features are order sensitive. We also setup the sysroot here. if ctx.attr.target_cpu == "k8": features += [linux_flags_feature] sysroot = None elif ctx.attr.target_cpu in ["darwin", "darwin_arm64"]: sysroot = sysroot_dir else: fail("Unsupported target platform!") # Finally append the libraries to link and any final flags. features += [ default_link_libraries_feature, final_flags_feature, ] return cc_common.create_cc_toolchain_config_info( ctx = ctx, features = features, action_configs = action_configs, cxx_builtin_include_directories = clang_include_dirs_list + [ # Add Clang's resource directory to the end of the builtin include # directories to cover the use of sanitizer resource files by the driver. clang_resource_dir + "/share", ], builtin_sysroot = sysroot, # This configuration only supports local non-cross builds so derive # everything from the target CPU selected. toolchain_identifier = "local-" + ctx.attr.target_cpu, host_system_name = "local-" + ctx.attr.target_cpu, target_system_name = "local-" + ctx.attr.target_cpu, target_cpu = ctx.attr.target_cpu, # These attributes aren't meaningful at all so just use placeholder # values. target_libc = "local", compiler = "local", abi_version = "local", abi_libc_version = "local", # We do have to pass in our tool paths. tool_paths = tool_paths, ) cc_toolchain_config = rule( implementation = _impl, attrs = { "target_cpu": attr.string(mandatory = True), }, provides = [CcToolchainConfigInfo], )