blob: cb8f24ded5005704b315419eb0887de382c37725 [file] [log] [blame]
# Copyright 2023 The Bazel Authors. All rights reserved.
#
# 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.
"""A helper for creating CcToolchainProvider."""
load(":common/cc/cc_helper.bzl", "cc_helper")
load(":common/paths.bzl", "paths")
load(":common/cc/cc_common.bzl", "cc_common")
cc_internal = _builtins.internal.cc_internal
_TOOL_PATH_ONLY_TOOLS = [
"gcov-tool",
"gcov",
"llvm-profdata",
"llvm-cov",
]
_REQUIRED_TOOLS = [
"ar",
"cpp",
"gcc",
"ld",
"nm",
"objdump",
"strip",
]
_SYSROOT_START = "%sysroot%/"
_WORKSPACE_START = "%workspace%/"
_CROSSTOOL_START = "%crosstool_top%/"
_PACKAGE_START = "%package("
_PACKAGE_END = ")%"
_BUILTIN_INCLUDE_FILE_SUFFIX = "include/stdc-predef.h"
def _builtin_includes(libc):
result = []
for artifact in libc.to_list():
if artifact.path.endswith(_BUILTIN_INCLUDE_FILE_SUFFIX):
result.append(artifact)
return result
def _legacy_cc_flags_make_variable(toolchain_make_vars):
legacy_cc_flags = ""
for variable in toolchain_make_vars:
if variable[0] == "CC_FLAGS":
legacy_cc_flags = variable[1]
return legacy_cc_flags
def _additional_make_variables(toolchain_make_vars):
make_vars = {}
# The following are to be used to allow some build rules to avoid the limits on stack frame
# sizes and variable-length arrays.
# These variables are initialized here, but may be overridden by the getMakeVariables() checks.
make_vars["STACK_FRAME_UNLIMITED"] = ""
for variable in toolchain_make_vars:
make_vars[variable[0]] = variable[1]
make_vars.pop("CC_FLAGS", None)
return make_vars
def _compute_tool_paths(toolchain_config_info, crosstool_top_path):
tool_paths_collector = {}
for tool in toolchain_config_info.tool_paths():
path_str = tool[1]
if not paths.is_normalized(path_str):
fail("The include path '" + path_str + "' is not normalized.")
tool_paths_collector[tool[0]] = paths.get_relative(crosstool_top_path, path_str)
# These tools can only be declared using tool paths, so action-only toolchains should still
# be allowed to declared them while still being treated as an action-only toolchain. If a tool
# that can be specified with actions is declared using paths, the toolchain will be treated as
# a tool-path toolchain to enforce users to migrate their toolchain fully.
contains_all = True
for key in tool_paths_collector.keys():
if key not in _TOOL_PATH_ONLY_TOOLS:
contains_all = False
break
if contains_all:
for tool in _REQUIRED_TOOLS:
tool_paths_collector[tool] = paths.get_relative(crosstool_top_path, tool)
else:
for tool in _REQUIRED_TOOLS:
if tool not in tool_paths_collector.keys():
fail("Tool path for '" + tool + "' is missing")
return tool_paths_collector
def _resolve_include_dir(target_label, s, sysroot, crosstool_path):
""" Resolve the given include directory.
If it starts with %sysroot%/, that part is replaced with the actual sysroot.
If it starts with %workspace%/, that part is replaced with the empty string (essentially
making it relative to the build directory).
If it starts with %crosstool_top%/ or is any relative path, it is interpreted relative to
the crosstool top. The use of assumed-crosstool-relative specifications is considered
deprecated, and all such uses should eventually be replaced by "%crosstool_top%/".
If it is of the form %package(@repository//my/package)%/folder, then it is interpreted as
the named folder in the appropriate package. All of the normal package syntax is supported. The
/folder part is optional.
It is illegal if it starts with a % and does not match any of the above forms to avoid
accidentally silently ignoring misspelled prefixes.
If it is absolute, it remains unchanged.
"""
package_end_index = s.find(_PACKAGE_END)
if package_end_index != -1 and s.startswith(_PACKAGE_START):
package = s[len(_PACKAGE_START):package_end_index]
if package.find(":") >= 0:
fail("invalid package identifier '" + package + "': contains ':'")
# This is necessary to avoid the hard work of parsing,
# and use an already existing API.
dummy_label = target_label.relative(package + ":dummy_target")
repo_prefix = dummy_label.workspace_root
path_prefix = paths.get_relative(repo_prefix, dummy_label.package)
path_start_index = package_end_index + len(_PACKAGE_END)
if path_start_index + 1 < len(s):
if s[path_start_index] != "/":
fail("The path in the package for '" + s + "' is not valid")
path_string = s[path_start_index + 1:]
else:
path_string = ""
elif s.startswith(_SYSROOT_START):
if sysroot == None:
fail("A %sysroot% prefix is only allowed if the default_sysroot option is set")
path_prefix = sysroot
path_string = s[len(_SYSROOT_START):len(s)]
elif s.startswith(_WORKSPACE_START):
path_prefix = ""
path_string = s[len(_WORKSPACE_START):len(s)]
else:
path_prefix = crosstool_path
if s.startswith(_CROSSTOOL_START):
path_string = s[len(_CROSSTOOL_START):len(s)]
elif s.startswith("%"):
fail("The include path '" + s + "' has an " + "unrecognized %prefix%")
else:
path_string = s
return paths.get_relative(path_prefix, path_string)
def get_cc_toolchain_provider(ctx, attributes, has_apple_fragment):
"""Constructs a CcToolchainProvider instance.
Args:
ctx: rule context.
attributes: an instance of CcToolchainAttributesProvider.
has_apple_fragment: whether an instance of ctx.fragments contains "apple".
Returns:
A constructed CcToolchainProvider instance.
"""
toolchain_config_info = attributes.cc_toolchain_config_info()
tools_directory = cc_helper.package_exec_path(
ctx,
attributes.cc_toolchain_label().package,
ctx.configuration.is_sibling_repository_layout(),
)
tool_paths = _compute_tool_paths(toolchain_config_info, tools_directory)
toolchain_features = cc_internal.cc_toolchain_features(toolchain_config_info = toolchain_config_info, tools_directory = tools_directory)
fdo_context = cc_internal.fdo_context(
ctx = ctx,
attributes = attributes,
configuration = ctx.configuration,
cpp_config = ctx.fragments.cpp,
tool_paths = tool_paths,
)
if fdo_context == None:
return None
runtime_solib_dir_base = attributes.runtime_solib_dir_base()
runtime_solib_dir = paths.get_relative(ctx.bin_dir.path, runtime_solib_dir_base)
solib_directory = "_solib_" + toolchain_config_info.target_cpu()
default_sysroot = None
if toolchain_config_info.builtin_sysroot() != "":
default_sysroot = toolchain_config_info.builtin_sysroot()
if attributes.libc_top_label() == None:
sysroot = default_sysroot
else:
sysroot = attributes.libc_top_label().package
if attributes.target_libc_top_label() == None:
target_sysroot = sysroot
else:
target_sysroot = attributes.target_libc_top_label().package
static_runtime_lib = attributes.static_runtime_lib()
if static_runtime_lib != None:
static_runtime_link_inputs = static_runtime_lib[DefaultInfo].files
else:
static_runtime_link_inputs = None
dynamic_runtime_lib = attributes.dynamic_runtime_lib()
if dynamic_runtime_lib != None:
dynamic_runtime_link_symlinks_elems = []
for artifact in dynamic_runtime_lib[DefaultInfo].files.to_list():
if cc_helper.is_valid_shared_library_artifact(artifact):
dynamic_runtime_link_symlinks_elems.append(cc_internal.solib_symlink_action(
ctx = ctx,
artifact = artifact,
solib_directory = solib_directory,
runtime_solib_dir_base = runtime_solib_dir_base,
))
if len(dynamic_runtime_link_symlinks_elems) == 0:
dynamic_runtime_link_symlinks = depset()
else:
dynamic_runtime_link_symlinks = depset(direct = dynamic_runtime_link_symlinks_elems)
else:
dynamic_runtime_link_symlinks = None
module_map = None
if attributes.module_map() != None and attributes.module_map_artifact() != None:
module_map = cc_common.create_module_map(file = attributes.module_map_artifact(), name = "crosstool")
cc_compilation_context = cc_common.create_compilation_context(module_map = module_map)
builtin_include_directories = []
for s in toolchain_config_info.cxx_builtin_include_directories():
builtin_include_directories.append(_resolve_include_dir(ctx.label, s, sysroot, tools_directory))
if has_apple_fragment:
build_vars = attributes.build_vars_func()(ctx.fragments.apple.single_arch_platform, ctx.fragments.apple.cpu(), ctx.fragments.cpp, sysroot)
else:
build_vars = attributes.build_vars_func()("", "", ctx.fragments.cpp, sysroot)
return cc_internal.construct_toolchain_provider(
ctx = ctx,
cpp_config = ctx.fragments.cpp,
toolchain_features = toolchain_features,
tools_directory = tools_directory,
attributes = attributes,
static_runtime_link_inputs = static_runtime_link_inputs,
dynamic_runtime_link_symlinks = dynamic_runtime_link_symlinks,
runtime_solib_dir = runtime_solib_dir,
cc_compilation_context = cc_compilation_context,
builtin_include_files = _builtin_includes(attributes.libc()),
target_builtin_include_files = _builtin_includes(attributes.target_libc()),
builtin_include_directories = builtin_include_directories,
sysroot = sysroot,
target_sysroot = target_sysroot,
fdo_context = fdo_context,
is_tool_configuration = ctx.configuration.is_tool_configuration(),
tool_paths = tool_paths,
toolchain_config_info = toolchain_config_info,
default_sysroot = default_sysroot,
# The runtime sysroot should really be set from --grte_top. However, currently libc has
# no way to set the sysroot. The CROSSTOOL file does set the runtime sysroot, in the
# builtin_sysroot field. This implies that you can not arbitrarily mix and match
# Crosstool and libc versions, you must always choose compatible ones.
runtime_sysroot = default_sysroot,
solib_directory = solib_directory,
additional_make_variables = _additional_make_variables(toolchain_config_info.make_variables()),
legacy_cc_flags_make_variable = _legacy_cc_flags_make_variable(toolchain_config_info.make_variables()),
objcopy = tool_paths.get("objcopy", ""),
compiler = tool_paths.get("gcc", ""),
preprocessor = tool_paths.get("cpp", ""),
nm = tool_paths.get("nm", ""),
objdump = tool_paths.get("objdump", ""),
ar = tool_paths.get("ar", ""),
strip = tool_paths.get("strip", ""),
ld = tool_paths.get("ld", ""),
gcov = tool_paths.get("gcov", ""),
vars = build_vars,
)