瀏覽代碼

Merge topic 'cmake-server-basic-commands'

89043267 server-mode: Add command to compute the build system
0a8ad670 server-mode: Add a configure command
544f65f4 server-mode: Set global configuration of cmake via a command
82104cc7 server-mode: Query global configuration of cmake via a command
Brad King 9 年之前
父節點
當前提交
e8ff565d94

+ 126 - 0
Help/manual/cmake-server.7.rst

@@ -248,3 +248,129 @@ which will result in a response type "reply"::
   ]== CMake Server ==]
 
 indicating that the server is ready for action.
+
+
+Type "globalSettings"
+^^^^^^^^^^^^^^^^^^^^^
+
+This request can be sent after the initial handshake. It will return a
+JSON structure with information on cmake state.
+
+Example::
+
+  [== CMake Server ==[
+  {"type":"globalSettings"}
+  ]== CMake Server ==]
+
+which will result in a response type "reply"::
+
+  [== CMake Server ==[
+  {
+    "buildDirectory": "/tmp/test-build",
+    "capabilities": {
+      "generators": [
+        {
+          "extraGenerators": [],
+          "name": "Watcom WMake",
+          "platformSupport": false,
+          "toolsetSupport": false
+        },
+        <...>
+      ],
+      "serverMode": false,
+      "version": {
+        "isDirty": false,
+        "major": 3,
+        "minor": 6,
+        "patch": 20160830,
+        "string": "3.6.20160830-gd6abad",
+        "suffix": "gd6abad"
+      }
+    },
+    "checkSystemVars": false,
+    "cookie": "",
+    "extraGenerator": "",
+    "generator": "Ninja",
+    "debugOutput": false,
+    "inReplyTo": "globalSettings",
+    "sourceDirectory": "/home/code/cmake",
+    "trace": false,
+    "traceExpand": false,
+    "type": "reply",
+    "warnUninitialized": false,
+    "warnUnused": false,
+    "warnUnusedCli": true
+  }
+  ]== CMake Server ==]
+
+
+Type "setGlobalSettings"
+^^^^^^^^^^^^^^^^^^^^^^^^
+
+This request can be sent to change the global settings attributes. Unknown
+attributes are going to be ignored. Read-only attributes reported by
+"globalSettings" are all capabilities, buildDirectory, generator,
+extraGenerator and sourceDirectory. Any attempt to set these will be ignored,
+too.
+
+All other settings will be changed.
+
+The server will respond with an empty reply message or an error.
+
+Example::
+
+  [== CMake Server ==[
+  {"type":"setGlobalSettings","debugOutput":true}
+  ]== CMake Server ==]
+
+CMake will reply to this with::
+
+  [== CMake Server ==[
+  {"inReplyTo":"setGlobalSettings","type":"reply"}
+  ]== CMake Server ==]
+
+
+Type "configure"
+^^^^^^^^^^^^^^^^
+
+This request will configure a project for build.
+
+To configure a build directory already containing cmake files, it is enough to
+set "buildDirectory" via "setGlobalSettings". To create a fresh build directory
+you also need to set "currentGenerator" and "sourceDirectory" via "setGlobalSettings"
+in addition to "buildDirectory".
+
+You may a list of strings to "configure" via the "cacheArguments" key. These
+strings will be interpreted similar to command line arguments related to
+cache handling that are passed to the cmake command line client.
+
+Example::
+
+  [== CMake Server ==[
+  {"type":"configure", "cacheArguments":["-Dsomething=else"]}
+  ]== CMake Server ==]
+
+CMake will reply like this (after reporting progress for some time)::
+
+  [== CMake Server ==[
+  {"cookie":"","inReplyTo":"configure","type":"reply"}
+  ]== CMake Server ==]
+
+
+Type "compute"
+^^^^^^^^^^^^^^
+
+This requist will generate build system files in the build directory and
+is only available after a project was successfully "configure"d.
+
+Example::
+
+  [== CMake Server ==[
+  {"type":"compute"}
+  ]== CMake Server ==]
+
+CMake will reply (after reporting progress information)::
+
+  [== CMake Server ==[
+  {"cookie":"","inReplyTo":"compute","type":"reply"}
+  ]== CMake Server ==]

+ 13 - 0
Source/cmServerDictionary.h

@@ -16,15 +16,23 @@
 
 // Vocabulary:
 
+static const std::string kCOMPUTE_TYPE = "compute";
+static const std::string kCONFIGURE_TYPE = "configure";
 static const std::string kERROR_TYPE = "error";
+static const std::string kGLOBAL_SETTINGS_TYPE = "globalSettings";
 static const std::string kHANDSHAKE_TYPE = "handshake";
 static const std::string kMESSAGE_TYPE = "message";
 static const std::string kPROGRESS_TYPE = "progress";
 static const std::string kREPLY_TYPE = "reply";
+static const std::string kSET_GLOBAL_SETTINGS_TYPE = "setGlobalSettings";
 static const std::string kSIGNAL_TYPE = "signal";
 
 static const std::string kBUILD_DIRECTORY_KEY = "buildDirectory";
+static const std::string kCACHE_ARGUMENTS_KEY = "cacheArguments";
+static const std::string kCAPABILITIES_KEY = "capabilities";
+static const std::string kCHECK_SYSTEM_VARS_KEY = "checkSystemVars";
 static const std::string kCOOKIE_KEY = "cookie";
+static const std::string kDEBUG_OUTPUT_KEY = "debugOutput";
 static const std::string kERROR_MESSAGE_KEY = "errorMessage";
 static const std::string kEXTRA_GENERATOR_KEY = "extraGenerator";
 static const std::string kGENERATOR_KEY = "generator";
@@ -43,7 +51,12 @@ static const std::string kSOURCE_DIRECTORY_KEY = "sourceDirectory";
 static const std::string kSUPPORTED_PROTOCOL_VERSIONS =
   "supportedProtocolVersions";
 static const std::string kTITLE_KEY = "title";
+static const std::string kTRACE_EXPAND_KEY = "traceExpand";
+static const std::string kTRACE_KEY = "trace";
 static const std::string kTYPE_KEY = "type";
+static const std::string kWARN_UNINITIALIZED_KEY = "warnUninitialized";
+static const std::string kWARN_UNUSED_CLI_KEY = "warnUnusedCli";
+static const std::string kWARN_UNUSED_KEY = "warnUnused";
 
 static const std::string kSTART_MAGIC = "[== CMake Server ==[";
 static const std::string kEND_MAGIC = "]== CMake Server ==]";

+ 190 - 0
Source/cmServerProtocol.cxx

@@ -13,6 +13,7 @@
 #include "cmServerProtocol.h"
 
 #include "cmExternalMakefileProjectGenerator.h"
+#include "cmGlobalGenerator.h"
 #include "cmServer.h"
 #include "cmServerDictionary.h"
 #include "cmSystemTools.h"
@@ -279,6 +280,19 @@ const cmServerResponse cmServerProtocol1_0::Process(
 {
   assert(this->m_State >= STATE_ACTIVE);
 
+  if (request.Type == kCOMPUTE_TYPE) {
+    return this->ProcessCompute(request);
+  }
+  if (request.Type == kCONFIGURE_TYPE) {
+    return this->ProcessConfigure(request);
+  }
+  if (request.Type == kGLOBAL_SETTINGS_TYPE) {
+    return this->ProcessGlobalSettings(request);
+  }
+  if (request.Type == kSET_GLOBAL_SETTINGS_TYPE) {
+    return this->ProcessSetGlobalSettings(request);
+  }
+
   return request.ReportError("Unknown command!");
 }
 
@@ -286,3 +300,179 @@ bool cmServerProtocol1_0::IsExperimental() const
 {
   return true;
 }
+
+cmServerResponse cmServerProtocol1_0::ProcessCompute(
+  const cmServerRequest& request)
+{
+  if (this->m_State > STATE_CONFIGURED) {
+    return request.ReportError("This build system was already generated.");
+  }
+  if (this->m_State < STATE_CONFIGURED) {
+    return request.ReportError("This project was not configured yet.");
+  }
+
+  cmake* cm = this->CMakeInstance();
+  int ret = cm->Generate();
+
+  if (ret < 0) {
+    return request.ReportError("Failed to compute build system.");
+  }
+  m_State = STATE_COMPUTED;
+  return request.Reply(Json::Value());
+}
+
+cmServerResponse cmServerProtocol1_0::ProcessConfigure(
+  const cmServerRequest& request)
+{
+  if (this->m_State == STATE_INACTIVE) {
+    return request.ReportError("This instance is inactive.");
+  }
+
+  // Make sure the types of cacheArguments matches (if given):
+  std::vector<std::string> cacheArgs;
+  bool cacheArgumentsError = false;
+  const Json::Value passedArgs = request.Data[kCACHE_ARGUMENTS_KEY];
+  if (!passedArgs.isNull()) {
+    if (passedArgs.isString()) {
+      cacheArgs.push_back(passedArgs.asString());
+    } else if (passedArgs.isArray()) {
+      for (auto i = passedArgs.begin(); i != passedArgs.end(); ++i) {
+        if (!i->isString()) {
+          cacheArgumentsError = true;
+          break;
+        }
+        cacheArgs.push_back(i->asString());
+      }
+    } else {
+      cacheArgumentsError = true;
+    }
+  }
+  if (cacheArgumentsError) {
+    request.ReportError(
+      "cacheArguments must be unset, a string or an array of strings.");
+  }
+
+  cmake* cm = this->CMakeInstance();
+  std::string sourceDir = cm->GetHomeDirectory();
+  const std::string buildDir = cm->GetHomeOutputDirectory();
+
+  if (buildDir.empty()) {
+    return request.ReportError(
+      "No build directory set via setGlobalSettings.");
+  }
+
+  if (cm->LoadCache(buildDir)) {
+    // build directory has been set up before
+    const char* cachedSourceDir =
+      cm->GetState()->GetInitializedCacheValue("CMAKE_HOME_DIRECTORY");
+    if (!cachedSourceDir) {
+      return request.ReportError("No CMAKE_HOME_DIRECTORY found in cache.");
+    }
+    if (sourceDir.empty()) {
+      sourceDir = std::string(cachedSourceDir);
+      cm->SetHomeDirectory(sourceDir);
+    }
+
+    const char* cachedGenerator =
+      cm->GetState()->GetInitializedCacheValue("CMAKE_GENERATOR");
+    if (cachedGenerator) {
+      cmGlobalGenerator* gen = cm->GetGlobalGenerator();
+      if (gen && gen->GetName() != cachedGenerator) {
+        return request.ReportError("Configured generator does not match with "
+                                   "CMAKE_GENERATOR found in cache.");
+      }
+    }
+  } else {
+    // build directory has not been set up before
+    if (sourceDir.empty()) {
+      return request.ReportError("No sourceDirectory set via "
+                                 "setGlobalSettings and no cache found in "
+                                 "buildDirectory.");
+    }
+  }
+
+  if (cm->AddCMakePaths() != 1) {
+    return request.ReportError("Failed to set CMake paths.");
+  }
+
+  if (!cm->SetCacheArgs(cacheArgs)) {
+    return request.ReportError("cacheArguments could not be set.");
+  }
+
+  int ret = cm->Configure();
+  if (ret < 0) {
+    return request.ReportError("Configuration failed.");
+  }
+  m_State = STATE_CONFIGURED;
+  return request.Reply(Json::Value());
+}
+
+cmServerResponse cmServerProtocol1_0::ProcessGlobalSettings(
+  const cmServerRequest& request)
+{
+  cmake* cm = this->CMakeInstance();
+  Json::Value obj = Json::objectValue;
+
+  // Capabilities information:
+  obj[kCAPABILITIES_KEY] = cm->ReportCapabilitiesJson(true);
+
+  obj[kDEBUG_OUTPUT_KEY] = cm->GetDebugOutput();
+  obj[kTRACE_KEY] = cm->GetTrace();
+  obj[kTRACE_EXPAND_KEY] = cm->GetTraceExpand();
+  obj[kWARN_UNINITIALIZED_KEY] = cm->GetWarnUninitialized();
+  obj[kWARN_UNUSED_KEY] = cm->GetWarnUnused();
+  obj[kWARN_UNUSED_CLI_KEY] = cm->GetWarnUnusedCli();
+  obj[kCHECK_SYSTEM_VARS_KEY] = cm->GetCheckSystemVars();
+
+  obj[kSOURCE_DIRECTORY_KEY] = cm->GetHomeDirectory();
+  obj[kBUILD_DIRECTORY_KEY] = cm->GetHomeOutputDirectory();
+
+  // Currently used generator:
+  cmGlobalGenerator* gen = cm->GetGlobalGenerator();
+  obj[kGENERATOR_KEY] = gen ? gen->GetName() : std::string();
+  obj[kEXTRA_GENERATOR_KEY] =
+    gen ? gen->GetExtraGeneratorName() : std::string();
+
+  return request.Reply(obj);
+}
+
+static void setBool(const cmServerRequest& request, const std::string& key,
+                    std::function<void(bool)> setter)
+{
+  if (request.Data[key].isNull()) {
+    return;
+  }
+  setter(request.Data[key].asBool());
+}
+
+cmServerResponse cmServerProtocol1_0::ProcessSetGlobalSettings(
+  const cmServerRequest& request)
+{
+  const std::vector<std::string> boolValues = {
+    kDEBUG_OUTPUT_KEY,       kTRACE_KEY,       kTRACE_EXPAND_KEY,
+    kWARN_UNINITIALIZED_KEY, kWARN_UNUSED_KEY, kWARN_UNUSED_CLI_KEY,
+    kCHECK_SYSTEM_VARS_KEY
+  };
+  for (auto i : boolValues) {
+    if (!request.Data[i].isNull() && !request.Data[i].isBool()) {
+      return request.ReportError("\"" + i +
+                                 "\" must be unset or a bool value.");
+    }
+  }
+
+  cmake* cm = this->CMakeInstance();
+
+  setBool(request, kDEBUG_OUTPUT_KEY,
+          [cm](bool e) { cm->SetDebugOutputOn(e); });
+  setBool(request, kTRACE_KEY, [cm](bool e) { cm->SetTrace(e); });
+  setBool(request, kTRACE_EXPAND_KEY, [cm](bool e) { cm->SetTraceExpand(e); });
+  setBool(request, kWARN_UNINITIALIZED_KEY,
+          [cm](bool e) { cm->SetWarnUninitialized(e); });
+  setBool(request, kWARN_UNUSED_KEY, [cm](bool e) { cm->SetWarnUnused(e); });
+  setBool(request, kWARN_UNUSED_CLI_KEY,
+          [cm](bool e) { cm->SetWarnUnusedCli(e); });
+  setBool(request, kCHECK_SYSTEM_VARS_KEY,
+          [cm](bool e) { cm->SetCheckSystemVars(e); });
+
+  return request.Reply(Json::Value());
+}

+ 10 - 1
Source/cmServerProtocol.h

@@ -13,6 +13,7 @@
 #pragma once
 
 #include "cmListFileCache.h"
+#include "cmake.h"
 
 #if defined(CMAKE_BUILD_WITH_CMAKE)
 #include "cm_jsoncpp_writer.h"
@@ -116,10 +117,18 @@ private:
   bool DoActivate(const cmServerRequest& request,
                   std::string* errorMessage) override;
 
+  // Handle requests:
+  cmServerResponse ProcessCompute(const cmServerRequest& request);
+  cmServerResponse ProcessConfigure(const cmServerRequest& request);
+  cmServerResponse ProcessGlobalSettings(const cmServerRequest& request);
+  cmServerResponse ProcessSetGlobalSettings(const cmServerRequest& request);
+
   enum State
   {
     STATE_INACTIVE,
-    STATE_ACTIVE
+    STATE_ACTIVE,
+    STATE_CONFIGURED,
+    STATE_COMPUTED
   };
   State m_State = STATE_INACTIVE;
 };

+ 1 - 0
Tests/Server/CMakeLists.txt

@@ -19,5 +19,6 @@ macro(do_test bsname file)
 endmacro()
 
 do_test("test_handshake" "tc_handshake.json")
+do_test("test_globalSettings" "tc_globalSettings.json")
 
 add_executable(Server empty.cpp)

+ 64 - 2
Tests/Server/cmakelib.py

@@ -106,6 +106,7 @@ def waitForReply(cmakeCommand, originalType, cookie):
   packet = waitForRawMessage(cmakeCommand)
   if packet['cookie'] != cookie or packet['type'] != 'reply' or packet['inReplyTo'] != originalType:
     sys.exit(1)
+  return packet
 
 def waitForError(cmakeCommand, originalType, cookie, message):
   packet = waitForRawMessage(cmakeCommand)
@@ -117,10 +118,71 @@ def waitForProgress(cmakeCommand, originalType, cookie, current, message):
   if packet['cookie'] != cookie or packet['type'] != 'progress' or packet['inReplyTo'] != originalType or packet['progressCurrent'] != current or packet['progressMessage'] != message:
     sys.exit(1)
 
-def handshake(cmakeCommand, major, minor):
+def handshake(cmakeCommand, major, minor, source, build, generator, extraGenerator):
   version = { 'major': major }
   if minor >= 0:
     version['minor'] = minor
 
-  writePayload(cmakeCommand, { 'type': 'handshake', 'protocolVersion': version, 'cookie': 'TEST_HANDSHAKE' })
+  writePayload(cmakeCommand, { 'type': 'handshake', 'protocolVersion': version,
+    'cookie': 'TEST_HANDSHAKE', 'sourceDirectory': source, 'buildDirectory': build,
+    'generator': generator, 'extraGenerator': extraGenerator })
   waitForReply(cmakeCommand, 'handshake', 'TEST_HANDSHAKE')
+
+def validateGlobalSettings(cmakeCommand, cmakeCommandPath, data):
+  packet = waitForReply(cmakeCommand, 'globalSettings', '')
+
+  capabilities = packet['capabilities']
+
+  # validate version:
+  cmakeoutput = subprocess.check_output([ cmakeCommandPath, "--version" ], universal_newlines=True)
+  cmakeVersion = cmakeoutput.splitlines()[0][14:]
+
+  version = capabilities['version']
+  versionString = version['string']
+  vs = str(version['major']) + '.' + str(version['minor']) + '.' + str(version['patch'])
+  if (versionString != vs and not versionString.startswith(vs + '-')):
+    sys.exit(1)
+  if (versionString != cmakeVersion):
+    sys.exit(1)
+
+  # validate generators:
+  generatorObjects = capabilities['generators']
+
+  cmakeoutput = subprocess.check_output([ cmakeCommandPath, "--help" ], universal_newlines=True)
+  index = cmakeoutput.index('\nGenerators\n\n')
+  cmakeGenerators = []
+  for line in cmakeoutput[index + 12:].splitlines():
+    if not line.startswith('  '):
+      continue
+    if line.startswith('    '):
+      continue
+    equalPos = line.find('=')
+    tmp = ''
+    if (equalPos > 0):
+      tmp = line[2:equalPos].strip()
+    else:
+      tmp = line.strip()
+    if tmp.endswith(" [arch]"):
+      tmp = tmp[0:len(tmp) - 7]
+    if (len(tmp) > 0) and (" - " not in tmp) and (tmp != 'KDevelop3'):
+      cmakeGenerators.append(tmp)
+
+  generators = []
+  for genObj in generatorObjects:
+    generators.append(genObj['name'])
+
+  generators.sort()
+  cmakeGenerators.sort()
+
+  for gen in cmakeGenerators:
+    if (not gen in generators):
+        sys.exit(1)
+
+  gen = packet['generator']
+  if (gen != '' and not (gen in generators)):
+    sys.exit(1)
+
+  for i in data:
+    print("Validating", i)
+    if (packet[i] != data[i]):
+      sys.exit(1)

+ 17 - 1
Tests/Server/server-test.py

@@ -68,9 +68,25 @@ for obj in testData:
         if debug: print("Doing handshake:", json.dumps(data))
         major = -1
         minor = -1
+        generator = 'Ninja'
+        extraGenerator = 'CodeBlocks'
+        sourceDirectory = sourceDir
+        buildDirectory = buildDir
         if 'major' in data: major = data['major']
         if 'minor' in data: minor = data['minor']
-        cmakelib.handshake(proc, major, minor)
+        if 'buildDirectory' in data: buildDirectory = data['buildDirectory']
+        if 'sourceDirectory' in data: sourceDirectory = data['sourceDirectory']
+        if 'generator' in data: generator = data['generator']
+        if 'extraGenerator' in data: extraGenerator = data['extraGenerator']
+        cmakelib.handshake(proc, major, minor, sourceDirectory, buildDirectory,
+          generator, extraGenerator)
+    elif 'validateGlobalSettings' in obj:
+        data = obj['validateGlobalSettings']
+        if not 'buildDirectory' in data: data['buildDirectory'] = buildDir
+        if not 'sourceDirectory' in data: data['sourceDirectory'] = sourceDir
+        if not 'generator' in data: data['generator'] = 'Ninja'
+        if not 'extraGenerator' in data: data['extraGenerator'] = 'CodeBlocks'
+        cmakelib.validateGlobalSettings(proc, cmakeCommand, data)
     elif 'message' in obj:
         print("MESSAGE:", obj["message"])
     else:

+ 140 - 0
Tests/Server/tc_globalSettings.json

@@ -0,0 +1,140 @@
+[
+{ "message": "Testing globalSettings" },
+
+{ "handshake": {"major": 1} },
+
+{ "send": { "type": "globalSettings"} },
+{ "validateGlobalSettings": { "warnUnused": false, "debugOutput": false, "warnUninitialized": false, "traceExpand": false, "trace": false, "warnUnusedCli": true, "checkSystemVars": false } },
+
+
+
+{ "message": "Change settings:" },
+
+{ "send": { "type": "setGlobalSettings", "warnUnused": true } },
+{ "reply": { "type": "setGlobalSettings" } },
+
+{ "send": { "type": "globalSettings"} },
+{ "validateGlobalSettings": { "warnUnused": true, "debugOutput": false, "warnUninitialized": false, "traceExpand": false, "trace": false, "warnUnusedCli": true, "checkSystemVars": false } },
+
+{ "send": { "type": "setGlobalSettings", "warnUnused": false } },
+{ "reply": { "type": "setGlobalSettings" } },
+
+{ "send": { "type": "globalSettings"} },
+{ "validateGlobalSettings": { "warnUnused": false, "debugOutput": false, "warnUninitialized": false, "traceExpand": false, "trace": false, "warnUnusedCli": true, "checkSystemVars": false } },
+
+{ "send": { "type": "setGlobalSettings", "debugOutput": true } },
+{ "reply": { "type": "setGlobalSettings" } },
+
+{ "send": { "type": "globalSettings"} },
+{ "validateGlobalSettings": { "warnUnused": false, "debugOutput": true, "warnUninitialized": false, "traceExpand": false, "trace": false, "warnUnusedCli": true, "checkSystemVars": false } },
+
+{ "send": { "type": "setGlobalSettings", "debugOutput": false } },
+{ "reply": { "type": "setGlobalSettings" } },
+
+{ "send": { "type": "globalSettings"} },
+{ "validateGlobalSettings": { "warnUnused": false, "debugOutput": false, "warnUninitialized": false, "traceExpand": false, "trace": false, "warnUnusedCli": true, "checkSystemVars": false } },
+
+{ "send": { "type": "setGlobalSettings", "warnUninitialized": true } },
+{ "reply": { "type": "setGlobalSettings" } },
+
+{ "send": { "type": "globalSettings"} },
+{ "validateGlobalSettings": { "warnUnused": false, "debugOutput": false, "warnUninitialized": true, "traceExpand": false, "trace": false, "warnUnusedCli": true, "checkSystemVars": false } },
+
+{ "send": { "type": "setGlobalSettings", "warnUninitialized": false } },
+{ "reply": { "type": "setGlobalSettings" } },
+
+{ "send": { "type": "globalSettings"} },
+{ "validateGlobalSettings": { "warnUnused": false, "debugOutput": false, "warnUninitialized": false, "traceExpand": false, "trace": false, "warnUnusedCli": true, "checkSystemVars": false } },
+
+{ "send": { "type": "setGlobalSettings", "traceExpand": true } },
+{ "reply": { "type": "setGlobalSettings" } },
+
+{ "send": { "type": "globalSettings"} },
+{ "validateGlobalSettings": { "warnUnused": false, "debugOutput": false, "warnUninitialized": false, "traceExpand": true, "trace": false, "warnUnusedCli": true, "checkSystemVars": false } },
+
+{ "send": { "type": "setGlobalSettings", "traceExpand": false } },
+{ "reply": { "type": "setGlobalSettings" } },
+
+{ "send": { "type": "globalSettings"} },
+{ "validateGlobalSettings": { "warnUnused": false, "debugOutput": false, "warnUninitialized": false, "traceExpand": false, "trace": false, "warnUnusedCli": true, "checkSystemVars": false } },
+
+
+
+{ "send": { "type": "setGlobalSettings", "trace": true } },
+{ "reply": { "type": "setGlobalSettings" } },
+
+{ "send": { "type": "globalSettings"} },
+{ "validateGlobalSettings": { "warnUnused": false, "debugOutput": false, "warnUninitialized": false, "traceExpand": false, "trace": true, "warnUnusedCli": true, "checkSystemVars": false } },
+
+{ "send": { "type": "setGlobalSettings", "trace": false } },
+{ "reply": { "type": "setGlobalSettings" } },
+
+{ "send": { "type": "globalSettings"} },
+{ "validateGlobalSettings": { "warnUnused": false, "debugOutput": false, "warnUninitialized": false, "traceExpand": false, "trace": false, "warnUnusedCli": true, "checkSystemVars": false } },
+
+{ "send": { "type": "setGlobalSettings", "warnUnusedCli": false } },
+{ "reply": { "type": "setGlobalSettings" } },
+
+{ "send": { "type": "globalSettings"} },
+{ "validateGlobalSettings": { "warnUnused": false, "debugOutput": false, "warnUninitialized": false, "traceExpand": false, "trace": false, "warnUnusedCli": false, "checkSystemVars": false } },
+
+{ "send": { "type": "setGlobalSettings", "warnUnusedCli": true } },
+{ "reply": { "type": "setGlobalSettings" } },
+
+{ "send": { "type": "globalSettings"} },
+{ "validateGlobalSettings": { "warnUnused": false, "debugOutput": false, "warnUninitialized": false, "traceExpand": false, "trace": false, "warnUnusedCli": true, "checkSystemVars": false } },
+
+{ "send": { "type": "setGlobalSettings", "checkSystemVars": true } },
+{ "reply": { "type": "setGlobalSettings" } },
+
+{ "send": { "type": "globalSettings"} },
+{ "validateGlobalSettings": { "warnUnused": false, "debugOutput": false, "warnUninitialized": false, "traceExpand": false, "trace": false, "warnUnusedCli": true, "checkSystemVars": true } },
+
+{ "send": { "type": "setGlobalSettings", "checkSystemVars": false } },
+{ "reply": { "type": "setGlobalSettings" } },
+
+{ "send": { "type": "globalSettings"} },
+{ "validateGlobalSettings": { "warnUnused": false, "debugOutput": false, "warnUninitialized": false, "traceExpand": false, "trace": false, "warnUnusedCli": true, "checkSystemVars": false } },
+
+{ "send": { "type": "setGlobalSettings", "warnUnused": true, "debugOutput": true, "warnUninitialized": true, "traceExpand": true, "trace": true, "warnUnusedCli": false, "checkSystemVars": true } },
+{ "reply": { "type": "setGlobalSettings" } },
+
+{ "send": { "type": "globalSettings"} },
+{ "validateGlobalSettings": { "warnUnused": true, "debugOutput": true, "warnUninitialized": true, "traceExpand": true, "trace": true, "warnUnusedCli": false, "checkSystemVars": true } },
+
+{ "message": "Ignore unknown/readonly" },
+
+{ "send": { "type": "setGlobalSettings", "unknownKey": "unknownValue", "extraGenerator": "XXX", "generator": "YYY", "sourceDirectory": "/tmp/source", "buildDirectory": "/tmp/build" } },
+{ "reply": { "type": "setGlobalSettings" } },
+
+{ "send": { "type": "globalSettings"} },
+{ "validateGlobalSettings": { "warnUnused": true, "debugOutput": true, "warnUninitialized": true, "traceExpand": true, "trace": true, "warnUnusedCli": false, "checkSystemVars": true } },
+
+{ "message": "Error paths:" },
+
+{ "send": { "type": "setGlobalSettings", "debugOutput": true, "warnUnused": 1 } },
+{ "error": { "type": "setGlobalSettings", "message": "\"warnUnused\" must be unset or a bool value." } },
+
+{ "send": { "type": "setGlobalSettings", "warnUnused": true, "debugOutput": 1 } },
+{ "error": { "type": "setGlobalSettings", "message": "\"debugOutput\" must be unset or a bool value." } },
+
+{ "send": { "type": "setGlobalSettings", "warnUninitialized": 1, "warnUnused": true, "debugOutput": true } },
+{ "error": { "type": "setGlobalSettings", "message": "\"warnUninitialized\" must be unset or a bool value." } },
+
+{ "send": { "type": "setGlobalSettings", "warnUnused": true, "debugOutput": true, "traceExpand": 1 } },
+{ "error": { "type": "setGlobalSettings", "message": "\"traceExpand\" must be unset or a bool value." } },
+
+{ "send": { "type": "setGlobalSettings", "debugOutput": true, "trace": 1, "warnUnused": true } },
+{ "error": { "type": "setGlobalSettings", "message": "\"trace\" must be unset or a bool value." } },
+
+{ "send": { "type": "setGlobalSettings", "warnUnused": true, "debugOutput": true, "warnUnusedCli": 1.0 } },
+{ "error": { "type": "setGlobalSettings", "message": "\"warnUnusedCli\" must be unset or a bool value." } },
+
+{ "send": { "type": "setGlobalSettings", "warnUnused": true, "debugOutput": true, "checkSystemVars": "some string" } },
+{ "error": { "type": "setGlobalSettings", "message": "\"checkSystemVars\" must be unset or a bool value." } },
+
+{ "send": { "type": "globalSettings"} },
+{ "validateGlobalSettings": { "warnUnused": true, "debugOutput": true, "warnUninitialized": true, "traceExpand": true, "trace": true, "warnUnusedCli": false, "checkSystemVars": true } },
+
+{ "message": "Everything ok." }
+]