blob: fbf4078ab15c5fd9376bb968cb21c4d1e971f618 [file] [log] [blame]
# Copyright 2024 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.
# LINT.IfChange(forked_exports)
"""
Utility functions for C++ rules that don't depend on cc_common.
Only use those within C++ implementation. The others need to go through cc_common.
"""
load("@bazel_skylib//lib:paths.bzl", "paths")
load("//cc/private:cc_internal.bzl", _cc_internal = "cc_internal")
load("//cc/private:paths.bzl", "is_path_absolute")
def check_private_api():
_cc_internal.check_private_api(allowlist = PRIVATE_STARLARKIFICATION_ALLOWLIST, depth = 2)
def wrap_with_check_private_api(symbol):
"""
Protects the symbol so it can only be used internally.
Returns:
A function. When the function is invoked (without any params), the check
is done and if it passes the symbol is returned.
"""
def callback():
_cc_internal.check_private_api(allowlist = PRIVATE_STARLARKIFICATION_ALLOWLIST)
return symbol
return callback
CPP_SOURCE_TYPE_HEADER = "HEADER"
CPP_SOURCE_TYPE_SOURCE = "SOURCE"
CPP_SOURCE_TYPE_CLIF_INPUT_PROTO = "CLIF_INPUT_PROTO"
CREATE_COMPILE_ACTION_API_ALLOWLISTED_PACKAGES = [("", "devtools/rust/cc_interop"), ("", "third_party/crubit"), ("", "tools/build_defs/clif")]
PRIVATE_STARLARKIFICATION_ALLOWLIST = [
("_builtins", ""),
# Android rules
("", "tools/build_defs/android"),
("", "third_party/bazel_rules/rules_android"),
("build_bazel_rules_android", ""),
("rules_android", ""),
# Apple rules
("", "third_party/bazel_rules/rules_apple"),
("apple_support", ""),
("build_bazel_apple_support", ""),
("rules_apple", ""),
("build_bazel_rules_apple", ""),
# C++ rules
("", "bazel_internal/test_rules/cc"),
("", "third_party/bazel_rules/rules_cc"),
("", "tools/build_defs/cc"),
("rules_cc", ""),
# CUDA rules
("", "third_party/gpus/cuda"),
# Go rules
("", "tools/build_defs/go"),
# Java rules
("", "third_party/bazel_rules/rules_java"),
("rules_java", ""),
# Objc rules
("", "tools/build_defs/objc"),
# Protobuf rules
("", "third_party/protobuf"),
("protobuf", ""),
("com_google_protobuf", ""),
# Rust rules
("", "third_party/bazel_rules/rules_rust/rust/private"),
("rules_rust", "rust/private"),
# Python rules
("", "third_party/bazel_rules/rules_python"),
# Various
("", "research/colab"),
("", "javatests/com/google/devtools/grok/kythe"),
] + CREATE_COMPILE_ACTION_API_ALLOWLISTED_PACKAGES
_CC_SOURCE = [".cc", ".cpp", ".cxx", ".c++", ".C", ".cu", ".cl"]
_C_SOURCE = [".c"]
_OBJC_SOURCE = [".m"]
_OBJCPP_SOURCE = [".mm"]
_CLIF_INPUT_PROTO = [".ipb"]
_CLIF_OUTPUT_PROTO = [".opb"]
_CC_HEADER = [".h", ".hh", ".hpp", ".ipp", ".hxx", ".h++", ".inc", ".inl", ".tlh", ".tli", ".H", ".tcc"]
_CC_TEXTUAL_INCLUDE = [".inc"]
_ASSEMBLER_WITH_C_PREPROCESSOR = [".S"]
_ASSEMBLER = [".s", ".asm"]
_ARCHIVE = [".a", ".lib"]
_PIC_ARCHIVE = [".pic.a"]
_ALWAYSLINK_LIBRARY = [".lo"]
_ALWAYSLINK_PIC_LIBRARY = [".pic.lo"]
_SHARED_LIBRARY = [".so", ".dylib", ".dll", ".pyd", ".wasm"]
_INTERFACE_SHARED_LIBRARY = [".ifso", ".tbd", ".lib", ".dll.a"]
_OBJECT_FILE = [".o", ".obj"]
_PIC_OBJECT_FILE = [".pic.o"]
_CPP_MODULE = [".pcm", ".gcm", ".ifc"]
_CPP_MODULE_MAP = [".cppmap"]
_LTO_INDEXING_OBJECT_FILE = [".indexing.o"]
_CC_AND_OBJC = []
_CC_AND_OBJC.extend(_CC_SOURCE)
_CC_AND_OBJC.extend(_C_SOURCE)
_CC_AND_OBJC.extend(_OBJC_SOURCE)
_CC_AND_OBJC.extend(_OBJCPP_SOURCE)
_CC_AND_OBJC.extend(_CC_HEADER)
_CC_AND_OBJC.extend(_ASSEMBLER)
_CC_AND_OBJC.extend(_ASSEMBLER_WITH_C_PREPROCESSOR)
_DISALLOWED_HDRS_FILES = []
_DISALLOWED_HDRS_FILES.extend(_ARCHIVE)
_DISALLOWED_HDRS_FILES.extend(_PIC_ARCHIVE)
_DISALLOWED_HDRS_FILES.extend(_ALWAYSLINK_LIBRARY)
_DISALLOWED_HDRS_FILES.extend(_ALWAYSLINK_PIC_LIBRARY)
_DISALLOWED_HDRS_FILES.extend(_SHARED_LIBRARY)
_DISALLOWED_HDRS_FILES.extend(_INTERFACE_SHARED_LIBRARY)
_DISALLOWED_HDRS_FILES.extend(_OBJECT_FILE)
_DISALLOWED_HDRS_FILES.extend(_PIC_OBJECT_FILE)
extensions = struct(
CC_SOURCE = _CC_SOURCE,
C_SOURCE = _C_SOURCE,
OBJC_SOURCE = _OBJC_SOURCE,
OBJCPP_SOURCE = _OBJCPP_SOURCE,
CC_HEADER = _CC_HEADER,
CC_TEXTUAL_INCLUDE = _CC_TEXTUAL_INCLUDE,
ASSEMBLER_WITH_C_PREPROCESSOR = _ASSEMBLER_WITH_C_PREPROCESSOR,
# TODO(b/345158656): Remove ASSESMBLER_WITH_C_PREPROCESSOR after next blaze release
ASSESMBLER_WITH_C_PREPROCESSOR = _ASSEMBLER_WITH_C_PREPROCESSOR,
ASSEMBLER = _ASSEMBLER,
CLIF_INPUT_PROTO = _CLIF_INPUT_PROTO,
CLIF_OUTPUT_PROTO = _CLIF_OUTPUT_PROTO,
ARCHIVE = _ARCHIVE,
PIC_ARCHIVE = _PIC_ARCHIVE,
ALWAYSLINK_LIBRARY = _ALWAYSLINK_LIBRARY,
ALWAYSLINK_PIC_LIBRARY = _ALWAYSLINK_PIC_LIBRARY,
SHARED_LIBRARY = _SHARED_LIBRARY,
OBJECT_FILE = _OBJECT_FILE,
PIC_OBJECT_FILE = _PIC_OBJECT_FILE,
CC_AND_OBJC = _CC_AND_OBJC,
DISALLOWED_HDRS_FILES = _DISALLOWED_HDRS_FILES, # Also includes VERSIONED_SHARED_LIBRARY files.
CPP_MODULE = _CPP_MODULE,
CPP_MODULE_MAP = _CPP_MODULE_MAP,
LTO_INDEXING_OBJECT_FILE = _LTO_INDEXING_OBJECT_FILE,
)
def _artifact_category_info_init(name, default_prefix, *extensions):
return {
"allowed_extensions": extensions,
"default_extension": extensions[0],
"default_prefix": default_prefix,
"name": name,
}
# buildifier: disable=unused-variable
_ArtifactCategoryInfo, _unused_new_aci = provider(
"""A category of artifacts that are candidate input/output to an action, for
which the toolchain can select a single artifact.""",
fields = ["name", "default_prefix", "default_extension", "allowed_extensions"],
init = _artifact_category_info_init,
)
# TODO: b/433485282 - remove duplicated extensions lists with above constants
_artifact_categories = [
_ArtifactCategoryInfo("STATIC_LIBRARY", "lib", ".a", ".lib"),
_ArtifactCategoryInfo("ALWAYSLINK_STATIC_LIBRARY", "lib", ".lo", ".lo.lib"),
_ArtifactCategoryInfo("DYNAMIC_LIBRARY", "lib", ".so", ".dylib", ".dll", ".pyd", ".wasm"),
_ArtifactCategoryInfo("EXECUTABLE", "", "", ".exe", ".wasm"),
_ArtifactCategoryInfo("INTERFACE_LIBRARY", "lib", ".ifso", ".tbd", ".if.lib", ".lib"),
_ArtifactCategoryInfo("PIC_FILE", "", ".pic"),
_ArtifactCategoryInfo("INCLUDED_FILE_LIST", "", ".d"),
_ArtifactCategoryInfo("SERIALIZED_DIAGNOSTICS_FILE", "", ".dia"),
_ArtifactCategoryInfo("OBJECT_FILE", "", ".o", ".obj"),
_ArtifactCategoryInfo("PIC_OBJECT_FILE", "", ".pic.o"),
_ArtifactCategoryInfo("CPP_MODULE", "", ".pcm", ".gcm", ".ifc"),
_ArtifactCategoryInfo("CPP_MODULES_INFO", "", ".CXXModules.json"),
_ArtifactCategoryInfo("CPP_MODULES_DDI", "", ".ddi"),
_ArtifactCategoryInfo("CPP_MODULES_MODMAP", "", ".modmap"),
_ArtifactCategoryInfo("CPP_MODULES_MODMAP_INPUT", "", ".modmap.input"),
_ArtifactCategoryInfo("GENERATED_ASSEMBLY", "", ".s", ".asm"),
_ArtifactCategoryInfo("PROCESSED_HEADER", "", ".processed"),
_ArtifactCategoryInfo("GENERATED_HEADER", "", ".h"),
_ArtifactCategoryInfo("PREPROCESSED_C_SOURCE", "", ".i"),
_ArtifactCategoryInfo("PREPROCESSED_CPP_SOURCE", "", ".ii"),
_ArtifactCategoryInfo("COVERAGE_DATA_FILE", "", ".gcno"),
# A matched-clif protobuf. Typically in binary format, but could be text
# depending on the options passed to the clif_matcher.
_ArtifactCategoryInfo("CLIF_OUTPUT_PROTO", "", ".opb"),
]
artifact_category_names = struct(**{ac.name: ac.name for ac in _artifact_categories})
output_subdirectories = struct(
OBJS = "_objs",
PIC_OBJS = "_pic_objs",
DOTD_FILES = "_dotd",
PIC_DOTD_FILES = "_pic_dotd",
DIA_FILES = "_dia",
PIC_DIA_FILES = "_pic_dia",
)
def should_create_per_object_debug_info(feature_configuration, cpp_configuration):
return cpp_configuration.fission_active_for_current_compilation_mode() and \
feature_configuration.is_enabled("per_object_debug_info")
def is_versioned_shared_library_extension_valid(shared_library_name):
"""Validates the name against the regex "^.+\\.((so)|(dylib))(\\.\\d\\w*)+$",
Args:
shared_library_name: (str) the name to validate
Returns:
(bool)
"""
# must match VERSIONED_SHARED_LIBRARY.
for ext in (".so.", ".dylib."):
name, _, version = shared_library_name.rpartition(ext)
if name and version:
version_parts = version.split(".")
for part in version_parts:
if not part[0].isdigit():
return False
for c in part[1:].elems():
if not (c.isalnum() or c == "_"):
return False
return True
return False
def _is_repository_main(repository):
return repository == ""
def package_source_root(repository, package, sibling_repository_layout):
"""
Determines the source root for a given repository and package.
Args:
repository: The repository to get the source root for.
package: The package to get the source root for.
sibling_repository_layout: Whether the repository layout is a sibling repository layout.
Returns:
The source root for the given repository and package.
"""
if _is_repository_main(repository) or sibling_repository_layout:
return package
if repository.startswith("@"):
repository = repository[1:]
return get_relative_path(get_relative_path("external", repository), package)
def repository_exec_path(repository, sibling_repository_layout):
"""
Determines the exec path for a given repository.
Args:
repository: The repository to get the exec path for.
sibling_repository_layout: Whether the repository layout is a sibling repository layout.
Returns:
The exec path for the given repository.
"""
if _is_repository_main(repository):
return ""
prefix = "external"
if sibling_repository_layout:
prefix = ".."
if repository.startswith("@"):
repository = repository[1:]
return get_relative_path(prefix, repository)
def is_stamping_enabled(ctx):
"""Returns whether to encode build information into the binary.
Args:
ctx: The rule context.
Returns:
(int) Possible values are:
1: Always stamp the build information into the binary, even in [--nostamp][stamp] builds.
This setting should be avoided, since it potentially kills remote caching for the binary and
any downstream actions that depend on it.
0: Always replace build information by constant values. This gives good build result caching.
-1: Embedding of build information is controlled by the [--[no]stamp][stamp] flag.
"""
if ctx.configuration.is_tool_configuration():
return 0
stamp = 0
if hasattr(ctx.attr, "stamp"):
stamp = ctx.attr.stamp
return stamp
def is_shared_library(file):
return file.extension in ["so", "dylib", "dll", "pyd", "wasm", "tgt", "vpi"]
def is_versioned_shared_library(file):
# Because regex matching can be slow, we first do a quick check for ".so." and ".dylib."
# substring before risking the full-on regex match. This should eliminate the performance
# hit on practically every non-qualifying file type.
if ".so." not in file.basename and ".dylib." not in file.basename:
return False
return is_versioned_shared_library_extension_valid(file.basename)
def use_pic_for_binaries(cpp_config, feature_configuration):
"""
Returns whether binaries must be compiled with position independent code.
"""
return cpp_config.force_pic() or (
feature_configuration.is_enabled("supports_pic") and
(cpp_config.compilation_mode() != "opt" or feature_configuration.is_enabled("prefer_pic_for_opt_binaries"))
)
def use_pic_for_dynamic_libs(cpp_config, feature_configuration):
"""Determines if we should apply -fPIC for this rule's C++ compilations.
This determination is
generally made by the global C++ configuration settings "needsPic" and "usePicForBinaries".
However, an individual rule may override these settings by applying -fPIC" to its "nocopts"
attribute. This allows incompatible rules to "opt out" of global PIC settings (see bug:
"Provide a way to turn off -fPIC for targets that can't be built that way").
Returns:
true if this rule's compilations should apply -fPIC, false otherwise
"""
return (cpp_config.force_pic() or
feature_configuration.is_enabled("supports_pic"))
# LINT.ThenChange(https://github.com/bazelbuild/bazel/blob/master/src/main/starlark/builtins_bzl/common/cc/cc_helper_internal.bzl:forked_exports)
def get_relative_path(path_a, path_b):
if is_path_absolute(path_b):
return path_b
return paths.normalize(paths.join(path_a, path_b))
def path_contains_up_level_references(path):
return path.startswith("..") and (len(path) == 2 or path[2] == "/")
def root_relative_path(file):
"""Returns the path of `file` relative to its root.
A Starlark implementation of `Artifact.getRootRelativePath()`.
Args:
file: (File) The file to get the root-relative path for.
Returns:
(str) The root-relative path of the file.
"""
if not file.is_source:
return paths.relativize(file.path, file.root.path)
short_path = file.short_path
if not short_path.startswith("../"):
return short_path
# This is a file in an external repo, skip over the repo name.
return short_path[short_path.index("/", 3) + 1:]