Просмотр исходного кода

cmd: implement json version output

AJIOB 4 недель назад
Родитель
Сommit
39a3915022

+ 109 - 0
Help/manual/cmake/version-schema.json

@@ -0,0 +1,109 @@
+{
+  "$schema": "http://json-schema.org/draft-07/schema#",
+  "type": "object",
+  "properties": {
+    "dependencies": {
+      "type": "array",
+      "items": {
+        "type": "object",
+        "description": "Information about a single dependency.",
+        "properties": {
+          "name": {
+            "type": "string",
+            "description": "The name of the dependency."
+          },
+          "type": {
+            "type": "string",
+            "description": "The type of the dependency.",
+            "enum": [
+              "system",
+              "bundled"
+            ]
+          },
+          "version": {
+            "type": "string",
+            "description": "The version of the dependency if available."
+          },
+          "via": {
+            "type": "string",
+            "description": "The source from which the dependency is came from. Not presented for direct CMake dependencies."
+          }
+        },
+        "required": [
+          "name",
+          "type"
+        ],
+        "additionalProperties": false
+      },
+      "additionalItems": false
+    },
+    "program": {
+      "type": "object",
+      "description": "Information about the CMake tool.",
+      "properties": {
+        "name": {
+          "type": "string",
+          "description": "The name of the CMake tool."
+        },
+        "version": {
+          "type": "object",
+          "description": "Information about the version of the CMake tool.",
+          "properties": {
+            "major": {
+              "type": "integer",
+              "description": "The major version of the CMake tool."
+            },
+            "minor": {
+              "type": "integer",
+              "description": "The minor version of the CMake tool."
+            },
+            "patch": {
+              "type": "integer",
+              "description": "The patch version of the CMake tool."
+            },
+            "string": {
+              "type": "string",
+              "description": "The full version string of the CMake tool."
+            }
+          },
+          "required": [
+            "major",
+            "minor",
+            "patch",
+            "string"
+          ],
+          "additionalProperties": false
+        }
+      },
+      "required": [
+        "name",
+        "version"
+      ],
+      "additionalProperties": false
+    },
+    "version": {
+      "type": "object",
+      "properties": {
+        "major": {
+          "type": "integer",
+          "description": "The major version of the JSON output format."
+        },
+        "minor": {
+          "type": "integer",
+          "description": "The minor version of the JSON output format."
+        }
+      },
+      "required": [
+        "major",
+        "minor"
+      ],
+      "additionalProperties": false
+    }
+  },
+  "required": [
+    "dependencies",
+    "program",
+    "version"
+  ],
+  "additionalProperties": false
+}

+ 8 - 2
Help/manual/include/OPTIONS_HELP.rst

@@ -1,10 +1,16 @@
 .. |file| replace:: The output is printed to a named ``<file>`` if given.
 
-.. option:: -version [<file>], --version [<file>], /V [<file>]
+.. option:: -version[=json-v1] [<file>], --version[=json-v1] [<file>], /V[=json-v1] [<file>], /version[=json-v1] [<file>]
 
- Show program name/version banner and exit.
+ Show program name/version banner and exit. If ``json-v1`` is
+ specified, print extended version information in JSON format.
+ The JSON output contains the versions for the CMake and its
+ dependencies.
  |file|
 
+ The JSON output format is described in machine-readable form by
+ :download:`this JSON schema </manual/cmake/version-schema.json>`.
+
 .. option:: -h, -H, --help, -help, -usage, /?
 
  Print usage information and exit.

+ 5 - 0
Help/release/dev/cmd-version-json.rst

@@ -0,0 +1,5 @@
+cmd-version-json
+----------------
+
+* The :option:`cmake --version` option and variants now support a ``=json-v1``
+  value to print detailed version information in a JSON format.

+ 1 - 0
Source/CMakeLists.txt

@@ -522,6 +522,7 @@ add_library(
   cmUVStreambuf.h
   cmVariableWatch.cxx
   cmVariableWatch.h
+  cmVersion_Dependencies.cxx
   cmVersion.cxx
   cmVersion.h
   cmWindowsRegistry.cxx

+ 74 - 2
Source/cmDocumentation.cxx

@@ -11,6 +11,13 @@
 #include "cmsys/Glob.hxx"
 #include "cmsys/RegularExpression.hxx"
 
+#if !defined(CMAKE_BOOTSTRAP)
+#  include <memory>
+
+#  include <cm3p/json/value.h>
+#  include <cm3p/json/writer.h>
+#endif
+
 #include "cmDocumentationEntry.h"
 #include "cmDocumentationSection.h"
 #include "cmRST.h"
@@ -21,7 +28,9 @@
 namespace {
 cmDocumentationEntry const cmDocumentationStandardOptions[21] = {
   { "-h,-H,--help,-help,-usage,/?", "Print usage information and exit." },
-  { "--version,-version,/V [<file>]", "Print version number and exit." },
+  { "--version[=json-v1],-version[=json-v1],/V[=json-v1],/version[=json-v1] "
+    "[<file>]",
+    "Print version number and exit." },
   { "--help <keyword> [<file>]", "Print help for one keyword and exit." },
   { "--help-full [<file>]", "Print all help manuals and exit." },
   { "--help-manual <man> [<file>]", "Print one help manual and exit." },
@@ -86,6 +95,60 @@ bool cmDocumentation::PrintVersion(std::ostream& os)
   return true;
 }
 
+bool cmDocumentation::PrintVersionJson(std::ostream& os)
+{
+#if !defined(CMAKE_BOOTSTRAP)
+  Json::Value root = Json::objectValue;
+
+  // Output version information
+  root["version"] = Json::objectValue;
+  root["version"]["major"] = 1;
+  root["version"]["minor"] = 0;
+
+  // CMake tool name
+  root["program"] = Json::objectValue;
+  root["program"]["name"] = this->GetNameString();
+
+  // CMake version information
+  root["program"]["version"] = Json::objectValue;
+  root["program"]["version"]["string"] = cmVersion::GetCMakeVersion();
+  root["program"]["version"]["major"] = cmVersion::GetMajorVersion();
+  root["program"]["version"]["minor"] = cmVersion::GetMinorVersion();
+  root["program"]["version"]["patch"] = cmVersion::GetPatchVersion();
+
+  // Dependencies
+  root["dependencies"] = Json::arrayValue;
+  std::vector<cmVersion::DependencyInfo> const& deps =
+    cmVersion::CollectDependencyInfo();
+  for (cmVersion::DependencyInfo const& dep : deps) {
+    Json::Value depJson = Json::objectValue;
+    depJson["name"] = dep.name;
+    if (!dep.version.empty()) {
+      depJson["version"] = dep.version;
+    }
+    depJson["type"] =
+      dep.type == cmVersion::DependencyType::System ? "system" : "bundled";
+    if (!dep.cameFrom.empty()) {
+      depJson["via"] = dep.cameFrom;
+    }
+    root["dependencies"].append(std::move(depJson));
+  }
+
+  // Output JSON
+  Json::StreamWriterBuilder builder;
+  builder["indentation"] = "  ";
+  std::unique_ptr<Json::StreamWriter> writer(builder.newStreamWriter());
+  writer->write(root, &os);
+  os << std::endl;
+
+  return true;
+#else
+  os << "{\"error\":\"JSON version output not available in bootstrap build\"}"
+     << std::endl;
+  return false;
+#endif
+}
+
 bool cmDocumentation::PrintDocumentation(Type ht, std::ostream& os)
 {
   switch (ht) {
@@ -125,6 +188,8 @@ bool cmDocumentation::PrintDocumentation(Type ht, std::ostream& os)
       return this->PrintHelpListGenerators(os);
     case cmDocumentation::Version:
       return this->PrintVersion(os);
+    case cmDocumentation::VersionJson:
+      return this->PrintVersionJson(os);
     case cmDocumentation::OldCustomModules:
       return this->PrintOldCustomModules(os);
     default:
@@ -376,9 +441,16 @@ bool cmDocumentation::CheckOptions(int argc, char const* const* argv,
       return true;
     } else if ((strcmp(argv[i], "--version") == 0) ||
                (strcmp(argv[i], "-version") == 0) ||
-               (strcmp(argv[i], "/V") == 0)) {
+               (strcmp(argv[i], "/V") == 0) ||
+               (strcmp(argv[i], "/version") == 0)) {
       help.HelpType = cmDocumentation::Version;
       i += int(get_opt_argument(i + 1, help.Filename));
+    } else if (strcmp(argv[i], "--version=json-v1") == 0 ||
+               strcmp(argv[i], "-version=json-v1") == 0 ||
+               strcmp(argv[i], "/V=json-v1") == 0 ||
+               strcmp(argv[i], "/version=json-v1") == 0) {
+      help.HelpType = cmDocumentation::VersionJson;
+      i += int(get_opt_argument(i + 1, help.Filename));
     }
     if (help.HelpType != None) {
       // This is a help option.  See if there is a file name given.

+ 2 - 0
Source/cmDocumentation.h

@@ -24,6 +24,7 @@ public:
   {
     None,
     Version,
+    VersionJson,
     Usage,
     Help,
     Full,
@@ -116,6 +117,7 @@ private:
   bool PrintFiles(std::ostream& os, std::string const& pattern);
 
   bool PrintVersion(std::ostream& os);
+  bool PrintVersionJson(std::ostream& os);
   bool PrintUsage(std::ostream& os);
   bool PrintHelp(std::ostream& os);
   bool PrintHelpFull(std::ostream& os);

+ 36 - 0
Source/cmVersion.h

@@ -2,6 +2,9 @@
    file LICENSE.rst or https://cmake.org/licensing for details.  */
 #pragma once
 
+#include <string>
+#include <vector>
+
 /** \class cmVersion
  * \brief Helper class for providing CMake and CTest version information.
  *
@@ -10,6 +13,37 @@
 class cmVersion
 {
 public:
+  enum class DependencyType
+  {
+    System,
+    Bundled,
+  };
+
+  struct DependencyInfo
+  {
+    /**
+     * The name of the dependency.
+     * e.g. "curl", "libarchive", "zlib", etc.
+     */
+    std::string name;
+    /**
+     * The version of the dependency if available.
+     * e.g. "7.66.0", "3.8.0", "1.2.12", etc.
+     */
+    std::string version;
+
+    /**
+     * The type of the dependency.
+     */
+    DependencyType type;
+    /**
+     * The source of the dependency.
+     * e.g. "curl", "libarchive", etc.
+     * Empty if the dependency is directly passed from CMake.
+     */
+    std::string cameFrom;
+  };
+
   /**
    * Return major and minor version numbers for cmake.
    */
@@ -18,4 +52,6 @@ public:
   static unsigned int GetPatchVersion();
   static unsigned int GetTweakVersion();
   static char const* GetCMakeVersion();
+
+  static std::vector<DependencyInfo> const& CollectDependencyInfo();
 };

+ 363 - 0
Source/cmVersion_Dependencies.cxx

@@ -0,0 +1,363 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file LICENSE.rst or https://cmake.org/licensing for details.  */
+#include "cmVersion.h"
+
+#if !defined(CMAKE_BOOTSTRAP)
+#  include <cstddef>
+#  include <string>
+#  include <utility>
+#  include <vector>
+
+#  include <cm3p/archive.h>
+#  include <cm3p/curl/curl.h>
+#  include <cm3p/expat.h>
+#  include <cm3p/json/version.h>
+#  include <cm3p/kwiml/version.h>
+
+#  include "cmThirdParty.h" // IWYU pragma: keep
+
+#  ifdef CMAKE_USE_SYSTEM_LIBRHASH
+#    include <cstdint>
+
+#    include <cm3p/rhash.h>
+#  endif
+#  include <cm3p/uv.h>
+#  include <cm3p/zlib.h>
+
+#  include "cmStringAlgorithms.h"
+
+std::vector<cmVersion::DependencyInfo> const&
+cmVersion::CollectDependencyInfo()
+{
+  static std::vector<DependencyInfo> deps;
+  if (!deps.empty()) {
+    return deps;
+  }
+
+  // BZIP2 is not directly used in CMake, so it is not included here
+
+  // BZIP2 (libarchive)
+  {
+    char const* bzip2Version = archive_bzlib_version();
+    if (bzip2Version) {
+      DependencyInfo info;
+      info.name = "bzip2";
+      info.version = bzip2Version;
+      info.cameFrom = "libarchive";
+      size_t pos = info.version.find(',');
+      if (pos != std::string::npos) {
+        // Convert `1.0.8, 30-Mar-2009` to `1.0.8`
+        info.version.erase(pos);
+      }
+#  if defined(CMAKE_USE_SYSTEM_BZIP2) || defined(CMAKE_USE_SYSTEM_LIBARCHIVE)
+      // System BZIP2 can be used by system or bundled libarchive
+      // System libarchive always uses system BZIP2
+      info.type = DependencyType::System;
+#  else
+      info.type = DependencyType::Bundled;
+#  endif
+      deps.emplace_back(std::move(info));
+    }
+  }
+
+  // CPPDAP
+  {
+    DependencyInfo info;
+    info.name = "cppdap";
+#  ifdef CMAKE_USE_SYSTEM_CPPDAP
+    info.type = DependencyType::System;
+    // Cannot get runtime version from cppdap library
+#  else
+    info.type = DependencyType::Bundled;
+    // Hardcoded in protocol.h header file comments
+    info.version = "1.65.0";
+#  endif
+    deps.emplace_back(std::move(info));
+  }
+
+  // CURL
+  {
+    curl_version_info_data* curlVersion = curl_version_info(CURLVERSION_NOW);
+    if (curlVersion) {
+      // CURL itself
+      {
+        DependencyInfo info;
+        info.name = "curl";
+        if (curlVersion->version) {
+          info.version = curlVersion->version;
+        }
+#  ifdef CMAKE_USE_SYSTEM_CURL
+        info.type = DependencyType::System;
+#  else
+        info.type = DependencyType::Bundled;
+#  endif
+        deps.emplace_back(std::move(info));
+      }
+
+// Cannot use CURL_AT_LEAST_VERSION and CURL_VERSION_BITS macros,
+// because they needs at least curl 7.43.0,
+// but we support curl 7.29.0 from CentOS 7
+#  if LIBCURL_VERSION_NUM >= 0x074200
+      // NGHTTP2 (curl)
+      // Added in curl 7.66.0 (0x074200), CURLVERSION_SIXTH
+      if (curlVersion->age >= CURLVERSION_SIXTH &&
+          curlVersion->nghttp2_version) {
+        DependencyInfo info;
+        info.name = "nghttp2";
+        info.cameFrom = "curl";
+
+        info.version = curlVersion->nghttp2_version;
+
+#    if defined(CMAKE_USE_SYSTEM_NGHTTP2) || defined(CMAKE_USE_SYSTEM_CURL)
+        // System CURL always uses system NGHTTP2
+        // System NGHTTP2 can be used by system or bundled CURL
+        info.type = DependencyType::System;
+#    else
+        info.type = DependencyType::Bundled;
+#    endif
+        deps.emplace_back(std::move(info));
+      }
+#  endif
+
+      // OPENSSL (curl)
+      if (curlVersion->ssl_version) {
+        DependencyInfo info;
+        info.name = "ssl";
+        info.cameFrom = "curl";
+        info.version = curlVersion->ssl_version;
+
+        // With Multi-SSL, the version string is `OpenSSL/3.3.5,
+        // BoringSSL/3.3.5`, etc.
+        if (cmHasLiteralPrefix(info.version, "OpenSSL/") &&
+            info.version.find('/', 8) == std::string::npos) {
+          info.name = "openssl";
+          info.version.erase(0, 8);
+        }
+        // Bundled version of OpenSSL is not presented
+        // Multi-SSL can be used by system CURL only,
+        // so the SSL library is always system
+        info.type = DependencyType::System;
+        deps.emplace_back(std::move(info));
+      }
+
+      // ZLIB (curl)
+      if (curlVersion->libz_version) {
+        DependencyInfo info;
+        info.name = "zlib";
+        info.cameFrom = "curl";
+        info.version = curlVersion->libz_version;
+#  if defined(CMAKE_USE_SYSTEM_ZLIB) || defined(CMAKE_USE_SYSTEM_CURL)
+        // System CURL always uses system ZLIB
+        // System ZLIB can be used by system or bundled CURL
+        info.type = DependencyType::System;
+#  else
+        info.type = DependencyType::Bundled;
+#  endif
+        deps.emplace_back(std::move(info));
+      }
+    }
+  }
+
+  // EXPAT
+  {
+    DependencyInfo info;
+    info.name = "expat";
+
+    XML_Expat_Version version = XML_ExpatVersionInfo();
+    info.version =
+      cmStrCat(version.major, '.', version.minor, '.', version.micro);
+#  ifdef CMAKE_USE_SYSTEM_EXPAT
+    info.type = DependencyType::System;
+#  else
+    info.type = DependencyType::Bundled;
+#  endif
+    deps.emplace_back(std::move(info));
+  }
+
+  // FORM
+  {
+    DependencyInfo info;
+    info.name = "form";
+    // Cannot get any version from form library
+#  ifdef CMAKE_USE_SYSTEM_FORM
+    info.type = DependencyType::System;
+#  else
+    info.type = DependencyType::Bundled;
+#  endif
+    deps.emplace_back(std::move(info));
+  }
+
+  // JSONCPP
+  {
+    DependencyInfo info;
+    info.name = "jsoncpp";
+    info.version = JSONCPP_VERSION_STRING;
+#  ifdef CMAKE_USE_SYSTEM_JSONCPP
+    info.type = DependencyType::System;
+#  else
+    info.type = DependencyType::Bundled;
+#  endif
+    deps.emplace_back(std::move(info));
+  }
+
+  // KWIML
+  {
+    DependencyInfo info;
+    info.name = "kwiml";
+
+    // Library is header-only, so we can safely use the defined version
+    info.version = KWIML_VERSION_STRING;
+
+#  ifdef CMAKE_USE_SYSTEM_KWIML
+    info.type = DependencyType::System;
+#  else
+    info.type = DependencyType::Bundled;
+#  endif
+    deps.emplace_back(std::move(info));
+  }
+
+  // LIBARCHIVE
+  {
+    DependencyInfo info;
+    info.name = "libarchive";
+    info.version = archive_version_string();
+    if (cmHasLiteralPrefix(info.version, "libarchive ")) {
+      info.version.erase(0, 11);
+    }
+#  ifdef CMAKE_USE_SYSTEM_LIBARCHIVE
+    info.type = DependencyType::System;
+#  else
+    info.type = DependencyType::Bundled;
+#  endif
+    deps.emplace_back(std::move(info));
+  }
+
+  // LIBLZMA is not directly used in CMake, so it is not included here
+
+  // LIBLZMA (libarchive)
+  {
+    char const* liblzmaVersion = archive_liblzma_version();
+    if (liblzmaVersion) {
+      DependencyInfo info;
+      info.name = "liblzma";
+      info.cameFrom = "libarchive";
+      info.version = liblzmaVersion;
+#  if defined(CMAKE_USE_SYSTEM_LIBLZMA) || defined(CMAKE_USE_SYSTEM_LIBARCHIVE)
+      // System LIBLZMA can be used by system or bundled libarchive
+      // System libarchive always uses system LIBLZMA
+      info.type = DependencyType::System;
+#  else
+      info.type = DependencyType::Bundled;
+#  endif
+      deps.emplace_back(std::move(info));
+    }
+  }
+
+  // LIBRHASH
+  {
+    DependencyInfo info;
+    info.name = "librhash";
+#  ifdef CMAKE_USE_SYSTEM_LIBRHASH
+    info.type = DependencyType::System;
+    std::uint64_t version = rhash_get_version();
+    std::uint8_t major = (version >> 24) & 0xFFu;
+    std::uint8_t minor = (version >> 16) & 0xFFu;
+    std::uint8_t patch = (version >> 8) & 0xFFu;
+    info.version = cmStrCat(major, '.', minor, '.', patch);
+#  else
+    info.type = DependencyType::Bundled;
+    // Hardcoded in `update-librhash.bash` script
+    info.version = "1.4.4";
+#  endif
+    deps.emplace_back(std::move(info));
+  }
+
+  // LIBUV
+  {
+    DependencyInfo info;
+    info.name = "libuv";
+    info.version = uv_version_string();
+#  ifdef CMAKE_USE_SYSTEM_LIBUV
+    info.type = DependencyType::System;
+#  else
+    info.type = DependencyType::Bundled;
+#  endif
+    deps.emplace_back(std::move(info));
+  }
+
+  // OPENSSL is not directly used in CMake, so it is not included here
+
+#  if ARCHIVE_VERSION_NUMBER >= 3008000
+  // OPENSSL (libarchive)
+  // This function is available in libarchive 3.8.0 (3008000) and newer
+  {
+    char const* opensslVersion = archive_openssl_version();
+    if (opensslVersion) {
+      DependencyInfo info;
+      info.name = "openssl";
+      info.cameFrom = "libarchive";
+      info.version = opensslVersion;
+      // Bundled version of OpenSSL is not presented
+      info.type = DependencyType::System;
+      deps.emplace_back(std::move(info));
+    }
+  }
+#  endif
+
+  // ZLIB
+  {
+    DependencyInfo info;
+    info.name = "zlib";
+    info.version = zlibVersion();
+#  ifdef CMAKE_USE_SYSTEM_ZLIB
+    info.type = DependencyType::System;
+#  else
+    info.type = DependencyType::Bundled;
+#  endif
+    deps.emplace_back(std::move(info));
+  }
+
+  // ZLIB (libarchive)
+  {
+    char const* zlibVersion = archive_zlib_version();
+    if (zlibVersion) {
+      DependencyInfo info;
+      info.name = "zlib";
+      info.cameFrom = "libarchive";
+      info.version = zlibVersion;
+#  if defined(CMAKE_USE_SYSTEM_ZLIB) || defined(CMAKE_USE_SYSTEM_LIBARCHIVE)
+      // System ZLIB can be used by system or bundled libarchive
+      // System libarchive always uses system ZLIB
+      info.type = DependencyType::System;
+#  else
+      info.type = DependencyType::Bundled;
+#  endif
+      deps.emplace_back(std::move(info));
+    }
+  }
+
+  // ZSTD is not directly used in CMake, so it is not included here
+
+  // ZSTD (libarchive)
+  {
+    char const* zstdVersion = archive_libzstd_version();
+    if (zstdVersion) {
+      DependencyInfo info;
+      info.name = "zstd";
+      info.cameFrom = "libarchive";
+      info.version = zstdVersion;
+#  if defined(CMAKE_USE_SYSTEM_ZSTD) || defined(CMAKE_USE_SYSTEM_LIBARCHIVE)
+      // System ZSTD can be used by system or bundled libarchive
+      // System libarchive always uses system ZSTD
+      info.type = DependencyType::System;
+#  else
+      info.type = DependencyType::Bundled;
+#  endif
+      deps.emplace_back(std::move(info));
+    }
+  }
+
+  return deps;
+}
+
+#endif

+ 1 - 0
Tests/RunCMake/CMakeLists.txt

@@ -1080,6 +1080,7 @@ endif()
 add_executable(pseudo_llvm-rc pseudo_llvm-rc.c)
 add_RunCMake_test(CommandLine -DLLVM_RC=$<TARGET_FILE:pseudo_llvm-rc> -DCMAKE_SYSTEM_NAME=${CMAKE_SYSTEM_NAME}
                   -DCYGWIN=${CYGWIN} -DMSYS=${MSYS} -DPython_EXECUTABLE=${Python_EXECUTABLE}
+                  -DCMake_TEST_JSON_SCHEMA=${CMake_TEST_JSON_SCHEMA}
                   -DEXIT_CODE_EXE=$<TARGET_FILE:exit_code>
                   -DPRINT_STDIN_EXE=$<TARGET_FILE:print_stdin>)
 if(CMake_TEST_LibArchive_VERSION)

+ 43 - 0
Tests/RunCMake/CommandLine/RunCMakeTest.cmake

@@ -2,6 +2,49 @@ cmake_minimum_required(VERSION 3.10)
 
 include(RunCMake)
 
+cmake_policy(SET CMP0140 NEW)
+
+function(version_json_check_python v is_json_ready)
+  if(RunCMake_TEST_FAILED OR NOT Python_EXECUTABLE OR NOT CMake_TEST_JSON_SCHEMA)
+    return()
+  endif()
+  set(json_file "${RunCMake_TEST_BINARY_DIR}/version-v${v}.json")
+  if (NOT is_json_ready)
+    file(WRITE "${json_file}" "${actual_stdout}")
+    set(actual_stdout "" PARENT_SCOPE)
+  endif()
+
+  execute_process(
+    COMMAND ${Python_EXECUTABLE} "${RunCMake_SOURCE_DIR}/version_json_validate_schema.py" "${json_file}"
+    RESULT_VARIABLE result
+    OUTPUT_VARIABLE output
+    ERROR_VARIABLE output
+  )
+  if(NOT result STREQUAL 0)
+    string(REPLACE "\n" "\n  " output "${output}")
+    string(APPEND RunCMake_TEST_FAILED "Failed to validate version ${v} JSON schema for file: ${json_file}\nOutput:\n${output}\n")
+  endif()
+  return(PROPAGATE RunCMake_TEST_FAILED)
+endfunction()
+
+run_cmake_command(versionSingleDash ${CMAKE_COMMAND} -version version.txt)
+run_cmake_command(versionSingleDashJson ${CMAKE_COMMAND} -version=json-v1 version-v1.json)
+run_cmake_command(versionDoubleDash ${CMAKE_COMMAND} --version version.txt)
+run_cmake_command(versionDoubleDashJson ${CMAKE_COMMAND} --version=json-v1 version-v1.json)
+run_cmake_command(versionSlash ${CMAKE_COMMAND} /version version.txt)
+run_cmake_command(versionSlashJson ${CMAKE_COMMAND} /version=json-v1 version-v1.json)
+run_cmake_command(versionV ${CMAKE_COMMAND} /V version.txt)
+run_cmake_command(versionVJson ${CMAKE_COMMAND} /V=json-v1 version-v1.json)
+
+run_cmake_command(versionSingleDashNoArg ${CMAKE_COMMAND} -version)
+run_cmake_command(versionSingleDashJsonNoArg ${CMAKE_COMMAND} -version=json-v1)
+run_cmake_command(versionDoubleDashNoArg ${CMAKE_COMMAND} --version)
+run_cmake_command(versionDoubleDashJsonNoArg ${CMAKE_COMMAND} --version=json-v1)
+run_cmake_command(versionSlashNoArg ${CMAKE_COMMAND} /version)
+run_cmake_command(versionSlashJsonNoArg ${CMAKE_COMMAND} /version=json-v1)
+run_cmake_command(versionVNoArg ${CMAKE_COMMAND} /V)
+run_cmake_command(versionVJsonNoArg ${CMAKE_COMMAND} /V=json-v1)
+
 run_cmake_command(NoArgs ${CMAKE_COMMAND})
 run_cmake_command(InvalidArg1 ${CMAKE_COMMAND} -invalid)
 run_cmake_command(InvalidArg2 ${CMAKE_COMMAND} --invalid)

+ 1 - 0
Tests/RunCMake/CommandLine/versionDoubleDashJson-check.cmake

@@ -0,0 +1 @@
+version_json_check_python(1 TRUE)

+ 1 - 0
Tests/RunCMake/CommandLine/versionDoubleDashJsonNoArg-check.cmake

@@ -0,0 +1 @@
+version_json_check_python(1 FALSE)

+ 1 - 0
Tests/RunCMake/CommandLine/versionSingleDashJson-check.cmake

@@ -0,0 +1 @@
+version_json_check_python(1 TRUE)

+ 1 - 0
Tests/RunCMake/CommandLine/versionSingleDashJsonNoArg-check.cmake

@@ -0,0 +1 @@
+version_json_check_python(1 FALSE)

+ 1 - 0
Tests/RunCMake/CommandLine/versionSlashJson-check.cmake

@@ -0,0 +1 @@
+version_json_check_python(1 TRUE)

+ 1 - 0
Tests/RunCMake/CommandLine/versionSlashJsonNoArg-check.cmake

@@ -0,0 +1 @@
+version_json_check_python(1 FALSE)

+ 1 - 0
Tests/RunCMake/CommandLine/versionVJson-check.cmake

@@ -0,0 +1 @@
+version_json_check_python(1 TRUE)

+ 1 - 0
Tests/RunCMake/CommandLine/versionVJsonNoArg-check.cmake

@@ -0,0 +1 @@
+version_json_check_python(1 FALSE)

+ 16 - 0
Tests/RunCMake/CommandLine/version_json_validate_schema.py

@@ -0,0 +1,16 @@
+import json
+import jsonschema
+import os.path
+import sys
+
+
+with open(sys.argv[1], "r", encoding="utf-8-sig") as f:
+    contents = json.load(f)
+
+schema_file = os.path.join(
+    os.path.dirname(__file__),
+    "..", "..", "..", "Help", "manual", "cmake", "version-schema.json")
+with open(schema_file, "r", encoding="utf-8") as f:
+    schema = json.load(f)
+
+jsonschema.validate(contents, schema)

+ 1 - 1
Utilities/Scripts/update-librhash.bash

@@ -8,7 +8,7 @@ readonly name="librhash"
 readonly ownership="librhash upstream <[email protected]>"
 readonly subtree="Utilities/cmlibrhash"
 readonly repo="https://github.com/rhash/rhash.git"
-readonly tag="v1.4.4"
+readonly tag="v1.4.4" # When updating, sync version in cmVersion_Dependencies.cxx!
 readonly shortlog=false
 readonly exact_tree_match=false
 readonly paths="