diff --git a/scripts/bootstrap/compile.sh b/scripts/bootstrap/compile.sh
index f1ce4db068554e..0a72858f6bac65 100755
--- a/scripts/bootstrap/compile.sh
+++ b/scripts/bootstrap/compile.sh
@@ -281,6 +281,10 @@ EOF
link_file "${PWD}/tools/cpp/runfiles/BUILD.tools" \
"${BAZEL_TOOLS_REPO}/tools/cpp/runfiles/BUILD"
+ # Create @bazel_tools//tools/cpp/modules_tools
+ mkdir -p ${BAZEL_TOOLS_REPO}/tools/cpp/modules_tools
+ link_file "${PWD}/tools/cpp/modules_tools/BUILD.tools" "${BAZEL_TOOLS_REPO}/tools/cpp/modules_tools/BUILD"
+
# Create @bazel_tools//tools/sh
mkdir -p ${BAZEL_TOOLS_REPO}/tools/sh
link_file "${PWD}/tools/sh/sh_configure.bzl" "${BAZEL_TOOLS_REPO}/tools/sh/sh_configure.bzl"
diff --git a/src/main/starlark/builtins_bzl/common/cc/cc_toolchain.bzl b/src/main/starlark/builtins_bzl/common/cc/cc_toolchain.bzl
index 609c02570bea5e..f1525298b7094a 100644
--- a/src/main/starlark/builtins_bzl/common/cc/cc_toolchain.bzl
+++ b/src/main/starlark/builtins_bzl/common/cc/cc_toolchain.bzl
@@ -98,6 +98,8 @@ def _attributes(ctx):
objcopy_files = _files(ctx, "objcopy_files"),
link_dynamic_library_tool = ctx.file._link_dynamic_library_tool,
grep_includes = grep_includes,
+ aggregate_ddi = _single_file(ctx, "_aggregate_ddi"),
+ generate_modmap = _single_file(ctx, "_generate_modmap"),
module_map = ctx.attr.module_map,
as_files = _files(ctx, "as_files"),
ar_files = _files(ctx, "ar_files"),
@@ -342,5 +344,5 @@ The label of the rule providing cc_toolchain_config_info.""",
default = semantics.BUILD_INFO_TRANLATOR_LABEL,
providers = [OutputGroupInfo],
),
- },
+ } | semantics.cpp_modules_tools(),
)
diff --git a/src/main/starlark/builtins_bzl/common/cc/cc_toolchain_info.bzl b/src/main/starlark/builtins_bzl/common/cc/cc_toolchain_info.bzl
index ebdb81b6dfc76b..4049ebbe3fa179 100644
--- a/src/main/starlark/builtins_bzl/common/cc/cc_toolchain_info.bzl
+++ b/src/main/starlark/builtins_bzl/common/cc/cc_toolchain_info.bzl
@@ -95,7 +95,9 @@ def _create_cc_toolchain_info(
grep_includes,
allowlist_for_layering_check,
build_info_files,
- objcopy_files):
+ objcopy_files,
+ aggregate_ddi,
+ generate_modmap):
cc_toolchain_info = dict(
needs_pic_for_dynamic_libraries = (lambda *, feature_configuration: True) if cpp_configuration.force_pic() else _needs_pic_for_dynamic_libraries,
built_in_include_directories = built_in_include_directories,
@@ -157,6 +159,8 @@ def _create_cc_toolchain_info(
_allowlist_for_layering_check = allowlist_for_layering_check,
_cc_info = cc_info,
_objcopy_files = objcopy_files,
+ _aggregate_ddi = aggregate_ddi,
+ _generate_modmap = generate_modmap,
)
return cc_toolchain_info
@@ -250,6 +254,8 @@ CcToolchainInfo, _ = provider(
"_allowlist_for_layering_check": "INTERNAL API, DO NOT USE!",
"_cc_info": "INTERNAL API, DO NOT USE!",
"_objcopy_files": "INTERNAL API, DO NOT USE!",
+ "_aggregate_ddi": "INTERNAL API, DO NOT USE!",
+ "_generate_modmap": "INTERNAL API, DO NOT USE!",
},
init = _create_cc_toolchain_info,
)
diff --git a/src/main/starlark/builtins_bzl/common/cc/cc_toolchain_provider_helper.bzl b/src/main/starlark/builtins_bzl/common/cc/cc_toolchain_provider_helper.bzl
index 17fa0032fc5be0..ba9336baa12c75 100644
--- a/src/main/starlark/builtins_bzl/common/cc/cc_toolchain_provider_helper.bzl
+++ b/src/main/starlark/builtins_bzl/common/cc/cc_toolchain_provider_helper.bzl
@@ -297,6 +297,8 @@ def get_cc_toolchain_provider(ctx, attributes):
supports_header_parsing = attributes.supports_header_parsing,
link_dynamic_library_tool = attributes.link_dynamic_library_tool,
grep_includes = attributes.grep_includes,
+ aggregate_ddi = attributes.aggregate_ddi,
+ generate_modmap = attributes.generate_modmap,
allowlist_for_layering_check = attributes.allowlist_for_layering_check,
build_info_files = attributes.build_info_files,
toolchain_label = ctx.label,
diff --git a/src/main/starlark/builtins_bzl/common/cc/semantics.bzl b/src/main/starlark/builtins_bzl/common/cc/semantics.bzl
index ae6c79e3ed0ed7..988eb1d5f8fe15 100644
--- a/src/main/starlark/builtins_bzl/common/cc/semantics.bzl
+++ b/src/main/starlark/builtins_bzl/common/cc/semantics.bzl
@@ -134,6 +134,20 @@ def _get_experimental_link_static_libraries_once(ctx):
def _check_cc_shared_library_tags(ctx):
pass
+def _cpp_modules_tools():
+ return {
+ "_aggregate_ddi": attr.label(
+ executable = True,
+ cfg = "exec",
+ default = "@" + _get_repo() + "//tools/cpp:aggregate-ddi",
+ ),
+ "_generate_modmap": attr.label(
+ executable = True,
+ cfg = "exec",
+ default = "@" + _get_repo() + "//tools/cpp:generate-modmap",
+ ),
+ }
+
semantics = struct(
toolchain = "@bazel_tools//tools/cpp:toolchain_type",
ALLOWED_RULES_IN_DEPS = [
@@ -172,6 +186,7 @@ semantics = struct(
get_proto_aspects = _get_proto_aspects,
get_nocopts_attr = _get_nocopts_attr,
get_experimental_link_static_libraries_once = _get_experimental_link_static_libraries_once,
+ cpp_modules_tools = _cpp_modules_tools,
check_cc_shared_library_tags = _check_cc_shared_library_tags,
BUILD_INFO_TRANLATOR_LABEL = "@bazel_tools//tools/build_defs/build_info:cc_build_info",
CC_PROTO_TOOLCHAIN = "@rules_cc//cc/proto:toolchain_type",
diff --git a/src/test/java/com/google/devtools/build/lib/packages/util/Crosstool.java b/src/test/java/com/google/devtools/build/lib/packages/util/Crosstool.java
index 4017331d266e56..2f51c20a1d75c0 100644
--- a/src/test/java/com/google/devtools/build/lib/packages/util/Crosstool.java
+++ b/src/test/java/com/google/devtools/build/lib/packages/util/Crosstool.java
@@ -582,7 +582,18 @@ public void write() throws IOException {
// We add an empty :malloc target in case we need it.
"cc_library(name = 'malloc')",
// Fake targets to get us through loading/analysis.
- "exports_files(['grep-includes', 'link_dynamic_library'])");
+ "exports_files(['grep-includes', 'link_dynamic_library'])",
+ "",
+ "filegroup(",
+ " name = 'aggregate-ddi',",
+ " srcs = ['aggregate-ddi.sh'],",
+ ")",
+ "",
+ "filegroup(",
+ " name = 'generate-modmap',",
+ " srcs = ['generate-modmap.sh'],",
+ ")"
+ );
config.create(crosstoolTop + "/mock_version/x86/bin/gcc");
config.create(crosstoolTop + "/mock_version/x86/bin/ld");
@@ -598,6 +609,8 @@ public void write() throws IOException {
config.create(crosstoolTop + "/grep-includes");
config.create(crosstoolTop + "/build_interface_so");
config.create(crosstoolTop + "/link_dynamic_library");
+ config.create(crosstoolTop + "/aggregate-ddi.sh");
+ config.create(crosstoolTop + "/generate-modmap.sh");
}
public void writeOSX() throws IOException {
diff --git a/src/test/shell/bazel/testdata/embedded_tools_srcs_deps b/src/test/shell/bazel/testdata/embedded_tools_srcs_deps
index c4155e5d2f1beb..b8c8429c6bb4df 100644
--- a/src/test/shell/bazel/testdata/embedded_tools_srcs_deps
+++ b/src/test/shell/bazel/testdata/embedded_tools_srcs_deps
@@ -24,3 +24,8 @@
//src/main/cpp/util:port
//src/main/cpp/util:blaze_exit_code
//src/main/cpp/util:logging
+//tools/cpp/modules_tools:aggregate-ddi
+//tools/cpp/modules_tools:aggregate-ddi-lib
+//tools/cpp/modules_tools:common
+//tools/cpp/modules_tools:generate-modmap
+//tools/cpp/modules_tools:generate-modmap-lib
diff --git a/tools/cpp/BUILD b/tools/cpp/BUILD
index d3217b08e14ceb..5d9f4cc847dcc8 100644
--- a/tools/cpp/BUILD
+++ b/tools/cpp/BUILD
@@ -21,6 +21,7 @@ exports_files(glob(["*.bzl"]))
filegroup(
name = "srcs",
srcs = glob(["**"]) + [
+ "//tools/cpp/modules_tools:srcs",
"//tools/cpp/runfiles:srcs",
],
)
@@ -28,6 +29,7 @@ filegroup(
filegroup(
name = "embedded_tools",
srcs = glob(["**"]) + [
+ "//tools/cpp/modules_tools:embedded_tools",
"//tools/cpp/runfiles:embedded_tools",
],
)
diff --git a/tools/cpp/BUILD.tools b/tools/cpp/BUILD.tools
index 2aa1538b786707..d950d1ede4761e 100644
--- a/tools/cpp/BUILD.tools
+++ b/tools/cpp/BUILD.tools
@@ -96,6 +96,16 @@ filegroup(
srcs = ["grep-includes.sh"],
)
+filegroup(
+ name = "aggregate-ddi",
+ srcs = ["//tools/cpp/modules_tools:aggregate-ddi-bin"],
+)
+
+filegroup(
+ name = "generate-modmap",
+ srcs = ["//tools/cpp/modules_tools:generate-modmap-bin"],
+)
+
filegroup(
name = "empty",
srcs = [],
diff --git a/tools/cpp/modules_tools/BUILD b/tools/cpp/modules_tools/BUILD
new file mode 100644
index 00000000000000..6eb530843be553
--- /dev/null
+++ b/tools/cpp/modules_tools/BUILD
@@ -0,0 +1,126 @@
+# 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.
+
+filegroup(
+ name = "srcs",
+ srcs = glob(
+ ["**"],
+ ),
+ visibility = ["//tools/cpp:__pkg__"],
+)
+
+config_setting(
+ name = "windows",
+ constraint_values = ["@platforms//os:windows"],
+)
+
+COPTS = select({
+ ":windows": ["/std:c++17"],
+ "//conditions:default": ["-std=c++17"],
+})
+
+cc_library(
+ name = "common",
+ srcs = [
+ "common/common.cc",
+ ],
+ hdrs = [
+ "common/common.h",
+ "common/json.hpp",
+ ],
+ copts = COPTS,
+ includes = ["."],
+)
+
+cc_library(
+ name = "aggregate-ddi-lib",
+ srcs = ["aggregate-ddi/aggregate-ddi.cc"],
+ hdrs = ["aggregate-ddi/aggregate-ddi.h"],
+ copts = COPTS,
+ deps = [":common"],
+)
+
+cc_binary(
+ name = "aggregate-ddi",
+ srcs = ["aggregate-ddi/main.cc"],
+ copts = COPTS,
+ deps = [
+ ":aggregate-ddi-lib",
+ ],
+)
+
+cc_library(
+ name = "generate-modmap-lib",
+ srcs = ["generate-modmap/generate-modmap.cc"],
+ hdrs = ["generate-modmap/generate-modmap.h"],
+ copts = COPTS,
+ deps = [":common"],
+)
+
+cc_binary(
+ name = "generate-modmap",
+ srcs = ["generate-modmap/main.cc"],
+ copts = COPTS,
+ deps = [":generate-modmap-lib"],
+)
+
+filegroup(
+ name = "embedded_tools",
+ srcs = [
+ "BUILD.tools",
+ ":aggregate-ddi",
+ ":generate-modmap",
+ ],
+ visibility = ["//tools/cpp:__pkg__"],
+)
+
+cc_test(
+ name = "generate-modmap_test",
+ srcs = ["generate-modmap/generate-modmap_test.cc"],
+ copts = COPTS,
+ deps = [
+ ":generate-modmap-lib",
+ "@com_google_googletest//:gtest_main",
+ ],
+)
+
+cc_test(
+ name = "aggregate-ddi_test",
+ srcs = ["aggregate-ddi/aggregate-ddi_test.cc"],
+ copts = COPTS,
+ deps = [
+ ":aggregate-ddi-lib",
+ "@com_google_googletest//:gtest_main",
+ ],
+)
+
+cc_test(
+ name = "common_test",
+ srcs = ["common/common_test.cc"],
+ copts = COPTS,
+ deps = [
+ ":common",
+ "@com_google_googletest//:gtest_main",
+ ],
+)
+
+cc_test(
+ name = "json_test",
+ srcs = ["common/json_test.cc"],
+ copts = COPTS,
+ deps = [
+ ":common",
+ "@com_google_googletest//:gtest_main",
+ ],
+)
diff --git a/tools/cpp/modules_tools/BUILD.tools b/tools/cpp/modules_tools/BUILD.tools
new file mode 100644
index 00000000000000..9b12917a544e65
--- /dev/null
+++ b/tools/cpp/modules_tools/BUILD.tools
@@ -0,0 +1,22 @@
+package(default_visibility = ["//visibility:public"])
+
+config_setting(
+ name = "windows",
+ constraint_values = ["@platforms//os:windows"],
+)
+
+filegroup(
+ name = "aggregate-ddi-bin",
+ srcs = select({
+ ":windows": ["aggregate-ddi.exe"],
+ "//conditions:default": ["aggregate-ddi"],
+ }),
+)
+
+filegroup(
+ name = "generate-modmap-bin",
+ srcs = select({
+ ":windows": ["generate-modmap.exe"],
+ "//conditions:default": ["generate-modmap"],
+ }),
+)
diff --git a/tools/cpp/modules_tools/README.md b/tools/cpp/modules_tools/README.md
new file mode 100644
index 00000000000000..cd4d93927b27c5
--- /dev/null
+++ b/tools/cpp/modules_tools/README.md
@@ -0,0 +1,89 @@
+# C++20 Modules Tools
+
+## Overview
+
+This folder contains two tools: `aggregate-ddi` and `generate-modmap`. These tools are designed to facilitate the processing of C++20 modules information and direct dependent information (DDI). They can aggregate module information, process dependencies, and generate module maps for use in C++20 modular projects.
+
+## The format of DDI
+
+The format of DDI content is [p1689](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2022/p1689r5.html).
+for example,
+
+```
+{
+ "revision": 0,
+ "rules": [
+ {
+ "primary-output": "path/to/a.pcm",
+ "provides": [
+ {
+ "is-interface": true,
+ "logical-name": "a",
+ "source-path": "path/to/a.cppm"
+ }
+ ],
+ "requires": [
+ {
+ "logical-name": "b"
+ }
+ ]
+ }
+ ],
+ "version": 1
+}
+```
+
+## Tools
+
+### `aggregate-ddi`
+
+#### Description
+
+`aggregate-ddi` is a tool that aggregates C++20 module information from multiple sources and processes DDI files to generate a consolidated output containing module paths and their dependencies.
+
+#### Usage
+
+```sh
+aggregate-ddi -m -m ... -d -d ... -o
+```
+
+#### Command Line Arguments
+
+- `-m `: Path to a JSON file containing C++20 module information.
+- `-d `: Path to a DDI file and its associated PCM path.
+- `-o `: Path to the output file where the aggregated information will be stored.
+
+#### Example
+
+```sh
+aggregate-ddi -m module-info1.json -m module-info2.json -d ddi1.json /path/to/pcm1 -d ddi2.json /path/to/pcm2 -o output.json
+```
+
+### `generate-modmap`
+
+#### Description
+
+`generate-modmap` is a tool that generates a module map from a DDI file and C++20 modules information file. It creates two output files: one for the module map and one for the input module paths.
+
+#### Usage
+
+```sh
+generate-modmap
+```
+
+#### Command Line Arguments
+
+- ``: Path to the DDI file containing module dependencies.
+- ``: Path to the JSON file containing C++20 modules information.
+- ``: Path to the output file where the module map will be stored.
+- ``: Compiler type the modmap to use. Only `clang`, `gcc`, `msvc-cl` supported.
+
+#### Example
+
+```sh
+generate-modmap ddi.json cpp20modules-info.json modmap clang
+```
+
+This command will generate two files:
+- `modmap`: containing the module map.
+- `modmap.input`: containing the module paths.
diff --git a/tools/cpp/modules_tools/aggregate-ddi/aggregate-ddi.cc b/tools/cpp/modules_tools/aggregate-ddi/aggregate-ddi.cc
new file mode 100644
index 00000000000000..d232d542dd601e
--- /dev/null
+++ b/tools/cpp/modules_tools/aggregate-ddi/aggregate-ddi.cc
@@ -0,0 +1,41 @@
+// 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.
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "aggregate-ddi.h"
+void write_output(std::ostream &output, const Cpp20ModulesInfo &info) {
+ JsonValue::ObjectType obj;
+ JsonValue::ObjectType modules;
+ JsonValue::ObjectType usages;
+ for (const auto &item : info.modules) {
+ modules[item.first] = JsonValue(item.second);
+ }
+ for (const auto &item : info.usages) {
+ JsonValue::ArrayType list;
+ for (const auto &require_item : item.second) {
+ list.push_back(JsonValue(require_item));
+ }
+ usages[item.first] = list;
+ }
+ obj["modules"] = modules;
+ obj["usages"] = usages;
+ output << to_json(obj);
+}
diff --git a/tools/cpp/modules_tools/aggregate-ddi/aggregate-ddi.h b/tools/cpp/modules_tools/aggregate-ddi/aggregate-ddi.h
new file mode 100644
index 00000000000000..5b8c6a85408504
--- /dev/null
+++ b/tools/cpp/modules_tools/aggregate-ddi/aggregate-ddi.h
@@ -0,0 +1,22 @@
+// 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.
+
+#pragma once
+#include
+#include
+#include
+
+#include "common/common.h"
+
+void write_output(std::ostream &output, const Cpp20ModulesInfo &info);
diff --git a/tools/cpp/modules_tools/aggregate-ddi/aggregate-ddi_test.cc b/tools/cpp/modules_tools/aggregate-ddi/aggregate-ddi_test.cc
new file mode 100644
index 00000000000000..73e2c512c170c6
--- /dev/null
+++ b/tools/cpp/modules_tools/aggregate-ddi/aggregate-ddi_test.cc
@@ -0,0 +1,30 @@
+// 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.
+
+#include "aggregate-ddi.h"
+#include
+
+TEST(WriteOutputTest, BasicFunctionality) {
+ Cpp20ModulesInfo info;
+ info.modules["module1"] = "/path/to/module1";
+ info.modules["module2"] = "/path/to/module2";
+ info.usages["module1"].push_back("module2");
+
+ std::ostringstream output_stream;
+ write_output(output_stream, info);
+
+ std::string expected_output =
+ R"({"modules":{"module1":"/path/to/module1","module2":"/path/to/module2"},"usages":{"module1":["module2"]}})";
+ EXPECT_EQ(output_stream.str(), expected_output);
+}
diff --git a/tools/cpp/modules_tools/aggregate-ddi/main.cc b/tools/cpp/modules_tools/aggregate-ddi/main.cc
new file mode 100644
index 00000000000000..aee9abb1b89d54
--- /dev/null
+++ b/tools/cpp/modules_tools/aggregate-ddi/main.cc
@@ -0,0 +1,74 @@
+// 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.
+
+#include "aggregate-ddi.h"
+#include
+#include
+// Main function
+int main(int argc, char *argv[]) {
+ std::vector cpp20modules_info;
+ std::vector ddi;
+ std::vector module_file;
+ std::string output;
+ for (int i = 1; i < argc; ++i) {
+ std::string arg = argv[i];
+ if (arg == "-m" && i + 1 < argc) {
+ cpp20modules_info.emplace_back(argv[++i]);
+ } else if (arg == "-d" && i + 2 < argc) {
+ ddi.emplace_back(argv[++i]);
+ module_file.emplace_back(argv[++i]);
+ } else if (arg == "-o" && i + 1 < argc) {
+ output = argv[++i];
+ } else {
+ std::cerr << "ERROR: Unknown or incomplete argument: " << arg
+ << std::endl;
+ std::exit(1);
+ }
+ }
+ if (output.empty()) {
+ std::cerr << "ERROR: output not specified" << std::endl;
+ std::exit(1);
+ }
+
+ Cpp20ModulesInfo full_info{};
+
+ // Process cpp20modules_info files
+ for (const auto &info_filename : cpp20modules_info) {
+ std::ifstream info_stream(info_filename);
+ auto info = parse_info(info_stream);
+ full_info.merge(info);
+ }
+
+ // Process ddi files
+ for (std::size_t i = 0; i < ddi.size(); i++) {
+ auto ddi_filename = ddi[i];
+ auto pcm_path = module_file[i];
+ std::ifstream ddi_stream(ddi_filename);
+ auto dep = parse_ddi(ddi_stream);
+ if (dep.gen_bmi) {
+ full_info.modules[dep.name] = pcm_path;
+ full_info.usages[dep.name] = dep.require_list;
+ }
+ }
+
+ // Write final output to file
+ std::ofstream of(output);
+ if (!of.is_open()) {
+ std::cerr << "ERROR: Failed to open the file " << output << "\n";
+ std::exit(1);
+ }
+ write_output(of, full_info);
+
+ return 0;
+}
diff --git a/tools/cpp/modules_tools/common/common.cc b/tools/cpp/modules_tools/common/common.cc
new file mode 100644
index 00000000000000..3e9eedd702bee8
--- /dev/null
+++ b/tools/cpp/modules_tools/common/common.cc
@@ -0,0 +1,165 @@
+// 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.
+
+#include "common.h"
+#include "json.hpp"
+
+#include
+void die(const std::string &msg) {
+ std::cerr << msg << std::endl;
+ std::exit(1);
+}
+void parse_provides(const JsonValue &provides_data, ModuleDep &dep) {
+ if (provides_data.is_null()) {
+ return;
+ }
+ if (!provides_data.is_array()) {
+ die("require ddi content 'rules[0][\"provides\"]' is JSON array");
+ }
+ // Only 1 provide in rule
+ // In C++20 Modules, one TU provide only one module.
+ // Fortran can provide more than one module per TU.
+ // This check is fine for C++20 Modules.
+ auto provides = provides_data.as_array();
+ if (provides.size() > 1) {
+ die("require ddi content 'rules[0][\"provides\"]' has only 1 provide");
+ }
+ if (provides.size() == 1) {
+ auto provide_data = provides[0];
+ if (!provide_data.is_object()) {
+ die("require ddi content 'rules[0][\"provides\"][0]' is JSON object");
+ }
+ auto provide_obj = provide_data.as_object();
+ if (provide_obj.find("logical-name") == provide_obj.end()) {
+ die("require 'logical-name' in 'rules[0][\"provides\"][0]'");
+ }
+ auto name_data = provide_obj.at("logical-name");
+ if (!name_data.is_string()) {
+ die("require ddi content 'rules[0][\"provides\"][0][\"logical-name\"]' "
+ "is JSON string");
+ }
+ dep.gen_bmi = true;
+ dep.name = name_data.as_string();
+ }
+}
+void parse_requires(const JsonValue &requires_data, ModuleDep &dep) {
+ if (requires_data.is_null()) {
+ return;
+ }
+ if (!requires_data.is_array()) {
+ die("require ddi content 'rules[0][\"requires\"]' is JSON array");
+ }
+ for (const auto &item_data : requires_data.as_array()) {
+ if (!item_data.is_object()) {
+ die("require JSON object, but got " + item_data.dump());
+ }
+ auto item_obj = item_data.as_object();
+ if (item_obj.find("logical-name") == item_obj.end()) {
+ die("requrie 'logical-name' in 'rules[0][\"requires\"]' item");
+ }
+ auto name_data = item_obj.at("logical-name");
+ if (!name_data.is_string()) {
+ die("require JSON string, but got " + name_data.dump());
+ }
+ dep.require_list.push_back(name_data.as_string());
+ }
+}
+ModuleDep parse_ddi(std::istream &ddi_stream) {
+ ModuleDep dep{};
+ std::string ddi_string((std::istreambuf_iterator(ddi_stream)),
+ std::istreambuf_iterator());
+ JsonValue data = parse_json(ddi_string);
+ if (!data.is_object()) {
+ die("require ddi content is JSON object");
+ }
+
+ auto data_obj = data.as_object();
+ if (data_obj.find("rules") == data_obj.end()) {
+ die("require 'rules' in ddi content");
+ }
+
+ auto rules_data = data.as_object().at("rules");
+ if (!rules_data.is_array()) {
+ die("require ddi content 'rules' is JSON array");
+ }
+ auto rules = rules_data.as_array();
+ // Only 1 rule in DDI file
+ // DDI files can contain multiple rules (in general).
+ // bazel does per-TU scanning rather than batch scanning.
+ // Therefore, report error if multiple rules here
+ if (rules.size() > 1) {
+ die("require ddi content 'rules' has only 1 rule");
+ }
+ if (rules.empty()) {
+ return dep;
+ }
+ auto rule_data = rules[0];
+ if (!rule_data.is_object()) {
+ die("require ddi content 'rules[0]' is JSON object");
+ }
+ auto rule = rule_data.as_object();
+ auto provides_data = rule["provides"];
+ auto requires_data = rule["requires"];
+ parse_provides(provides_data, dep);
+ parse_requires(requires_data, dep);
+ return dep;
+}
+Cpp20ModulesInfo parse_info(std::istream &info_stream) {
+ std::string info_string((std::istreambuf_iterator(info_stream)),
+ std::istreambuf_iterator());
+ JsonValue data = parse_json(info_string);
+ if (!data.is_object()) {
+ die("require content is JSON object");
+ }
+ auto data_obj = data.as_object();
+ if (data_obj.find("modules") == data_obj.end()) {
+ die("require 'modules' in JSON object");
+ }
+ auto modules_data = data_obj.at("modules");
+ if (!modules_data.is_object()) {
+ die("require 'modules' is JSON object");
+ }
+ if (data_obj.find("usages") == data_obj.end()) {
+ die("require 'usages' in JSON object");
+ }
+ auto usages_data = data_obj.at("usages");
+ if (!usages_data.is_object()) {
+ die("require 'usages' is JSON object");
+ }
+ Cpp20ModulesInfo info;
+ for (const auto &item_data : modules_data.as_object()) {
+ auto name = item_data.first;
+ auto bmi_data = item_data.second;
+ if (!bmi_data.is_string()) {
+ die("require JSON string, but got " + bmi_data.dump());
+ }
+ info.modules[name] = bmi_data.as_string();
+ }
+ for (const auto &item_data : usages_data.as_object()) {
+ auto name = item_data.first;
+ auto require_list_data = item_data.second;
+ if (!require_list_data.is_array()) {
+ die("require JSON array");
+ }
+ std::vector require_list;
+ for (const auto &require_item_data : require_list_data.as_array()) {
+ if (!require_item_data.is_string()) {
+ die("require JSON string, but got " + require_item_data.dump());
+ }
+ require_list.push_back(require_item_data.as_string());
+ }
+ info.usages[name] = require_list;
+ }
+ return info;
+}
diff --git a/tools/cpp/modules_tools/common/common.h b/tools/cpp/modules_tools/common/common.h
new file mode 100644
index 00000000000000..7f95cb9aa1c798
--- /dev/null
+++ b/tools/cpp/modules_tools/common/common.h
@@ -0,0 +1,43 @@
+// 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.
+
+#pragma once
+
+#include "json.hpp"
+
+#include
+#include
+#include
+
+struct Cpp20ModulesInfo {
+ std::unordered_map modules;
+ std::unordered_map> usages;
+
+ void merge(const Cpp20ModulesInfo &info) {
+ for (const auto &item : info.modules) {
+ modules[item.first] = item.second;
+ }
+ for (const auto &item : info.usages) {
+ usages[item.first] = item.second;
+ }
+ }
+};
+struct ModuleDep {
+ bool gen_bmi;
+ std::string name;
+ std::vector require_list;
+};
+
+ModuleDep parse_ddi(std::istream &ddi_stream);
+Cpp20ModulesInfo parse_info(std::istream &info_stream);
diff --git a/tools/cpp/modules_tools/common/common_test.cc b/tools/cpp/modules_tools/common/common_test.cc
new file mode 100644
index 00000000000000..6daa131aae7106
--- /dev/null
+++ b/tools/cpp/modules_tools/common/common_test.cc
@@ -0,0 +1,154 @@
+// 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.
+
+#include "common.h"
+#include
+
+TEST(Cpp20ModulesInfoTest, BasicFunctionality) {
+ std::string info_content = R"({
+ "modules": {
+ "module1": "/path/to/module1",
+ "module2": "/path/to/module2"
+ },
+ "usages": {
+ "module1": ["module2"],
+ "module2": []
+ }
+ })";
+
+ std::istringstream info_stream(info_content);
+ Cpp20ModulesInfo full_info = parse_info(info_stream);
+
+ EXPECT_EQ(full_info.modules["module1"], "/path/to/module1");
+ EXPECT_EQ(full_info.modules["module2"], "/path/to/module2");
+ EXPECT_EQ(full_info.usages["module1"].size(), 1);
+ EXPECT_EQ(full_info.usages["module1"][0], "module2");
+ EXPECT_EQ(full_info.usages["module2"].size(), 0);
+}
+
+TEST(Cpp20ModulesInfoTest, BasicFunctionalityWithTwoFile) {
+ std::string info_content = R"({
+ "modules": {
+ "module1": "/path/to/module1",
+ "module2": "/path/to/module2"
+ },
+ "usages": {
+ "module1": ["module2"],
+ "module2": []
+ }
+ })";
+
+ std::string info_content2 = R"({
+ "modules": {
+ "foo": "/path/to/foo",
+ "bar": "/path/to/bar"
+ },
+ "usages": {
+ "foo": [],
+ "bar": ["foo"]
+ }
+ })";
+
+ std::istringstream info_stream(info_content);
+ std::istringstream info_stream2(info_content2);
+
+ Cpp20ModulesInfo full_info{};
+ auto info1 = parse_info(info_stream);
+ auto info2 = parse_info(info_stream2);
+ full_info.merge(info1);
+ full_info.merge(info2);
+
+ EXPECT_EQ(full_info.modules["module1"], "/path/to/module1");
+ EXPECT_EQ(full_info.modules["module2"], "/path/to/module2");
+ EXPECT_EQ(full_info.modules["foo"], "/path/to/foo");
+ EXPECT_EQ(full_info.modules["bar"], "/path/to/bar");
+ EXPECT_EQ(full_info.usages["module1"].size(), 1);
+ EXPECT_EQ(full_info.usages["module1"][0], "module2");
+ EXPECT_EQ(full_info.usages["module2"].size(), 0);
+ EXPECT_EQ(full_info.usages["bar"].size(), 1);
+ EXPECT_EQ(full_info.usages["bar"][0], "foo");
+ EXPECT_EQ(full_info.usages["foo"].size(), 0);
+}
+
+TEST(DdiTest, BasicFunctionality) {
+ Cpp20ModulesInfo full_info;
+ std::string ddi_content = R"({
+ "rules": [{
+ "provides": [{
+ "logical-name": "foo"
+ }],
+ "requires": [{
+ "logical-name": "bar"
+ }]
+ }]
+ })";
+
+ std::istringstream ddi_stream(ddi_content);
+ auto ddi = parse_ddi(ddi_stream);
+ EXPECT_EQ(ddi.name, "foo");
+ EXPECT_EQ(ddi.gen_bmi, true);
+ EXPECT_EQ(ddi.require_list, std::vector{"bar"});
+}
+
+TEST(DdiTest, BasicEmpty) {
+ std::string ddi_content = R"(
+ {
+ "revision": 0,
+ "rules": [
+ {
+ "primary-output": "main.ddi"
+ }
+ ],
+ "version": 1
+ })";
+ std::istringstream ddi_stream(ddi_content);
+ auto ddi = parse_ddi(ddi_stream);
+ EXPECT_EQ(ddi.name, "");
+ EXPECT_EQ(ddi.gen_bmi, false);
+ EXPECT_TRUE(ddi.require_list.empty());
+}
+
+TEST(DdiTest, EmptyRequires) {
+ Cpp20ModulesInfo full_info;
+ std::string ddi_content = R"({
+ "rules": [{
+ "provides": [{
+ "logical-name": "foo"
+ }]
+ }]
+ })";
+
+ std::istringstream ddi_stream(ddi_content);
+ auto ddi = parse_ddi(ddi_stream);
+ EXPECT_EQ(ddi.name, "foo");
+ EXPECT_EQ(ddi.gen_bmi, true);
+ EXPECT_TRUE(ddi.require_list.empty());
+}
+
+TEST(DdiTest, EmptyProvides) {
+ Cpp20ModulesInfo full_info;
+ std::string ddi_content = R"({
+ "rules": [{
+ "requires": [{
+ "logical-name": "bar"
+ }]
+ }]
+ })";
+
+ std::istringstream ddi_stream(ddi_content);
+ auto ddi = parse_ddi(ddi_stream);
+ EXPECT_EQ(ddi.name, "");
+ EXPECT_EQ(ddi.gen_bmi, false);
+ EXPECT_EQ(ddi.require_list, std::vector{"bar"});
+}
diff --git a/tools/cpp/modules_tools/common/json.hpp b/tools/cpp/modules_tools/common/json.hpp
new file mode 100644
index 00000000000000..e1b9a102444427
--- /dev/null
+++ b/tools/cpp/modules_tools/common/json.hpp
@@ -0,0 +1,413 @@
+// 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.
+
+#pragma once
+
+#include
+#include
+#include
+#include
+#include
+#include