diff options
author | Ben Boeckel <ben.boeckel@kitware.com> | 2019-03-12 19:16:46 (GMT) |
---|---|---|
committer | Brad King <brad.king@kitware.com> | 2020-11-17 18:16:05 (GMT) |
commit | a02c4ccabc1736712fc90b12c63e4997ccf34284 (patch) | |
tree | 640044bc99ab554d7b4951e5be5ed795e2b0ec18 | |
parent | fa18069ebd01ecaef6d7500fcb0de29995d6b516 (diff) | |
download | CMake-a02c4ccabc1736712fc90b12c63e4997ccf34284.zip CMake-a02c4ccabc1736712fc90b12c63e4997ccf34284.tar.gz CMake-a02c4ccabc1736712fc90b12c63e4997ccf34284.tar.bz2 |
cmScanDepFormat: add reader and writer for the format from P1689R2
This format is currently subject to change, but is not too far from the
end goal.
Some bits are currently unimplemented (see TODO comments).
-rw-r--r-- | Source/CMakeLists.txt | 1 | ||||
-rw-r--r-- | Source/cmScanDepFormat.cxx | 289 | ||||
-rw-r--r-- | Source/cmScanDepFormat.h | 30 |
3 files changed, 320 insertions, 0 deletions
diff --git a/Source/CMakeLists.txt b/Source/CMakeLists.txt index cb954e5..d1616ad 100644 --- a/Source/CMakeLists.txt +++ b/Source/CMakeLists.txt @@ -834,6 +834,7 @@ endif() # Ninja support set(SRCS ${SRCS} + cmScanDepFormat.cxx cmGlobalNinjaGenerator.cxx cmGlobalNinjaGenerator.h cmNinjaTypes.h diff --git a/Source/cmScanDepFormat.cxx b/Source/cmScanDepFormat.cxx new file mode 100644 index 0000000..b157a8b --- /dev/null +++ b/Source/cmScanDepFormat.cxx @@ -0,0 +1,289 @@ +/* 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 <cctype> +#include <cstdio> + +#include <cm3p/json/reader.h> +#include <cm3p/json/value.h> +#include <cm3p/json/writer.h> + +#include "cmsys/FStream.hxx" + +#include "cm_utf8.h" + +#include "cmGeneratedFileStream.h" +#include "cmStringAlgorithms.h" +#include "cmSystemTools.h" + +static bool ParseFilename(Json::Value const& val, std::string& result) +{ + if (val.isObject()) { + Json::Value const& format = val["format"]; + if (format == "raw8") { + Json::Value const& data = val["data"]; + for (auto const& byte : data) { + result.push_back(static_cast<char>(byte.asUInt())); + } + } else /* TODO: if (format == "raw16") */ { + return false; + } + } else if (val.isString()) { + result = val.asString(); + } else { + return false; + } + + return true; +} + +static Json::Value EncodeFilename(std::string const& path) +{ + if (cm_utf8_is_valid(path.c_str())) { + std::string valid_data; + valid_data.reserve(path.size()); + + for (auto const& byte : path) { + if (std::iscntrl(byte)) { + // Control characters. + valid_data.append("\\u"); + char buf[5]; + std::snprintf(buf, sizeof(buf), "%04x", byte); + valid_data.append(buf); + } else if (byte == '"' || byte == '\\') { + // Special JSON characters. + valid_data.push_back('\\'); + valid_data.push_back(byte); + } else { + // Other data. + valid_data.push_back(byte); + } + } + + return valid_data; + } + + Json::Value data; + data["format"] = "raw8"; + Json::Value& code_units = data["code-units"]; + for (auto const& code_unit : path) { + code_units.append(static_cast<int>(code_unit)); + } + + return data; +} + +#define PARSE_BLOB(val, res) \ + do { \ + if (!ParseFilename(val, res)) { \ + cmSystemTools::Error( \ + cmStrCat("-E cmake_ninja_depends 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_depends failed to parse ", arg_pp, \ + ": invalid filename")); \ + return false; \ + } \ + \ + if (!cmSystemTools::FileIsFullPath(res)) { \ + res = cmStrCat(work_directory, '/', res); \ + } \ + } while (0) + +bool cmScanDepFormat_P1689_Parse(std::string const& arg_pp, cmSourceInfo* 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_depends failed to parse ", + arg_pp, + reader.getFormattedErrorMessages())); + return false; + } + } + + Json::Value const& version = ppi["version"]; + if (version.asUInt() != 0) { + cmSystemTools::Error(cmStrCat("-E cmake_ninja_depends failed to parse ", + arg_pp, ": version ", version.asString())); + return false; + } + + Json::Value const& workdir = ppi["work-directory"]; + if (!workdir.isString()) { + cmSystemTools::Error(cmStrCat("-E cmake_ninja_depends failed to parse ", + arg_pp, ": work-directory is not a string")); + return false; + } + std::string work_directory; + PARSE_BLOB(workdir, work_directory); + + Json::Value const& rules = ppi["rules"]; + if (rules.isArray()) { + if (rules.size() != 1) { + cmSystemTools::Error(cmStrCat("-E cmake_ninja_depends failed to parse ", + arg_pp, ": expected 1 source entry")); + return false; + } + + for (auto const& rule : rules) { + Json::Value const& depends = rule["depends"]; + if (depends.isArray()) { + std::string depend_filename; + for (auto const& depend : depends) { + PARSE_FILENAME(depend, depend_filename); + info->Includes.push_back(depend_filename); + } + } + + if (rule.isMember("future-compile")) { + Json::Value const& future_compile = rule["future-compile"]; + + if (future_compile.isMember("outputs")) { + Json::Value const& outputs = future_compile["outputs"]; + if (outputs.isArray()) { + if (outputs.empty()) { + cmSystemTools::Error( + cmStrCat("-E cmake_ninja_depends failed to parse ", arg_pp, + ": expected at least one 1 output")); + return false; + } + + PARSE_FILENAME(outputs[0], info->PrimaryOutput); + } + } + + if (future_compile.isMember("provides")) { + Json::Value const& provides = future_compile["provides"]; + if (provides.isArray()) { + 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); + } else { + provide_info.CompiledModulePath = + cmStrCat(provide_info.LogicalName, ".mod"); + } + + info->Provides.push_back(provide_info); + } + } + } + + if (future_compile.isMember("requires")) { + Json::Value const& reqs = future_compile["requires"]; + if (reqs.isArray()) { + 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); + } + + info->Requires.push_back(require_info); + } + } + } + } + } + } + + return true; +} + +bool cmScanDepFormat_P1689_Write(std::string const& path, + std::string const& input, + cmSourceInfo const& info) +{ + Json::Value ddi(Json::objectValue); + ddi["version"] = 0; + ddi["revision"] = 0; + ddi["work-directory"] = + EncodeFilename(cmSystemTools::GetCurrentWorkingDirectory()); + + Json::Value& rules = ddi["rules"] = Json::arrayValue; + + Json::Value rule(Json::objectValue); + Json::Value& inputs = rule["inputs"] = Json::arrayValue; + inputs.append(EncodeFilename(input)); + + Json::Value& rule_outputs = rule["outputs"] = Json::arrayValue; + rule_outputs.append(EncodeFilename(path)); + + Json::Value& depends = rule["depends"] = Json::arrayValue; + for (auto const& include : info.Includes) { + depends.append(EncodeFilename(include)); + } + + Json::Value& future_compile = rule["future-compile"] = Json::objectValue; + + Json::Value& outputs = future_compile["outputs"] = Json::arrayValue; + outputs.append(info.PrimaryOutput); + + Json::Value& provides = future_compile["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"] = encoded; + } else { + provide_obj["compiled-module-path"] = + EncodeFilename(provide.CompiledModulePath); + } + + // TODO: Source file tracking. See below. + + provides.append(provide_obj); + } + + Json::Value& reqs = future_compile["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"] = encoded; + } else { + require_obj["compiled-module-path"] = + EncodeFilename(require.CompiledModulePath); + } + + // TODO: Source filename inclusion. Requires collating with the provides + // filenames (as a sanity check if available on both sides). + + reqs.append(require_obj); + } + + rules.append(rule); + + cmGeneratedFileStream ddif(path); + ddif << ddi; + + return !!ddif; +} diff --git a/Source/cmScanDepFormat.h b/Source/cmScanDepFormat.h new file mode 100644 index 0000000..1ad0ecf --- /dev/null +++ b/Source/cmScanDepFormat.h @@ -0,0 +1,30 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ +#pragma once + +#include <string> +#include <vector> + +struct cmSourceReqInfo +{ + std::string LogicalName; + std::string CompiledModulePath; +}; + +struct cmSourceInfo +{ + std::string PrimaryOutput; + + // Set of provided and required modules. + std::vector<cmSourceReqInfo> Provides; + std::vector<cmSourceReqInfo> Requires; + + // Set of files included in the translation unit. + std::vector<std::string> Includes; +}; + +bool cmScanDepFormat_P1689_Parse(std::string const& arg_pp, + cmSourceInfo* info); +bool cmScanDepFormat_P1689_Write(std::string const& path, + std::string const& input, + cmSourceInfo const& info); |