| // 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 "tools/cpp/modules_tools/common/common.h" | |
| #include <iostream> | |
| #include "json.hpp" | |
| 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<char>(ddi_stream)), | |
| std::istreambuf_iterator<char>()); | |
| 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<char>(info_stream)), | |
| std::istreambuf_iterator<char>()); | |
| 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<std::string> 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; | |
| } | |