Explorar el Código

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).
Ben Boeckel hace 6 años
padre
commit
a02c4ccabc
Se han modificado 3 ficheros con 320 adiciones y 0 borrados
  1. 1 0
      Source/CMakeLists.txt
  2. 289 0
      Source/cmScanDepFormat.cxx
  3. 30 0
      Source/cmScanDepFormat.h

+ 1 - 0
Source/CMakeLists.txt

@@ -834,6 +834,7 @@ endif()
 
 # Ninja support
 set(SRCS ${SRCS}
+  cmScanDepFormat.cxx
   cmGlobalNinjaGenerator.cxx
   cmGlobalNinjaGenerator.h
   cmNinjaTypes.h

+ 289 - 0
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;
+}

+ 30 - 0
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);