/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying file Copyright.txt or https://cmake.org/licensing for details. */ #include "cmScanDepFormat.h" #include #include #include #include #include #include #include #include #include #include "cmsys/FStream.hxx" #include "cmGeneratedFileStream.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 (std::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; cmsys::ifstream ppf(arg_pp.c_str(), std::ios::in | std::ios::binary); { Json::Reader reader; if (!reader.parse(ppf, ppio, false)) { cmSystemTools::Error(cmStrCat("-E cmake_ninja_dyndep failed to parse ", arg_pp, reader.getFormattedErrorMessages())); 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 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); } const char* 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; }