| /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying | |
| file LICENSE.rst or https://cmake.org/licensing for details. */ | |
| #include "cmScanDepFormat.h" | |
| #include <cstdio> | |
| #include <utility> | |
| #include <cm/optional> | |
| #include <cm/string_view> | |
| #include <cmext/string_view> | |
| #include <cm3p/json/value.h> | |
| #include <cm3p/json/writer.h> | |
| #include "cmsys/FStream.hxx" | |
| #include "cmsys/String.h" | |
| #include "cmGeneratedFileStream.h" | |
| #include "cmJSONState.h" | |
| #include "cmStringAlgorithms.h" | |
| #include "cmSystemTools.h" | |
| static bool ParseFilename(Json::Value const& val, std::string& result) | |
| { | |
| if (val.isString()) { | |
| result = val.asString(); | |
| } else { | |
| return false; | |
| } | |
| return true; | |
| } | |
| static Json::Value EncodeFilename(std::string const& path) | |
| { | |
| std::string data; | |
| data.reserve(path.size()); | |
| for (auto const& byte : path) { | |
| if (cmsysString_iscntrl(byte)) { | |
| // Control characters. | |
| data.append("\\u"); | |
| char buf[5]; | |
| std::snprintf(buf, sizeof(buf), "%04x", byte); | |
| data.append(buf); | |
| } else if (byte == '"' || byte == '\\') { | |
| // Special JSON characters. | |
| data.push_back('\\'); | |
| data.push_back(byte); | |
| } else { | |
| // Other data. | |
| data.push_back(byte); | |
| } | |
| } | |
| return data; | |
| } | |
| #define PARSE_BLOB(val, res) \ | |
| do { \ | |
| if (!ParseFilename(val, res)) { \ | |
| cmSystemTools::Error(cmStrCat("-E cmake_ninja_dyndep failed to parse ", \ | |
| arg_pp, ": invalid blob")); \ | |
| return false; \ | |
| } \ | |
| } while (0) | |
| #define PARSE_FILENAME(val, res) \ | |
| do { \ | |
| if (!ParseFilename(val, res)) { \ | |
| cmSystemTools::Error(cmStrCat("-E cmake_ninja_dyndep failed to parse ", \ | |
| arg_pp, ": invalid filename")); \ | |
| return false; \ | |
| } \ | |
| \ | |
| if (work_directory && !work_directory->empty() && \ | |
| !cmSystemTools::FileIsFullPath(res)) { \ | |
| res = cmStrCat(*work_directory, '/', res); \ | |
| } \ | |
| } while (0) | |
| bool cmScanDepFormat_P1689_Parse(std::string const& arg_pp, | |
| cmScanDepInfo* info) | |
| { | |
| Json::Value ppio; | |
| Json::Value const& ppi = ppio; | |
| { | |
| cmJSONState parseState(arg_pp, &ppio); | |
| if (!parseState.errors.empty()) { | |
| cmSystemTools::Error(cmStrCat("-E cmake_ninja_dyndep failed to parse ", | |
| arg_pp, parseState.GetErrorMessage())); | |
| return false; | |
| } | |
| } | |
| Json::Value const& version = ppi["version"]; | |
| if (version.asUInt() > 1) { | |
| cmSystemTools::Error(cmStrCat("-E cmake_ninja_dyndep failed to parse ", | |
| arg_pp, ": version ", version.asString())); | |
| return false; | |
| } | |
| Json::Value const& rules = ppi["rules"]; | |
| if (rules.isArray()) { | |
| if (rules.size() != 1) { | |
| cmSystemTools::Error(cmStrCat("-E cmake_ninja_dyndep failed to parse ", | |
| arg_pp, ": expected 1 source entry")); | |
| return false; | |
| } | |
| for (auto const& rule : rules) { | |
| cm::optional<std::string> work_directory; | |
| Json::Value const& workdir = rule["work-directory"]; | |
| if (workdir.isString()) { | |
| std::string wd; | |
| PARSE_BLOB(workdir, wd); | |
| work_directory = std::move(wd); | |
| } else if (!workdir.isNull()) { | |
| cmSystemTools::Error(cmStrCat("-E cmake_ninja_dyndep failed to parse ", | |
| arg_pp, | |
| ": work-directory is not a string")); | |
| return false; | |
| } | |
| if (rule.isMember("primary-output")) { | |
| Json::Value const& primary_output = rule["primary-output"]; | |
| PARSE_FILENAME(primary_output, info->PrimaryOutput); | |
| } | |
| if (rule.isMember("outputs")) { | |
| Json::Value const& outputs = rule["outputs"]; | |
| if (outputs.isArray()) { | |
| for (auto const& output : outputs) { | |
| std::string extra_output; | |
| PARSE_FILENAME(output, extra_output); | |
| info->ExtraOutputs.emplace_back(extra_output); | |
| } | |
| } | |
| } | |
| if (rule.isMember("provides")) { | |
| Json::Value const& provides = rule["provides"]; | |
| if (!provides.isArray()) { | |
| cmSystemTools::Error( | |
| cmStrCat("-E cmake_ninja_dyndep failed to parse ", arg_pp, | |
| ": provides is not an array")); | |
| return false; | |
| } | |
| for (auto const& provide : provides) { | |
| cmSourceReqInfo provide_info; | |
| Json::Value const& logical_name = provide["logical-name"]; | |
| PARSE_BLOB(logical_name, provide_info.LogicalName); | |
| if (provide.isMember("compiled-module-path")) { | |
| Json::Value const& compiled_module_path = | |
| provide["compiled-module-path"]; | |
| PARSE_FILENAME(compiled_module_path, | |
| provide_info.CompiledModulePath); | |
| } | |
| if (provide.isMember("unique-on-source-path")) { | |
| Json::Value const& unique_on_source_path = | |
| provide["unique-on-source-path"]; | |
| if (!unique_on_source_path.isBool()) { | |
| cmSystemTools::Error( | |
| cmStrCat("-E cmake_ninja_dyndep failed to parse ", arg_pp, | |
| ": unique-on-source-path is not a boolean")); | |
| return false; | |
| } | |
| provide_info.UseSourcePath = unique_on_source_path.asBool(); | |
| } else { | |
| provide_info.UseSourcePath = false; | |
| } | |
| if (provide.isMember("source-path")) { | |
| Json::Value const& source_path = provide["source-path"]; | |
| PARSE_FILENAME(source_path, provide_info.SourcePath); | |
| } else if (provide_info.UseSourcePath) { | |
| cmSystemTools::Error( | |
| cmStrCat("-E cmake_ninja_dyndep failed to parse ", arg_pp, | |
| ": source-path is missing")); | |
| return false; | |
| } | |
| if (provide.isMember("is-interface")) { | |
| Json::Value const& is_interface = provide["is-interface"]; | |
| if (!is_interface.isBool()) { | |
| cmSystemTools::Error( | |
| cmStrCat("-E cmake_ninja_dyndep failed to parse ", arg_pp, | |
| ": is-interface is not a boolean")); | |
| return false; | |
| } | |
| provide_info.IsInterface = is_interface.asBool(); | |
| } else { | |
| provide_info.IsInterface = true; | |
| } | |
| info->Provides.push_back(provide_info); | |
| } | |
| } | |
| if (rule.isMember("requires")) { | |
| Json::Value const& reqs = rule["requires"]; | |
| if (!reqs.isArray()) { | |
| cmSystemTools::Error( | |
| cmStrCat("-E cmake_ninja_dyndep failed to parse ", arg_pp, | |
| ": requires is not an array")); | |
| return false; | |
| } | |
| for (auto const& require : reqs) { | |
| cmSourceReqInfo require_info; | |
| Json::Value const& logical_name = require["logical-name"]; | |
| PARSE_BLOB(logical_name, require_info.LogicalName); | |
| if (require.isMember("compiled-module-path")) { | |
| Json::Value const& compiled_module_path = | |
| require["compiled-module-path"]; | |
| PARSE_FILENAME(compiled_module_path, | |
| require_info.CompiledModulePath); | |
| } | |
| if (require.isMember("unique-on-source-path")) { | |
| Json::Value const& unique_on_source_path = | |
| require["unique-on-source-path"]; | |
| if (!unique_on_source_path.isBool()) { | |
| cmSystemTools::Error( | |
| cmStrCat("-E cmake_ninja_dyndep failed to parse ", arg_pp, | |
| ": unique-on-source-path is not a boolean")); | |
| return false; | |
| } | |
| require_info.UseSourcePath = unique_on_source_path.asBool(); | |
| } else { | |
| require_info.UseSourcePath = false; | |
| } | |
| if (require.isMember("source-path")) { | |
| Json::Value const& source_path = require["source-path"]; | |
| PARSE_FILENAME(source_path, require_info.SourcePath); | |
| } else if (require_info.UseSourcePath) { | |
| cmSystemTools::Error( | |
| cmStrCat("-E cmake_ninja_dyndep failed to parse ", arg_pp, | |
| ": source-path is missing")); | |
| return false; | |
| } | |
| if (require.isMember("lookup-method")) { | |
| Json::Value const& lookup_method = require["lookup-method"]; | |
| if (!lookup_method.isString()) { | |
| cmSystemTools::Error( | |
| cmStrCat("-E cmake_ninja_dyndep failed to parse ", arg_pp, | |
| ": lookup-method is not a string")); | |
| return false; | |
| } | |
| std::string lookup_method_str = lookup_method.asString(); | |
| if (lookup_method_str == "by-name"_s) { | |
| require_info.Method = LookupMethod::ByName; | |
| } else if (lookup_method_str == "include-angle"_s) { | |
| require_info.Method = LookupMethod::IncludeAngle; | |
| } else if (lookup_method_str == "include-quote"_s) { | |
| require_info.Method = LookupMethod::IncludeQuote; | |
| } else { | |
| cmSystemTools::Error(cmStrCat( | |
| "-E cmake_ninja_dyndep failed to parse ", arg_pp, | |
| ": lookup-method is not a valid: ", lookup_method_str)); | |
| return false; | |
| } | |
| } else if (require_info.UseSourcePath) { | |
| require_info.Method = LookupMethod::ByName; | |
| } | |
| info->Requires.push_back(require_info); | |
| } | |
| } | |
| } | |
| } | |
| return true; | |
| } | |
| bool cmScanDepFormat_P1689_Write(std::string const& path, | |
| cmScanDepInfo const& info) | |
| { | |
| Json::Value ddi(Json::objectValue); | |
| ddi["version"] = 0; | |
| ddi["revision"] = 0; | |
| Json::Value& rules = ddi["rules"] = Json::arrayValue; | |
| Json::Value rule(Json::objectValue); | |
| rule["primary-output"] = EncodeFilename(info.PrimaryOutput); | |
| Json::Value& rule_outputs = rule["outputs"] = Json::arrayValue; | |
| for (auto const& output : info.ExtraOutputs) { | |
| rule_outputs.append(EncodeFilename(output)); | |
| } | |
| Json::Value& provides = rule["provides"] = Json::arrayValue; | |
| for (auto const& provide : info.Provides) { | |
| Json::Value provide_obj(Json::objectValue); | |
| auto const encoded = EncodeFilename(provide.LogicalName); | |
| provide_obj["logical-name"] = encoded; | |
| if (!provide.CompiledModulePath.empty()) { | |
| provide_obj["compiled-module-path"] = | |
| EncodeFilename(provide.CompiledModulePath); | |
| } | |
| if (provide.UseSourcePath) { | |
| provide_obj["unique-on-source-path"] = true; | |
| provide_obj["source-path"] = EncodeFilename(provide.SourcePath); | |
| } else if (!provide.SourcePath.empty()) { | |
| provide_obj["source-path"] = EncodeFilename(provide.SourcePath); | |
| } | |
| provide_obj["is-interface"] = provide.IsInterface; | |
| provides.append(provide_obj); | |
| } | |
| Json::Value& reqs = rule["requires"] = Json::arrayValue; | |
| for (auto const& require : info.Requires) { | |
| Json::Value require_obj(Json::objectValue); | |
| auto const encoded = EncodeFilename(require.LogicalName); | |
| require_obj["logical-name"] = encoded; | |
| if (!require.CompiledModulePath.empty()) { | |
| require_obj["compiled-module-path"] = | |
| EncodeFilename(require.CompiledModulePath); | |
| } | |
| if (require.UseSourcePath) { | |
| require_obj["unique-on-source-path"] = true; | |
| require_obj["source-path"] = EncodeFilename(require.SourcePath); | |
| } else if (!require.SourcePath.empty()) { | |
| require_obj["source-path"] = EncodeFilename(require.SourcePath); | |
| } | |
| char const* lookup_method = nullptr; | |
| switch (require.Method) { | |
| case LookupMethod::ByName: | |
| // No explicit value needed for the default. | |
| break; | |
| case LookupMethod::IncludeAngle: | |
| lookup_method = "include-angle"; | |
| break; | |
| case LookupMethod::IncludeQuote: | |
| lookup_method = "include-quote"; | |
| break; | |
| } | |
| if (lookup_method) { | |
| require_obj["lookup-method"] = lookup_method; | |
| } | |
| reqs.append(require_obj); | |
| } | |
| rules.append(rule); | |
| cmGeneratedFileStream ddif(path); | |
| ddif << ddi; | |
| return !!ddif; | |
| } |