| // 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; |
| } |
| |