| # 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. |
| |
| """Compilation helper for C++ rules.""" |
| |
| load(":common/cc/cc_common.bzl", "cc_common") |
| load(":common/cc/cc_helper.bzl", "cc_helper") |
| load(":common/paths.bzl", "paths") |
| |
| cc_internal = _builtins.internal.cc_internal |
| |
| _VIRTUAL_INCLUDES_DIR = "_virtual_includes" |
| |
| def _include_dir(directory, repo_path, sibling_repo_layout): |
| if sibling_repo_layout: |
| return directory |
| else: |
| return paths.get_relative(directory, repo_path) |
| |
| def _repo_relative_path(artifact): |
| relative_path = artifact.path |
| if artifact.is_source: |
| if artifact.owner.workspace_root: |
| relative_path = "/".join(relative_path.split("/")[2:]) |
| else: |
| relative_path = paths.relativize(relative_path, artifact.root.path) |
| |
| if (artifact.owner.workspace_root.startswith("external/") or artifact.owner.workspace_root.startswith("../")) and \ |
| relative_path.startswith("external"): |
| relative_path = "/".join(relative_path.split("/")[2:]) |
| |
| return relative_path |
| |
| def _enabled(feature_configuration, feature_name): |
| return cc_common.is_enabled(feature_configuration = feature_configuration, feature_name = feature_name) |
| |
| def _compute_public_headers( |
| actions, |
| config, |
| public_headers_artifacts, |
| include_prefix, |
| strip_include_prefix, |
| label, |
| non_module_map_headers, |
| is_sibling_repository_layout): |
| if include_prefix: |
| if not paths.is_normalized(include_prefix, False): |
| fail("include prefix should not contain uplevel references: " + include_prefix) |
| if paths.is_absolute(include_prefix): |
| fail("include prefix should be a relative path: " + include_prefix) |
| |
| if strip_include_prefix: |
| if not paths.is_normalized(strip_include_prefix, False): |
| fail("strip include prefix should not contain uplevel references: " + strip_include_prefix) |
| strip_prefix = strip_include_prefix |
| if strip_prefix.startswith("//"): |
| strip_prefix = strip_prefix[1:] |
| if paths.is_absolute(strip_prefix): |
| # Very crude way of determining driver length, but the best one at the same time. |
| strip_driver_length = 0 |
| if strip_prefix.startswith("/"): # Unix |
| strip_driver_length = 1 |
| elif len(strip_prefix) > 1 and strip_prefix[1] == ":": # Windows |
| strip_driver_length = 3 |
| strip_prefix = strip_prefix[strip_driver_length:] |
| else: |
| # paths.normalize differs from Java normalize call in a way |
| # that if string to be normalized is "." Starlark version returns ".", |
| # while Java version returns an empty string "". |
| # Because of this if label.package is an empty string and strip_prefix |
| # is "." paths.get_relative("", ".") returns "." instead of "". |
| strip_prefix = paths.get_relative(label.package, strip_prefix) |
| if strip_prefix == ".": |
| strip_prefix = "" |
| |
| elif include_prefix: |
| strip_prefix = label.package |
| else: |
| strip_prefix = None |
| |
| if strip_prefix and strip_prefix.startswith("/"): |
| strip_prefix = strip_prefix[1:] |
| |
| if include_prefix and include_prefix.startswith("/"): |
| include_prefix = include_prefix[1:] |
| |
| if not strip_prefix and not include_prefix: |
| return struct( |
| headers = public_headers_artifacts + non_module_map_headers, |
| module_map_headers = public_headers_artifacts, |
| virtual_include_path = None, |
| virtual_to_original_headers = depset(), |
| ) |
| |
| module_map_headers = [] |
| virtual_to_original_headers_list = [] |
| for original_header in public_headers_artifacts: |
| repo_relative_path = _repo_relative_path(original_header) |
| if not repo_relative_path.startswith(strip_prefix): |
| fail("header '{}' is not under the specified strip prefix '{}'".format(repo_relative_path, strip_prefix)) |
| include_path = paths.relativize(repo_relative_path, strip_prefix) |
| if include_prefix != None: |
| include_path = paths.get_relative(include_prefix, include_path) |
| |
| if not original_header.path == include_path: |
| virtual_include_dir = paths.join(paths.join(cc_helper.package_source_root(label.workspace_name, label.package, is_sibling_repository_layout), _VIRTUAL_INCLUDES_DIR), label.name) |
| virtual_header = actions.declare_shareable_artifact(paths.join(virtual_include_dir, include_path)) |
| actions.symlink( |
| output = virtual_header, |
| target_file = original_header, |
| progress_message = "Symlinking virtual headers for " + label.name, |
| use_exec_root_for_source = True, |
| ) |
| module_map_headers.append(virtual_header) |
| if config.coverage_enabled: |
| virtual_to_original_headers_list.append((virtual_header.path, original_header.path)) |
| |
| module_map_headers.append(original_header) |
| |
| virtual_headers = module_map_headers + non_module_map_headers |
| |
| return struct( |
| headers = virtual_headers, |
| module_map_headers = module_map_headers, |
| virtual_include_path = cc_internal.bin_or_genfiles_relative_to_unique_directory(actions = actions, unique_directory = _VIRTUAL_INCLUDES_DIR), |
| virtual_to_original_headers = depset(virtual_to_original_headers_list), |
| ) |
| |
| def _generates_header_module(feature_configuration, public_headers, private_headers, generate_action): |
| return _enabled(feature_configuration, "header_modules") and \ |
| (public_headers or private_headers) and \ |
| generate_action |
| |
| def _header_module_artifact(actions, label, is_sibling_repository_layout, suffix, extension): |
| object_dir = paths.join(paths.join(cc_helper.package_source_root(label.workspace_name, label.package, is_sibling_repository_layout), "_objs"), label.name) |
| base_name = label.name.split("/")[-1] |
| output_path = paths.join(object_dir, base_name + suffix + extension) |
| return actions.declare_shareable_artifact(output_path) |
| |
| def _collect_module_maps(deps, cc_toolchain_compilation_context, additional_cpp_module_maps): |
| # TODO(bazel-team): Here we use the implementationDeps to build the dependents of this rule's |
| # module map. This is technically incorrect for the following reasons: |
| # - Clang will not issue a layering_check warning if headers from implementation deps are |
| # included from headers of this library. |
| # - If we were to ever build with modules, Clang might store this dependency inside the .pcm |
| # It should be evaluated whether this is ok. If this turned into a problem at some |
| # point, we could probably just declare two different modules with different use-declarations |
| # in the module map file. |
| module_maps = [] |
| for cc_context in deps: |
| if cc_context.module_map() != None: |
| module_maps.append(cc_context.module_map()) |
| module_maps.extend(cc_context.exporting_module_maps()) |
| |
| if cc_toolchain_compilation_context != None and cc_toolchain_compilation_context.module_map() != None: |
| module_maps.append(cc_toolchain_compilation_context.module_map()) |
| |
| for additional_cpp_module_map in additional_cpp_module_maps: |
| module_maps.append(additional_cpp_module_map) |
| |
| return module_maps |
| |
| def _init_cc_compilation_context( |
| # DO NOT use ctx, this is a temporary placeholder |
| # to avoid adding a new field to CcCompilationHelper. |
| # Once compile is in Starlark we can directly pass in actions here. |
| ctx, |
| binfiles_dir, |
| genfiles_dir, |
| label, |
| config, |
| quote_include_dirs, |
| framework_include_dirs, |
| system_include_dirs, |
| include_dirs, |
| feature_configuration, |
| public_headers_artifacts, |
| include_prefix, |
| strip_include_prefix, |
| non_module_map_headers, |
| cc_toolchain_compilation_context, |
| defines, |
| local_defines, |
| public_textual_headers, |
| private_headers_artifacts, |
| additional_inputs, |
| separate_module_headers, |
| generate_module_map, |
| generate_pic_action, |
| generate_no_pic_action, |
| module_map, |
| propagate_module_map_to_compile_action, |
| additional_exported_headers, |
| deps, |
| purpose, |
| implementation_deps, |
| additional_cpp_module_maps): |
| # Single usage of ctx. |
| actions = ctx.actions |
| |
| # Setup the include path; local include directories come before those inherited from deps or |
| # from the toolchain; in case of aliasing (same include file found on different entries), |
| # prefer the local include rather than the inherited one. |
| # Add in the roots for well-formed include names for source files and |
| # generated files. It is important that the execRoot (EMPTY_FRAGMENT) comes |
| # before the genfilesFragment to preferably pick up source files. Otherwise |
| # we might pick up stale generated files. |
| sibling_repo_layout = config.is_sibling_repository_layout() |
| repo_name = label.workspace_name |
| repo_path = cc_helper.repository_exec_path(repo_name, sibling_repo_layout) |
| gen_include_dir = _include_dir(genfiles_dir, repo_path, sibling_repo_layout) |
| bin_include_dir = _include_dir(binfiles_dir, repo_path, sibling_repo_layout) |
| quote_include_dirs_for_context = [repo_path, gen_include_dir, bin_include_dir] + quote_include_dirs |
| external = repo_name != "" and _enabled(feature_configuration, "external_include_paths") |
| external_include_dirs = [] |
| declared_include_srcs = [] |
| |
| if not external: |
| system_include_dirs_for_context = list(system_include_dirs) |
| include_dirs_for_context = list(include_dirs) |
| else: |
| # Do not add system_include_dirs and include_dirs directly to compilation context. |
| system_include_dirs_for_context = [] |
| include_dirs_for_context = [] |
| |
| external_include_dirs.append(repo_path) |
| external_include_dirs.append(gen_include_dir) |
| external_include_dirs.append(bin_include_dir) |
| external_include_dirs.extend(quote_include_dirs_for_context) |
| external_include_dirs.extend(system_include_dirs) |
| external_include_dirs.extend(include_dirs) |
| |
| public_headers = _compute_public_headers( |
| actions, |
| config, |
| public_headers_artifacts, |
| include_prefix, |
| strip_include_prefix, |
| label, |
| non_module_map_headers, |
| sibling_repo_layout, |
| ) |
| if public_headers.virtual_include_path: |
| if external: |
| external_include_dirs.append(public_headers.virtual_include_path) |
| else: |
| include_dirs_for_context.append(public_headers.virtual_include_path) |
| |
| if config.coverage_enabled: |
| # Populate the map only when code coverage collection is enabled, to report the actual |
| # source file name in the coverage output file. |
| virtual_to_original_headers = public_headers.virtual_to_original_headers |
| else: |
| virtual_to_original_headers = depset() |
| |
| declared_include_srcs.extend(public_headers.headers) |
| declared_include_srcs.extend(public_textual_headers) |
| declared_include_srcs.extend(private_headers_artifacts) |
| declared_include_srcs.extend(additional_inputs) |
| |
| generates_pic_header_module = _generates_header_module(feature_configuration, public_headers_artifacts, private_headers_artifacts, generate_pic_action) |
| generates_no_pic_header_module = _generates_header_module(feature_configuration, public_headers_artifacts, private_headers_artifacts, generate_no_pic_action) |
| if separate_module_headers: |
| if not (_enabled(feature_configuration, "module_maps") and |
| generate_module_map and |
| (generates_pic_header_module or generates_no_pic_header_module)): |
| fail("Should use separate headers only when building modules: " + label.name) |
| |
| separate_public_headers = _compute_public_headers( |
| actions, |
| config, |
| separate_module_headers, |
| include_prefix, |
| strip_include_prefix, |
| label, |
| non_module_map_headers, |
| sibling_repo_layout, |
| ) |
| |
| separate_module = None |
| separate_pic_module = None |
| pic_header_module = None |
| header_module = None |
| if _enabled(feature_configuration, "module_maps"): |
| if not module_map: |
| module_map = cc_common.create_module_map( |
| file = actions.declare_file(label.name + ".cppmap"), |
| name = label.workspace_name + "//" + label.package + ":" + label.name, |
| ) |
| |
| # There are different modes for module compilation: |
| # 1. We create the module map and compile the module so that libraries depending on us can |
| # use the resulting module artifacts in their compilation (compiled is true). |
| # 2. We create the module map so that libraries depending on us will include the headers |
| # textually (compiled is false). |
| if generate_module_map: |
| compiled = _enabled(feature_configuration, "header_modules") or \ |
| _enabled(feature_configuration, "compile_all_modules") |
| umbrella_header = module_map.umbrella_header() |
| |
| if _enabled(feature_configuration, "only_doth_headers_in_module_maps"): |
| public_headers_for_module_map_action = [header for header in public_headers.module_map_headers if (header.is_directory or header.extension == "h")] |
| else: |
| public_headers_for_module_map_action = public_headers.module_map_headers |
| |
| if umbrella_header != None: |
| cc_internal.create_umbrella_header_action( |
| actions = actions, |
| umbrella_header = umbrella_header, |
| public_headers = public_headers_for_module_map_action, |
| additional_exported_headers = additional_exported_headers, |
| ) |
| |
| private_headers_for_module_map_action = private_headers_artifacts |
| if _enabled(feature_configuration, "exclude_private_headers_in_module_maps"): |
| private_headers_for_module_map_action = [] |
| dependent_module_maps = _collect_module_maps(deps + implementation_deps, cc_toolchain_compilation_context, additional_cpp_module_maps) |
| cc_internal.create_module_map_action( |
| actions = actions, |
| feature_configuration = feature_configuration, |
| module_map = module_map, |
| public_headers = public_headers_for_module_map_action, |
| separate_module_headers = separate_public_headers.module_map_headers, |
| dependent_module_maps = dependent_module_maps, |
| private_headers = private_headers_for_module_map_action, |
| additional_exported_headers = additional_exported_headers, |
| compiled_module = compiled, |
| module_map_home_is_cwd = _enabled(feature_configuration, "module_map_home_cwd"), |
| generate_submodules = _enabled(feature_configuration, "generate_submodules"), |
| without_extern_dependencies = not _enabled(feature_configuration, "module_map_without_extern_module"), |
| ) |
| |
| if generates_pic_header_module: |
| pic_header_module = _header_module_artifact( |
| actions, |
| label, |
| config.is_sibling_repository_layout(), |
| "", |
| ".pic.pcm", |
| ) |
| if generates_no_pic_header_module: |
| header_module = _header_module_artifact( |
| actions, |
| label, |
| config.is_sibling_repository_layout(), |
| "", |
| ".pcm", |
| ) |
| if separate_module_headers: |
| declared_include_srcs.extend(separate_public_headers.headers) |
| if generates_no_pic_header_module: |
| separate_module = _header_module_artifact( |
| actions, |
| label, |
| config.is_sibling_repository_layout(), |
| ".sep", |
| ".pcm", |
| ) |
| if generates_pic_header_module: |
| separate_pic_module = _header_module_artifact( |
| actions, |
| label, |
| config.is_sibling_repository_layout(), |
| ".sep", |
| ".pic.pcm", |
| ) |
| |
| else: |
| # Do not set module map related attributes. |
| module_map = None |
| propagate_module_map_to_compile_action = True |
| |
| dependent_cc_compilation_contexts = [] |
| if cc_toolchain_compilation_context != None: |
| dependent_cc_compilation_contexts.append(cc_toolchain_compilation_context) |
| dependent_cc_compilation_contexts.extend(deps) |
| |
| main_context = cc_common.create_compilation_context( |
| actions = actions, |
| label = label, |
| quote_includes = depset(quote_include_dirs_for_context), |
| framework_includes = depset(framework_include_dirs), |
| external_includes = depset(external_include_dirs), |
| system_includes = depset(system_include_dirs_for_context), |
| includes = depset(include_dirs_for_context), |
| virtual_to_original_headers = virtual_to_original_headers, |
| dependent_cc_compilation_contexts = dependent_cc_compilation_contexts, |
| non_code_inputs = additional_inputs, |
| defines = depset(defines), |
| local_defines = depset(local_defines), |
| headers = depset(declared_include_srcs), |
| direct_public_headers = public_headers.headers, |
| direct_private_headers = private_headers_artifacts, |
| direct_textual_headers = public_textual_headers, |
| propagate_module_map_to_compile_action = propagate_module_map_to_compile_action, |
| module_map = module_map, |
| pic_header_module = pic_header_module, |
| header_module = header_module, |
| separate_module_headers = separate_public_headers.headers, |
| separate_module = separate_module, |
| separate_pic_module = separate_pic_module, |
| purpose = purpose, |
| add_public_headers_to_modular_headers = False, |
| ) |
| implementation_deps_context = None |
| if implementation_deps: |
| implementation_deps_context = cc_common.create_compilation_context( |
| actions = actions, |
| label = label, |
| quote_includes = depset(quote_include_dirs_for_context), |
| framework_includes = depset(framework_include_dirs), |
| external_includes = depset(external_include_dirs), |
| system_includes = depset(system_include_dirs_for_context), |
| includes = depset(include_dirs_for_context), |
| virtual_to_original_headers = virtual_to_original_headers, |
| dependent_cc_compilation_contexts = dependent_cc_compilation_contexts + implementation_deps, |
| non_code_inputs = additional_inputs, |
| defines = depset(defines), |
| local_defines = depset(local_defines), |
| headers = depset(declared_include_srcs), |
| direct_public_headers = public_headers.headers, |
| direct_private_headers = private_headers_artifacts, |
| direct_textual_headers = public_textual_headers, |
| propagate_module_map_to_compile_action = propagate_module_map_to_compile_action, |
| module_map = module_map, |
| pic_header_module = pic_header_module, |
| header_module = header_module, |
| separate_module_headers = separate_public_headers.headers, |
| separate_module = separate_module, |
| separate_pic_module = separate_pic_module, |
| purpose = purpose + "_impl", |
| add_public_headers_to_modular_headers = False, |
| ) |
| |
| return main_context, implementation_deps_context |
| |
| cc_compilation_helper = struct( |
| init_cc_compilation_context = _init_cc_compilation_context, |
| ) |