[1/5] support C++20 Modules, add module_interfaces attr
I split the XXL PR https://github.com/bazelbuild/bazel/pull/19940 into several small patches.
This is the first patch of Support C++20 Modules, I add `module_interfaces` attr only
example
- foo.cppm
```
// foo.cppm
export module foo;
// ...
```
- BUILD.bazel
```
cc_library(
name="foo",
copts=["-std=c++20"],
module_interfaces=["foo.cppm"],
# features=["cpp20_module"]
)
```
build failed with the following message
```
➜ bazel build :foo
ERROR: bazel_demo/BUILD.bazel:1:11: in cc_library rule //:foo:
Traceback (most recent call last):
File "/virtual_builtins_bzl/common/cc/cc_library.bzl", line 40, column 42, in _cc_library_impl
File "/virtual_builtins_bzl/common/cc/semantics.bzl", line 123, column 13, in _check_can_module_interfaces
Error in fail: attribute module_interfaces: requires --experimental_cpp20_modules
ERROR: bazel_demo/BUILD.bazel:1:11: Analysis of target '//:foo' failed
ERROR: Analysis of target '//:foo' failed; build aborted
INFO: Elapsed time: 0.106s, Critical Path: 0.00s
INFO: 1 process: 1 internal.
ERROR: Build did NOT complete successfully
```
To build with C++20 Modules, the flag `--experimental_cpp20_modules` must be added.
```
➜ bazel build :foo --experimental_cpp20_modules
ERROR: bazel_demo/BUILD.bazel:1:11: in cc_library rule //:foo:
Traceback (most recent call last):
File "/virtual_builtins_bzl/common/cc/cc_library.bzl", line 41, column 34, in _cc_library_impl
File "/virtual_builtins_bzl/common/cc/cc_helper.bzl", line 1225, column 13, in _check_cpp20_modules
Error in fail: to use C++20 Modules, the feature cpp20_modules must be enabled
ERROR: bazel_demo/BUILD.bazel:1:11: Analysis of target '//:foo' failed
ERROR: Analysis of target '//:foo' failed; build aborted
INFO: Elapsed time: 0.091s, Critical Path: 0.00s
INFO: 1 process: 1 internal.
ERROR: Build did NOT complete successfully
```
To build with C++20 Modules, the feature `cpp20_modules` must be enabled.
```
bazel build :foo --experimental_cpp20_modules --features cpp20_modules
```
the flag `--experimental_cpp20_modules` works on global and
the feature `cpp20_modules` work on each target
but in this patch, do nothing with C++20 Module Interfaces.
Closes #22425.
PiperOrigin-RevId: 643303029
Change-Id: I08d8a1186d2ddd1c632f1e768442e504b87a0691
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/CcCompilationHelper.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/CcCompilationHelper.java
index 74dbb15..97f3ece 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/cpp/CcCompilationHelper.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/CcCompilationHelper.java
@@ -269,6 +269,7 @@
private final List<PathFragment> additionalExportedHeaders = new ArrayList<>();
private final List<CppModuleMap> additionalCppModuleMaps = new ArrayList<>();
private final LinkedHashMap<Artifact, CppSource> compilationUnitSources = new LinkedHashMap<>();
+ private final LinkedHashMap<Artifact, CppSource> moduleInterfaceSources = new LinkedHashMap<>();
private ImmutableList<String> copts = ImmutableList.of();
private CoptsFilter coptsFilter = CoptsFilter.alwaysPasses();
private final Set<String> defines = new LinkedHashSet<>();
@@ -518,6 +519,27 @@
return addSources(Arrays.asList(sources));
}
+ @CanIgnoreReturnValue
+ public CcCompilationHelper addModuleInterfaceSources(Collection<Artifact> sources) {
+ for (Artifact source : sources) {
+ addModuleInterfaceSource(source, label);
+ }
+ return this;
+ }
+
+ @CanIgnoreReturnValue
+ public CcCompilationHelper addModuleInterfaceSources(Iterable<Pair<Artifact, Label>> sources) {
+ for (Pair<Artifact, Label> source : sources) {
+ addModuleInterfaceSource(source.first, source.second);
+ }
+ return this;
+ }
+
+ private void addModuleInterfaceSource(Artifact source, Label label) {
+ Preconditions.checkNotNull(featureConfiguration);
+ moduleInterfaceSources.put(source, CppSource.create(source, label, CppSource.Type.SOURCE));
+ }
+
/** Add the corresponding files as non-header, non-source input files. */
@CanIgnoreReturnValue
public CcCompilationHelper addAdditionalInputs(Collection<Artifact> inputs) {
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/CcModule.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/CcModule.java
index 95461fc..1ab8951 100755
--- a/src/main/java/com/google/devtools/build/lib/rules/cpp/CcModule.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/CcModule.java
@@ -2063,6 +2063,7 @@
Object purposeObject,
Object coptsFilterObject,
Object separateModuleHeadersObject,
+ Sequence<?> moduleInterfacesUnchecked, // <Artifact> expected
Object nonCompilationAdditionalInputsObject,
StarlarkThread thread)
throws EvalException, InterruptedException {
@@ -2151,23 +2152,37 @@
configuration.getFragment(CppConfiguration.class)));
boolean tuple =
(!sourcesUnchecked.isEmpty() && sourcesUnchecked.get(0) instanceof Tuple)
+ || (!moduleInterfacesUnchecked.isEmpty()
+ && moduleInterfacesUnchecked.get(0) instanceof Tuple)
|| (!publicHeadersUnchecked.isEmpty() && publicHeadersUnchecked.get(0) instanceof Tuple)
|| (!privateHeadersUnchecked.isEmpty()
&& privateHeadersUnchecked.get(0) instanceof Tuple);
if (tuple) {
ImmutableList<Pair<Artifact, Label>> sources = convertSequenceTupleToPair(sourcesUnchecked);
+ ImmutableList<Pair<Artifact, Label>> moduleInterfaces =
+ convertSequenceTupleToPair(moduleInterfacesUnchecked);
ImmutableList<Pair<Artifact, Label>> publicHeaders =
convertSequenceTupleToPair(publicHeadersUnchecked);
ImmutableList<Pair<Artifact, Label>> privateHeaders =
convertSequenceTupleToPair(privateHeadersUnchecked);
- helper.addPublicHeaders(publicHeaders).addPrivateHeaders(privateHeaders).addSources(sources);
+ helper
+ .addPublicHeaders(publicHeaders)
+ .addPrivateHeaders(privateHeaders)
+ .addSources(sources)
+ .addModuleInterfaceSources(moduleInterfaces);
} else {
List<Artifact> sources = Sequence.cast(sourcesUnchecked, Artifact.class, "srcs");
+ List<Artifact> moduleInterfaces =
+ Sequence.cast(moduleInterfacesUnchecked, Artifact.class, "module_interfaces");
List<Artifact> publicHeaders =
Sequence.cast(publicHeadersUnchecked, Artifact.class, "public_hdrs");
List<Artifact> privateHeaders =
Sequence.cast(privateHeadersUnchecked, Artifact.class, "private_hdrs");
- helper.addPublicHeaders(publicHeaders).addPrivateHeaders(privateHeaders).addSources(sources);
+ helper
+ .addPublicHeaders(publicHeaders)
+ .addPrivateHeaders(privateHeaders)
+ .addSources(sources)
+ .addModuleInterfaceSources(moduleInterfaces);
}
List<String> includes =
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/CppConfiguration.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/CppConfiguration.java
index f75e4b7..f4ec901 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/cpp/CppConfiguration.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/CppConfiguration.java
@@ -924,10 +924,20 @@
return experimentalCcImplementationDeps();
}
+ @StarlarkMethod(name = "experimental_cpp_modules", documented = false, useStarlarkThread = true)
+ public boolean experimentalCppModulesForStarlark(StarlarkThread thread) throws EvalException {
+ CcModule.checkPrivateStarlarkificationAllowlist(thread);
+ return experimentalCppModules();
+ }
+
public boolean experimentalCcImplementationDeps() {
return cppOptions.experimentalCcImplementationDeps;
}
+ public boolean experimentalCppModules() {
+ return cppOptions.experimentalCppModules;
+ }
+
public boolean getExperimentalCppCompileResourcesEstimation() {
return cppOptions.experimentalCppCompileResourcesEstimation;
}
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/CppOptions.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/CppOptions.java
index 8b9b65f..b6871ea 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/cpp/CppOptions.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/CppOptions.java
@@ -1039,6 +1039,23 @@
public boolean experimentalCcImplementationDeps;
@Option(
+ name = "experimental_cpp_modules",
+ defaultValue = "false",
+ documentationCategory = OptionDocumentationCategory.UNDOCUMENTED,
+ effectTags = {
+ OptionEffectTag.LOADING_AND_ANALYSIS,
+ OptionEffectTag.EXECUTION,
+ OptionEffectTag.CHANGES_INPUTS
+ },
+ metadataTags = {OptionMetadataTag.EXPERIMENTAL},
+ help =
+ "Enables experimental C++20 modules support. Use it with `module_interfaces` attribute on"
+ + " `cc_binary` and `cc_library`. While the support is behind the experimental flag,"
+ + " there are no guarantees about incompatible changes to it or even keeping the"
+ + " support in the future. Consider those risks when using it.")
+ public boolean experimentalCppModules;
+
+ @Option(
name = "experimental_link_static_libraries_once",
defaultValue = "true",
documentationCategory = OptionDocumentationCategory.UNDOCUMENTED,
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/CppRuleClasses.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/CppRuleClasses.java
index bed2699..67f604e 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/cpp/CppRuleClasses.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/CppRuleClasses.java
@@ -107,6 +107,9 @@
*/
public static final String MODULE_MAPS = "module_maps";
+ /** A string constant for the cpp_modules feature. */
+ public static final String CPP_MODULES = "cpp_modules";
+
/**
* A string constant for the random_seed feature. This is used by gcc and Clangfor the
* randomization of symbol names that are in the anonymous namespace but have external linkage.
diff --git a/src/main/java/com/google/devtools/build/lib/starlarkbuildapi/cpp/CcModuleApi.java b/src/main/java/com/google/devtools/build/lib/starlarkbuildapi/cpp/CcModuleApi.java
index ada290f..bc53c21 100755
--- a/src/main/java/com/google/devtools/build/lib/starlarkbuildapi/cpp/CcModuleApi.java
+++ b/src/main/java/com/google/devtools/build/lib/starlarkbuildapi/cpp/CcModuleApi.java
@@ -361,6 +361,14 @@
allowedTypes = {@ParamType(type = Sequence.class)},
defaultValue = "unbound"),
@Param(
+ name = "module_interfaces",
+ doc =
+ "The list of module interfaces source files to be compiled. Note: this is an"
+ + " experimental feature, only enabled with --experimental_cpp_modules",
+ positional = false,
+ named = true,
+ defaultValue = "unbound"),
+ @Param(
name = "non_compilation_additional_inputs",
positional = false,
named = true,
@@ -405,6 +413,7 @@
Object purposeObject,
Object coptsFilterObject,
Object separateModuleHeadersObject,
+ Sequence<?> moduleInterfacesUnchecked, // <Artifact> expected
Object nonCompilationAdditionalInputsObject,
StarlarkThread thread)
throws EvalException, InterruptedException;
diff --git a/src/main/starlark/builtins_bzl/common/builtin_exec_platforms.bzl b/src/main/starlark/builtins_bzl/common/builtin_exec_platforms.bzl
index 6823a3a..7ebdd28 100644
--- a/src/main/starlark/builtins_bzl/common/builtin_exec_platforms.bzl
+++ b/src/main/starlark/builtins_bzl/common/builtin_exec_platforms.bzl
@@ -277,6 +277,7 @@
"//command_line_option:target libcTop label",
"//command_line_option:experimental_link_static_libraries_once",
"//command_line_option:experimental_cc_implementation_deps",
+ "//command_line_option:experimental_cpp_modules",
"//command_line_option:start_end_lib",
"//command_line_option:experimental_inmemory_dotd_files",
"//command_line_option:incompatible_disable_legacy_cc_provider",
diff --git a/src/main/starlark/builtins_bzl/common/cc/attrs.bzl b/src/main/starlark/builtins_bzl/common/cc/attrs.bzl
index c7e3717..f02ab98 100644
--- a/src/main/starlark/builtins_bzl/common/cc/attrs.bzl
+++ b/src/main/starlark/builtins_bzl/common/cc/attrs.bzl
@@ -84,6 +84,23 @@
</p>
""",
),
+ "module_interfaces": attr.label_list(
+ allow_files = True,
+ doc = """
+The list of files are regarded as C++20 Modules Interface.
+
+<p>
+C++ Standard has no restriction about module interface file extension
+<ul>
+<li>Clang use cppm </li>
+<li>GCC can use any source file extension </li>
+<li>MSVC use ixx </li>
+</ul>
+</p>
+<p>The use is guarded by the flag
+<code>--experimental_cpp_modules</code>.</p>
+ """,
+ ),
"data": attr.label_list(
allow_files = True,
flags = ["SKIP_CONSTRAINTS_OVERRIDE"],
diff --git a/src/main/starlark/builtins_bzl/common/cc/cc_binary.bzl b/src/main/starlark/builtins_bzl/common/cc/cc_binary.bzl
index d2343bd..eaf01fb 100644
--- a/src/main/starlark/builtins_bzl/common/cc/cc_binary.bzl
+++ b/src/main/starlark/builtins_bzl/common/cc/cc_binary.bzl
@@ -487,6 +487,9 @@
requested_features = features,
unsupported_features = disabled_features,
)
+
+ cc_helper.check_cpp_modules(ctx, feature_configuration)
+
all_deps = ctx.attr.deps + semantics.get_cc_runtimes(ctx, _is_link_shared(ctx))
compilation_context_deps = [dep[CcInfo].compilation_context for dep in all_deps if CcInfo in dep]
@@ -508,6 +511,7 @@
public_hdrs = cc_helper.get_public_hdrs(ctx),
copts_filter = cc_helper.copts_filter(ctx, additional_make_variable_substitutions),
srcs = cc_helper.get_srcs(ctx),
+ module_interfaces = cc_helper.get_cpp_module_interfaces(ctx),
compilation_contexts = compilation_context_deps,
code_coverage_enabled = cc_helper.is_code_coverage_enabled(ctx = ctx),
)
diff --git a/src/main/starlark/builtins_bzl/common/cc/cc_common.bzl b/src/main/starlark/builtins_bzl/common/cc/cc_common.bzl
index 3b67687..b37eb48 100644
--- a/src/main/starlark/builtins_bzl/common/cc/cc_common.bzl
+++ b/src/main/starlark/builtins_bzl/common/cc/cc_common.bzl
@@ -676,6 +676,7 @@
purpose = _UNBOUND,
copts_filter = _UNBOUND,
separate_module_headers = _UNBOUND,
+ module_interfaces = _UNBOUND,
non_compilation_additional_inputs = _UNBOUND):
if module_map != _UNBOUND or \
additional_module_maps != _UNBOUND or \
@@ -688,6 +689,7 @@
implementation_compilation_contexts != _UNBOUND or \
copts_filter != _UNBOUND or \
separate_module_headers != _UNBOUND or \
+ module_interfaces != _UNBOUND or \
non_compilation_additional_inputs != _UNBOUND:
cc_common_internal.check_private_api(allowlist = _PRIVATE_STARLARKIFICATION_ALLOWLIST)
@@ -713,10 +715,12 @@
copts_filter = None
if separate_module_headers == _UNBOUND:
separate_module_headers = []
+ if module_interfaces == _UNBOUND:
+ module_interfaces = []
if non_compilation_additional_inputs == _UNBOUND:
non_compilation_additional_inputs = []
- has_tuple = _check_all_sources_contain_tuples_or_none_of_them([srcs, private_hdrs, public_hdrs])
+ has_tuple = _check_all_sources_contain_tuples_or_none_of_them([srcs, module_interfaces, private_hdrs, public_hdrs])
if has_tuple:
cc_common_internal.check_private_api(allowlist = _PRIVATE_STARLARKIFICATION_ALLOWLIST)
@@ -726,6 +730,7 @@
cc_toolchain = cc_toolchain,
name = name,
srcs = srcs,
+ module_interfaces = module_interfaces,
public_hdrs = public_hdrs,
private_hdrs = private_hdrs,
textual_hdrs = textual_hdrs,
diff --git a/src/main/starlark/builtins_bzl/common/cc/cc_helper.bzl b/src/main/starlark/builtins_bzl/common/cc/cc_helper.bzl
index 78a47e9..5d14be2 100644
--- a/src/main/starlark/builtins_bzl/common/cc/cc_helper.bzl
+++ b/src/main/starlark/builtins_bzl/common/cc/cc_helper.bzl
@@ -944,26 +944,37 @@
result.append((k, v))
return result
-# Returns a list of (Artifact, Label) tuples. Each tuple represents an input source
-# file and the label of the rule that generates it (or the label of the source file itself if it
-# is an input file).
+def _calculate_artifact_label_map(attr_list, attr_name):
+ """
+ Converts a label_list attribute into a list of (Artifact, Label) tuples.
+
+ Each tuple represents an input source file and the label of the rule that generates it
+ (or the label of the source file itself if it is an input file).
+ """
+ artifact_label_map = {}
+ for attr in attr_list:
+ if DefaultInfo in attr:
+ for artifact in attr[DefaultInfo].files.to_list():
+ if "." + artifact.extension not in CC_HEADER:
+ old_label = artifact_label_map.get(artifact, None)
+ artifact_label_map[artifact] = attr.label
+ if old_label != None and not _are_labels_equal(old_label, attr.label) and ("." + artifact.extension in CC_AND_OBJC or attr_name == "module_interfaces"):
+ fail(
+ "Artifact '{}' is duplicated (through '{}' and '{}')".format(artifact, old_label, attr),
+ attr = attr_name,
+ )
+ return artifact_label_map
+
def _get_srcs(ctx):
if not hasattr(ctx.attr, "srcs"):
return []
+ artifact_label_map = _calculate_artifact_label_map(ctx.attr.srcs, "srcs")
+ return _map_to_list(artifact_label_map)
- # "srcs" attribute is a LABEL_LIST in cc_rules, which might also contain files.
- artifact_label_map = {}
- for src in ctx.attr.srcs:
- if DefaultInfo in src:
- for artifact in src[DefaultInfo].files.to_list():
- if "." + artifact.extension not in CC_HEADER:
- old_label = artifact_label_map.get(artifact, None)
- artifact_label_map[artifact] = src.label
- if old_label != None and not _are_labels_equal(old_label, src.label) and "." + artifact.extension in CC_AND_OBJC:
- fail(
- "Artifact '{}' is duplicated (through '{}' and '{}')".format(artifact, old_label, src),
- attr = "srcs",
- )
+def _get_cpp_module_interfaces(ctx):
+ if not hasattr(ctx.attr, "module_interfaces"):
+ return []
+ artifact_label_map = _calculate_artifact_label_map(ctx.attr.module_interfaces, "module_interfaces")
return _map_to_list(artifact_label_map)
# Returns a list of (Artifact, Label) tuples. Each tuple represents an input source
@@ -1176,6 +1187,17 @@
)
)
+def _check_cpp_modules(ctx, feature_configuration):
+ if len(ctx.files.module_interfaces) == 0:
+ return
+ if not ctx.fragments.cpp.experimental_cpp_modules():
+ fail("requires --experimental_cpp_modules", attr = "module_interfaces")
+ if not cc_common.is_enabled(
+ feature_configuration = feature_configuration,
+ feature_name = "cpp_modules",
+ ):
+ fail("to use C++ modules, the feature cpp_modules must be enabled")
+
cc_helper = struct(
CPP_TOOLCHAIN_TYPE = _CPP_TOOLCHAIN_TYPE,
merge_cc_debug_contexts = _merge_cc_debug_contexts,
@@ -1225,6 +1247,7 @@
get_local_defines_for_runfiles_lookup = _get_local_defines_for_runfiles_lookup,
are_labels_equal = _are_labels_equal,
get_srcs = _get_srcs,
+ get_cpp_module_interfaces = _get_cpp_module_interfaces,
get_private_hdrs = _get_private_hdrs,
get_public_hdrs = _get_public_hdrs,
report_invalid_options = _report_invalid_options,
@@ -1242,4 +1265,5 @@
package_source_root = _package_source_root,
tokenize = _tokenize,
should_use_pic = _should_use_pic,
+ check_cpp_modules = _check_cpp_modules,
)
diff --git a/src/main/starlark/builtins_bzl/common/cc/cc_library.bzl b/src/main/starlark/builtins_bzl/common/cc/cc_library.bzl
index 7295719..1d5b5be 100755
--- a/src/main/starlark/builtins_bzl/common/cc/cc_library.bzl
+++ b/src/main/starlark/builtins_bzl/common/cc/cc_library.bzl
@@ -37,6 +37,8 @@
unsupported_features = ctx.disabled_features,
)
+ cc_helper.check_cpp_modules(ctx, feature_configuration)
+
precompiled_files = cc_helper.build_precompiled_files(ctx = ctx)
semantics.validate_attributes(ctx = ctx)
@@ -63,6 +65,7 @@
copts_filter = cc_helper.copts_filter(ctx, additional_make_variable_substitutions),
purpose = "cc_library-compile",
srcs = cc_helper.get_srcs(ctx),
+ module_interfaces = cc_helper.get_cpp_module_interfaces(ctx),
private_hdrs = cc_helper.get_private_hdrs(ctx),
public_hdrs = cc_helper.get_public_hdrs(ctx),
code_coverage_enabled = cc_helper.is_code_coverage_enabled(ctx),
diff --git a/src/test/java/com/google/devtools/build/lib/analysis/mock/cc_toolchain_config.bzl b/src/test/java/com/google/devtools/build/lib/analysis/mock/cc_toolchain_config.bzl
index da292f9..19b9be5 100644
--- a/src/test/java/com/google/devtools/build/lib/analysis/mock/cc_toolchain_config.bzl
+++ b/src/test/java/com/google/devtools/build/lib/analysis/mock/cc_toolchain_config.bzl
@@ -32,6 +32,7 @@
)
_FEATURE_NAMES = struct(
+ cpp_modules = "cpp_modules",
generate_pdb_file = "generate_pdb_file",
no_legacy_features = "no_legacy_features",
do_not_split_linking_cmdline = "do_not_split_linking_cmdline",
@@ -123,6 +124,11 @@
generate_linkmap = "generate_linkmap",
)
+_cpp_modules_feature = feature(
+ name = _FEATURE_NAMES.cpp_modules,
+ enabled = False,
+)
+
_no_copts_tokenization_feature = feature(name = _FEATURE_NAMES.no_copts_tokenization)
_disable_pbh_feature = feature(name = _FEATURE_NAMES.disable_pbh)
@@ -1365,6 +1371,7 @@
)
_feature_name_to_feature = {
+ _FEATURE_NAMES.cpp_modules: _cpp_modules_feature,
_FEATURE_NAMES.no_legacy_features: _no_legacy_features_feature,
_FEATURE_NAMES.do_not_split_linking_cmdline: _do_not_split_linking_cmdline_feature,
_FEATURE_NAMES.supports_dynamic_linker: _supports_dynamic_linker_feature,
diff --git a/src/test/java/com/google/devtools/build/lib/packages/util/mock/osx_cc_toolchain_config.bzl b/src/test/java/com/google/devtools/build/lib/packages/util/mock/osx_cc_toolchain_config.bzl
index abf6d81..94d8adf 100644
--- a/src/test/java/com/google/devtools/build/lib/packages/util/mock/osx_cc_toolchain_config.bzl
+++ b/src/test/java/com/google/devtools/build/lib/packages/util/mock/osx_cc_toolchain_config.bzl
@@ -7195,7 +7195,13 @@
else:
include_system_dirs_feature = None
+ cpp_modules_feature = feature(
+ name = "cpp_modules",
+ enabled = False,
+ )
+
features = [
+ cpp_modules_feature,
default_compile_flags_feature,
default_link_flags_feature,
no_legacy_features_feature,
diff --git a/src/test/java/com/google/devtools/build/lib/rules/cpp/BUILD b/src/test/java/com/google/devtools/build/lib/rules/cpp/BUILD
index 2245f20..f8f3f1e 100644
--- a/src/test/java/com/google/devtools/build/lib/rules/cpp/BUILD
+++ b/src/test/java/com/google/devtools/build/lib/rules/cpp/BUILD
@@ -745,3 +745,16 @@
"//third_party:truth",
],
)
+
+java_test(
+ name = "CppModulesConfiguredTargetTest",
+ srcs = ["CppModulesConfiguredTargetTest.java"],
+ deps = [
+ "//src/main/java/com/google/devtools/build/lib/rules/cpp",
+ "//src/test/java/com/google/devtools/build/lib/analysis/util",
+ "//src/test/java/com/google/devtools/build/lib/packages:testutil",
+ "//third_party:guava",
+ "//third_party:junit4",
+ "//third_party:truth",
+ ],
+)
diff --git a/src/test/java/com/google/devtools/build/lib/rules/cpp/CppModulesConfiguredTargetTest.java b/src/test/java/com/google/devtools/build/lib/rules/cpp/CppModulesConfiguredTargetTest.java
new file mode 100644
index 0000000..00b26dd
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/lib/rules/cpp/CppModulesConfiguredTargetTest.java
@@ -0,0 +1,271 @@
+// 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.
+package com.google.devtools.build.lib.rules.cpp;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.devtools.build.lib.analysis.util.AnalysisMock;
+import com.google.devtools.build.lib.analysis.util.BuildViewTestCase;
+import com.google.devtools.build.lib.packages.util.Crosstool;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class CppModulesConfiguredTargetTest extends BuildViewTestCase {
+ void useFeatures(String... args) throws Exception {
+ AnalysisMock.get()
+ .ccSupport()
+ .setupCcToolchainConfig(
+ mockToolsConfig, Crosstool.CcToolchainConfig.builder().withFeatures(args));
+ }
+
+ @Test
+ public void testCppModulesCcBinaryConfigurationNoFlags() throws Exception {
+ scratch.file(
+ "foo/BUILD",
+ """
+ cc_library(
+ name = 'lib',
+ module_interfaces = ["foo.cppm"],
+ )
+ """);
+ reporter.removeHandler(failFastHandler);
+ getConfiguredTarget("//foo:lib");
+ assertContainsEvent("requires --experimental_cpp_modules");
+ }
+
+ @Test
+ public void testCppModulesCcLibraryConfigurationNoFlags() throws Exception {
+ scratch.file(
+ "foo/BUILD",
+ """
+ cc_binary(
+ name = 'bin',
+ module_interfaces = ["foo.cppm"],
+ )
+ """);
+ reporter.removeHandler(failFastHandler);
+ getConfiguredTarget("//foo:bin");
+ assertContainsEvent("requires --experimental_cpp_modules");
+ }
+
+ @Test
+ public void testCppModulesCcTestConfigurationNoFlags() throws Exception {
+ scratch.file(
+ "foo/BUILD",
+ """
+ cc_test(
+ name = 'test',
+ module_interfaces = ["foo.cppm"],
+ )
+ """);
+ reporter.removeHandler(failFastHandler);
+ getConfiguredTarget("//foo:test");
+ assertContainsEvent("requires --experimental_cpp_modules");
+ }
+
+ @Test
+ public void testCppModulesCcLibraryConfigurationNoFeatures() throws Exception {
+ scratch.file(
+ "foo/BUILD",
+ """
+ cc_library(
+ name = 'lib',
+ module_interfaces = ["foo.cppm"],
+ )
+ """);
+ useConfiguration("--experimental_cpp_modules");
+
+ reporter.removeHandler(failFastHandler);
+ getConfiguredTarget("//foo:lib");
+ assertDoesNotContainEvent("requires --experimental_cpp_modules");
+ assertContainsEvent("the feature cpp_modules must be enabled");
+ }
+
+ @Test
+ public void testCppModulesCcBinaryConfigurationNoFeatures() throws Exception {
+ scratch.file(
+ "foo/BUILD",
+ """
+ cc_binary(
+ name = 'bin',
+ module_interfaces = ["foo.cppm"],
+ )
+ """);
+ useConfiguration("--experimental_cpp_modules");
+
+ reporter.removeHandler(failFastHandler);
+ getConfiguredTarget("//foo:bin");
+ assertDoesNotContainEvent("requires --experimental_cpp_modules");
+ assertContainsEvent("the feature cpp_modules must be enabled");
+ }
+
+ @Test
+ public void testCppModulesCcTestConfigurationNoFeatures() throws Exception {
+ scratch.file(
+ "foo/BUILD",
+ """
+ cc_test(
+ name = 'test',
+ module_interfaces = ["foo.cppm"],
+ )
+ """);
+ useConfiguration("--experimental_cpp_modules");
+
+ reporter.removeHandler(failFastHandler);
+ getConfiguredTarget("//foo:test");
+ assertDoesNotContainEvent("requires --experimental_cpp_modules");
+ assertContainsEvent("the feature cpp_modules must be enabled");
+ }
+
+ @Test
+ public void testCppModulesCcLibraryConfigurationWithFeatures() throws Exception {
+ scratch.file(
+ "foo/BUILD",
+ """
+ cc_library(
+ name = 'lib',
+ module_interfaces = ["foo.cppm"],
+ )
+ """);
+ useFeatures(CppRuleClasses.CPP_MODULES);
+ useConfiguration("--experimental_cpp_modules", "--features=cpp_modules");
+
+ ImmutableSet<String> features = getRuleContext(getConfiguredTarget("//foo:lib")).getFeatures();
+ assertThat(features).contains("cpp_modules");
+ assertDoesNotContainEvent("requires --experimental_cpp_modules");
+ assertDoesNotContainEvent("the feature cpp_modules must be enabled");
+ }
+
+ @Test
+ public void testCppModulesCcBinaryConfigurationWithFeatures() throws Exception {
+ scratch.file(
+ "foo/BUILD",
+ """
+ cc_binary(
+ name = 'bin',
+ module_interfaces = ["foo.cppm"],
+ )
+ """);
+ useFeatures(CppRuleClasses.CPP_MODULES);
+ useConfiguration("--experimental_cpp_modules", "--features=cpp_modules");
+
+ ImmutableSet<String> features = getRuleContext(getConfiguredTarget("//foo:bin")).getFeatures();
+ assertThat(features).contains("cpp_modules");
+ assertDoesNotContainEvent("requires --experimental_cpp_modules");
+ assertDoesNotContainEvent("the feature cpp_modules must be enabled");
+ }
+
+ @Test
+ public void testCppModulesCcTestConfigurationWithFeatures() throws Exception {
+ scratch.file(
+ "foo/BUILD",
+ """
+ cc_test(
+ name = 'test',
+ module_interfaces = ["foo.cppm"],
+ )
+ """);
+ useFeatures(CppRuleClasses.CPP_MODULES);
+ useConfiguration("--experimental_cpp_modules", "--features=cpp_modules");
+
+ ImmutableSet<String> features = getRuleContext(getConfiguredTarget("//foo:test")).getFeatures();
+ assertThat(features).contains("cpp_modules");
+ assertDoesNotContainEvent("requires --experimental_cpp_modules");
+ assertDoesNotContainEvent("the feature cpp_modules must be enabled");
+ }
+
+ @Test
+ public void testSameModuleInterfacesFileInCcLibraryTwice() throws Exception {
+ scratch.file(
+ "a/BUILD",
+ """
+ filegroup(
+ name = "a1",
+ srcs = ["a.cppm"],
+ )
+ filegroup(
+ name = "a2",
+ srcs = ["a.cppm"],
+ )
+ cc_library(
+ name = "lib",
+ module_interfaces = ["a1", "a2"],
+ )
+ """);
+
+ useFeatures(CppRuleClasses.CPP_MODULES);
+ useConfiguration("--experimental_cpp_modules", "--features=cpp_modules");
+
+ reporter.removeHandler(failFastHandler);
+ getConfiguredTarget("//a:lib");
+ assertContainsEvent("Artifact '<source file a/a.cppm>' is duplicated");
+ }
+
+ @Test
+ public void testSameModuleInterfacesFileInCcBinaryTwice() throws Exception {
+ scratch.file(
+ "a/BUILD",
+ """
+ filegroup(
+ name = "a1",
+ srcs = ["a.cppm"],
+ )
+ filegroup(
+ name = "a2",
+ srcs = ["a.cppm"],
+ )
+ cc_binary(
+ name = "bin",
+ module_interfaces = ["a1", "a2"],
+ )
+ """);
+
+ useFeatures(CppRuleClasses.CPP_MODULES);
+ useConfiguration("--experimental_cpp_modules", "--features=cpp_modules");
+
+ reporter.removeHandler(failFastHandler);
+ getConfiguredTarget("//a:bin");
+ assertContainsEvent("Artifact '<source file a/a.cppm>' is duplicated");
+ }
+
+ @Test
+ public void testSameModuleInterfacesFileInCcTestTwice() throws Exception {
+ scratch.file(
+ "a/BUILD",
+ """
+ filegroup(
+ name = "a1",
+ srcs = ["a.cppm"],
+ )
+ filegroup(
+ name = "a2",
+ srcs = ["a.cppm"],
+ )
+ cc_test(
+ name = "test",
+ module_interfaces = ["a1", "a2"],
+ )
+ """);
+
+ useFeatures(CppRuleClasses.CPP_MODULES);
+ useConfiguration("--experimental_cpp_modules", "--features=cpp_modules");
+
+ reporter.removeHandler(failFastHandler);
+ getConfiguredTarget("//a:test");
+ assertContainsEvent("Artifact '<source file a/a.cppm>' is duplicated");
+ }
+}
diff --git a/src/test/java/com/google/devtools/build/lib/rules/cpp/StarlarkCcCommonTest.java b/src/test/java/com/google/devtools/build/lib/rules/cpp/StarlarkCcCommonTest.java
index fd83cf6..ebbcd01 100755
--- a/src/test/java/com/google/devtools/build/lib/rules/cpp/StarlarkCcCommonTest.java
+++ b/src/test/java/com/google/devtools/build/lib/rules/cpp/StarlarkCcCommonTest.java
@@ -7383,6 +7383,7 @@
"build_test_dwp()",
"grte_top()",
"experimental_cc_implementation_deps()",
+ "experimental_cpp_modules()",
"share_native_deps()",
"experimental_platform_cc_test()");
scratch.file(
diff --git a/tools/cpp/unix_cc_toolchain_config.bzl b/tools/cpp/unix_cc_toolchain_config.bzl
index 61f6561..cec23f9 100644
--- a/tools/cpp/unix_cc_toolchain_config.bzl
+++ b/tools/cpp/unix_cc_toolchain_config.bzl
@@ -1429,11 +1429,22 @@
],
)
+ # Tell bazel we support C++ modules now
+ cpp_modules_feature = feature(
+ name = "cpp_modules",
+ # set default value to False
+ # to enable the feature
+ # use --features=cpp_modules
+ # or add cpp_modules to features attr
+ enabled = False,
+ )
+
# TODO(#8303): Mac crosstool should also declare every feature.
if is_linux:
# Linux artifact name patterns are the default.
artifact_name_patterns = []
features = [
+ cpp_modules_feature,
dependency_file_feature,
serialized_diagnostics_file_feature,
random_seed_feature,
@@ -1501,6 +1512,7 @@
),
]
features = [
+ cpp_modules_feature,
macos_minimum_os_feature,
macos_default_link_flags_feature,
libtool_feature,