diff --git a/cc/common/cc_helper.bzl b/cc/common/cc_helper.bzl index 51f2309b..1afb0105 100644 --- a/cc/common/cc_helper.bzl +++ b/cc/common/cc_helper.bzl @@ -814,13 +814,13 @@ def _get_cc_flags_make_variable(_ctx, feature_configuration, cc_toolchain): def _package_exec_path(ctx, package, sibling_repository_layout): return get_relative_path(_repository_exec_path(ctx.label.workspace_name, sibling_repository_layout), package) -def _include_dirs(ctx, additional_make_variable_substitutions): +def _include_dirs(ctx, additional_make_variable_substitutions, attr = "includes"): result = [] sibling_repository_layout = ctx.configuration.is_sibling_repository_layout() package = ctx.label.package package_exec_path = _package_exec_path(ctx, package, sibling_repository_layout) package_source_root = _package_source_root(ctx.label.workspace_name, package, sibling_repository_layout) - for include in ctx.attr.includes: + for include in getattr(ctx.attr, attr): includes_attr = _expand(ctx, include, additional_make_variable_substitutions) if is_path_absolute(includes_attr): continue diff --git a/cc/private/cc_common.bzl b/cc/private/cc_common.bzl index 9d496ecb..f37f643e 100644 --- a/cc/private/cc_common.bzl +++ b/cc/private/cc_common.bzl @@ -461,6 +461,7 @@ def _compile( textual_hdrs = [], additional_exported_hdrs = _UNBOUND, # TODO(ilist@): remove, there are no uses includes = [], + local_includes = [], quote_includes = [], system_includes = [], framework_includes = [], @@ -546,6 +547,7 @@ def _compile( textual_hdrs = textual_hdrs, additional_exported_hdrs = additional_exported_hdrs, includes = includes, + local_includes = local_includes, quote_includes = quote_includes, system_includes = system_includes, framework_includes = framework_includes, diff --git a/cc/private/cc_info.bzl b/cc/private/cc_info.bzl index 5ab8d735..a014ce42 100644 --- a/cc/private/cc_info.bzl +++ b/cc/private/cc_info.bzl @@ -27,6 +27,9 @@ CcCompilationContextInfo = provider( # CommandLineCcCompilationContext fields: "includes": "Returns the set of search paths (as strings) for header files referenced " + "both by angle bracket and quotes. Usually passed with -I.", + "local_includes": "Returns the set of search paths (as strings) for header files referenced " + + "both by angle bracket and quotes. Usually passed with -I. These values " + + "are not propagated to the target's transitive dependents.", "quote_includes": "Returns the set of search paths (as strings) for header files " + "referenced by quotes, e.g. #include \"foo/bar/header.h\". They can be " + "either relative to the exec root or absolute. Usually passed with -iquote.", @@ -114,6 +117,7 @@ EMPTY_COMPILATION_CONTEXT = CcCompilationContextInfo( direct_private_headers = [], direct_textual_headers = [], includes = depset(), + local_includes = depset(), quote_includes = depset(), system_includes = depset(), framework_includes = depset(), @@ -276,6 +280,7 @@ def create_compilation_context( *, headers = None, includes = None, + local_includes = None, quote_includes = None, system_includes = None, framework_includes = None, @@ -302,6 +307,7 @@ def create_compilation_context( Args: headers: A depset of headers to compile. includes: A depset of include directories. + local_includes: A depset of local include directories. quote_includes: A depset of quoted include directories. system_includes: A depset of system include directories. framework_includes: A depset of framework include directories. @@ -361,6 +367,7 @@ def create_compilation_context( direct_private_headers = header_info.modular_private_headers, direct_textual_headers = header_info.textual_headers, includes = includes, + local_includes = local_includes, quote_includes = quote_includes, system_includes = system_includes, framework_includes = framework_includes, @@ -466,6 +473,7 @@ def _merge_compilation_contexts(*, compilation_context = EMPTY_COMPILATION_CONTE transitive = [dep.defines for dep in all_deps] + [compilation_context.defines], ), local_defines = compilation_context.local_defines, + local_includes = compilation_context.local_includes, headers = depset( direct = compilation_context.headers.to_list(), transitive = [dep.headers for dep in all_deps], diff --git a/cc/private/compile/cc_compilation_helper.bzl b/cc/private/compile/cc_compilation_helper.bzl index 01aa11fc..2520bfe3 100644 --- a/cc/private/compile/cc_compilation_helper.bzl +++ b/cc/private/compile/cc_compilation_helper.bzl @@ -356,6 +356,7 @@ def _init_cc_compilation_context( framework_include_dirs, system_include_dirs, include_dirs, + local_includes, feature_configuration, public_headers_artifacts, include_prefix, @@ -559,6 +560,7 @@ def _init_cc_compilation_context( external_includes = depset(external_include_dirs), system_includes = depset(system_include_dirs_for_context), includes = depset(include_dirs_for_context), + local_includes = depset(local_includes), virtual_to_original_headers = virtual_to_original_headers, dependent_cc_compilation_contexts = dependent_cc_compilation_contexts, non_code_inputs = additional_inputs, diff --git a/cc/private/compile/compile.bzl b/cc/private/compile/compile.bzl index d958cc90..47229428 100644 --- a/cc/private/compile/compile.bzl +++ b/cc/private/compile/compile.bzl @@ -103,6 +103,7 @@ def compile( textual_hdrs = [], additional_exported_hdrs = [], includes = [], + local_includes = [], # TODO(b/396122076): seems unused; double-check and remove loose_includes = None, # buildifier: disable=unused-variable quote_includes = [], @@ -292,6 +293,7 @@ def compile( framework_include_dirs = framework_includes, system_include_dirs = system_includes, include_dirs = includes, + local_includes = local_includes, feature_configuration = feature_configuration, public_headers_artifacts = public_hdrs_artifacts, include_prefix = include_prefix, diff --git a/cc/private/compile/compile_build_variables.bzl b/cc/private/compile/compile_build_variables.bzl index 4867fcad..98b78f21 100644 --- a/cc/private/compile/compile_build_variables.bzl +++ b/cc/private/compile/compile_build_variables.bzl @@ -93,6 +93,7 @@ def create_compile_variables( output_file = None, user_compile_flags = None, includes = None, + local_includes = None, include_directories = None, quote_include_directories = None, system_include_directories = None, @@ -123,6 +124,7 @@ def create_compile_variables( includes: paths to headers that should be included using -include user_compile_flags: List of additional compilation flags (copts). include_directories: Depset of include directories. + local_includes: Depset of local include directories. quote_include_directories: Depset of quote include directories. system_include_directories: Depset of system include directories. framework_include_directories: Depset of framework include directories. @@ -158,6 +160,7 @@ def create_compile_variables( fdo_build_stamp = _get_fdo_build_stamp(cpp_configuration, fdo_context, feature_configuration), variables_extension = variables_extension, includes = includes or [], + local_includes = local_includes or depset(), include_dirs = include_directories or depset(), quote_include_dirs = quote_include_directories or depset(), system_include_dirs = system_include_directories or depset(), @@ -198,6 +201,7 @@ def setup_common_compile_build_variables( fdo_build_stamp = _get_fdo_build_stamp(cpp_configuration, fdo_context, feature_configuration), variables_extension = variables_extension, include_dirs = cc_compilation_context.includes, + local_includes = cc_compilation_context.local_includes, quote_include_dirs = cc_compilation_context.quote_includes, system_include_dirs = cc_compilation_context.system_includes, framework_include_dirs = cc_compilation_context.framework_includes, @@ -216,6 +220,7 @@ def _setup_common_compile_build_variables_internal( variables_extension = [], # [dict{str,object}] additional_build_variables = {}, # dict{str,str} include_dirs = depset(), + local_includes = depset(), quote_include_dirs = depset(), system_include_dirs = depset(), framework_include_dirs = depset(), @@ -226,7 +231,7 @@ def _setup_common_compile_build_variables_internal( if feature_configuration.is_enabled("use_header_modules"): result[_VARS.MODULE_FILES] = [] - result[_VARS.INCLUDE_PATHS] = include_dirs + result[_VARS.INCLUDE_PATHS] = depset(transitive = [include_dirs, local_includes]) result[_VARS.QUOTE_INCLUDE_PATHS] = quote_include_dirs result[_VARS.SYSTEM_INCLUDE_PATHS] = system_include_dirs if includes: diff --git a/cc/private/rules_impl/attrs.bzl b/cc/private/rules_impl/attrs.bzl index 876074cd..cff6d972 100644 --- a/cc/private/rules_impl/attrs.bzl +++ b/cc/private/rules_impl/attrs.bzl @@ -144,6 +144,20 @@ very careful, since this may have far-reaching effects. When in doubt, add The added include paths will include generated files as well as files in the source tree.

+"""), + "local_includes": attr.string_list(doc = """ +List of include dirs to be added to the compile line. +Subject to "Make variable" substitution. +Each string is prepended with the package path and passed to the C++ toolchain for +expansion via the "include_paths" CROSSTOOL feature. A toolchain running on a +POSIX system with typical feature definitions will produce +-I path_to_package/include_entry. + +Unlike INCLUDES, these flags are added for +this target and not added to every target that depends on it. + +The added include paths will include generated files as well as +files in the source tree. """), "defines": attr.string_list(doc = """ List of defines to add to the compile line of this and all dependent targets. diff --git a/cc/private/rules_impl/cc_binary.bzl b/cc/private/rules_impl/cc_binary.bzl index 5dccd2c9..8957971b 100644 --- a/cc/private/rules_impl/cc_binary.bzl +++ b/cc/private/rules_impl/cc_binary.bzl @@ -540,6 +540,7 @@ def cc_binary_impl(ctx, additional_linkopts, force_linkstatic = False): defines = cc_helper.defines(ctx, additional_make_variable_substitutions), local_defines = cc_helper.local_defines(ctx, additional_make_variable_substitutions) + cc_helper.get_local_defines_for_runfiles_lookup(ctx, ctx.attr.deps), includes = cc_helper.include_dirs(ctx, additional_make_variable_substitutions), + local_includes = cc_helper.include_dirs(ctx, additional_make_variable_substitutions, attr = "local_includes"), private_hdrs = cc_helper.get_private_hdrs(ctx), public_hdrs = cc_helper.get_public_hdrs(ctx), copts_filter = cc_helper.copts_filter(ctx, additional_make_variable_substitutions), diff --git a/cc/private/rules_impl/cc_library.bzl b/cc/private/rules_impl/cc_library.bzl index a6699594..2f849bff 100755 --- a/cc/private/rules_impl/cc_library.bzl +++ b/cc/private/rules_impl/cc_library.bzl @@ -64,6 +64,7 @@ def _cc_library_impl(ctx): defines = cc_helper.defines(ctx, additional_make_variable_substitutions), local_defines = cc_helper.local_defines(ctx, additional_make_variable_substitutions) + cc_helper.get_local_defines_for_runfiles_lookup(ctx, ctx.attr.deps + ctx.attr.implementation_deps), includes = cc_helper.include_dirs(ctx, additional_make_variable_substitutions), + local_includes = cc_helper.include_dirs(ctx, additional_make_variable_substitutions, attr = "local_includes"), copts_filter = cc_helper.copts_filter(ctx, additional_make_variable_substitutions), purpose = "cc_library-compile", srcs = cc_helper.get_srcs(ctx), diff --git a/tests/local_includes/BUILD b/tests/local_includes/BUILD new file mode 100644 index 00000000..90f7db33 --- /dev/null +++ b/tests/local_includes/BUILD @@ -0,0 +1,47 @@ +# Copyright 2025 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. + +load("//cc:cc_binary.bzl", "cc_binary") +load("//cc:cc_library.bzl", "cc_library") + +licenses(["notice"]) + +cc_library( + name = "lib", + srcs = [ + "lib/lib.c", + "lib/private/private.c", + "lib/private/private.h", + ], + hdrs = ["lib/include/public.h"], + includes = [ + "lib/include", + ], + local_includes = [ + "lib/private", + ], +) + +cc_binary( + name = "bin", + srcs = [ + "binary.c", + "binary_helper.c", + "private/binary_helper.h", + ], + local_includes = [ + "private", + ], + deps = [":lib"], +) diff --git a/tests/local_includes/binary.c b/tests/local_includes/binary.c new file mode 100644 index 00000000..a78687b6 --- /dev/null +++ b/tests/local_includes/binary.c @@ -0,0 +1,11 @@ +#include "binary_helper.h" +#include "public.h" + +#if __has_include("private.h") +#error "private.h should not be on the include path" +#endif + + +int main() { + return foo() + helper(); +} diff --git a/tests/local_includes/binary_helper.c b/tests/local_includes/binary_helper.c new file mode 100644 index 00000000..97f18b58 --- /dev/null +++ b/tests/local_includes/binary_helper.c @@ -0,0 +1,3 @@ +int helper() { + return 42; +} diff --git a/tests/local_includes/lib/include/public.h b/tests/local_includes/lib/include/public.h new file mode 100644 index 00000000..5d5f8f0c --- /dev/null +++ b/tests/local_includes/lib/include/public.h @@ -0,0 +1 @@ +int foo(); diff --git a/tests/local_includes/lib/lib.c b/tests/local_includes/lib/lib.c new file mode 100644 index 00000000..602f9fff --- /dev/null +++ b/tests/local_includes/lib/lib.c @@ -0,0 +1,5 @@ +#include "private.h" + +int foo() { + return bar(); +} diff --git a/tests/local_includes/lib/private/private.c b/tests/local_includes/lib/private/private.c new file mode 100644 index 00000000..a4957daa --- /dev/null +++ b/tests/local_includes/lib/private/private.c @@ -0,0 +1,3 @@ +int bar() { + return 0; +} diff --git a/tests/local_includes/lib/private/private.h b/tests/local_includes/lib/private/private.h new file mode 100644 index 00000000..c362dd03 --- /dev/null +++ b/tests/local_includes/lib/private/private.h @@ -0,0 +1 @@ +int bar(); diff --git a/tests/local_includes/private/binary_helper.h b/tests/local_includes/private/binary_helper.h new file mode 100644 index 00000000..7b89ca58 --- /dev/null +++ b/tests/local_includes/private/binary_helper.h @@ -0,0 +1 @@ +int helper();