From 3a66e7366c8f7f783d55b74ad3b8d212d803b146 Mon Sep 17 00:00:00 2001 From: PikachuHy Date: Mon, 22 Jul 2024 17:55:44 +0800 Subject: [PATCH 1/7] support C++20 Modules, add C++20 related tools --- scripts/bootstrap/compile.sh | 4 + .../builtins_bzl/common/cc/cc_toolchain.bzl | 12 + .../common/cc/cc_toolchain_info.bzl | 8 +- .../cc/cc_toolchain_provider_helper.bzl | 2 + .../build/lib/packages/util/Crosstool.java | 15 +- .../bazel/testdata/embedded_tools_srcs_deps | 5 + tools/cpp/BUILD | 2 + tools/cpp/BUILD.tools | 10 + tools/cpp/modules_tools/BUILD | 126 ++++++ tools/cpp/modules_tools/BUILD.tools | 22 + tools/cpp/modules_tools/README.md | 89 ++++ .../aggregate-ddi/aggregate-ddi.cc | 41 ++ .../aggregate-ddi/aggregate-ddi.h | 22 + .../aggregate-ddi/aggregate-ddi_test.cc | 30 ++ tools/cpp/modules_tools/aggregate-ddi/main.cc | 74 ++++ tools/cpp/modules_tools/common/common.cc | 153 +++++++ tools/cpp/modules_tools/common/common.h | 43 ++ tools/cpp/modules_tools/common/common_test.cc | 102 +++++ tools/cpp/modules_tools/common/json.hpp | 413 ++++++++++++++++++ tools/cpp/modules_tools/common/json_test.cc | 67 +++ .../generate-modmap/generate-modmap.cc | 105 +++++ .../generate-modmap/generate-modmap.h | 46 ++ .../generate-modmap/generate-modmap_test.cc | 89 ++++ .../cpp/modules_tools/generate-modmap/main.cc | 73 ++++ 24 files changed, 1551 insertions(+), 2 deletions(-) create mode 100644 tools/cpp/modules_tools/BUILD create mode 100644 tools/cpp/modules_tools/BUILD.tools create mode 100644 tools/cpp/modules_tools/README.md create mode 100644 tools/cpp/modules_tools/aggregate-ddi/aggregate-ddi.cc create mode 100644 tools/cpp/modules_tools/aggregate-ddi/aggregate-ddi.h create mode 100644 tools/cpp/modules_tools/aggregate-ddi/aggregate-ddi_test.cc create mode 100644 tools/cpp/modules_tools/aggregate-ddi/main.cc create mode 100644 tools/cpp/modules_tools/common/common.cc create mode 100644 tools/cpp/modules_tools/common/common.h create mode 100644 tools/cpp/modules_tools/common/common_test.cc create mode 100644 tools/cpp/modules_tools/common/json.hpp create mode 100644 tools/cpp/modules_tools/common/json_test.cc create mode 100644 tools/cpp/modules_tools/generate-modmap/generate-modmap.cc create mode 100644 tools/cpp/modules_tools/generate-modmap/generate-modmap.h create mode 100644 tools/cpp/modules_tools/generate-modmap/generate-modmap_test.cc create mode 100644 tools/cpp/modules_tools/generate-modmap/main.cc 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..ebf9c196643d5c 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,15 @@ The label of the rule providing cc_toolchain_config_info.""", default = semantics.BUILD_INFO_TRANLATOR_LABEL, providers = [OutputGroupInfo], ), + "_aggregate_ddi": attr.label( + executable = True, + cfg = "exec", + default = "@" + semantics.get_repo() + "//tools/cpp:aggregate-ddi", + ), + "_generate_modmap": attr.label( + executable = True, + cfg = "exec", + default = "@" + semantics.get_repo() + "//tools/cpp:generate-modmap", + ), }, ) 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/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..0a7052d3d827e4 --- /dev/null +++ b/tools/cpp/modules_tools/common/common.cc @@ -0,0 +1,153 @@ +// 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); +} +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"]; + 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(); + } + auto requires_data = rule["requires"]; + 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()); + } + 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..886913291103d3 --- /dev/null +++ b/tools/cpp/modules_tools/common/common_test.cc @@ -0,0 +1,102 @@ +// 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"}); +} 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 +#include +#include +#include +#include +#include +#include +// forward decl +struct JsonValue; +inline std::string to_json(const JsonValue &data); + +// Define a basic struct for JSON values +struct JsonValue { + using ObjectType = std::map; + using ArrayType = std::vector; + + std::variant + value; + + JsonValue() : value(nullptr) {} + JsonValue(const std::string &v) : value(v) {} + JsonValue(const char *v) : value(std::string(v)) {} + JsonValue(bool v) : value(v) {} + JsonValue(long v) : value(v) {} + JsonValue(int v) : value((long)v) {} + JsonValue(double v) : value(v) {} + JsonValue(const ObjectType &v) : value(v) {} + JsonValue(const ArrayType &v) : value(v) {} + JsonValue(std::nullptr_t) : value(nullptr) {} + + bool is_null() const { return std::holds_alternative(value); } + bool is_string() const { return std::holds_alternative(value); } + bool is_object() const { return std::holds_alternative(value); } + bool is_array() const { return std::holds_alternative(value); } + bool is_bool() const { return std::holds_alternative(value); } + bool is_long() const { return std::holds_alternative(value); } + bool is_double() const { return std::holds_alternative(value); } + + const std::string &as_string() const { return std::get(value); } + const ObjectType &as_object() const { return std::get(value); } + const ArrayType &as_array() const { return std::get(value); } + bool as_bool() const { return std::get(value); } + long as_long() const { return std::get(value); } + double as_double() const { return std::get(value); } + + // Implement equality operator + bool operator==(const JsonValue &other) const { + if (value.index() != other.value.index()) + return false; + + if (is_null()) + return true; + if (is_string()) + return as_string() == other.as_string(); + if (is_bool()) + return as_bool() == other.as_bool(); + if (is_long()) + return as_long() == other.as_long(); + if (is_double()) + return as_double() == other.as_double(); + if (is_object()) + return as_object() == other.as_object(); + if (is_array()) + return as_array() == other.as_array(); + + return false; + } + + bool operator!=(const JsonValue &other) const { return !(*this == other); } + + std::string dump() const { return to_json(*this); } +}; + +// Define the JSON parser class +class Json { +public: + // Singleton instance + static Json &instance() { + static Json INSTANCE; + return INSTANCE; + } + + // Function to encode an object to JSON + std::string encode(const JsonValue &x) const { + Encoder enc; + try { + enc.encode(x); + } catch (const std::overflow_error &e) { + throw std::runtime_error("nesting depth limit exceeded"); + } + return enc.out.str(); + } + + // Function to decode a JSON string to an object + JsonValue decode(const std::string &x) const { + try { + return Decoder(x).decode(); + } catch (const std::runtime_error &e) { + throw std::runtime_error("Invalid JSON string"); + } + } + +private: + Json() {} + + // Encoder class to serialize objects to JSON + class Encoder { + public: + void encode(const JsonValue &x) { + if (x.is_null()) { + out << "null"; + return; + } + if (x.is_string()) { + append_quoted(x.as_string()); + return; + } + if (x.is_bool()) { + out << std::boolalpha << x.as_bool(); + return; + } + if (x.is_long()) { + out << x.as_long(); + return; + } + if (x.is_double()) { + double d = x.as_double(); + if (!std::isfinite(d)) { + throw std::runtime_error("Cannot encode non-finite float"); + } + out << std::setprecision(std::numeric_limits::digits10) << d; + return; + } + if (x.is_object()) { + const auto &m = x.as_object(); + out << '{'; + std::string sep = ""; + for (const auto &item : m) { + out << sep; + sep = ","; + append_quoted(item.first); + out << ':'; + encode(item.second); + } + out << '}'; + return; + } + if (x.is_array()) { + const auto &v = x.as_array(); + out << '['; + std::string sep = ""; + for (const auto &value : v) { + out << sep; + sep = ","; + encode(value); + } + out << ']'; + return; + } + // Add more cases for other types as needed + throw std::runtime_error("Cannot encode value as JSON"); + } + + std::ostringstream out; + + private: + void append_quoted(const std::string &s) { + out << '"'; + for (char c : s) { + switch (c) { + case '"': + out << "\\\""; + break; + case '\\': + out << "\\\\"; + break; + case '\b': + out << "\\b"; + break; + case '\f': + out << "\\f"; + break; + case '\n': + out << "\\n"; + break; + case '\r': + out << "\\r"; + break; + case '\t': + out << "\\t"; + break; + default: + if ('\x00' <= c && c <= '\x1f') { + out << "\\u" << std::hex << std::setw(4) << std::setfill('0') + << static_cast(c); + } else { + out << c; + } + } + } + out << '"'; + } + }; + + // Decoder class to parse JSON strings to objects + class Decoder { + public: + Decoder(const std::string &s) : s(s), i(0) {} + + JsonValue decode() { + auto x = parse(); + if (skip_space()) { + throw std::runtime_error("Unexpected character after value"); + } + return x; + } + + private: + std::string s; + size_t i; + + JsonValue parse() { + char c = next(); + switch (c) { + case '"': + return parse_string(); + case 'n': + if (s.substr(i, 4) == "null") { + i += 4; + return nullptr; + } + break; + case 't': + if (s.substr(i, 4) == "true") { + i += 4; + return true; + } + break; + case 'f': + if (s.substr(i, 5) == "false") { + i += 5; + return false; + } + break; + case '[': + return parse_array(); + case '{': + return parse_object(); + default: + if (isdigit(c) || c == '-') { + return parse_number(c); + } + } + throw std::runtime_error("Unexpected character"); + } + + JsonValue parse_string() { + i++; // skip " + std::ostringstream str; + while (i < s.size()) { + char c = s[i++]; + if (c == '"') + return str.str(); + if (c == '\\') { + c = s[i++]; + switch (c) { + case 'b': + str << '\b'; + break; + case 'f': + str << '\f'; + break; + case 'n': + str << '\n'; + break; + case 'r': + str << '\r'; + break; + case 't': + str << '\t'; + break; + case 'u': + // Handle \uXXXX + str << static_cast(std::stoi(s.substr(i, 4), nullptr, 16)); + i += 4; + break; + default: + str << c; + } + } else { + str << c; + } + } + throw std::runtime_error("Unclosed string literal"); + } + + JsonValue parse_array() { + JsonValue::ArrayType array; + i++; // skip [ + if (next() != ']') { + while (true) { + array.push_back(parse()); + char c = next(); + if (c != ',') { + if (c != ']') + throw std::runtime_error("Expected ',' or ']'"); + break; + } + i++; // skip , + } + } + i++; // skip ] + return array; + } + + JsonValue parse_object() { + JsonValue::ObjectType object; + i++; // skip { + if (next() != '}') { + while (true) { + std::string key = std::get(parse().value); + if (next() != ':') + throw std::runtime_error("Expected ':'"); + i++; // skip : + object[key] = parse(); + char c = next(); + if (c != ',') { + if (c != '}') + throw std::runtime_error("Expected ',' or '}'"); + break; + } + i++; // skip , + } + } + i++; // skip } + return object; + } + + JsonValue parse_number(char c) { + size_t j = i + 1; + bool isfloat = false; + while (j < s.size()) { + c = s[j]; + if (isdigit(c) || c == '-' || c == '+' || c == '.' || c == 'e' || + c == 'E') { + if (c == '.' || c == 'e' || c == 'E' || c == '+' || c == '-') + isfloat = true; + j++; + } else { + break; + } + } + std::string num = s.substr(i, j - i); + i = j; + std::istringstream iss(num); + if (isfloat) { + double d; + iss >> d; + return d; + } else { + long n; + iss >> n; + return n; + } + } + + bool skip_space() { + while (i < s.size() && isspace(s[i])) { + i++; + } + return i < s.size(); + } + + char next() { + if (skip_space()) { + return s[i]; + } + throw std::runtime_error("Unexpected end of input"); + } + }; +}; + +inline JsonValue parse_json(const std::string &data) { + Json &json = Json::instance(); + return json.decode(data); +} + +inline std::string to_json(const JsonValue &data) { + Json &json = Json::instance(); + return json.encode(data); +} diff --git a/tools/cpp/modules_tools/common/json_test.cc b/tools/cpp/modules_tools/common/json_test.cc new file mode 100644 index 00000000000000..a57a82fcd86a43 --- /dev/null +++ b/tools/cpp/modules_tools/common/json_test.cc @@ -0,0 +1,67 @@ +#include "json.hpp" +#include +#include +#include +#include +#include + +TEST(JsonTest, EncodeTest) { + Json &json = Json::instance(); + + ASSERT_EQ(json.encode(nullptr), "null"); + ASSERT_EQ(json.encode(true), "true"); + ASSERT_EQ(json.encode(false), "false"); + ASSERT_EQ(json.encode(-123), "-123"); + + // This implementation don't support large number + // ASSERT_EQ(json.encode(12345 * 12345 * 12345 * 12345 * 12345 * 12345), + // "3539537889086624823140625"); + // ASSERT_EQ(json.encode(static_cast(12345 * 12345 * 12345 * 12345 * + // 12345 * 12345)), "3.539537889086625e+24"); + ASSERT_EQ(json.encode(12.345e67), "1.2345e+68"); + ASSERT_EQ(json.encode("hello"), "\"hello\""); + ASSERT_EQ(json.encode("\t"), "\"\\t\""); + ASSERT_EQ(json.encode("\r"), "\"\\r\""); + ASSERT_EQ(json.encode("\n"), "\"\\n\""); + ASSERT_EQ(json.encode("'"), "\"'\""); + ASSERT_EQ(json.encode("\""), "\"\\\"\""); + ASSERT_EQ(json.encode("/"), "\"/\""); + ASSERT_EQ(json.encode("\\"), "\"\\\\\""); + ASSERT_EQ(json.encode(""), "\"\""); + // ASSERT_EQ(json.encode(std::string("😹").substr(0, 1)), "\"�\""); + JsonValue::ArrayType arr = {JsonValue(1), JsonValue(2), JsonValue(3)}; + ASSERT_EQ(json.encode(arr), "[1,2,3]"); + + // Mapping of key-values for mapping JSON of objects/dictionaries + JsonValue::ObjectType m = {{"x", JsonValue(1)}, {"y", JsonValue("two")}}; + ASSERT_EQ(json.encode(m), "{\"x\":1,\"y\":\"two\"}"); +} + +TEST(JsonTest, DecodeTest) { + Json &json = Json::instance(); + + ASSERT_EQ(json.decode("null"), JsonValue(nullptr)); + ASSERT_EQ(json.decode("true"), JsonValue(true)); + ASSERT_EQ(json.decode("false"), JsonValue(false)); + ASSERT_EQ(json.decode("-123"), JsonValue(-123)); + ASSERT_EQ(json.decode("-0"), JsonValue(0)); + // This implementation don't support large number + // ASSERT_EQ(json.decode("3539537889086624823140625"), + // JsonValue(3539537889086624823140625ll)); + // ASSERT_EQ(json.decode("3539537889086624823140625.0"), + // JsonValue(static_cast(3539537889086624823140625ll))); + + // Additional decoding examples + ASSERT_EQ(json.decode("[]").as_array().size(), 0); + ASSERT_EQ(json.decode("[1]").as_array().size(), 1); + auto arr = json.decode("[1, 2, 3]").as_array(); + ASSERT_EQ(arr.size(), 3); + ASSERT_EQ(arr[0].as_long(), 1); + ASSERT_EQ(arr[1].as_long(), 2); + ASSERT_EQ(arr[2].as_long(), 3); + + auto obj = json.decode("{\"one\": 1, \"two\": 2}").as_object(); + ASSERT_EQ(obj.size(), 2); + ASSERT_EQ(obj["one"].as_long(), 1); + ASSERT_EQ(obj["two"].as_long(), 2); +} diff --git a/tools/cpp/modules_tools/generate-modmap/generate-modmap.cc b/tools/cpp/modules_tools/generate-modmap/generate-modmap.cc new file mode 100644 index 00000000000000..3c01802eef317a --- /dev/null +++ b/tools/cpp/modules_tools/generate-modmap/generate-modmap.cc @@ -0,0 +1,105 @@ +// 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 "generate-modmap.h" +// This function writes parameters about the required modules. +// +// Format of the modmap file +// Clang: -fmodule-file== +// GCC: +// MSVC: /reference = +// +// NOTE: For the GCC compiler, additional `$root .` is added +// +// Another special consideration for GCC is that it cannot specify +// the output module name when compiling a Module Interface. +// Therefore, the generated module name and BMI file path is added +// +// see also https://gcc.gnu.org/onlinedocs/gcc/C_002b_002b-Module-Mapper.html +void write_modmap(std::ostream &modmap_file_stream, + std::ostream &modmap_file_dot_input_stream, + const std::unordered_set &modmap, + const std::string &compiler, + const std::optional &generated) { + + if (compiler == "gcc") { + modmap_file_stream << "$root ." + << "\n"; + if (generated.has_value()) { + modmap_file_stream << generated.value().name << " " + << generated.value().path << "\n"; + } + } + for (const auto &item : modmap) { + if (compiler == "clang") { + modmap_file_stream << "-fmodule-file=" << item.name << "=" << item.path + << "\n"; + } else if (compiler == "gcc") { + modmap_file_stream << item.name << " " << item.path << "\n"; + } else if (compiler == "msvc-cl") { + modmap_file_stream << "/reference " << item.name << "=" << item.path + << "\n"; + } else { + std::cerr << "bad compiler: " << compiler << std::endl; + std::exit(1); + } + modmap_file_dot_input_stream << item.path << "\n"; + } +} + +std::unordered_set process(const ModuleDep &dep, + const Cpp20ModulesInfo &info) { + + std::queue q; + for (const auto &item : dep.require_list) { + q.push(item); + } + // Get all dependencies + std::unordered_set s; + while (!q.empty()) { + std::string name = q.front(); + q.pop(); + s.insert(name); + auto it = info.usages.find(name); + if (it == info.usages.end()) { + continue; + } + auto deps = it->second; + for (const auto &dep : deps) { + if (s.count(dep)) { + continue; + } + q.push(dep); + } + } + + // Construct modmap + std::unordered_set modmap; + for (const auto &name : s) { + auto it = info.modules.find(name); + if (it == info.modules.end()) { + std::cerr << "ERROR: Module not found: " << name << std::endl; + std::exit(1); + } + modmap.insert(ModmapItem{name, it->second}); + } + return modmap; +} diff --git a/tools/cpp/modules_tools/generate-modmap/generate-modmap.h b/tools/cpp/modules_tools/generate-modmap/generate-modmap.h new file mode 100644 index 00000000000000..cdd130f7ec810a --- /dev/null +++ b/tools/cpp/modules_tools/generate-modmap/generate-modmap.h @@ -0,0 +1,46 @@ +// 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 "common/common.h" + +struct ModmapItem { + std::string name; + std::string path; + bool operator==(const ModmapItem &other) const { + return name == other.name && path == other.path; + } + friend std::ostream &operator<<(std::ostream &os, const ModmapItem &item) { + os << "ModmapItem{name: " << item.name << ", path: " << item.path << "}"; + return os; + } +}; +// Define the hash function for the ModmapItem +namespace std { +template <> struct hash { + size_t operator()(const ModmapItem &item) const { + return hash()(item.name) ^ (hash()(item.path) << 1); + } +}; +} // namespace std +std::unordered_set process(const ModuleDep &dep, + const Cpp20ModulesInfo &info); +void write_modmap(std::ostream &modmap_file_stream, + std::ostream &modmap_file_dot_input_stream, + const std::unordered_set &modmap, + const std::string &compiler, + const std::optional &generated); diff --git a/tools/cpp/modules_tools/generate-modmap/generate-modmap_test.cc b/tools/cpp/modules_tools/generate-modmap/generate-modmap_test.cc new file mode 100644 index 00000000000000..3d3d988c513a80 --- /dev/null +++ b/tools/cpp/modules_tools/generate-modmap/generate-modmap_test.cc @@ -0,0 +1,89 @@ +// 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 "generate-modmap.h" +#include + +TEST(ModmapTest, EmptyInput) { + ModuleDep dep{}; + Cpp20ModulesInfo info{}; + auto modmap = process(dep, info); + + std::unordered_set expected_modmap; + EXPECT_EQ(modmap, expected_modmap); +} + +TEST(ModmapTest, BasicFunctionality) { + ModuleDep dep{}; + Cpp20ModulesInfo info{}; + + dep.require_list.push_back("module1"); + dep.require_list.push_back("module2"); + + info.modules["module1"] = "/path/to/module1"; + info.modules["module2"] = "/path/to/module2"; + + info.usages["module1"].push_back("module2"); + + auto modmap = process(dep, info); + + std::unordered_set expected_modmap = { + {"module1", "/path/to/module1"}, {"module2", "/path/to/module2"}}; + EXPECT_EQ(modmap, expected_modmap); +} + +TEST(ModmapTest, BasicFunctionality2) { + ModuleDep dep{}; + Cpp20ModulesInfo info{}; + + dep.require_list.push_back("module1"); + + info.modules["module1"] = "/path/to/module1"; + info.modules["module2"] = "/path/to/module2"; + info.modules["module3"] = "/path/to/module3"; + + info.usages["module1"].push_back("module2"); + info.usages["module2"].push_back("module3"); + + auto modmap = process(dep, info); + + std::unordered_set expected_modmap = { + {"module1", "/path/to/module1"}, + {"module2", "/path/to/module2"}, + {"module3", "/path/to/module3"}}; + EXPECT_EQ(modmap, expected_modmap); +} + +TEST(ModmapTest, BasicFunctionality3) { + ModuleDep dep{}; + Cpp20ModulesInfo info{}; + + dep.require_list.push_back("module1"); + dep.require_list.push_back("module4"); + + info.modules["module1"] = "/path/to/module1"; + info.modules["module2"] = "/path/to/module2"; + info.modules["module3"] = "/path/to/module3"; + info.modules["module4"] = "/path/to/module4"; + + info.usages["module1"].push_back("module2"); + + auto modmap = process(dep, info); + + std::unordered_set expected_modmap = { + {"module1", "/path/to/module1"}, + {"module2", "/path/to/module2"}, + {"module4", "/path/to/module4"}}; + EXPECT_EQ(modmap, expected_modmap); +} diff --git a/tools/cpp/modules_tools/generate-modmap/main.cc b/tools/cpp/modules_tools/generate-modmap/main.cc new file mode 100644 index 00000000000000..9a03d96be5864b --- /dev/null +++ b/tools/cpp/modules_tools/generate-modmap/main.cc @@ -0,0 +1,73 @@ +// 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 "generate-modmap.h" +#include +int main(int argc, char *argv[]) { + if (argc != 5) { + std::cerr << "Usage: generate-modmap " + " " + << std::endl; + std::exit(1); + } + + // Retrieve the values of the flags + std::string ddi_filename = argv[1]; + std::string info_filename = argv[2]; + std::string output = argv[3]; + std::string compiler = argv[4]; + + std::ifstream info_stream(info_filename); + if (!info_stream.is_open()) { + std::cerr << "ERROR: Failed to open the file " << info_filename + << std::endl; + std::exit(1); + } + std::ifstream ddi_stream(ddi_filename); + if (!ddi_stream.is_open()) { + std::cerr << "ERROR: Failed to open the file " << ddi_filename << std::endl; + std::exit(1); + } + auto dep = parse_ddi(ddi_stream); + auto info = parse_info(info_stream); + auto modmap = process(dep, info); + + std::string modmap_filename = output; + std::string modmap_dot_input_filename = modmap_filename + ".input"; + std::ofstream modmap_file_stream(modmap_filename); + std::ofstream modmap_file_dot_input_stream(modmap_dot_input_filename); + if (!modmap_file_stream.is_open()) { + std::cerr << "ERROR: Failed to open the file " << modmap_filename + << std::endl; + std::exit(1); + } + if (!modmap_file_dot_input_stream.is_open()) { + std::cerr << "ERROR: Failed to open the file " << modmap_dot_input_filename + << std::endl; + std::exit(1); + } + std::optional generated; + if (dep.gen_bmi) { + ModmapItem item; + item.name = dep.name; + item.path = info.modules[dep.name]; + generated = item; + } + write_modmap(modmap_file_stream, modmap_file_dot_input_stream, modmap, + compiler, generated); + modmap_file_stream.close(); + modmap_file_dot_input_stream.close(); + + return 0; +} From 9a89363373fd3425f4104249883bdf1e46581140 Mon Sep 17 00:00:00 2001 From: PikachuHy Date: Mon, 22 Jul 2024 15:41:32 +0800 Subject: [PATCH 2/7] fix empty provides or requires --- tools/cpp/modules_tools/common/common.cc | 82 ++++++++++++++---------- 1 file changed, 47 insertions(+), 35 deletions(-) diff --git a/tools/cpp/modules_tools/common/common.cc b/tools/cpp/modules_tools/common/common.cc index 0a7052d3d827e4..3e9eedd702bee8 100644 --- a/tools/cpp/modules_tools/common/common.cc +++ b/tools/cpp/modules_tools/common/common.cc @@ -20,41 +20,10 @@ void die(const std::string &msg) { std::cerr << msg << std::endl; std::exit(1); } -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"); +void parse_provides(const JsonValue &provides_data, ModuleDep &dep) { + if (provides_data.is_null()) { + return; } - - 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"]; if (!provides_data.is_array()) { die("require ddi content 'rules[0][\"provides\"]' is JSON array"); } @@ -83,7 +52,11 @@ ModuleDep parse_ddi(std::istream &ddi_stream) { dep.gen_bmi = true; dep.name = name_data.as_string(); } - auto requires_data = rule["requires"]; +} +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"); } @@ -101,6 +74,45 @@ ModuleDep parse_ddi(std::istream &ddi_stream) { } 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) { From 4d895e0f1febea00cfe9abf832bb156c1649f364 Mon Sep 17 00:00:00 2001 From: PikachuHy Date: Mon, 22 Jul 2024 18:02:45 +0800 Subject: [PATCH 3/7] add empty tests --- tools/cpp/modules_tools/common/common_test.cc | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/tools/cpp/modules_tools/common/common_test.cc b/tools/cpp/modules_tools/common/common_test.cc index 886913291103d3..6daa131aae7106 100644 --- a/tools/cpp/modules_tools/common/common_test.cc +++ b/tools/cpp/modules_tools/common/common_test.cc @@ -100,3 +100,55 @@ TEST(DdiTest, BasicFunctionality) { 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"}); +} From 27ae7bd3a0f2d417fd21fa61510c36bd4dfc27aa Mon Sep 17 00:00:00 2001 From: PikachuHy Date: Tue, 23 Jul 2024 10:23:55 +0800 Subject: [PATCH 4/7] format --- tools/cpp/modules_tools/generate-modmap/main.cc | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tools/cpp/modules_tools/generate-modmap/main.cc b/tools/cpp/modules_tools/generate-modmap/main.cc index 9a03d96be5864b..a224bace25ba4f 100644 --- a/tools/cpp/modules_tools/generate-modmap/main.cc +++ b/tools/cpp/modules_tools/generate-modmap/main.cc @@ -59,10 +59,10 @@ int main(int argc, char *argv[]) { } std::optional generated; if (dep.gen_bmi) { - ModmapItem item; - item.name = dep.name; - item.path = info.modules[dep.name]; - generated = item; + ModmapItem item; + item.name = dep.name; + item.path = info.modules[dep.name]; + generated = item; } write_modmap(modmap_file_stream, modmap_file_dot_input_stream, modmap, compiler, generated); From d9954dfc54dd9fd9d728ad3888c11567e5880692 Mon Sep 17 00:00:00 2001 From: PikachuHy Date: Wed, 24 Jul 2024 10:21:11 +0800 Subject: [PATCH 5/7] refactor: put tools under semantics --- .../builtins_bzl/common/cc/cc_toolchain.bzl | 12 ++---------- .../builtins_bzl/common/cc/semantics.bzl | 16 ++++++++++++++++ 2 files changed, 18 insertions(+), 10 deletions(-) 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 ebf9c196643d5c..00908d89e054b2 100644 --- a/src/main/starlark/builtins_bzl/common/cc/cc_toolchain.bzl +++ b/src/main/starlark/builtins_bzl/common/cc/cc_toolchain.bzl @@ -344,15 +344,7 @@ The label of the rule providing cc_toolchain_config_info.""", default = semantics.BUILD_INFO_TRANLATOR_LABEL, providers = [OutputGroupInfo], ), - "_aggregate_ddi": attr.label( - executable = True, - cfg = "exec", - default = "@" + semantics.get_repo() + "//tools/cpp:aggregate-ddi", - ), - "_generate_modmap": attr.label( - executable = True, - cfg = "exec", - default = "@" + semantics.get_repo() + "//tools/cpp:generate-modmap", - ), + "_aggregate_ddi": semantics.get_aggregate_ddi(), + "_generate_modmap": semantics.get_generate_modmap(), }, ) diff --git a/src/main/starlark/builtins_bzl/common/cc/semantics.bzl b/src/main/starlark/builtins_bzl/common/cc/semantics.bzl index ae6c79e3ed0ed7..a3eef6323f98f0 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 _get_aggregate_ddi(): + return attr.label( + executable = True, + cfg = "exec", + default = "@" + _get_repo() + "//tools/cpp:aggregate-ddi", + ) + +def _get_generate_modmap(): + return 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,8 @@ 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, + get_aggregate_ddi = _get_aggregate_ddi, + get_generate_modmap = _get_generate_modmap, 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", From e37f7002eb35267717ea48707fc37830180f5ea9 Mon Sep 17 00:00:00 2001 From: PikachuHy Date: Tue, 30 Jul 2024 14:40:26 +0800 Subject: [PATCH 6/7] refactor: get_cpp_modules_tools --- .../builtins_bzl/common/cc/cc_toolchain.bzl | 4 +-- .../builtins_bzl/common/cc/semantics.bzl | 29 +++++++++---------- 2 files changed, 15 insertions(+), 18 deletions(-) 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 00908d89e054b2..851381a2377b7f 100644 --- a/src/main/starlark/builtins_bzl/common/cc/cc_toolchain.bzl +++ b/src/main/starlark/builtins_bzl/common/cc/cc_toolchain.bzl @@ -344,7 +344,5 @@ The label of the rule providing cc_toolchain_config_info.""", default = semantics.BUILD_INFO_TRANLATOR_LABEL, providers = [OutputGroupInfo], ), - "_aggregate_ddi": semantics.get_aggregate_ddi(), - "_generate_modmap": semantics.get_generate_modmap(), - }, + } | semantics.get_cpp_modules_tools(), ) diff --git a/src/main/starlark/builtins_bzl/common/cc/semantics.bzl b/src/main/starlark/builtins_bzl/common/cc/semantics.bzl index a3eef6323f98f0..c4061dfff5b5df 100644 --- a/src/main/starlark/builtins_bzl/common/cc/semantics.bzl +++ b/src/main/starlark/builtins_bzl/common/cc/semantics.bzl @@ -134,19 +134,19 @@ def _get_experimental_link_static_libraries_once(ctx): def _check_cc_shared_library_tags(ctx): pass -def _get_aggregate_ddi(): - return attr.label( - executable = True, - cfg = "exec", - default = "@" + _get_repo() + "//tools/cpp:aggregate-ddi", - ) - -def _get_generate_modmap(): - return attr.label( - executable = True, - cfg = "exec", - default = "@" + _get_repo() + "//tools/cpp:generate-modmap", - ) +def _get_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", @@ -186,8 +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, - get_aggregate_ddi = _get_aggregate_ddi, - get_generate_modmap = _get_generate_modmap, + get_cpp_modules_tools = _get_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", From 2e64f787830507f85c5bc16c55eafb185af98a3f Mon Sep 17 00:00:00 2001 From: PikachuHy Date: Tue, 30 Jul 2024 14:52:14 +0800 Subject: [PATCH 7/7] rename get_cpp_modules_tools to cpp_modules_tools --- src/main/starlark/builtins_bzl/common/cc/cc_toolchain.bzl | 2 +- src/main/starlark/builtins_bzl/common/cc/semantics.bzl | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) 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 851381a2377b7f..f1525298b7094a 100644 --- a/src/main/starlark/builtins_bzl/common/cc/cc_toolchain.bzl +++ b/src/main/starlark/builtins_bzl/common/cc/cc_toolchain.bzl @@ -344,5 +344,5 @@ The label of the rule providing cc_toolchain_config_info.""", default = semantics.BUILD_INFO_TRANLATOR_LABEL, providers = [OutputGroupInfo], ), - } | semantics.get_cpp_modules_tools(), + } | semantics.cpp_modules_tools(), ) diff --git a/src/main/starlark/builtins_bzl/common/cc/semantics.bzl b/src/main/starlark/builtins_bzl/common/cc/semantics.bzl index c4061dfff5b5df..988eb1d5f8fe15 100644 --- a/src/main/starlark/builtins_bzl/common/cc/semantics.bzl +++ b/src/main/starlark/builtins_bzl/common/cc/semantics.bzl @@ -134,7 +134,7 @@ def _get_experimental_link_static_libraries_once(ctx): def _check_cc_shared_library_tags(ctx): pass -def _get_cpp_modules_tools(): +def _cpp_modules_tools(): return { "_aggregate_ddi": attr.label( executable = True, @@ -186,7 +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, - get_cpp_modules_tools = _get_cpp_modules_tools, + 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",