mirror of
https://github.com/Kitware/CMake.git
synced 2025-10-14 02:08:27 +08:00
server: remove deprecated 'cmake -E server' mode
The server mode has been deprecated since commit 996e1885c4
(server:
deprecate in favor of the file-api, 2019-04-19, v3.15.0-rc1~198^2).
Clients should now be using the file-api. Remove the server mode.
This commit is contained in:
@@ -79,8 +79,8 @@ as the include directories, compile definitions, etc. used to build the
|
||||
artifacts. Such information can be obtained by using the
|
||||
:manual:`File API <cmake-file-api(7)>`. The manual page for the File API
|
||||
contains more information about the API and how to invoke it.
|
||||
:manual:`Server mode <cmake-server(7)>` is deprecated and should not be
|
||||
used on CMake 3.14 or later.
|
||||
:manual:`Server mode <cmake-server(7)>` was removed as of CMake 3.20 and
|
||||
should not be used on CMake 3.14 or later.
|
||||
|
||||
IDEs should avoid creating more build trees than necessary, and only create
|
||||
multiple build trees if the user wishes to switch to a different compiler,
|
||||
|
@@ -3,742 +3,5 @@
|
||||
cmake-server(7)
|
||||
***************
|
||||
|
||||
.. only:: html
|
||||
|
||||
.. contents::
|
||||
|
||||
.. deprecated:: 3.15
|
||||
|
||||
This will be removed from a future version of CMake.
|
||||
Clients should use the :manual:`cmake-file-api(7)` instead.
|
||||
|
||||
Introduction
|
||||
============
|
||||
|
||||
:manual:`cmake(1)` is capable of providing semantic information about
|
||||
CMake code it executes to generate a buildsystem. If executed with
|
||||
the ``-E server`` command line options, it starts in a long running mode
|
||||
and allows a client to request the available information via a JSON protocol.
|
||||
|
||||
The protocol is designed to be useful to IDEs, refactoring tools, and
|
||||
other tools which have a need to understand the buildsystem in entirety.
|
||||
|
||||
A single :manual:`cmake-buildsystem(7)` may describe buildsystem contents
|
||||
and build properties which differ based on
|
||||
:manual:`generation-time context <cmake-generator-expressions(7)>`
|
||||
including:
|
||||
|
||||
* The Platform (eg, Windows, APPLE, Linux).
|
||||
* The build configuration (eg, Debug, Release, Coverage).
|
||||
* The Compiler (eg, MSVC, GCC, Clang) and compiler version.
|
||||
* The language of the source files compiled.
|
||||
* Available compile features (eg CXX variadic templates).
|
||||
* CMake policies.
|
||||
|
||||
The protocol aims to provide information to tooling to satisfy several
|
||||
needs:
|
||||
|
||||
#. Provide a complete and easily parsed source of all information relevant
|
||||
to the tooling as it relates to the source code. There should be no need
|
||||
for tooling to parse generated buildsystems to access include directories
|
||||
or compile definitions for example.
|
||||
#. Semantic information about the CMake buildsystem itself.
|
||||
#. Provide a stable interface for reading the information in the CMake cache.
|
||||
#. Information for determining when cmake needs to be re-run as a result of
|
||||
file changes.
|
||||
|
||||
|
||||
Operation
|
||||
=========
|
||||
|
||||
Start :manual:`cmake(1)` in the server command mode, supplying the path to
|
||||
the build directory to process::
|
||||
|
||||
cmake -E server (--debug|--pipe=<NAMED_PIPE>)
|
||||
|
||||
The server will communicate using stdin/stdout (with the ``--debug`` parameter)
|
||||
or using a named pipe (with the ``--pipe=<NAMED_PIPE>`` parameter). Note
|
||||
that "named pipe" refers to a local domain socket on Unix and to a named pipe
|
||||
on Windows.
|
||||
|
||||
When connecting to the server (via named pipe or by starting it in ``--debug``
|
||||
mode), the server will reply with a hello message::
|
||||
|
||||
[== "CMake Server" ==[
|
||||
{"supportedProtocolVersions":[{"major":1,"minor":0}],"type":"hello"}
|
||||
]== "CMake Server" ==]
|
||||
|
||||
Messages sent to and from the process are wrapped in magic strings::
|
||||
|
||||
[== "CMake Server" ==[
|
||||
{
|
||||
... some JSON message ...
|
||||
}
|
||||
]== "CMake Server" ==]
|
||||
|
||||
The server is now ready to accept further requests via the named pipe
|
||||
or stdin.
|
||||
|
||||
|
||||
Debugging
|
||||
=========
|
||||
|
||||
CMake server mode can be asked to provide statistics on execution times, etc.
|
||||
or to dump a copy of the response into a file. This is done passing a "debug"
|
||||
JSON object as a child of the request.
|
||||
|
||||
The debug object supports the "showStats" key, which takes a boolean and makes
|
||||
the server mode return a "zzzDebug" object with stats as part of its response.
|
||||
"dumpToFile" takes a string value and will cause the cmake server to copy
|
||||
the response into the given filename.
|
||||
|
||||
This is a response from the cmake server with "showStats" set to true::
|
||||
|
||||
[== "CMake Server" ==[
|
||||
{
|
||||
"cookie":"",
|
||||
"errorMessage":"Waiting for type \"handshake\".",
|
||||
"inReplyTo":"unknown",
|
||||
"type":"error",
|
||||
"zzzDebug": {
|
||||
"dumpFile":"/tmp/error.txt",
|
||||
"jsonSerialization":0.011016,
|
||||
"size":111,
|
||||
"totalTime":0.025995
|
||||
}
|
||||
}
|
||||
]== "CMake Server" ==]
|
||||
|
||||
The server has made a copy of this response into the file /tmp/error.txt and
|
||||
took 0.011 seconds to turn the JSON response into a string, and it took 0.025
|
||||
seconds to process the request in total. The reply has a size of 111 bytes.
|
||||
|
||||
|
||||
Protocol API
|
||||
============
|
||||
|
||||
|
||||
General Message Layout
|
||||
----------------------
|
||||
|
||||
All messages need to have a "type" value, which identifies the type of
|
||||
message that is passed back or forth. E.g. the initial message sent by the
|
||||
server is of type "hello". Messages without a type will generate an response
|
||||
of type "error".
|
||||
|
||||
All requests sent to the server may contain a "cookie" value. This value
|
||||
will he handed back unchanged in all responses triggered by the request.
|
||||
|
||||
All responses will contain a value "inReplyTo", which may be empty in
|
||||
case of parse errors, but will contain the type of the request message
|
||||
in all other cases.
|
||||
|
||||
|
||||
Type "reply"
|
||||
^^^^^^^^^^^^
|
||||
|
||||
This type is used by the server to reply to requests.
|
||||
|
||||
The message may -- depending on the type of the original request --
|
||||
contain values.
|
||||
|
||||
Example::
|
||||
|
||||
[== "CMake Server" ==[
|
||||
{"cookie":"zimtstern","inReplyTo":"handshake","type":"reply"}
|
||||
]== "CMake Server" ==]
|
||||
|
||||
|
||||
Type "error"
|
||||
^^^^^^^^^^^^
|
||||
|
||||
This type is used to return an error condition to the client. It will
|
||||
contain an "errorMessage".
|
||||
|
||||
Example::
|
||||
|
||||
[== "CMake Server" ==[
|
||||
{"cookie":"","errorMessage":"Protocol version not supported.","inReplyTo":"handshake","type":"error"}
|
||||
]== "CMake Server" ==]
|
||||
|
||||
|
||||
Type "progress"
|
||||
^^^^^^^^^^^^^^^
|
||||
|
||||
When the server is busy for a long time, it is polite to send back replies of
|
||||
type "progress" to the client. These will contain a "progressMessage" with a
|
||||
string describing the action currently taking place as well as
|
||||
"progressMinimum", "progressMaximum" and "progressCurrent" with integer values
|
||||
describing the range of progress.
|
||||
|
||||
Messages of type "progress" will be followed by more "progress" messages or with
|
||||
a message of type "reply" or "error" that complete the request.
|
||||
|
||||
"progress" messages may not be emitted after the "reply" or "error" message for
|
||||
the request that triggered the responses was delivered.
|
||||
|
||||
|
||||
Type "message"
|
||||
^^^^^^^^^^^^^^
|
||||
|
||||
A message is triggered when the server processes a request and produces some
|
||||
form of output that should be displayed to the user. A Message has a "message"
|
||||
with the actual text to display as well as a "title" with a suggested dialog
|
||||
box title.
|
||||
|
||||
Example::
|
||||
|
||||
[== "CMake Server" ==[
|
||||
{"cookie":"","message":"Something happened.","title":"Title Text","inReplyTo":"handshake","type":"message"}
|
||||
]== "CMake Server" ==]
|
||||
|
||||
|
||||
Type "signal"
|
||||
^^^^^^^^^^^^^
|
||||
|
||||
The server can send signals when it detects changes in the system state. Signals
|
||||
are of type "signal", have an empty "cookie" and "inReplyTo" field and always
|
||||
have a "name" set to show which signal was sent.
|
||||
|
||||
|
||||
Specific Signals
|
||||
----------------
|
||||
|
||||
The cmake server may sent signals with the following names:
|
||||
|
||||
"dirty" Signal
|
||||
^^^^^^^^^^^^^^
|
||||
|
||||
The "dirty" signal is sent whenever the server determines that the configuration
|
||||
of the project is no longer up-to-date. This happens when any of the files that have
|
||||
an influence on the build system is changed.
|
||||
|
||||
The "dirty" signal may look like this::
|
||||
|
||||
[== "CMake Server" ==[
|
||||
{
|
||||
"cookie":"",
|
||||
"inReplyTo":"",
|
||||
"name":"dirty",
|
||||
"type":"signal"}
|
||||
]== "CMake Server" ==]
|
||||
|
||||
|
||||
"fileChange" Signal
|
||||
^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
The "fileChange" signal is sent whenever a watched file is changed. It contains
|
||||
the "path" that has changed and a list of "properties" with the kind of change
|
||||
that was detected. Possible changes are "change" and "rename".
|
||||
|
||||
The "fileChange" signal looks like this::
|
||||
|
||||
[== "CMake Server" ==[
|
||||
{
|
||||
"cookie":"",
|
||||
"inReplyTo":"",
|
||||
"name":"fileChange",
|
||||
"path":"/absolute/CMakeLists.txt",
|
||||
"properties":["change"],
|
||||
"type":"signal"}
|
||||
]== "CMake Server" ==]
|
||||
|
||||
|
||||
Specific Message Types
|
||||
----------------------
|
||||
|
||||
|
||||
Type "hello"
|
||||
^^^^^^^^^^^^
|
||||
|
||||
The initial message send by the cmake server on startup is of type "hello".
|
||||
This is the only message ever sent by the server that is not of type "reply",
|
||||
"progress" or "error".
|
||||
|
||||
It will contain "supportedProtocolVersions" with an array of server protocol
|
||||
versions supported by the cmake server. These are JSON objects with "major" and
|
||||
"minor" keys containing non-negative integer values. Some versions may be marked
|
||||
as experimental. These will contain the "isExperimental" key set to true. Enabling
|
||||
these requires a special command line argument when starting the cmake server mode.
|
||||
|
||||
Within a "major" version all "minor" versions are fully backwards compatible.
|
||||
New "minor" versions may introduce functionality in such a way that existing
|
||||
clients of the same "major" version will continue to work, provided they
|
||||
ignore keys in the output that they do not know about.
|
||||
|
||||
Example::
|
||||
|
||||
[== "CMake Server" ==[
|
||||
{"supportedProtocolVersions":[{"major":0,"minor":1}],"type":"hello"}
|
||||
]== "CMake Server" ==]
|
||||
|
||||
|
||||
Type "handshake"
|
||||
^^^^^^^^^^^^^^^^
|
||||
|
||||
The first request that the client may send to the server is of type "handshake".
|
||||
|
||||
This request needs to pass one of the "supportedProtocolVersions" of the "hello"
|
||||
type response received earlier back to the server in the "protocolVersion" field.
|
||||
Giving the "major" version of the requested protocol version will make the server
|
||||
use the latest minor version of that protocol. Use this if you do not explicitly
|
||||
need to depend on a specific minor version.
|
||||
|
||||
Protocol version 1.0 requires the following attributes to be set:
|
||||
|
||||
* "sourceDirectory" with a path to the sources
|
||||
* "buildDirectory" with a path to the build directory
|
||||
* "generator" with the generator name
|
||||
* "extraGenerator" (optional!) with the extra generator to be used
|
||||
* "platform" with the generator platform (if supported by the generator)
|
||||
* "toolset" with the generator toolset (if supported by the generator)
|
||||
|
||||
Protocol version 1.2 makes all but the build directory optional, provided
|
||||
there is a valid cache in the build directory that contains all the other
|
||||
information already.
|
||||
|
||||
Example::
|
||||
|
||||
[== "CMake Server" ==[
|
||||
{"cookie":"zimtstern","type":"handshake","protocolVersion":{"major":0},
|
||||
"sourceDirectory":"/home/code/cmake", "buildDirectory":"/tmp/testbuild",
|
||||
"generator":"Ninja"}
|
||||
]== "CMake Server" ==]
|
||||
|
||||
which will result in a response type "reply"::
|
||||
|
||||
[== "CMake Server" ==[
|
||||
{"cookie":"zimtstern","inReplyTo":"handshake","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 request 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" ==]
|
||||
|
||||
|
||||
Type "codemodel"
|
||||
^^^^^^^^^^^^^^^^
|
||||
|
||||
The "codemodel" request can be used after a project was "compute"d successfully.
|
||||
|
||||
It will list the complete project structure as it is known to cmake.
|
||||
|
||||
The reply will contain a key "configurations", which will contain a list of
|
||||
configuration objects. Configuration objects are used to destinquish between
|
||||
different configurations the build directory might have enabled. While most
|
||||
generators only support one configuration, others might support several.
|
||||
|
||||
Each configuration object can have the following keys:
|
||||
|
||||
"name"
|
||||
contains the name of the configuration. The name may be empty.
|
||||
"projects"
|
||||
contains a list of project objects, one for each build project.
|
||||
|
||||
Project objects define one (sub-)project defined in the cmake build system.
|
||||
|
||||
Each project object can have the following keys:
|
||||
|
||||
"name"
|
||||
contains the (sub-)projects name.
|
||||
"minimumCMakeVersion"
|
||||
contains the minimum cmake version allowed for this project, null if the
|
||||
project doesn't specify one.
|
||||
"hasInstallRule"
|
||||
true if the project contains any install rules, false otherwise.
|
||||
"sourceDirectory"
|
||||
contains the current source directory
|
||||
"buildDirectory"
|
||||
contains the current build directory.
|
||||
"targets"
|
||||
contains a list of build system target objects.
|
||||
|
||||
Target objects define individual build targets for a certain configuration.
|
||||
|
||||
Each target object can have the following keys:
|
||||
|
||||
"name"
|
||||
contains the name of the target.
|
||||
"type"
|
||||
defines the type of build of the target. Possible values are
|
||||
"STATIC_LIBRARY", "MODULE_LIBRARY", "SHARED_LIBRARY", "OBJECT_LIBRARY",
|
||||
"EXECUTABLE", "UTILITY" and "INTERFACE_LIBRARY".
|
||||
"fullName"
|
||||
contains the full name of the build result (incl. extensions, etc.).
|
||||
"sourceDirectory"
|
||||
contains the current source directory.
|
||||
"buildDirectory"
|
||||
contains the current build directory.
|
||||
"isGeneratorProvided"
|
||||
true if the target is auto-created by a generator, false otherwise
|
||||
"hasInstallRule"
|
||||
true if the target contains any install rules, false otherwise.
|
||||
"installPaths"
|
||||
full path to the destination directories defined by target install rules.
|
||||
"artifacts"
|
||||
with a list of build artifacts. The list is sorted with the most
|
||||
important artifacts first (e.g. a .DLL file is listed before a
|
||||
.PDB file on windows).
|
||||
"linkerLanguage"
|
||||
contains the language of the linker used to produce the artifact.
|
||||
"linkLibraries"
|
||||
with a list of libraries to link to. This value is encoded in the
|
||||
system's native shell format.
|
||||
"linkFlags"
|
||||
with a list of flags to pass to the linker. This value is encoded in
|
||||
the system's native shell format.
|
||||
"linkLanguageFlags"
|
||||
with the flags for a compiler using the linkerLanguage. This value is
|
||||
encoded in the system's native shell format.
|
||||
"frameworkPath"
|
||||
with the framework path (on Apple computers). This value is encoded
|
||||
in the system's native shell format.
|
||||
"linkPath"
|
||||
with the link path. This value is encoded in the system's native shell
|
||||
format.
|
||||
"sysroot"
|
||||
with the sysroot path.
|
||||
"fileGroups"
|
||||
contains the source files making up the target.
|
||||
|
||||
FileGroups are used to group sources using similar settings together.
|
||||
|
||||
Each fileGroup object may contain the following keys:
|
||||
|
||||
"language"
|
||||
contains the programming language used by all files in the group.
|
||||
"compileFlags"
|
||||
with a string containing all the flags passed to the compiler
|
||||
when building any of the files in this group. This value is encoded in
|
||||
the system's native shell format.
|
||||
"includePath"
|
||||
with a list of include paths. Each include path is an object
|
||||
containing a "path" with the actual include path and "isSystem" with a bool
|
||||
value informing whether this is a normal include or a system include. This
|
||||
value is encoded in the system's native shell format.
|
||||
"defines"
|
||||
with a list of defines in the form "SOMEVALUE" or "SOMEVALUE=42". This
|
||||
value is encoded in the system's native shell format.
|
||||
"sources"
|
||||
with a list of source files.
|
||||
|
||||
All file paths in the fileGroup are either absolute or relative to the
|
||||
sourceDirectory of the target.
|
||||
|
||||
Example::
|
||||
|
||||
[== "CMake Server" ==[
|
||||
{"type":"codemodel"}
|
||||
]== "CMake Server" ==]
|
||||
|
||||
CMake will reply::
|
||||
|
||||
[== "CMake Server" ==[
|
||||
{
|
||||
"configurations": [
|
||||
{
|
||||
"name": "",
|
||||
"projects": [
|
||||
{
|
||||
"buildDirectory": "/tmp/build/Source/CursesDialog/form",
|
||||
"name": "CMAKE_FORM",
|
||||
"sourceDirectory": "/home/code/src/cmake/Source/CursesDialog/form",
|
||||
"targets": [
|
||||
{
|
||||
"artifacts": [ "/tmp/build/Source/CursesDialog/form/libcmForm.a" ],
|
||||
"buildDirectory": "/tmp/build/Source/CursesDialog/form",
|
||||
"fileGroups": [
|
||||
{
|
||||
"compileFlags": " -std=gnu11",
|
||||
"defines": [ "CURL_STATICLIB", "LIBARCHIVE_STATIC" ],
|
||||
"includePath": [ { "path": "/tmp/build/Utilities" }, <...> ],
|
||||
"isGenerated": false,
|
||||
"language": "C",
|
||||
"sources": [ "fld_arg.c", <...> ]
|
||||
}
|
||||
],
|
||||
"fullName": "libcmForm.a",
|
||||
"linkerLanguage": "C",
|
||||
"name": "cmForm",
|
||||
"sourceDirectory": "/home/code/src/cmake/Source/CursesDialog/form",
|
||||
"type": "STATIC_LIBRARY"
|
||||
}
|
||||
]
|
||||
},
|
||||
<...>
|
||||
]
|
||||
}
|
||||
],
|
||||
"cookie": "",
|
||||
"inReplyTo": "codemodel",
|
||||
"type": "reply"
|
||||
}
|
||||
]== "CMake Server" ==]
|
||||
|
||||
|
||||
Type "ctestInfo"
|
||||
^^^^^^^^^^^^^^^^
|
||||
|
||||
The "ctestInfo" request can be used after a project was "compute"d successfully.
|
||||
|
||||
It will list the complete project test structure as it is known to cmake.
|
||||
|
||||
The reply will contain a key "configurations", which will contain a list of
|
||||
configuration objects. Configuration objects are used to destinquish between
|
||||
different configurations the build directory might have enabled. While most
|
||||
generators only support one configuration, others might support several.
|
||||
|
||||
Each configuration object can have the following keys:
|
||||
|
||||
"name"
|
||||
contains the name of the configuration. The name may be empty.
|
||||
"projects"
|
||||
contains a list of project objects, one for each build project.
|
||||
|
||||
Project objects define one (sub-)project defined in the cmake build system.
|
||||
|
||||
Each project object can have the following keys:
|
||||
|
||||
"name"
|
||||
contains the (sub-)projects name.
|
||||
"ctestInfo"
|
||||
contains a list of test objects.
|
||||
|
||||
Each test object can have the following keys:
|
||||
|
||||
"ctestName"
|
||||
contains the name of the test.
|
||||
"ctestCommand"
|
||||
contains the test command.
|
||||
"properties"
|
||||
contains a list of test property objects.
|
||||
|
||||
Each test property object can have the following keys:
|
||||
|
||||
"key"
|
||||
contains the test property key.
|
||||
"value"
|
||||
contains the test property value.
|
||||
|
||||
|
||||
Type "cmakeInputs"
|
||||
^^^^^^^^^^^^^^^^^^
|
||||
|
||||
The "cmakeInputs" requests will report files used by CMake as part
|
||||
of the build system itself.
|
||||
|
||||
This request is only available after a project was successfully
|
||||
"configure"d.
|
||||
|
||||
Example::
|
||||
|
||||
[== "CMake Server" ==[
|
||||
{"type":"cmakeInputs"}
|
||||
]== "CMake Server" ==]
|
||||
|
||||
CMake will reply with the following information::
|
||||
|
||||
[== "CMake Server" ==[
|
||||
{"buildFiles":
|
||||
[
|
||||
{"isCMake":true,"isTemporary":false,"sources":["/usr/lib/cmake/...", ... ]},
|
||||
{"isCMake":false,"isTemporary":false,"sources":["CMakeLists.txt", ...]},
|
||||
{"isCMake":false,"isTemporary":true,"sources":["/tmp/build/CMakeFiles/...", ...]}
|
||||
],
|
||||
"cmakeRootDirectory":"/usr/lib/cmake",
|
||||
"sourceDirectory":"/home/code/src/cmake",
|
||||
"cookie":"",
|
||||
"inReplyTo":"cmakeInputs",
|
||||
"type":"reply"
|
||||
}
|
||||
]== "CMake Server" ==]
|
||||
|
||||
All file names are either relative to the top level source directory or
|
||||
absolute.
|
||||
|
||||
The list of files which "isCMake" set to true are part of the cmake installation.
|
||||
|
||||
The list of files witch "isTemporary" set to true are part of the build directory
|
||||
and will not survive the build directory getting cleaned out.
|
||||
|
||||
|
||||
Type "cache"
|
||||
^^^^^^^^^^^^
|
||||
|
||||
The "cache" request will list the cached configuration values.
|
||||
|
||||
Example::
|
||||
|
||||
[== "CMake Server" ==[
|
||||
{"type":"cache"}
|
||||
]== "CMake Server" ==]
|
||||
|
||||
CMake will respond with the following output::
|
||||
|
||||
[== "CMake Server" ==[
|
||||
{
|
||||
"cookie":"","inReplyTo":"cache","type":"reply",
|
||||
"cache":
|
||||
[
|
||||
{
|
||||
"key":"SOMEVALUE",
|
||||
"properties":
|
||||
{
|
||||
"ADVANCED":"1",
|
||||
"HELPSTRING":"This is not helpful"
|
||||
}
|
||||
"type":"STRING",
|
||||
"value":"TEST"}
|
||||
]
|
||||
}
|
||||
]== "CMake Server" ==]
|
||||
|
||||
The output can be limited to a list of keys by passing an array of key names
|
||||
to the "keys" optional field of the "cache" request.
|
||||
|
||||
|
||||
Type "fileSystemWatchers"
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
The server can watch the filesystem for changes. The "fileSystemWatchers"
|
||||
command will report on the files and directories watched.
|
||||
|
||||
Example::
|
||||
|
||||
[== "CMake Server" ==[
|
||||
{"type":"fileSystemWatchers"}
|
||||
]== "CMake Server" ==]
|
||||
|
||||
CMake will respond with the following output::
|
||||
|
||||
[== "CMake Server" ==[
|
||||
{
|
||||
"cookie":"","inReplyTo":"fileSystemWatchers","type":"reply",
|
||||
"watchedFiles": [ "/absolute/path" ],
|
||||
"watchedDirectories": [ "/absolute" ]
|
||||
}
|
||||
]== "CMake Server" ==]
|
||||
The :manual:`cmake(1)` server mode has been removed since CMake 3.20.
|
||||
Clients should use the :manual:`cmake-file-api(7)` instead.
|
||||
|
@@ -906,6 +906,7 @@ Available commands are:
|
||||
|
||||
``serverMode``
|
||||
``true`` if cmake supports server-mode and ``false`` otherwise.
|
||||
Always false since CMake 3.20.
|
||||
|
||||
``cat <files>...``
|
||||
Concatenate files and print on the standard output.
|
||||
|
5
Help/release/dev/remove-server-mode.rst
Normal file
5
Help/release/dev/remove-server-mode.rst
Normal file
@@ -0,0 +1,5 @@
|
||||
remove-server-mode
|
||||
------------------
|
||||
|
||||
* The :manual:`cmake-server(7)` mode has been removed.
|
||||
Clients should use the :manual:`cmake-file-api(7)` instead.
|
@@ -1156,20 +1156,6 @@ add_executable(cmake cmakemain.cxx cmcmd.cxx cmcmd.h ${MANIFEST_FILE})
|
||||
list(APPEND _tools cmake)
|
||||
target_link_libraries(cmake CMakeLib)
|
||||
|
||||
add_library(CMakeServerLib
|
||||
cmConnection.h cmConnection.cxx
|
||||
cmFileMonitor.cxx cmFileMonitor.h
|
||||
cmJsonObjectDictionary.h
|
||||
cmJsonObjects.h
|
||||
cmJsonObjects.cxx
|
||||
cmPipeConnection.cxx cmPipeConnection.h
|
||||
cmServer.cxx cmServer.h
|
||||
cmServerConnection.cxx cmServerConnection.h
|
||||
cmServerProtocol.cxx cmServerProtocol.h
|
||||
)
|
||||
target_link_libraries(CMakeServerLib CMakeLib)
|
||||
target_link_libraries(cmake CMakeServerLib)
|
||||
|
||||
# Build CTest executable
|
||||
add_executable(ctest ctest.cxx ${MANIFEST_FILE})
|
||||
list(APPEND _tools ctest)
|
||||
|
@@ -1,173 +0,0 @@
|
||||
/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
|
||||
file Copyright.txt or https://cmake.org/licensing for details. */
|
||||
#include "cmConnection.h"
|
||||
|
||||
#include <cassert>
|
||||
#include <cstring>
|
||||
|
||||
#include <cm3p/uv.h>
|
||||
|
||||
#include "cmServer.h"
|
||||
|
||||
struct write_req_t
|
||||
{
|
||||
uv_write_t req;
|
||||
uv_buf_t buf;
|
||||
};
|
||||
|
||||
void cmEventBasedConnection::on_alloc_buffer(uv_handle_t* handle,
|
||||
size_t suggested_size,
|
||||
uv_buf_t* buf)
|
||||
{
|
||||
(void)(handle);
|
||||
#ifndef __clang_analyzer__
|
||||
char* rawBuffer = new char[suggested_size];
|
||||
*buf = uv_buf_init(rawBuffer, static_cast<unsigned int>(suggested_size));
|
||||
#else
|
||||
(void)(suggested_size);
|
||||
(void)(buf);
|
||||
#endif /* __clang_analyzer__ */
|
||||
}
|
||||
|
||||
void cmEventBasedConnection::on_read(uv_stream_t* stream, ssize_t nread,
|
||||
const uv_buf_t* buf)
|
||||
{
|
||||
auto conn = static_cast<cmEventBasedConnection*>(stream->data);
|
||||
if (conn) {
|
||||
if (nread >= 0) {
|
||||
conn->ReadData(std::string(buf->base, buf->base + nread));
|
||||
} else {
|
||||
conn->OnDisconnect(static_cast<int>(nread));
|
||||
}
|
||||
}
|
||||
|
||||
delete[](buf->base);
|
||||
}
|
||||
|
||||
void cmEventBasedConnection::on_close(uv_handle_t* /*handle*/)
|
||||
{
|
||||
}
|
||||
|
||||
void cmEventBasedConnection::on_write(uv_write_t* req, int status)
|
||||
{
|
||||
(void)(status);
|
||||
|
||||
// Free req and buffer
|
||||
write_req_t* wr = reinterpret_cast<write_req_t*>(req);
|
||||
delete[](wr->buf.base);
|
||||
delete wr;
|
||||
}
|
||||
|
||||
void cmEventBasedConnection::on_new_connection(uv_stream_t* stream, int status)
|
||||
{
|
||||
(void)(status);
|
||||
auto conn = static_cast<cmEventBasedConnection*>(stream->data);
|
||||
|
||||
if (conn) {
|
||||
conn->Connect(stream);
|
||||
}
|
||||
}
|
||||
|
||||
bool cmEventBasedConnection::IsOpen() const
|
||||
{
|
||||
return this->WriteStream != nullptr;
|
||||
}
|
||||
|
||||
void cmEventBasedConnection::WriteData(const std::string& _data)
|
||||
{
|
||||
#ifndef NDEBUG
|
||||
auto curr_thread_id = uv_thread_self();
|
||||
assert(this->Server);
|
||||
assert(uv_thread_equal(&curr_thread_id, &this->Server->ServeThreadId));
|
||||
#endif
|
||||
|
||||
#ifndef __clang_analyzer__
|
||||
auto data = _data;
|
||||
assert(this->WriteStream.get());
|
||||
if (BufferStrategy) {
|
||||
data = BufferStrategy->BufferOutMessage(data);
|
||||
}
|
||||
|
||||
auto ds = data.size();
|
||||
|
||||
write_req_t* req = new write_req_t;
|
||||
req->req.data = this;
|
||||
req->buf = uv_buf_init(new char[ds], static_cast<unsigned int>(ds));
|
||||
memcpy(req->buf.base, data.c_str(), ds);
|
||||
uv_write(reinterpret_cast<uv_write_t*>(req), this->WriteStream, &req->buf, 1,
|
||||
on_write);
|
||||
#else
|
||||
(void)(_data);
|
||||
#endif /* __clang_analyzer__ */
|
||||
}
|
||||
|
||||
void cmEventBasedConnection::ReadData(const std::string& data)
|
||||
{
|
||||
this->RawReadBuffer += data;
|
||||
if (BufferStrategy) {
|
||||
std::string packet = BufferStrategy->BufferMessage(this->RawReadBuffer);
|
||||
while (!packet.empty()) {
|
||||
ProcessRequest(packet);
|
||||
packet = BufferStrategy->BufferMessage(this->RawReadBuffer);
|
||||
}
|
||||
} else {
|
||||
ProcessRequest(this->RawReadBuffer);
|
||||
this->RawReadBuffer.clear();
|
||||
}
|
||||
}
|
||||
|
||||
cmEventBasedConnection::cmEventBasedConnection(
|
||||
cmConnectionBufferStrategy* bufferStrategy)
|
||||
: BufferStrategy(bufferStrategy)
|
||||
{
|
||||
}
|
||||
|
||||
void cmEventBasedConnection::Connect(uv_stream_t* server)
|
||||
{
|
||||
(void)server;
|
||||
Server->OnConnected(nullptr);
|
||||
}
|
||||
|
||||
void cmEventBasedConnection::OnDisconnect(int onerror)
|
||||
{
|
||||
(void)onerror;
|
||||
this->OnConnectionShuttingDown();
|
||||
if (this->Server) {
|
||||
this->Server->OnDisconnect(this);
|
||||
}
|
||||
}
|
||||
|
||||
cmConnection::~cmConnection() = default;
|
||||
|
||||
bool cmConnection::OnConnectionShuttingDown()
|
||||
{
|
||||
this->Server = nullptr;
|
||||
return true;
|
||||
}
|
||||
|
||||
void cmConnection::SetServer(cmServerBase* s)
|
||||
{
|
||||
Server = s;
|
||||
}
|
||||
|
||||
void cmConnection::ProcessRequest(const std::string& request)
|
||||
{
|
||||
Server->ProcessRequest(this, request);
|
||||
}
|
||||
|
||||
bool cmConnection::OnServeStart(std::string* errString)
|
||||
{
|
||||
(void)errString;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool cmEventBasedConnection::OnConnectionShuttingDown()
|
||||
{
|
||||
if (this->WriteStream.get()) {
|
||||
this->WriteStream->data = nullptr;
|
||||
}
|
||||
|
||||
WriteStream.reset();
|
||||
|
||||
return true;
|
||||
}
|
@@ -1,137 +0,0 @@
|
||||
/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
|
||||
file Copyright.txt or https://cmake.org/licensing for details. */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "cmConfigure.h" // IWYU pragma: keep
|
||||
|
||||
#include <cstddef>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include <cm3p/uv.h>
|
||||
|
||||
#include "cmUVHandlePtr.h"
|
||||
|
||||
class cmServerBase;
|
||||
|
||||
/***
|
||||
* Given a sequence of bytes with any kind of buffering, instances of this
|
||||
* class arrange logical chunks according to whatever the use case is for
|
||||
* the connection.
|
||||
*/
|
||||
class cmConnectionBufferStrategy
|
||||
{
|
||||
public:
|
||||
virtual ~cmConnectionBufferStrategy();
|
||||
|
||||
/***
|
||||
* Called whenever with an active raw buffer. If a logical chunk
|
||||
* becomes available, that chunk is returned and that portion is
|
||||
* removed from the rawBuffer
|
||||
*
|
||||
* @param rawBuffer in/out parameter. Receive buffer; the buffer strategy is
|
||||
* free to manipulate this buffer anyway it needs to.
|
||||
*
|
||||
* @return Next chunk from the stream. Returns the empty string if a chunk
|
||||
* isn't ready yet. Users of this interface should repeatedly call this
|
||||
* function until an empty string is returned since its entirely possible
|
||||
* multiple chunks come in a single raw buffer.
|
||||
*/
|
||||
virtual std::string BufferMessage(std::string& rawBuffer) = 0;
|
||||
|
||||
/***
|
||||
* Called to properly buffer an outgoing message.
|
||||
*
|
||||
* @param rawBuffer Message to format in the correct way
|
||||
*
|
||||
* @return Formatted message
|
||||
*/
|
||||
virtual std::string BufferOutMessage(const std::string& rawBuffer) const
|
||||
{
|
||||
return rawBuffer;
|
||||
};
|
||||
/***
|
||||
* Resets the internal state of the buffering
|
||||
*/
|
||||
virtual void clear();
|
||||
|
||||
// TODO: There should be a callback / flag set for errors
|
||||
};
|
||||
|
||||
class cmConnection
|
||||
{
|
||||
public:
|
||||
cmConnection() = default;
|
||||
|
||||
cmConnection(cmConnection const&) = delete;
|
||||
cmConnection& operator=(cmConnection const&) = delete;
|
||||
|
||||
virtual void WriteData(const std::string& data) = 0;
|
||||
|
||||
virtual ~cmConnection();
|
||||
|
||||
virtual bool OnConnectionShuttingDown();
|
||||
|
||||
virtual bool IsOpen() const = 0;
|
||||
|
||||
virtual void SetServer(cmServerBase* s);
|
||||
|
||||
virtual void ProcessRequest(const std::string& request);
|
||||
|
||||
virtual bool OnServeStart(std::string* pString);
|
||||
|
||||
protected:
|
||||
cmServerBase* Server = nullptr;
|
||||
};
|
||||
|
||||
/***
|
||||
* Abstraction of a connection; ties in event callbacks from libuv and notifies
|
||||
* the server when appropriate
|
||||
*/
|
||||
class cmEventBasedConnection : public cmConnection
|
||||
{
|
||||
|
||||
public:
|
||||
/***
|
||||
* @param bufferStrategy If no strategy is given, it will process the raw
|
||||
* chunks as they come in. The connection
|
||||
* owns the pointer given.
|
||||
*/
|
||||
cmEventBasedConnection(cmConnectionBufferStrategy* bufferStrategy = nullptr);
|
||||
|
||||
virtual void Connect(uv_stream_t* server);
|
||||
|
||||
virtual void ReadData(const std::string& data);
|
||||
|
||||
bool IsOpen() const override;
|
||||
|
||||
void WriteData(const std::string& data) override;
|
||||
bool OnConnectionShuttingDown() override;
|
||||
|
||||
virtual void OnDisconnect(int errorCode);
|
||||
|
||||
static void on_close(uv_handle_t* handle);
|
||||
|
||||
template <typename T>
|
||||
static void on_close_delete(uv_handle_t* handle)
|
||||
{
|
||||
delete reinterpret_cast<T*>(handle);
|
||||
}
|
||||
|
||||
protected:
|
||||
cm::uv_stream_ptr WriteStream;
|
||||
|
||||
std::string RawReadBuffer;
|
||||
|
||||
std::unique_ptr<cmConnectionBufferStrategy> BufferStrategy;
|
||||
|
||||
static void on_read(uv_stream_t* stream, ssize_t nread, const uv_buf_t* buf);
|
||||
|
||||
static void on_write(uv_write_t* req, int status);
|
||||
|
||||
static void on_new_connection(uv_stream_t* stream, int status);
|
||||
|
||||
static void on_alloc_buffer(uv_handle_t* handle, size_t suggested_size,
|
||||
uv_buf_t* buf);
|
||||
};
|
@@ -1,383 +0,0 @@
|
||||
/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
|
||||
file Copyright.txt or https://cmake.org/licensing for details. */
|
||||
#include "cmFileMonitor.h"
|
||||
|
||||
#include <cassert>
|
||||
#include <cstddef>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
|
||||
#include <cm/memory>
|
||||
|
||||
#include "cmsys/SystemTools.hxx"
|
||||
|
||||
namespace {
|
||||
void on_directory_change(uv_fs_event_t* handle, const char* filename,
|
||||
int events, int status);
|
||||
void on_fs_close(uv_handle_t* handle);
|
||||
} // namespace
|
||||
|
||||
class cmIBaseWatcher
|
||||
{
|
||||
public:
|
||||
virtual ~cmIBaseWatcher() = default;
|
||||
|
||||
virtual void Trigger(const std::string& pathSegment, int events,
|
||||
int status) const = 0;
|
||||
virtual std::string Path() const = 0;
|
||||
virtual uv_loop_t* Loop() const = 0;
|
||||
|
||||
virtual void StartWatching() = 0;
|
||||
virtual void StopWatching() = 0;
|
||||
|
||||
virtual std::vector<std::string> WatchedFiles() const = 0;
|
||||
virtual std::vector<std::string> WatchedDirectories() const = 0;
|
||||
};
|
||||
|
||||
class cmVirtualDirectoryWatcher : public cmIBaseWatcher
|
||||
{
|
||||
public:
|
||||
~cmVirtualDirectoryWatcher() override = default;
|
||||
|
||||
cmIBaseWatcher* Find(const std::string& ps)
|
||||
{
|
||||
const auto i = this->Children.find(ps);
|
||||
return (i == this->Children.end()) ? nullptr : i->second.get();
|
||||
}
|
||||
|
||||
void Trigger(const std::string& pathSegment, int events,
|
||||
int status) const final
|
||||
{
|
||||
if (pathSegment.empty()) {
|
||||
for (auto const& child : this->Children) {
|
||||
child.second->Trigger(std::string(), events, status);
|
||||
}
|
||||
} else {
|
||||
const auto i = this->Children.find(pathSegment);
|
||||
if (i != this->Children.end()) {
|
||||
i->second->Trigger(std::string(), events, status);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void StartWatching() override
|
||||
{
|
||||
for (auto const& child : this->Children) {
|
||||
child.second->StartWatching();
|
||||
}
|
||||
}
|
||||
|
||||
void StopWatching() override
|
||||
{
|
||||
for (auto const& child : this->Children) {
|
||||
child.second->StopWatching();
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::string> WatchedFiles() const final
|
||||
{
|
||||
std::vector<std::string> result;
|
||||
for (auto const& child : this->Children) {
|
||||
for (std::string const& f : child.second->WatchedFiles()) {
|
||||
result.push_back(f);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<std::string> WatchedDirectories() const override
|
||||
{
|
||||
std::vector<std::string> result;
|
||||
for (auto const& child : this->Children) {
|
||||
for (std::string const& dir : child.second->WatchedDirectories()) {
|
||||
result.push_back(dir);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void Reset() { this->Children.clear(); }
|
||||
|
||||
void AddChildWatcher(const std::string& ps, cmIBaseWatcher* watcher)
|
||||
{
|
||||
assert(!ps.empty());
|
||||
assert(this->Children.find(ps) == this->Children.end());
|
||||
assert(watcher);
|
||||
|
||||
this->Children.emplace(ps, std::unique_ptr<cmIBaseWatcher>(watcher));
|
||||
}
|
||||
|
||||
private:
|
||||
std::unordered_map<std::string, std::unique_ptr<cmIBaseWatcher>>
|
||||
Children; // owned!
|
||||
};
|
||||
|
||||
// Root of all the different (on windows!) root directories:
|
||||
class cmRootWatcher : public cmVirtualDirectoryWatcher
|
||||
{
|
||||
public:
|
||||
cmRootWatcher(uv_loop_t* loop)
|
||||
: mLoop(loop)
|
||||
{
|
||||
assert(loop);
|
||||
}
|
||||
|
||||
std::string Path() const final
|
||||
{
|
||||
assert(false);
|
||||
return std::string();
|
||||
}
|
||||
uv_loop_t* Loop() const final { return this->mLoop; }
|
||||
|
||||
private:
|
||||
uv_loop_t* const mLoop; // no ownership!
|
||||
};
|
||||
|
||||
// Real directories:
|
||||
class cmRealDirectoryWatcher : public cmVirtualDirectoryWatcher
|
||||
{
|
||||
public:
|
||||
cmRealDirectoryWatcher(cmVirtualDirectoryWatcher* p, const std::string& ps)
|
||||
: Parent(p)
|
||||
, PathSegment(ps)
|
||||
{
|
||||
assert(p);
|
||||
assert(!ps.empty());
|
||||
|
||||
p->AddChildWatcher(ps, this);
|
||||
}
|
||||
|
||||
void StartWatching() final
|
||||
{
|
||||
if (!this->Handle) {
|
||||
this->Handle = new uv_fs_event_t;
|
||||
|
||||
uv_fs_event_init(this->Loop(), this->Handle);
|
||||
this->Handle->data = this;
|
||||
uv_fs_event_start(this->Handle, &on_directory_change, Path().c_str(), 0);
|
||||
}
|
||||
cmVirtualDirectoryWatcher::StartWatching();
|
||||
}
|
||||
|
||||
void StopWatching() final
|
||||
{
|
||||
if (this->Handle) {
|
||||
uv_fs_event_stop(this->Handle);
|
||||
if (!uv_is_closing(reinterpret_cast<uv_handle_t*>(this->Handle))) {
|
||||
uv_close(reinterpret_cast<uv_handle_t*>(this->Handle), &on_fs_close);
|
||||
}
|
||||
this->Handle = nullptr;
|
||||
}
|
||||
cmVirtualDirectoryWatcher::StopWatching();
|
||||
}
|
||||
|
||||
uv_loop_t* Loop() const final { return this->Parent->Loop(); }
|
||||
|
||||
std::vector<std::string> WatchedDirectories() const override
|
||||
{
|
||||
std::vector<std::string> result = { Path() };
|
||||
for (std::string const& dir :
|
||||
cmVirtualDirectoryWatcher::WatchedDirectories()) {
|
||||
result.push_back(dir);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
protected:
|
||||
cmVirtualDirectoryWatcher* const Parent;
|
||||
const std::string PathSegment;
|
||||
|
||||
private:
|
||||
uv_fs_event_t* Handle = nullptr; // owner!
|
||||
};
|
||||
|
||||
// Root directories:
|
||||
class cmRootDirectoryWatcher : public cmRealDirectoryWatcher
|
||||
{
|
||||
public:
|
||||
cmRootDirectoryWatcher(cmRootWatcher* p, const std::string& ps)
|
||||
: cmRealDirectoryWatcher(p, ps)
|
||||
{
|
||||
}
|
||||
|
||||
std::string Path() const final { return this->PathSegment; }
|
||||
};
|
||||
|
||||
// Normal directories below root:
|
||||
class cmDirectoryWatcher : public cmRealDirectoryWatcher
|
||||
{
|
||||
public:
|
||||
cmDirectoryWatcher(cmRealDirectoryWatcher* p, const std::string& ps)
|
||||
: cmRealDirectoryWatcher(p, ps)
|
||||
{
|
||||
}
|
||||
|
||||
std::string Path() const final
|
||||
{
|
||||
return this->Parent->Path() + this->PathSegment + "/";
|
||||
}
|
||||
};
|
||||
|
||||
class cmFileWatcher : public cmIBaseWatcher
|
||||
{
|
||||
public:
|
||||
cmFileWatcher(cmRealDirectoryWatcher* p, const std::string& ps,
|
||||
cmFileMonitor::Callback cb)
|
||||
: Parent(p)
|
||||
, PathSegment(ps)
|
||||
, CbList({ std::move(cb) })
|
||||
{
|
||||
assert(p);
|
||||
assert(!ps.empty());
|
||||
p->AddChildWatcher(ps, this);
|
||||
}
|
||||
|
||||
void StartWatching() final {}
|
||||
|
||||
void StopWatching() final {}
|
||||
|
||||
void AppendCallback(cmFileMonitor::Callback const& cb)
|
||||
{
|
||||
this->CbList.push_back(cb);
|
||||
}
|
||||
|
||||
std::string Path() const final
|
||||
{
|
||||
return this->Parent->Path() + this->PathSegment;
|
||||
}
|
||||
|
||||
std::vector<std::string> WatchedDirectories() const final { return {}; }
|
||||
|
||||
std::vector<std::string> WatchedFiles() const final
|
||||
{
|
||||
return { this->Path() };
|
||||
}
|
||||
|
||||
void Trigger(const std::string& ps, int events, int status) const final
|
||||
{
|
||||
assert(ps.empty());
|
||||
assert(status == 0);
|
||||
static_cast<void>(ps);
|
||||
|
||||
const std::string path = this->Path();
|
||||
for (cmFileMonitor::Callback const& cb : this->CbList) {
|
||||
cb(path, events, status);
|
||||
}
|
||||
}
|
||||
|
||||
uv_loop_t* Loop() const final { return this->Parent->Loop(); }
|
||||
|
||||
private:
|
||||
cmRealDirectoryWatcher* Parent;
|
||||
const std::string PathSegment;
|
||||
std::vector<cmFileMonitor::Callback> CbList;
|
||||
};
|
||||
|
||||
namespace {
|
||||
|
||||
void on_directory_change(uv_fs_event_t* handle, const char* filename,
|
||||
int events, int status)
|
||||
{
|
||||
const cmIBaseWatcher* const watcher =
|
||||
static_cast<const cmIBaseWatcher*>(handle->data);
|
||||
const std::string pathSegment(filename ? filename : "");
|
||||
watcher->Trigger(pathSegment, events, status);
|
||||
}
|
||||
|
||||
void on_fs_close(uv_handle_t* handle)
|
||||
{
|
||||
delete reinterpret_cast<uv_fs_event_t*>(handle);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
cmFileMonitor::cmFileMonitor(uv_loop_t* l)
|
||||
: Root(cm::make_unique<cmRootWatcher>(l))
|
||||
{
|
||||
}
|
||||
|
||||
cmFileMonitor::~cmFileMonitor() = default;
|
||||
|
||||
void cmFileMonitor::MonitorPaths(const std::vector<std::string>& paths,
|
||||
Callback const& cb)
|
||||
{
|
||||
for (std::string const& p : paths) {
|
||||
std::vector<std::string> pathSegments;
|
||||
cmsys::SystemTools::SplitPath(p, pathSegments, true);
|
||||
const bool pathIsFile = !cmsys::SystemTools::FileIsDirectory(p);
|
||||
|
||||
const size_t segmentCount = pathSegments.size();
|
||||
if (segmentCount < 2) { // Expect at least rootdir and filename
|
||||
continue;
|
||||
}
|
||||
cmVirtualDirectoryWatcher* currentWatcher = this->Root.get();
|
||||
for (size_t i = 0; i < segmentCount; ++i) {
|
||||
assert(currentWatcher);
|
||||
|
||||
const bool fileSegment = (i == segmentCount - 1 && pathIsFile);
|
||||
const bool rootSegment = (i == 0);
|
||||
assert(
|
||||
!(fileSegment &&
|
||||
rootSegment)); // Can not be both filename and root part of the path!
|
||||
|
||||
const std::string& currentSegment = pathSegments[i];
|
||||
if (currentSegment.empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
cmIBaseWatcher* nextWatcher = currentWatcher->Find(currentSegment);
|
||||
if (!nextWatcher) {
|
||||
if (rootSegment) { // Root part
|
||||
assert(currentWatcher == this->Root.get());
|
||||
nextWatcher =
|
||||
new cmRootDirectoryWatcher(this->Root.get(), currentSegment);
|
||||
assert(currentWatcher->Find(currentSegment) == nextWatcher);
|
||||
} else if (fileSegment) { // File part
|
||||
assert(currentWatcher != this->Root.get());
|
||||
nextWatcher = new cmFileWatcher(
|
||||
dynamic_cast<cmRealDirectoryWatcher*>(currentWatcher),
|
||||
currentSegment, cb);
|
||||
assert(currentWatcher->Find(currentSegment) == nextWatcher);
|
||||
} else { // Any normal directory in between
|
||||
nextWatcher = new cmDirectoryWatcher(
|
||||
dynamic_cast<cmRealDirectoryWatcher*>(currentWatcher),
|
||||
currentSegment);
|
||||
assert(currentWatcher->Find(currentSegment) == nextWatcher);
|
||||
}
|
||||
} else {
|
||||
if (fileSegment) {
|
||||
auto filePtr = dynamic_cast<cmFileWatcher*>(nextWatcher);
|
||||
assert(filePtr);
|
||||
filePtr->AppendCallback(cb);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
currentWatcher = dynamic_cast<cmVirtualDirectoryWatcher*>(nextWatcher);
|
||||
}
|
||||
}
|
||||
this->Root->StartWatching();
|
||||
}
|
||||
|
||||
void cmFileMonitor::StopMonitoring()
|
||||
{
|
||||
this->Root->StopWatching();
|
||||
this->Root->Reset();
|
||||
}
|
||||
|
||||
std::vector<std::string> cmFileMonitor::WatchedFiles() const
|
||||
{
|
||||
std::vector<std::string> result;
|
||||
if (this->Root) {
|
||||
result = this->Root->WatchedFiles();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<std::string> cmFileMonitor::WatchedDirectories() const
|
||||
{
|
||||
std::vector<std::string> result;
|
||||
if (this->Root) {
|
||||
result = this->Root->WatchedDirectories();
|
||||
}
|
||||
return result;
|
||||
}
|
@@ -1,35 +0,0 @@
|
||||
/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
|
||||
file Copyright.txt or https://cmake.org/licensing for details. */
|
||||
#pragma once
|
||||
|
||||
#include "cmConfigure.h" // IWYU pragma: keep
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <cm3p/uv.h>
|
||||
|
||||
class cmRootWatcher;
|
||||
|
||||
class cmFileMonitor
|
||||
{
|
||||
|
||||
public:
|
||||
cmFileMonitor(uv_loop_t* l);
|
||||
~cmFileMonitor();
|
||||
|
||||
cmFileMonitor(cmFileMonitor const&) = delete;
|
||||
cmFileMonitor& operator=(cmFileMonitor const&) = delete;
|
||||
|
||||
using Callback = std::function<void(const std::string&, int, int)>;
|
||||
void MonitorPaths(const std::vector<std::string>& paths, Callback const& cb);
|
||||
void StopMonitoring();
|
||||
|
||||
std::vector<std::string> WatchedFiles() const;
|
||||
std::vector<std::string> WatchedDirectories() const;
|
||||
|
||||
private:
|
||||
std::unique_ptr<cmRootWatcher> Root;
|
||||
};
|
@@ -1,45 +0,0 @@
|
||||
/* 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>
|
||||
|
||||
// Vocabulary:
|
||||
|
||||
static const std::string kARTIFACTS_KEY = "artifacts";
|
||||
static const std::string kBUILD_DIRECTORY_KEY = "buildDirectory";
|
||||
static const std::string kCOMPILE_FLAGS_KEY = "compileFlags";
|
||||
static const std::string kCONFIGURATIONS_KEY = "configurations";
|
||||
static const std::string kDEFINES_KEY = "defines";
|
||||
static const std::string kFILE_GROUPS_KEY = "fileGroups";
|
||||
static const std::string kFRAMEWORK_PATH_KEY = "frameworkPath";
|
||||
static const std::string kFULL_NAME_KEY = "fullName";
|
||||
static const std::string kINCLUDE_PATH_KEY = "includePath";
|
||||
static const std::string kIS_CMAKE_KEY = "isCMake";
|
||||
static const std::string kIS_GENERATED_KEY = "isGenerated";
|
||||
static const std::string kIS_SYSTEM_KEY = "isSystem";
|
||||
static const std::string kIS_TEMPORARY_KEY = "isTemporary";
|
||||
static const std::string kKEY_KEY = "key";
|
||||
static const std::string kLANGUAGE_KEY = "language";
|
||||
static const std::string kLINKER_LANGUAGE_KEY = "linkerLanguage";
|
||||
static const std::string kLINK_FLAGS_KEY = "linkFlags";
|
||||
static const std::string kLINK_LANGUAGE_FLAGS_KEY = "linkLanguageFlags";
|
||||
static const std::string kLINK_LIBRARIES_KEY = "linkLibraries";
|
||||
static const std::string kLINK_PATH_KEY = "linkPath";
|
||||
static const std::string kNAME_KEY = "name";
|
||||
static const std::string kPATH_KEY = "path";
|
||||
static const std::string kPROJECTS_KEY = "projects";
|
||||
static const std::string kPROPERTIES_KEY = "properties";
|
||||
static const std::string kSOURCE_DIRECTORY_KEY = "sourceDirectory";
|
||||
static const std::string kSOURCES_KEY = "sources";
|
||||
static const std::string kSYSROOT_KEY = "sysroot";
|
||||
static const std::string kTARGETS_KEY = "targets";
|
||||
static const std::string kTYPE_KEY = "type";
|
||||
static const std::string kVALUE_KEY = "value";
|
||||
static const std::string kHAS_INSTALL_RULE = "hasInstallRule";
|
||||
static const std::string kINSTALL_PATHS = "installPaths";
|
||||
static const std::string kCTEST_NAME = "ctestName";
|
||||
static const std::string kCTEST_COMMAND = "ctestCommand";
|
||||
static const std::string kCTEST_INFO = "ctestInfo";
|
||||
static const std::string kMINIMUM_CMAKE_VERSION = "minimumCMakeVersion";
|
||||
static const std::string kIS_GENERATOR_PROVIDED_KEY = "isGeneratorProvided";
|
@@ -1,692 +0,0 @@
|
||||
/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
|
||||
file Copyright.txt or https://cmake.org/licensing for details. */
|
||||
#include "cmJsonObjects.h" // IWYU pragma: keep
|
||||
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <cstddef>
|
||||
#include <functional>
|
||||
#include <limits>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <cmext/algorithm>
|
||||
|
||||
#include "cmGeneratorExpression.h"
|
||||
#include "cmGeneratorTarget.h"
|
||||
#include "cmGlobalGenerator.h"
|
||||
#include "cmInstallGenerator.h"
|
||||
#include "cmInstallSubdirectoryGenerator.h"
|
||||
#include "cmInstallTargetGenerator.h"
|
||||
#include "cmJsonObjectDictionary.h"
|
||||
#include "cmJsonObjects.h"
|
||||
#include "cmLinkLineComputer.h"
|
||||
#include "cmLocalGenerator.h"
|
||||
#include "cmMakefile.h"
|
||||
#include "cmProperty.h"
|
||||
#include "cmPropertyMap.h"
|
||||
#include "cmSourceFile.h"
|
||||
#include "cmState.h"
|
||||
#include "cmStateDirectory.h"
|
||||
#include "cmStateSnapshot.h"
|
||||
#include "cmStateTypes.h"
|
||||
#include "cmStringAlgorithms.h"
|
||||
#include "cmSystemTools.h"
|
||||
#include "cmTarget.h"
|
||||
#include "cmTest.h"
|
||||
#include "cmake.h"
|
||||
|
||||
namespace {
|
||||
|
||||
std::vector<std::string> getConfigurations(const cmake* cm)
|
||||
{
|
||||
std::vector<std::string> configurations;
|
||||
const auto& makefiles = cm->GetGlobalGenerator()->GetMakefiles();
|
||||
if (makefiles.empty()) {
|
||||
return configurations;
|
||||
}
|
||||
|
||||
return makefiles[0]->GetGeneratorConfigs(cmMakefile::IncludeEmptyConfig);
|
||||
}
|
||||
|
||||
bool hasString(const Json::Value& v, const std::string& s)
|
||||
{
|
||||
return !v.isNull() &&
|
||||
std::any_of(v.begin(), v.end(),
|
||||
[s](const Json::Value& i) { return i.asString() == s; });
|
||||
}
|
||||
|
||||
template <class T>
|
||||
Json::Value fromStringList(const T& in)
|
||||
{
|
||||
Json::Value result = Json::arrayValue;
|
||||
for (std::string const& i : in) {
|
||||
result.append(i);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void cmGetCMakeInputs(const cmGlobalGenerator* gg,
|
||||
const std::string& sourceDir,
|
||||
const std::string& buildDir,
|
||||
std::vector<std::string>* internalFiles,
|
||||
std::vector<std::string>* explicitFiles,
|
||||
std::vector<std::string>* tmpFiles)
|
||||
{
|
||||
const std::string cmakeRootDir = cmSystemTools::GetCMakeRoot() + '/';
|
||||
auto const& makefiles = gg->GetMakefiles();
|
||||
for (const auto& mf : makefiles) {
|
||||
for (std::string const& lf : mf->GetListFiles()) {
|
||||
|
||||
const std::string startOfFile = lf.substr(0, cmakeRootDir.size());
|
||||
const bool isInternal = (startOfFile == cmakeRootDir);
|
||||
const bool isTemporary =
|
||||
!isInternal && (cmHasPrefix(lf, buildDir + '/'));
|
||||
|
||||
std::string toAdd = lf;
|
||||
if (!sourceDir.empty()) {
|
||||
const std::string& relative =
|
||||
cmSystemTools::RelativePath(sourceDir, lf);
|
||||
if (toAdd.size() > relative.size()) {
|
||||
toAdd = relative;
|
||||
}
|
||||
}
|
||||
|
||||
if (isInternal) {
|
||||
if (internalFiles) {
|
||||
internalFiles->push_back(std::move(toAdd));
|
||||
}
|
||||
} else {
|
||||
if (isTemporary) {
|
||||
if (tmpFiles) {
|
||||
tmpFiles->push_back(std::move(toAdd));
|
||||
}
|
||||
} else {
|
||||
if (explicitFiles) {
|
||||
explicitFiles->push_back(std::move(toAdd));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Json::Value cmDumpCMakeInputs(const cmake* cm)
|
||||
{
|
||||
const cmGlobalGenerator* gg = cm->GetGlobalGenerator();
|
||||
const std::string& buildDir = cm->GetHomeOutputDirectory();
|
||||
const std::string& sourceDir = cm->GetHomeDirectory();
|
||||
|
||||
std::vector<std::string> internalFiles;
|
||||
std::vector<std::string> explicitFiles;
|
||||
std::vector<std::string> tmpFiles;
|
||||
cmGetCMakeInputs(gg, sourceDir, buildDir, &internalFiles, &explicitFiles,
|
||||
&tmpFiles);
|
||||
|
||||
Json::Value array = Json::arrayValue;
|
||||
|
||||
Json::Value tmp = Json::objectValue;
|
||||
tmp[kIS_CMAKE_KEY] = true;
|
||||
tmp[kIS_TEMPORARY_KEY] = false;
|
||||
tmp[kSOURCES_KEY] = fromStringList(internalFiles);
|
||||
array.append(tmp);
|
||||
|
||||
tmp = Json::objectValue;
|
||||
tmp[kIS_CMAKE_KEY] = false;
|
||||
tmp[kIS_TEMPORARY_KEY] = false;
|
||||
tmp[kSOURCES_KEY] = fromStringList(explicitFiles);
|
||||
array.append(tmp);
|
||||
|
||||
tmp = Json::objectValue;
|
||||
tmp[kIS_CMAKE_KEY] = false;
|
||||
tmp[kIS_TEMPORARY_KEY] = true;
|
||||
tmp[kSOURCES_KEY] = fromStringList(tmpFiles);
|
||||
array.append(tmp);
|
||||
|
||||
return array;
|
||||
}
|
||||
|
||||
class LanguageData
|
||||
{
|
||||
public:
|
||||
bool operator==(const LanguageData& other) const;
|
||||
|
||||
void SetDefines(const std::set<std::string>& defines);
|
||||
|
||||
bool IsGenerated = false;
|
||||
std::string Language;
|
||||
std::string Flags;
|
||||
std::vector<std::string> Defines;
|
||||
std::vector<std::pair<std::string, bool>> IncludePathList;
|
||||
};
|
||||
|
||||
bool LanguageData::operator==(const LanguageData& other) const
|
||||
{
|
||||
return Language == other.Language && Defines == other.Defines &&
|
||||
Flags == other.Flags && IncludePathList == other.IncludePathList &&
|
||||
IsGenerated == other.IsGenerated;
|
||||
}
|
||||
|
||||
void LanguageData::SetDefines(const std::set<std::string>& defines)
|
||||
{
|
||||
std::vector<std::string> result;
|
||||
result.reserve(defines.size());
|
||||
for (std::string const& i : defines) {
|
||||
result.push_back(i);
|
||||
}
|
||||
std::sort(result.begin(), result.end());
|
||||
Defines = std::move(result);
|
||||
}
|
||||
|
||||
namespace std {
|
||||
|
||||
template <>
|
||||
struct hash<LanguageData>
|
||||
{
|
||||
std::size_t operator()(const LanguageData& in) const
|
||||
{
|
||||
using std::hash;
|
||||
size_t result =
|
||||
hash<std::string>()(in.Language) ^ hash<std::string>()(in.Flags);
|
||||
for (auto const& i : in.IncludePathList) {
|
||||
result = result ^
|
||||
(hash<std::string>()(i.first) ^
|
||||
(i.second ? std::numeric_limits<size_t>::max() : 0));
|
||||
}
|
||||
for (auto const& i : in.Defines) {
|
||||
result = result ^ hash<std::string>()(i);
|
||||
}
|
||||
result =
|
||||
result ^ (in.IsGenerated ? std::numeric_limits<size_t>::max() : 0);
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace std
|
||||
|
||||
static Json::Value DumpSourceFileGroup(const LanguageData& data,
|
||||
const std::vector<std::string>& files,
|
||||
const std::string& baseDir)
|
||||
{
|
||||
Json::Value result = Json::objectValue;
|
||||
|
||||
if (!data.Language.empty()) {
|
||||
result[kLANGUAGE_KEY] = data.Language;
|
||||
if (!data.Flags.empty()) {
|
||||
result[kCOMPILE_FLAGS_KEY] = data.Flags;
|
||||
}
|
||||
if (!data.IncludePathList.empty()) {
|
||||
Json::Value includes = Json::arrayValue;
|
||||
for (auto const& i : data.IncludePathList) {
|
||||
Json::Value tmp = Json::objectValue;
|
||||
tmp[kPATH_KEY] = i.first;
|
||||
if (i.second) {
|
||||
tmp[kIS_SYSTEM_KEY] = i.second;
|
||||
}
|
||||
includes.append(tmp);
|
||||
}
|
||||
result[kINCLUDE_PATH_KEY] = includes;
|
||||
}
|
||||
if (!data.Defines.empty()) {
|
||||
result[kDEFINES_KEY] = fromStringList(data.Defines);
|
||||
}
|
||||
}
|
||||
|
||||
result[kIS_GENERATED_KEY] = data.IsGenerated;
|
||||
|
||||
Json::Value sourcesValue = Json::arrayValue;
|
||||
for (auto const& i : files) {
|
||||
const std::string relPath = cmSystemTools::RelativePath(baseDir, i);
|
||||
sourcesValue.append(relPath.size() < i.size() ? relPath : i);
|
||||
}
|
||||
|
||||
result[kSOURCES_KEY] = sourcesValue;
|
||||
return result;
|
||||
}
|
||||
|
||||
static Json::Value DumpSourceFilesList(
|
||||
cmGeneratorTarget* target, const std::string& config,
|
||||
const std::map<std::string, LanguageData>& languageDataMap)
|
||||
{
|
||||
// Collect sourcefile groups:
|
||||
|
||||
std::vector<cmSourceFile*> files;
|
||||
target->GetSourceFiles(files, config);
|
||||
|
||||
std::unordered_map<LanguageData, std::vector<std::string>> fileGroups;
|
||||
for (cmSourceFile* file : files) {
|
||||
LanguageData fileData;
|
||||
fileData.Language = file->GetOrDetermineLanguage();
|
||||
if (!fileData.Language.empty()) {
|
||||
const LanguageData& ld = languageDataMap.at(fileData.Language);
|
||||
cmLocalGenerator* lg = target->GetLocalGenerator();
|
||||
cmGeneratorExpressionInterpreter genexInterpreter(lg, config, target,
|
||||
fileData.Language);
|
||||
|
||||
std::string compileFlags = ld.Flags;
|
||||
const std::string COMPILE_FLAGS("COMPILE_FLAGS");
|
||||
if (cmProp cflags = file->GetProperty(COMPILE_FLAGS)) {
|
||||
lg->AppendFlags(compileFlags,
|
||||
genexInterpreter.Evaluate(*cflags, COMPILE_FLAGS));
|
||||
}
|
||||
const std::string COMPILE_OPTIONS("COMPILE_OPTIONS");
|
||||
if (cmProp coptions = file->GetProperty(COMPILE_OPTIONS)) {
|
||||
lg->AppendCompileOptions(
|
||||
compileFlags, genexInterpreter.Evaluate(*coptions, COMPILE_OPTIONS));
|
||||
}
|
||||
fileData.Flags = compileFlags;
|
||||
|
||||
// Add include directories from source file properties.
|
||||
std::vector<std::string> includes;
|
||||
|
||||
const std::string INCLUDE_DIRECTORIES("INCLUDE_DIRECTORIES");
|
||||
if (cmProp cincludes = file->GetProperty(INCLUDE_DIRECTORIES)) {
|
||||
const std::string& evaluatedIncludes =
|
||||
genexInterpreter.Evaluate(*cincludes, INCLUDE_DIRECTORIES);
|
||||
lg->AppendIncludeDirectories(includes, evaluatedIncludes, *file);
|
||||
|
||||
for (const auto& include : includes) {
|
||||
fileData.IncludePathList.emplace_back(
|
||||
include,
|
||||
target->IsSystemIncludeDirectory(include, config,
|
||||
fileData.Language));
|
||||
}
|
||||
}
|
||||
|
||||
fileData.IncludePathList.insert(fileData.IncludePathList.end(),
|
||||
ld.IncludePathList.begin(),
|
||||
ld.IncludePathList.end());
|
||||
|
||||
const std::string COMPILE_DEFINITIONS("COMPILE_DEFINITIONS");
|
||||
std::set<std::string> defines;
|
||||
if (cmProp defs = file->GetProperty(COMPILE_DEFINITIONS)) {
|
||||
lg->AppendDefines(
|
||||
defines, genexInterpreter.Evaluate(*defs, COMPILE_DEFINITIONS));
|
||||
}
|
||||
|
||||
const std::string defPropName =
|
||||
"COMPILE_DEFINITIONS_" + cmSystemTools::UpperCase(config);
|
||||
if (cmProp config_defs = file->GetProperty(defPropName)) {
|
||||
lg->AppendDefines(
|
||||
defines,
|
||||
genexInterpreter.Evaluate(*config_defs, COMPILE_DEFINITIONS));
|
||||
}
|
||||
|
||||
defines.insert(ld.Defines.begin(), ld.Defines.end());
|
||||
|
||||
fileData.SetDefines(defines);
|
||||
}
|
||||
|
||||
fileData.IsGenerated = file->GetIsGenerated();
|
||||
std::vector<std::string>& groupFileList = fileGroups[fileData];
|
||||
groupFileList.push_back(file->ResolveFullPath());
|
||||
}
|
||||
|
||||
const std::string& baseDir = target->Makefile->GetCurrentSourceDirectory();
|
||||
Json::Value result = Json::arrayValue;
|
||||
for (auto const& it : fileGroups) {
|
||||
Json::Value group = DumpSourceFileGroup(it.first, it.second, baseDir);
|
||||
if (!group.isNull()) {
|
||||
result.append(group);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static Json::Value DumpCTestInfo(cmLocalGenerator* lg, cmTest* testInfo,
|
||||
const std::string& config)
|
||||
{
|
||||
Json::Value result = Json::objectValue;
|
||||
result[kCTEST_NAME] = testInfo->GetName();
|
||||
|
||||
// Concat command entries together. After the first should be the arguments
|
||||
// for the command
|
||||
std::string command;
|
||||
for (auto const& cmd : testInfo->GetCommand()) {
|
||||
command.append(cmd);
|
||||
command.append(" ");
|
||||
}
|
||||
|
||||
// Remove any config specific variables from the output.
|
||||
result[kCTEST_COMMAND] =
|
||||
cmGeneratorExpression::Evaluate(command, lg, config);
|
||||
|
||||
// Build up the list of properties that may have been specified
|
||||
Json::Value properties = Json::arrayValue;
|
||||
for (auto& prop : testInfo->GetProperties().GetList()) {
|
||||
Json::Value entry = Json::objectValue;
|
||||
entry[kKEY_KEY] = prop.first;
|
||||
|
||||
// Remove config variables from the value too.
|
||||
entry[kVALUE_KEY] =
|
||||
cmGeneratorExpression::Evaluate(prop.second, lg, config);
|
||||
properties.append(entry);
|
||||
}
|
||||
result[kPROPERTIES_KEY] = properties;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static void DumpMakefileTests(cmLocalGenerator* lg, const std::string& config,
|
||||
Json::Value* result)
|
||||
{
|
||||
auto mf = lg->GetMakefile();
|
||||
std::vector<cmTest*> tests;
|
||||
mf->GetTests(config, tests);
|
||||
for (auto test : tests) {
|
||||
Json::Value tmp = DumpCTestInfo(lg, test, config);
|
||||
if (!tmp.isNull()) {
|
||||
result->append(tmp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static Json::Value DumpCTestProjectList(const cmake* cm,
|
||||
std::string const& config)
|
||||
{
|
||||
Json::Value result = Json::arrayValue;
|
||||
|
||||
auto globalGen = cm->GetGlobalGenerator();
|
||||
|
||||
for (const auto& projectIt : globalGen->GetProjectMap()) {
|
||||
Json::Value pObj = Json::objectValue;
|
||||
pObj[kNAME_KEY] = projectIt.first;
|
||||
|
||||
Json::Value tests = Json::arrayValue;
|
||||
|
||||
// Gather tests for every generator
|
||||
for (const auto& lg : projectIt.second) {
|
||||
// Make sure they're generated.
|
||||
lg->GenerateTestFiles();
|
||||
DumpMakefileTests(lg, config, &tests);
|
||||
}
|
||||
|
||||
pObj[kCTEST_INFO] = tests;
|
||||
|
||||
result.append(pObj);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static Json::Value DumpCTestConfiguration(const cmake* cm,
|
||||
const std::string& config)
|
||||
{
|
||||
Json::Value result = Json::objectValue;
|
||||
result[kNAME_KEY] = config;
|
||||
|
||||
result[kPROJECTS_KEY] = DumpCTestProjectList(cm, config);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static Json::Value DumpCTestConfigurationsList(const cmake* cm)
|
||||
{
|
||||
Json::Value result = Json::arrayValue;
|
||||
|
||||
for (const std::string& c : getConfigurations(cm)) {
|
||||
result.append(DumpCTestConfiguration(cm, c));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
Json::Value cmDumpCTestInfo(const cmake* cm)
|
||||
{
|
||||
Json::Value result = Json::objectValue;
|
||||
result[kCONFIGURATIONS_KEY] = DumpCTestConfigurationsList(cm);
|
||||
return result;
|
||||
}
|
||||
|
||||
static Json::Value DumpTarget(cmGeneratorTarget* target,
|
||||
const std::string& config)
|
||||
{
|
||||
cmLocalGenerator* lg = target->GetLocalGenerator();
|
||||
|
||||
const cmStateEnums::TargetType type = target->GetType();
|
||||
const std::string typeName = cmState::GetTargetTypeName(type);
|
||||
|
||||
Json::Value ttl = Json::arrayValue;
|
||||
ttl.append("EXECUTABLE");
|
||||
ttl.append("STATIC_LIBRARY");
|
||||
ttl.append("SHARED_LIBRARY");
|
||||
ttl.append("MODULE_LIBRARY");
|
||||
ttl.append("OBJECT_LIBRARY");
|
||||
ttl.append("UTILITY");
|
||||
ttl.append("INTERFACE_LIBRARY");
|
||||
|
||||
if (!hasString(ttl, typeName) || target->IsImported()) {
|
||||
return Json::Value();
|
||||
}
|
||||
|
||||
Json::Value result = Json::objectValue;
|
||||
result[kNAME_KEY] = target->GetName();
|
||||
result[kIS_GENERATOR_PROVIDED_KEY] =
|
||||
target->Target->GetIsGeneratorProvided();
|
||||
result[kTYPE_KEY] = typeName;
|
||||
result[kSOURCE_DIRECTORY_KEY] = lg->GetCurrentSourceDirectory();
|
||||
result[kBUILD_DIRECTORY_KEY] = lg->GetCurrentBinaryDirectory();
|
||||
|
||||
if (type == cmStateEnums::INTERFACE_LIBRARY) {
|
||||
return result;
|
||||
}
|
||||
|
||||
result[kFULL_NAME_KEY] = target->GetFullName(config);
|
||||
|
||||
if (target->Target->GetHaveInstallRule()) {
|
||||
result[kHAS_INSTALL_RULE] = true;
|
||||
|
||||
Json::Value installPaths = Json::arrayValue;
|
||||
for (const auto& installGenerator :
|
||||
target->Makefile->GetInstallGenerators()) {
|
||||
auto installTargetGenerator =
|
||||
dynamic_cast<cmInstallTargetGenerator*>(installGenerator.get());
|
||||
if (installTargetGenerator != nullptr &&
|
||||
installTargetGenerator->GetTarget()->Target == target->Target) {
|
||||
auto dest = installTargetGenerator->GetDestination(config);
|
||||
|
||||
std::string installPath;
|
||||
if (!dest.empty() && cmSystemTools::FileIsFullPath(dest)) {
|
||||
installPath = dest;
|
||||
} else {
|
||||
installPath = cmStrCat(
|
||||
target->Makefile->GetSafeDefinition("CMAKE_INSTALL_PREFIX"), '/',
|
||||
dest);
|
||||
}
|
||||
|
||||
installPaths.append(installPath);
|
||||
}
|
||||
}
|
||||
|
||||
result[kINSTALL_PATHS] = installPaths;
|
||||
}
|
||||
|
||||
if (target->HaveWellDefinedOutputFiles()) {
|
||||
Json::Value artifacts = Json::arrayValue;
|
||||
artifacts.append(
|
||||
target->GetFullPath(config, cmStateEnums::RuntimeBinaryArtifact));
|
||||
if (target->HasImportLibrary(config)) {
|
||||
artifacts.append(
|
||||
target->GetFullPath(config, cmStateEnums::ImportLibraryArtifact));
|
||||
}
|
||||
if (target->IsDLLPlatform()) {
|
||||
const cmGeneratorTarget::OutputInfo* output =
|
||||
target->GetOutputInfo(config);
|
||||
if (output && !output->PdbDir.empty()) {
|
||||
artifacts.append(output->PdbDir + '/' + target->GetPDBName(config));
|
||||
}
|
||||
}
|
||||
result[kARTIFACTS_KEY] = artifacts;
|
||||
|
||||
result[kLINKER_LANGUAGE_KEY] = target->GetLinkerLanguage(config);
|
||||
|
||||
std::string linkLibs;
|
||||
std::string linkFlags;
|
||||
std::string linkLanguageFlags;
|
||||
std::string frameworkPath;
|
||||
std::string linkPath;
|
||||
cmLinkLineComputer linkLineComputer(lg,
|
||||
lg->GetStateSnapshot().GetDirectory());
|
||||
lg->GetTargetFlags(&linkLineComputer, config, linkLibs, linkLanguageFlags,
|
||||
linkFlags, frameworkPath, linkPath, target);
|
||||
|
||||
linkLibs = cmTrimWhitespace(linkLibs);
|
||||
linkFlags = cmTrimWhitespace(linkFlags);
|
||||
linkLanguageFlags = cmTrimWhitespace(linkLanguageFlags);
|
||||
frameworkPath = cmTrimWhitespace(frameworkPath);
|
||||
linkPath = cmTrimWhitespace(linkPath);
|
||||
|
||||
if (!cmTrimWhitespace(linkLibs).empty()) {
|
||||
result[kLINK_LIBRARIES_KEY] = linkLibs;
|
||||
}
|
||||
if (!cmTrimWhitespace(linkFlags).empty()) {
|
||||
result[kLINK_FLAGS_KEY] = linkFlags;
|
||||
}
|
||||
if (!cmTrimWhitespace(linkLanguageFlags).empty()) {
|
||||
result[kLINK_LANGUAGE_FLAGS_KEY] = linkLanguageFlags;
|
||||
}
|
||||
if (!frameworkPath.empty()) {
|
||||
result[kFRAMEWORK_PATH_KEY] = frameworkPath;
|
||||
}
|
||||
if (!linkPath.empty()) {
|
||||
result[kLINK_PATH_KEY] = linkPath;
|
||||
}
|
||||
const std::string sysroot =
|
||||
lg->GetMakefile()->GetSafeDefinition("CMAKE_SYSROOT");
|
||||
if (!sysroot.empty()) {
|
||||
result[kSYSROOT_KEY] = sysroot;
|
||||
}
|
||||
}
|
||||
|
||||
std::set<std::string> languages;
|
||||
target->GetLanguages(languages, config);
|
||||
std::map<std::string, LanguageData> languageDataMap;
|
||||
|
||||
for (std::string const& lang : languages) {
|
||||
LanguageData& ld = languageDataMap[lang];
|
||||
ld.Language = lang;
|
||||
lg->GetTargetCompileFlags(target, config, lang, ld.Flags);
|
||||
std::set<std::string> defines;
|
||||
lg->GetTargetDefines(target, config, lang, defines);
|
||||
ld.SetDefines(defines);
|
||||
std::vector<std::string> includePathList;
|
||||
lg->GetIncludeDirectories(includePathList, target, lang, config);
|
||||
for (std::string const& i : includePathList) {
|
||||
ld.IncludePathList.emplace_back(
|
||||
i, target->IsSystemIncludeDirectory(i, config, lang));
|
||||
}
|
||||
}
|
||||
|
||||
Json::Value sourceGroupsValue =
|
||||
DumpSourceFilesList(target, config, languageDataMap);
|
||||
if (!sourceGroupsValue.empty()) {
|
||||
result[kFILE_GROUPS_KEY] = sourceGroupsValue;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static Json::Value DumpTargetsList(
|
||||
const std::vector<cmLocalGenerator*>& generators, const std::string& config)
|
||||
{
|
||||
Json::Value result = Json::arrayValue;
|
||||
|
||||
std::vector<cmGeneratorTarget*> targetList;
|
||||
for (auto const& lgIt : generators) {
|
||||
cm::append(targetList, lgIt->GetGeneratorTargets());
|
||||
}
|
||||
std::sort(targetList.begin(), targetList.end());
|
||||
|
||||
for (cmGeneratorTarget* target : targetList) {
|
||||
Json::Value tmp = DumpTarget(target, config);
|
||||
if (!tmp.isNull()) {
|
||||
result.append(tmp);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static Json::Value DumpProjectList(const cmake* cm, std::string const& config)
|
||||
{
|
||||
Json::Value result = Json::arrayValue;
|
||||
|
||||
auto globalGen = cm->GetGlobalGenerator();
|
||||
|
||||
for (auto const& projectIt : globalGen->GetProjectMap()) {
|
||||
Json::Value pObj = Json::objectValue;
|
||||
pObj[kNAME_KEY] = projectIt.first;
|
||||
|
||||
// All Projects must have at least one local generator
|
||||
assert(!projectIt.second.empty());
|
||||
const cmLocalGenerator* lg = projectIt.second.at(0);
|
||||
|
||||
// Project structure information:
|
||||
const cmMakefile* mf = lg->GetMakefile();
|
||||
auto minVersion = mf->GetSafeDefinition("CMAKE_MINIMUM_REQUIRED_VERSION");
|
||||
pObj[kMINIMUM_CMAKE_VERSION] = minVersion;
|
||||
pObj[kSOURCE_DIRECTORY_KEY] = mf->GetCurrentSourceDirectory();
|
||||
pObj[kBUILD_DIRECTORY_KEY] = mf->GetCurrentBinaryDirectory();
|
||||
pObj[kTARGETS_KEY] = DumpTargetsList(projectIt.second, config);
|
||||
|
||||
// For a project-level install rule it might be defined in any of its
|
||||
// associated generators.
|
||||
bool hasInstallRule = false;
|
||||
for (const auto generator : projectIt.second) {
|
||||
for (const auto& installGen :
|
||||
generator->GetMakefile()->GetInstallGenerators()) {
|
||||
if (!dynamic_cast<cmInstallSubdirectoryGenerator*>(installGen.get())) {
|
||||
hasInstallRule = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (hasInstallRule) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
pObj[kHAS_INSTALL_RULE] = hasInstallRule;
|
||||
|
||||
result.append(pObj);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static Json::Value DumpConfiguration(const cmake* cm,
|
||||
const std::string& config)
|
||||
{
|
||||
Json::Value result = Json::objectValue;
|
||||
result[kNAME_KEY] = config;
|
||||
|
||||
result[kPROJECTS_KEY] = DumpProjectList(cm, config);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static Json::Value DumpConfigurationsList(const cmake* cm)
|
||||
{
|
||||
Json::Value result = Json::arrayValue;
|
||||
|
||||
for (std::string const& c : getConfigurations(cm)) {
|
||||
result.append(DumpConfiguration(cm, c));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
Json::Value cmDumpCodeModel(const cmake* cm)
|
||||
{
|
||||
Json::Value result = Json::objectValue;
|
||||
result[kCONFIGURATIONS_KEY] = DumpConfigurationsList(cm);
|
||||
return result;
|
||||
}
|
@@ -1,24 +0,0 @@
|
||||
/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
|
||||
file Copyright.txt or https://cmake.org/licensing for details. */
|
||||
#pragma once
|
||||
|
||||
#include "cmConfigure.h" // IWYU pragma: keep
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <cm3p/json/value.h>
|
||||
|
||||
class cmake;
|
||||
class cmGlobalGenerator;
|
||||
|
||||
extern void cmGetCMakeInputs(const cmGlobalGenerator* gg,
|
||||
const std::string& sourceDir,
|
||||
const std::string& buildDir,
|
||||
std::vector<std::string>* internalFiles,
|
||||
std::vector<std::string>* explicitFiles,
|
||||
std::vector<std::string>* tmpFiles);
|
||||
|
||||
extern Json::Value cmDumpCodeModel(const cmake* cm);
|
||||
extern Json::Value cmDumpCTestInfo(const cmake* cm);
|
||||
extern Json::Value cmDumpCMakeInputs(const cmake* cm);
|
@@ -1,71 +0,0 @@
|
||||
/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
|
||||
file Copyright.txt or https://cmake.org/licensing for details. */
|
||||
#include "cmPipeConnection.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include "cmServer.h"
|
||||
|
||||
cmPipeConnection::cmPipeConnection(std::string name,
|
||||
cmConnectionBufferStrategy* bufferStrategy)
|
||||
: cmEventBasedConnection(bufferStrategy)
|
||||
, PipeName(std::move(name))
|
||||
{
|
||||
}
|
||||
|
||||
void cmPipeConnection::Connect(uv_stream_t* server)
|
||||
{
|
||||
if (this->WriteStream.get()) {
|
||||
// Accept and close all pipes but the first:
|
||||
cm::uv_pipe_ptr rejectPipe;
|
||||
|
||||
rejectPipe.init(*this->Server->GetLoop(), 0);
|
||||
uv_accept(server, rejectPipe);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
cm::uv_pipe_ptr ClientPipe;
|
||||
ClientPipe.init(*this->Server->GetLoop(), 0,
|
||||
static_cast<cmEventBasedConnection*>(this));
|
||||
|
||||
if (uv_accept(server, ClientPipe) != 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
uv_read_start(ClientPipe, on_alloc_buffer, on_read);
|
||||
WriteStream = std::move(ClientPipe);
|
||||
Server->OnConnected(this);
|
||||
}
|
||||
|
||||
bool cmPipeConnection::OnServeStart(std::string* errorMessage)
|
||||
{
|
||||
this->ServerPipe.init(*this->Server->GetLoop(), 0,
|
||||
static_cast<cmEventBasedConnection*>(this));
|
||||
|
||||
int r;
|
||||
if ((r = uv_pipe_bind(this->ServerPipe, this->PipeName.c_str())) != 0) {
|
||||
*errorMessage = std::string("Internal Error with ") + this->PipeName +
|
||||
": " + uv_err_name(r);
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((r = uv_listen(this->ServerPipe, 1, on_new_connection)) != 0) {
|
||||
*errorMessage = std::string("Internal Error listening on ") +
|
||||
this->PipeName + ": " + uv_err_name(r);
|
||||
return false;
|
||||
}
|
||||
|
||||
return cmConnection::OnServeStart(errorMessage);
|
||||
}
|
||||
|
||||
bool cmPipeConnection::OnConnectionShuttingDown()
|
||||
{
|
||||
if (this->WriteStream.get()) {
|
||||
this->WriteStream->data = nullptr;
|
||||
}
|
||||
|
||||
this->ServerPipe.reset();
|
||||
|
||||
return cmEventBasedConnection::OnConnectionShuttingDown();
|
||||
}
|
@@ -1,29 +0,0 @@
|
||||
/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
|
||||
file Copyright.txt or https://cmake.org/licensing for details. */
|
||||
#pragma once
|
||||
|
||||
#include "cmConfigure.h" // IWYU pragma: keep
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <cm3p/uv.h>
|
||||
|
||||
#include "cmConnection.h"
|
||||
#include "cmUVHandlePtr.h"
|
||||
|
||||
class cmPipeConnection : public cmEventBasedConnection
|
||||
{
|
||||
public:
|
||||
cmPipeConnection(std::string name,
|
||||
cmConnectionBufferStrategy* bufferStrategy = nullptr);
|
||||
|
||||
bool OnServeStart(std::string* pString) override;
|
||||
|
||||
bool OnConnectionShuttingDown() override;
|
||||
|
||||
void Connect(uv_stream_t* server) override;
|
||||
|
||||
private:
|
||||
const std::string PipeName;
|
||||
cm::uv_pipe_ptr ServerPipe;
|
||||
};
|
@@ -1,570 +0,0 @@
|
||||
/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
|
||||
file Copyright.txt or https://cmake.org/licensing for details. */
|
||||
#include "cmServer.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <csignal>
|
||||
#include <cstdint>
|
||||
#include <iostream>
|
||||
#include <mutex>
|
||||
#include <utility>
|
||||
|
||||
#include <cm/memory>
|
||||
#include <cm/shared_mutex>
|
||||
|
||||
#include <cm3p/json/reader.h>
|
||||
#include <cm3p/json/writer.h>
|
||||
|
||||
#include "cmsys/FStream.hxx"
|
||||
|
||||
#include "cmConnection.h"
|
||||
#include "cmFileMonitor.h"
|
||||
#include "cmJsonObjectDictionary.h"
|
||||
#include "cmServerDictionary.h"
|
||||
#include "cmServerProtocol.h"
|
||||
#include "cmSystemTools.h"
|
||||
#include "cmake.h"
|
||||
|
||||
void on_signal(uv_signal_t* signal, int signum)
|
||||
{
|
||||
auto conn = static_cast<cmServerBase*>(signal->data);
|
||||
conn->OnSignal(signum);
|
||||
}
|
||||
|
||||
static void on_walk_to_shutdown(uv_handle_t* handle, void* arg)
|
||||
{
|
||||
(void)arg;
|
||||
assert(uv_is_closing(handle));
|
||||
if (!uv_is_closing(handle)) {
|
||||
uv_close(handle, &cmEventBasedConnection::on_close);
|
||||
}
|
||||
}
|
||||
|
||||
class cmServer::DebugInfo
|
||||
{
|
||||
public:
|
||||
DebugInfo()
|
||||
: StartTime(uv_hrtime())
|
||||
{
|
||||
}
|
||||
|
||||
bool PrintStatistics = false;
|
||||
|
||||
std::string OutputFile;
|
||||
uint64_t StartTime;
|
||||
};
|
||||
|
||||
cmServer::cmServer(cmConnection* conn, bool supportExperimental)
|
||||
: cmServerBase(conn)
|
||||
, SupportExperimental(supportExperimental)
|
||||
{
|
||||
// Register supported protocols:
|
||||
this->RegisterProtocol(cm::make_unique<cmServerProtocol1>());
|
||||
}
|
||||
|
||||
cmServer::~cmServer()
|
||||
{
|
||||
Close();
|
||||
}
|
||||
|
||||
void cmServer::ProcessRequest(cmConnection* connection,
|
||||
const std::string& input)
|
||||
{
|
||||
Json::Reader reader;
|
||||
Json::Value value;
|
||||
if (!reader.parse(input, value)) {
|
||||
this->WriteParseError(connection, "Failed to parse JSON input.");
|
||||
return;
|
||||
}
|
||||
|
||||
std::unique_ptr<DebugInfo> debug;
|
||||
Json::Value debugValue = value["debug"];
|
||||
if (!debugValue.isNull()) {
|
||||
debug = cm::make_unique<DebugInfo>();
|
||||
debug->OutputFile = debugValue["dumpToFile"].asString();
|
||||
debug->PrintStatistics = debugValue["showStats"].asBool();
|
||||
}
|
||||
|
||||
const cmServerRequest request(this, connection, value[kTYPE_KEY].asString(),
|
||||
value[kCOOKIE_KEY].asString(), value);
|
||||
|
||||
if (request.Type.empty()) {
|
||||
cmServerResponse response(request);
|
||||
response.SetError("No type given in request.");
|
||||
this->WriteResponse(connection, response, nullptr);
|
||||
return;
|
||||
}
|
||||
|
||||
cmSystemTools::SetMessageCallback(
|
||||
[&request](const std::string& msg, const char* title) {
|
||||
reportMessage(msg, title, request);
|
||||
});
|
||||
|
||||
if (this->Protocol) {
|
||||
this->Protocol->CMakeInstance()->SetProgressCallback(
|
||||
[&request](const std::string& msg, float prog) {
|
||||
reportProgress(msg, prog, request);
|
||||
});
|
||||
this->WriteResponse(connection, this->Protocol->Process(request),
|
||||
debug.get());
|
||||
} else {
|
||||
this->WriteResponse(connection, this->SetProtocolVersion(request),
|
||||
debug.get());
|
||||
}
|
||||
}
|
||||
|
||||
void cmServer::RegisterProtocol(std::unique_ptr<cmServerProtocol> protocol)
|
||||
{
|
||||
if (protocol->IsExperimental() && !this->SupportExperimental) {
|
||||
protocol.reset();
|
||||
return;
|
||||
}
|
||||
auto version = protocol->ProtocolVersion();
|
||||
assert(version.first >= 0);
|
||||
assert(version.second >= 0);
|
||||
auto it = std::find_if(
|
||||
this->SupportedProtocols.begin(), this->SupportedProtocols.end(),
|
||||
[version](const std::unique_ptr<cmServerProtocol>& p) {
|
||||
return p->ProtocolVersion() == version;
|
||||
});
|
||||
if (it == this->SupportedProtocols.end()) {
|
||||
this->SupportedProtocols.push_back(std::move(protocol));
|
||||
}
|
||||
}
|
||||
|
||||
void cmServer::PrintHello(cmConnection* connection) const
|
||||
{
|
||||
Json::Value hello = Json::objectValue;
|
||||
hello[kTYPE_KEY] = "hello";
|
||||
|
||||
Json::Value& protocolVersions = hello[kSUPPORTED_PROTOCOL_VERSIONS] =
|
||||
Json::arrayValue;
|
||||
|
||||
for (auto const& proto : this->SupportedProtocols) {
|
||||
auto version = proto->ProtocolVersion();
|
||||
Json::Value tmp = Json::objectValue;
|
||||
tmp[kMAJOR_KEY] = version.first;
|
||||
tmp[kMINOR_KEY] = version.second;
|
||||
if (proto->IsExperimental()) {
|
||||
tmp[kIS_EXPERIMENTAL_KEY] = true;
|
||||
}
|
||||
protocolVersions.append(tmp);
|
||||
}
|
||||
|
||||
this->WriteJsonObject(connection, hello, nullptr);
|
||||
}
|
||||
|
||||
void cmServer::reportProgress(const std::string& msg, float progress,
|
||||
const cmServerRequest& request)
|
||||
{
|
||||
if (progress < 0.0f || progress > 1.0f) {
|
||||
request.ReportMessage(msg, "");
|
||||
} else {
|
||||
request.ReportProgress(0, static_cast<int>(progress * 1000), 1000, msg);
|
||||
}
|
||||
}
|
||||
|
||||
void cmServer::reportMessage(const std::string& msg, const char* title,
|
||||
const cmServerRequest& request)
|
||||
{
|
||||
std::string titleString;
|
||||
if (title) {
|
||||
titleString = title;
|
||||
}
|
||||
request.ReportMessage(msg, titleString);
|
||||
}
|
||||
|
||||
cmServerResponse cmServer::SetProtocolVersion(const cmServerRequest& request)
|
||||
{
|
||||
if (request.Type != kHANDSHAKE_TYPE) {
|
||||
return request.ReportError("Waiting for type \"" + kHANDSHAKE_TYPE +
|
||||
"\".");
|
||||
}
|
||||
|
||||
Json::Value requestedProtocolVersion = request.Data[kPROTOCOL_VERSION_KEY];
|
||||
if (requestedProtocolVersion.isNull()) {
|
||||
return request.ReportError("\"" + kPROTOCOL_VERSION_KEY +
|
||||
"\" is required for \"" + kHANDSHAKE_TYPE +
|
||||
"\".");
|
||||
}
|
||||
|
||||
if (!requestedProtocolVersion.isObject()) {
|
||||
return request.ReportError("\"" + kPROTOCOL_VERSION_KEY +
|
||||
"\" must be a JSON object.");
|
||||
}
|
||||
|
||||
Json::Value majorValue = requestedProtocolVersion[kMAJOR_KEY];
|
||||
if (!majorValue.isInt()) {
|
||||
return request.ReportError("\"" + kMAJOR_KEY +
|
||||
"\" must be set and an integer.");
|
||||
}
|
||||
|
||||
Json::Value minorValue = requestedProtocolVersion[kMINOR_KEY];
|
||||
if (!minorValue.isNull() && !minorValue.isInt()) {
|
||||
return request.ReportError("\"" + kMINOR_KEY +
|
||||
"\" must be unset or an integer.");
|
||||
}
|
||||
|
||||
const int major = majorValue.asInt();
|
||||
const int minor = minorValue.isNull() ? -1 : minorValue.asInt();
|
||||
if (major < 0) {
|
||||
return request.ReportError("\"" + kMAJOR_KEY + "\" must be >= 0.");
|
||||
}
|
||||
if (!minorValue.isNull() && minor < 0) {
|
||||
return request.ReportError("\"" + kMINOR_KEY +
|
||||
"\" must be >= 0 when set.");
|
||||
}
|
||||
|
||||
this->Protocol =
|
||||
cmServer::FindMatchingProtocol(this->SupportedProtocols, major, minor);
|
||||
if (!this->Protocol) {
|
||||
return request.ReportError("Protocol version not supported.");
|
||||
}
|
||||
|
||||
std::string errorMessage;
|
||||
if (!this->Protocol->Activate(this, request, &errorMessage)) {
|
||||
this->Protocol = nullptr;
|
||||
return request.ReportError("Failed to activate protocol version: " +
|
||||
errorMessage);
|
||||
}
|
||||
return request.Reply(Json::objectValue);
|
||||
}
|
||||
|
||||
bool cmServer::Serve(std::string* errorMessage)
|
||||
{
|
||||
if (this->SupportedProtocols.empty()) {
|
||||
*errorMessage =
|
||||
"No protocol versions defined. Maybe you need --experimental?";
|
||||
return false;
|
||||
}
|
||||
assert(!this->Protocol);
|
||||
|
||||
return cmServerBase::Serve(errorMessage);
|
||||
}
|
||||
|
||||
cmFileMonitor* cmServer::FileMonitor() const
|
||||
{
|
||||
return fileMonitor.get();
|
||||
}
|
||||
|
||||
void cmServer::WriteJsonObject(const Json::Value& jsonValue,
|
||||
const DebugInfo* debug) const
|
||||
{
|
||||
cm::shared_lock<cm::shared_mutex> lock(ConnectionsMutex);
|
||||
for (auto& connection : this->Connections) {
|
||||
WriteJsonObject(connection.get(), jsonValue, debug);
|
||||
}
|
||||
}
|
||||
|
||||
void cmServer::WriteJsonObject(cmConnection* connection,
|
||||
const Json::Value& jsonValue,
|
||||
const DebugInfo* debug) const
|
||||
{
|
||||
Json::FastWriter writer;
|
||||
|
||||
auto beforeJson = uv_hrtime();
|
||||
std::string result = writer.write(jsonValue);
|
||||
|
||||
if (debug) {
|
||||
Json::Value copy = jsonValue;
|
||||
if (debug->PrintStatistics) {
|
||||
Json::Value stats = Json::objectValue;
|
||||
auto endTime = uv_hrtime();
|
||||
|
||||
stats["jsonSerialization"] = double(endTime - beforeJson) / 1000000.0;
|
||||
stats["totalTime"] = double(endTime - debug->StartTime) / 1000000.0;
|
||||
stats["size"] = static_cast<int>(result.size());
|
||||
if (!debug->OutputFile.empty()) {
|
||||
stats["dumpFile"] = debug->OutputFile;
|
||||
}
|
||||
|
||||
copy["zzzDebug"] = stats;
|
||||
|
||||
result = writer.write(copy); // Update result to include debug info
|
||||
}
|
||||
|
||||
if (!debug->OutputFile.empty()) {
|
||||
cmsys::ofstream myfile(debug->OutputFile.c_str());
|
||||
myfile << result;
|
||||
}
|
||||
}
|
||||
|
||||
connection->WriteData(result);
|
||||
}
|
||||
|
||||
cmServerProtocol* cmServer::FindMatchingProtocol(
|
||||
const std::vector<std::unique_ptr<cmServerProtocol>>& protocols, int major,
|
||||
int minor)
|
||||
{
|
||||
cmServerProtocol* bestMatch = nullptr;
|
||||
for (const auto& protocol : protocols) {
|
||||
auto version = protocol->ProtocolVersion();
|
||||
if (major != version.first) {
|
||||
continue;
|
||||
}
|
||||
if (minor == version.second) {
|
||||
return protocol.get();
|
||||
}
|
||||
if (!bestMatch || bestMatch->ProtocolVersion().second < version.second) {
|
||||
bestMatch = protocol.get();
|
||||
}
|
||||
}
|
||||
return minor < 0 ? bestMatch : nullptr;
|
||||
}
|
||||
|
||||
void cmServer::WriteProgress(const cmServerRequest& request, int min,
|
||||
int current, int max,
|
||||
const std::string& message) const
|
||||
{
|
||||
assert(min <= current && current <= max);
|
||||
assert(message.length() != 0);
|
||||
|
||||
Json::Value obj = Json::objectValue;
|
||||
obj[kTYPE_KEY] = kPROGRESS_TYPE;
|
||||
obj[kREPLY_TO_KEY] = request.Type;
|
||||
obj[kCOOKIE_KEY] = request.Cookie;
|
||||
obj[kPROGRESS_MESSAGE_KEY] = message;
|
||||
obj[kPROGRESS_MINIMUM_KEY] = min;
|
||||
obj[kPROGRESS_MAXIMUM_KEY] = max;
|
||||
obj[kPROGRESS_CURRENT_KEY] = current;
|
||||
|
||||
this->WriteJsonObject(request.Connection, obj, nullptr);
|
||||
}
|
||||
|
||||
void cmServer::WriteMessage(const cmServerRequest& request,
|
||||
const std::string& message,
|
||||
const std::string& title) const
|
||||
{
|
||||
if (message.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Json::Value obj = Json::objectValue;
|
||||
obj[kTYPE_KEY] = kMESSAGE_TYPE;
|
||||
obj[kREPLY_TO_KEY] = request.Type;
|
||||
obj[kCOOKIE_KEY] = request.Cookie;
|
||||
obj[kMESSAGE_KEY] = message;
|
||||
if (!title.empty()) {
|
||||
obj[kTITLE_KEY] = title;
|
||||
}
|
||||
|
||||
WriteJsonObject(request.Connection, obj, nullptr);
|
||||
}
|
||||
|
||||
void cmServer::WriteParseError(cmConnection* connection,
|
||||
const std::string& message) const
|
||||
{
|
||||
Json::Value obj = Json::objectValue;
|
||||
obj[kTYPE_KEY] = kERROR_TYPE;
|
||||
obj[kERROR_MESSAGE_KEY] = message;
|
||||
obj[kREPLY_TO_KEY] = "";
|
||||
obj[kCOOKIE_KEY] = "";
|
||||
|
||||
this->WriteJsonObject(connection, obj, nullptr);
|
||||
}
|
||||
|
||||
void cmServer::WriteSignal(const std::string& name,
|
||||
const Json::Value& data) const
|
||||
{
|
||||
assert(data.isObject());
|
||||
Json::Value obj = data;
|
||||
obj[kTYPE_KEY] = kSIGNAL_TYPE;
|
||||
obj[kREPLY_TO_KEY] = "";
|
||||
obj[kCOOKIE_KEY] = "";
|
||||
obj[kNAME_KEY] = name;
|
||||
|
||||
WriteJsonObject(obj, nullptr);
|
||||
}
|
||||
|
||||
void cmServer::WriteResponse(cmConnection* connection,
|
||||
const cmServerResponse& response,
|
||||
const DebugInfo* debug) const
|
||||
{
|
||||
assert(response.IsComplete());
|
||||
|
||||
Json::Value obj = response.Data();
|
||||
obj[kCOOKIE_KEY] = response.Cookie;
|
||||
obj[kTYPE_KEY] = response.IsError() ? kERROR_TYPE : kREPLY_TYPE;
|
||||
obj[kREPLY_TO_KEY] = response.Type;
|
||||
if (response.IsError()) {
|
||||
obj[kERROR_MESSAGE_KEY] = response.ErrorMessage();
|
||||
}
|
||||
|
||||
this->WriteJsonObject(connection, obj, debug);
|
||||
}
|
||||
|
||||
void cmServer::OnConnected(cmConnection* connection)
|
||||
{
|
||||
PrintHello(connection);
|
||||
}
|
||||
|
||||
void cmServer::OnServeStart()
|
||||
{
|
||||
cmServerBase::OnServeStart();
|
||||
fileMonitor = std::make_shared<cmFileMonitor>(GetLoop());
|
||||
}
|
||||
|
||||
void cmServer::StartShutDown()
|
||||
{
|
||||
if (fileMonitor) {
|
||||
fileMonitor->StopMonitoring();
|
||||
fileMonitor.reset();
|
||||
}
|
||||
cmServerBase::StartShutDown();
|
||||
}
|
||||
|
||||
static void __start_thread(void* arg)
|
||||
{
|
||||
auto server = static_cast<cmServerBase*>(arg);
|
||||
std::string error;
|
||||
bool success = server->Serve(&error);
|
||||
if (!success || !error.empty()) {
|
||||
std::cerr << "Error during serve: " << error << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
bool cmServerBase::StartServeThread()
|
||||
{
|
||||
ServeThreadRunning = true;
|
||||
uv_thread_create(&ServeThread, __start_thread, this);
|
||||
return true;
|
||||
}
|
||||
|
||||
static void __shutdownThread(uv_async_t* arg)
|
||||
{
|
||||
auto server = static_cast<cmServerBase*>(arg->data);
|
||||
server->StartShutDown();
|
||||
}
|
||||
|
||||
bool cmServerBase::Serve(std::string* errorMessage)
|
||||
{
|
||||
#ifndef NDEBUG
|
||||
uv_thread_t blank_thread_t = {};
|
||||
assert(uv_thread_equal(&blank_thread_t, &ServeThreadId));
|
||||
ServeThreadId = uv_thread_self();
|
||||
#endif
|
||||
|
||||
errorMessage->clear();
|
||||
|
||||
ShutdownSignal.init(Loop, __shutdownThread, this);
|
||||
|
||||
SIGINTHandler.init(Loop, this);
|
||||
SIGHUPHandler.init(Loop, this);
|
||||
|
||||
SIGINTHandler.start(&on_signal, SIGINT);
|
||||
SIGHUPHandler.start(&on_signal, SIGHUP);
|
||||
|
||||
OnServeStart();
|
||||
|
||||
{
|
||||
cm::shared_lock<cm::shared_mutex> lock(ConnectionsMutex);
|
||||
for (auto& connection : Connections) {
|
||||
if (!connection->OnServeStart(errorMessage)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (uv_run(&Loop, UV_RUN_DEFAULT) != 0) {
|
||||
// It is important we don't ever let the event loop exit with open handles
|
||||
// at best this is a memory leak, but it can also introduce race conditions
|
||||
// which can hang the program.
|
||||
assert(false && "Event loop stopped in unclean state.");
|
||||
|
||||
*errorMessage = "Internal Error: Event loop stopped in unclean state.";
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void cmServerBase::OnConnected(cmConnection*)
|
||||
{
|
||||
}
|
||||
|
||||
void cmServerBase::OnServeStart()
|
||||
{
|
||||
}
|
||||
|
||||
void cmServerBase::StartShutDown()
|
||||
{
|
||||
ShutdownSignal.reset();
|
||||
SIGINTHandler.reset();
|
||||
SIGHUPHandler.reset();
|
||||
|
||||
{
|
||||
std::unique_lock<cm::shared_mutex> lock(ConnectionsMutex);
|
||||
for (auto& connection : Connections) {
|
||||
connection->OnConnectionShuttingDown();
|
||||
}
|
||||
Connections.clear();
|
||||
}
|
||||
|
||||
uv_walk(&Loop, on_walk_to_shutdown, nullptr);
|
||||
}
|
||||
|
||||
bool cmServerBase::OnSignal(int signum)
|
||||
{
|
||||
(void)signum;
|
||||
StartShutDown();
|
||||
return true;
|
||||
}
|
||||
|
||||
cmServerBase::cmServerBase(cmConnection* connection)
|
||||
{
|
||||
auto err = uv_loop_init(&Loop);
|
||||
(void)err;
|
||||
Loop.data = this;
|
||||
assert(err == 0);
|
||||
|
||||
AddNewConnection(connection);
|
||||
}
|
||||
|
||||
void cmServerBase::Close()
|
||||
{
|
||||
if (Loop.data) {
|
||||
if (ServeThreadRunning) {
|
||||
this->ShutdownSignal.send();
|
||||
uv_thread_join(&ServeThread);
|
||||
}
|
||||
|
||||
uv_loop_close(&Loop);
|
||||
Loop.data = nullptr;
|
||||
}
|
||||
}
|
||||
cmServerBase::~cmServerBase()
|
||||
{
|
||||
Close();
|
||||
}
|
||||
|
||||
void cmServerBase::AddNewConnection(cmConnection* ownedConnection)
|
||||
{
|
||||
{
|
||||
std::unique_lock<cm::shared_mutex> lock(ConnectionsMutex);
|
||||
Connections.emplace_back(ownedConnection);
|
||||
}
|
||||
ownedConnection->SetServer(this);
|
||||
}
|
||||
|
||||
uv_loop_t* cmServerBase::GetLoop()
|
||||
{
|
||||
return &Loop;
|
||||
}
|
||||
|
||||
void cmServerBase::OnDisconnect(cmConnection* pConnection)
|
||||
{
|
||||
auto pred = [pConnection](const std::unique_ptr<cmConnection>& m) {
|
||||
return m.get() == pConnection;
|
||||
};
|
||||
{
|
||||
std::unique_lock<cm::shared_mutex> lock(ConnectionsMutex);
|
||||
Connections.erase(
|
||||
std::remove_if(Connections.begin(), Connections.end(), pred),
|
||||
Connections.end());
|
||||
}
|
||||
|
||||
if (Connections.empty()) {
|
||||
this->ShutdownSignal.send();
|
||||
}
|
||||
}
|
@@ -1,162 +0,0 @@
|
||||
/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
|
||||
file Copyright.txt or https://cmake.org/licensing for details. */
|
||||
#pragma once
|
||||
|
||||
#include "cmConfigure.h" // IWYU pragma: keep
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <cm/shared_mutex>
|
||||
|
||||
#include <cm3p/json/value.h>
|
||||
#include <cm3p/uv.h>
|
||||
|
||||
#include "cmUVHandlePtr.h"
|
||||
|
||||
class cmConnection;
|
||||
class cmFileMonitor;
|
||||
class cmServerProtocol;
|
||||
class cmServerRequest;
|
||||
class cmServerResponse;
|
||||
|
||||
/***
|
||||
* This essentially hold and manages a libuv event queue and responds to
|
||||
* messages
|
||||
* on any of its connections.
|
||||
*/
|
||||
class cmServerBase
|
||||
{
|
||||
public:
|
||||
cmServerBase(cmConnection* connection);
|
||||
virtual ~cmServerBase();
|
||||
|
||||
virtual void AddNewConnection(cmConnection* ownedConnection);
|
||||
|
||||
/***
|
||||
* The main override responsible for tailoring behavior towards
|
||||
* whatever the given server is supposed to do
|
||||
*
|
||||
* This should almost always be called by the given connections
|
||||
* directly.
|
||||
*
|
||||
* @param connection The connection the request was received on
|
||||
* @param request The actual request
|
||||
*/
|
||||
virtual void ProcessRequest(cmConnection* connection,
|
||||
const std::string& request) = 0;
|
||||
virtual void OnConnected(cmConnection* connection);
|
||||
|
||||
/***
|
||||
* Start a dedicated thread. If this is used to start the server, it will
|
||||
* join on the
|
||||
* servers dtor.
|
||||
*/
|
||||
virtual bool StartServeThread();
|
||||
virtual bool Serve(std::string* errorMessage);
|
||||
|
||||
virtual void OnServeStart();
|
||||
virtual void StartShutDown();
|
||||
|
||||
virtual bool OnSignal(int signum);
|
||||
uv_loop_t* GetLoop();
|
||||
void Close();
|
||||
void OnDisconnect(cmConnection* pConnection);
|
||||
|
||||
protected:
|
||||
mutable cm::shared_mutex ConnectionsMutex;
|
||||
std::vector<std::unique_ptr<cmConnection>> Connections;
|
||||
|
||||
bool ServeThreadRunning = false;
|
||||
uv_thread_t ServeThread;
|
||||
cm::uv_async_ptr ShutdownSignal;
|
||||
#ifndef NDEBUG
|
||||
public:
|
||||
// When the server starts it will mark down it's current thread ID,
|
||||
// which is useful in other contexts to just assert that operations
|
||||
// are performed on that same thread.
|
||||
uv_thread_t ServeThreadId = {};
|
||||
|
||||
protected:
|
||||
#endif
|
||||
|
||||
uv_loop_t Loop;
|
||||
|
||||
cm::uv_signal_ptr SIGINTHandler;
|
||||
cm::uv_signal_ptr SIGHUPHandler;
|
||||
};
|
||||
|
||||
class cmServer : public cmServerBase
|
||||
{
|
||||
public:
|
||||
class DebugInfo;
|
||||
|
||||
cmServer(cmConnection* conn, bool supportExperimental);
|
||||
~cmServer() override;
|
||||
|
||||
cmServer(cmServer const&) = delete;
|
||||
cmServer& operator=(cmServer const&) = delete;
|
||||
|
||||
bool Serve(std::string* errorMessage) override;
|
||||
|
||||
cmFileMonitor* FileMonitor() const;
|
||||
|
||||
private:
|
||||
void RegisterProtocol(std::unique_ptr<cmServerProtocol> protocol);
|
||||
|
||||
// Callbacks from cmServerConnection:
|
||||
|
||||
void ProcessRequest(cmConnection* connection,
|
||||
const std::string& request) override;
|
||||
std::shared_ptr<cmFileMonitor> fileMonitor;
|
||||
|
||||
public:
|
||||
void OnServeStart() override;
|
||||
|
||||
void StartShutDown() override;
|
||||
|
||||
public:
|
||||
void OnConnected(cmConnection* connection) override;
|
||||
|
||||
private:
|
||||
static void reportProgress(const std::string& msg, float progress,
|
||||
const cmServerRequest& request);
|
||||
static void reportMessage(const std::string& msg, const char* title,
|
||||
const cmServerRequest& request);
|
||||
|
||||
// Handle requests:
|
||||
cmServerResponse SetProtocolVersion(const cmServerRequest& request);
|
||||
|
||||
void PrintHello(cmConnection* connection) const;
|
||||
|
||||
// Write responses:
|
||||
void WriteProgress(const cmServerRequest& request, int min, int current,
|
||||
int max, const std::string& message) const;
|
||||
void WriteMessage(const cmServerRequest& request, const std::string& message,
|
||||
const std::string& title) const;
|
||||
void WriteResponse(cmConnection* connection,
|
||||
const cmServerResponse& response,
|
||||
const DebugInfo* debug) const;
|
||||
void WriteParseError(cmConnection* connection,
|
||||
const std::string& message) const;
|
||||
void WriteSignal(const std::string& name, const Json::Value& obj) const;
|
||||
|
||||
void WriteJsonObject(Json::Value const& jsonValue,
|
||||
const DebugInfo* debug) const;
|
||||
|
||||
void WriteJsonObject(cmConnection* connection, Json::Value const& jsonValue,
|
||||
const DebugInfo* debug) const;
|
||||
|
||||
static cmServerProtocol* FindMatchingProtocol(
|
||||
const std::vector<std::unique_ptr<cmServerProtocol>>& protocols, int major,
|
||||
int minor);
|
||||
|
||||
const bool SupportExperimental;
|
||||
|
||||
cmServerProtocol* Protocol = nullptr;
|
||||
std::vector<std::unique_ptr<cmServerProtocol>> SupportedProtocols;
|
||||
|
||||
friend class cmServerProtocol;
|
||||
friend class cmServerRequest;
|
||||
};
|
@@ -1,165 +0,0 @@
|
||||
/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
|
||||
file Copyright.txt or https://cmake.org/licensing for details. */
|
||||
#include "cmConfigure.h"
|
||||
|
||||
#include "cmServerConnection.h"
|
||||
|
||||
#include <cm3p/uv.h>
|
||||
|
||||
#include "cmServer.h"
|
||||
#include "cmServerDictionary.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
# include "io.h"
|
||||
#else
|
||||
# include <unistd.h>
|
||||
#endif
|
||||
#include <cassert>
|
||||
#include <utility>
|
||||
|
||||
cmStdIoConnection::cmStdIoConnection(
|
||||
cmConnectionBufferStrategy* bufferStrategy)
|
||||
: cmEventBasedConnection(bufferStrategy)
|
||||
{
|
||||
}
|
||||
|
||||
cm::uv_stream_ptr cmStdIoConnection::SetupStream(int file_id)
|
||||
{
|
||||
switch (uv_guess_handle(file_id)) {
|
||||
case UV_TTY: {
|
||||
cm::uv_tty_ptr tty;
|
||||
tty.init(*this->Server->GetLoop(), file_id, file_id == 0,
|
||||
static_cast<cmEventBasedConnection*>(this));
|
||||
uv_tty_set_mode(tty, UV_TTY_MODE_NORMAL);
|
||||
return { std::move(tty) };
|
||||
}
|
||||
case UV_FILE:
|
||||
if (file_id == 0) {
|
||||
return nullptr;
|
||||
}
|
||||
// Intentional fallthrough; stdin can _not_ be treated as a named
|
||||
// pipe, however stdout can be.
|
||||
CM_FALLTHROUGH;
|
||||
case UV_NAMED_PIPE: {
|
||||
cm::uv_pipe_ptr pipe;
|
||||
pipe.init(*this->Server->GetLoop(), 0,
|
||||
static_cast<cmEventBasedConnection*>(this));
|
||||
uv_pipe_open(pipe, file_id);
|
||||
return { std::move(pipe) };
|
||||
}
|
||||
default:
|
||||
assert(false && "Unable to determine stream type");
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void cmStdIoConnection::SetServer(cmServerBase* s)
|
||||
{
|
||||
cmConnection::SetServer(s);
|
||||
if (!s) {
|
||||
return;
|
||||
}
|
||||
|
||||
this->ReadStream = SetupStream(0);
|
||||
this->WriteStream = SetupStream(1);
|
||||
}
|
||||
|
||||
void shutdown_connection(uv_prepare_t* prepare)
|
||||
{
|
||||
cmStdIoConnection* connection =
|
||||
static_cast<cmStdIoConnection*>(prepare->data);
|
||||
|
||||
if (!uv_is_closing(reinterpret_cast<uv_handle_t*>(prepare))) {
|
||||
uv_close(reinterpret_cast<uv_handle_t*>(prepare),
|
||||
&cmEventBasedConnection::on_close_delete<uv_prepare_t>);
|
||||
}
|
||||
connection->OnDisconnect(0);
|
||||
}
|
||||
|
||||
bool cmStdIoConnection::OnServeStart(std::string* pString)
|
||||
{
|
||||
Server->OnConnected(this);
|
||||
if (this->ReadStream.get()) {
|
||||
uv_read_start(this->ReadStream, on_alloc_buffer, on_read);
|
||||
} else if (uv_guess_handle(0) == UV_FILE) {
|
||||
char buffer[1024];
|
||||
while (auto len = read(0, buffer, sizeof(buffer))) {
|
||||
ReadData(std::string(buffer, buffer + len));
|
||||
}
|
||||
|
||||
// We can't start the disconnect from here, add a prepare hook to do that
|
||||
// for us
|
||||
auto prepare = new uv_prepare_t();
|
||||
prepare->data = this;
|
||||
uv_prepare_init(Server->GetLoop(), prepare);
|
||||
uv_prepare_start(prepare, shutdown_connection);
|
||||
}
|
||||
return cmConnection::OnServeStart(pString);
|
||||
}
|
||||
|
||||
bool cmStdIoConnection::OnConnectionShuttingDown()
|
||||
{
|
||||
if (ReadStream.get()) {
|
||||
uv_read_stop(ReadStream);
|
||||
ReadStream->data = nullptr;
|
||||
}
|
||||
|
||||
this->ReadStream.reset();
|
||||
|
||||
cmEventBasedConnection::OnConnectionShuttingDown();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
cmServerPipeConnection::cmServerPipeConnection(const std::string& name)
|
||||
: cmPipeConnection(name, new cmServerBufferStrategy)
|
||||
{
|
||||
}
|
||||
|
||||
cmServerStdIoConnection::cmServerStdIoConnection()
|
||||
: cmStdIoConnection(new cmServerBufferStrategy)
|
||||
{
|
||||
}
|
||||
|
||||
cmConnectionBufferStrategy::~cmConnectionBufferStrategy() = default;
|
||||
|
||||
void cmConnectionBufferStrategy::clear()
|
||||
{
|
||||
}
|
||||
|
||||
std::string cmServerBufferStrategy::BufferOutMessage(
|
||||
const std::string& rawBuffer) const
|
||||
{
|
||||
return std::string("\n") + kSTART_MAGIC + std::string("\n") + rawBuffer +
|
||||
kEND_MAGIC + std::string("\n");
|
||||
}
|
||||
|
||||
std::string cmServerBufferStrategy::BufferMessage(std::string& RawReadBuffer)
|
||||
{
|
||||
for (;;) {
|
||||
auto needle = RawReadBuffer.find('\n');
|
||||
|
||||
if (needle == std::string::npos) {
|
||||
return "";
|
||||
}
|
||||
std::string line = RawReadBuffer.substr(0, needle);
|
||||
const auto ls = line.size();
|
||||
if (ls > 1 && line.at(ls - 1) == '\r') {
|
||||
line.erase(ls - 1, 1);
|
||||
}
|
||||
RawReadBuffer.erase(RawReadBuffer.begin(),
|
||||
RawReadBuffer.begin() + static_cast<long>(needle) + 1);
|
||||
if (line == kSTART_MAGIC) {
|
||||
RequestBuffer.clear();
|
||||
continue;
|
||||
}
|
||||
if (line == kEND_MAGIC) {
|
||||
std::string rtn;
|
||||
rtn.swap(this->RequestBuffer);
|
||||
return rtn;
|
||||
}
|
||||
|
||||
this->RequestBuffer += line;
|
||||
this->RequestBuffer += "\n";
|
||||
}
|
||||
}
|
@@ -1,67 +0,0 @@
|
||||
/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
|
||||
file Copyright.txt or https://cmake.org/licensing for details. */
|
||||
#pragma once
|
||||
|
||||
#include "cmConfigure.h" // IWYU pragma: keep
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "cmConnection.h"
|
||||
#include "cmPipeConnection.h"
|
||||
#include "cmUVHandlePtr.h"
|
||||
|
||||
class cmServerBase;
|
||||
|
||||
/***
|
||||
* This connection buffer strategy accepts messages in the form of
|
||||
* [== "CMake Server" ==[
|
||||
{
|
||||
... some JSON message ...
|
||||
}
|
||||
]== "CMake Server" ==]
|
||||
* and only passes on the core json; it discards the envelope.
|
||||
*/
|
||||
class cmServerBufferStrategy : public cmConnectionBufferStrategy
|
||||
{
|
||||
public:
|
||||
std::string BufferMessage(std::string& rawBuffer) override;
|
||||
std::string BufferOutMessage(const std::string& rawBuffer) const override;
|
||||
|
||||
private:
|
||||
std::string RequestBuffer;
|
||||
};
|
||||
|
||||
/***
|
||||
* Generic connection over std io interfaces -- tty
|
||||
*/
|
||||
class cmStdIoConnection : public cmEventBasedConnection
|
||||
{
|
||||
public:
|
||||
cmStdIoConnection(cmConnectionBufferStrategy* bufferStrategy);
|
||||
|
||||
void SetServer(cmServerBase* s) override;
|
||||
|
||||
bool OnConnectionShuttingDown() override;
|
||||
|
||||
bool OnServeStart(std::string* pString) override;
|
||||
|
||||
private:
|
||||
cm::uv_stream_ptr SetupStream(int file_id);
|
||||
cm::uv_stream_ptr ReadStream;
|
||||
};
|
||||
|
||||
/***
|
||||
* These specific connections use the cmake server
|
||||
* buffering strategy.
|
||||
*/
|
||||
class cmServerStdIoConnection : public cmStdIoConnection
|
||||
{
|
||||
public:
|
||||
cmServerStdIoConnection();
|
||||
};
|
||||
|
||||
class cmServerPipeConnection : public cmPipeConnection
|
||||
{
|
||||
public:
|
||||
cmServerPipeConnection(const std::string& name);
|
||||
};
|
@@ -1,67 +0,0 @@
|
||||
/* 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>
|
||||
|
||||
// Vocabulary:
|
||||
|
||||
static const std::string kDIRTY_SIGNAL = "dirty";
|
||||
static const std::string kFILE_CHANGE_SIGNAL = "fileChange";
|
||||
|
||||
static const std::string kCACHE_TYPE = "cache";
|
||||
static const std::string kCMAKE_INPUTS_TYPE = "cmakeInputs";
|
||||
static const std::string kCODE_MODEL_TYPE = "codemodel";
|
||||
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 kFILESYSTEM_WATCHERS_TYPE = "fileSystemWatchers";
|
||||
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 kCTEST_INFO_TYPE = "ctestInfo";
|
||||
|
||||
static const std::string kBUILD_FILES_KEY = "buildFiles";
|
||||
static const std::string kCACHE_ARGUMENTS_KEY = "cacheArguments";
|
||||
static const std::string kCACHE_KEY = "cache";
|
||||
static const std::string kCAPABILITIES_KEY = "capabilities";
|
||||
static const std::string kCHECK_SYSTEM_VARS_KEY = "checkSystemVars";
|
||||
static const std::string kCMAKE_ROOT_DIRECTORY_KEY = "cmakeRootDirectory";
|
||||
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";
|
||||
static const std::string kIS_EXPERIMENTAL_KEY = "isExperimental";
|
||||
static const std::string kKEYS_KEY = "keys";
|
||||
static const std::string kMAJOR_KEY = "major";
|
||||
static const std::string kMESSAGE_KEY = "message";
|
||||
static const std::string kMINOR_KEY = "minor";
|
||||
static const std::string kPLATFORM_KEY = "platform";
|
||||
static const std::string kPROGRESS_CURRENT_KEY = "progressCurrent";
|
||||
static const std::string kPROGRESS_MAXIMUM_KEY = "progressMaximum";
|
||||
static const std::string kPROGRESS_MESSAGE_KEY = "progressMessage";
|
||||
static const std::string kPROGRESS_MINIMUM_KEY = "progressMinimum";
|
||||
static const std::string kPROTOCOL_VERSION_KEY = "protocolVersion";
|
||||
static const std::string kREPLY_TO_KEY = "inReplyTo";
|
||||
static const std::string kSUPPORTED_PROTOCOL_VERSIONS =
|
||||
"supportedProtocolVersions";
|
||||
static const std::string kTITLE_KEY = "title";
|
||||
static const std::string kTOOLSET_KEY = "toolset";
|
||||
static const std::string kTRACE_EXPAND_KEY = "traceExpand";
|
||||
static const std::string kTRACE_KEY = "trace";
|
||||
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 kWATCHED_DIRECTORIES_KEY = "watchedDirectories";
|
||||
static const std::string kWATCHED_FILES_KEY = "watchedFiles";
|
||||
|
||||
static const std::string kSTART_MAGIC = "[== \"CMake Server\" ==[";
|
||||
static const std::string kEND_MAGIC = "]== \"CMake Server\" ==]";
|
||||
|
||||
static const std::string kRENAME_PROPERTY_VALUE = "rename";
|
||||
static const std::string kCHANGE_PROPERTY_VALUE = "change";
|
@@ -1,760 +0,0 @@
|
||||
/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
|
||||
file Copyright.txt or https://cmake.org/licensing for details. */
|
||||
#include "cmServerProtocol.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <cm/memory>
|
||||
#include <cmext/algorithm>
|
||||
|
||||
#include <cm3p/uv.h>
|
||||
|
||||
#include "cmExternalMakefileProjectGenerator.h"
|
||||
#include "cmFileMonitor.h"
|
||||
#include "cmGlobalGenerator.h"
|
||||
#include "cmJsonObjectDictionary.h"
|
||||
#include "cmJsonObjects.h"
|
||||
#include "cmMessageType.h"
|
||||
#include "cmProperty.h"
|
||||
#include "cmServer.h"
|
||||
#include "cmServerDictionary.h"
|
||||
#include "cmState.h"
|
||||
#include "cmSystemTools.h"
|
||||
#include "cmake.h"
|
||||
|
||||
// Get rid of some windows macros:
|
||||
#undef max
|
||||
|
||||
namespace {
|
||||
|
||||
std::vector<std::string> toStringList(const Json::Value& in)
|
||||
{
|
||||
std::vector<std::string> result;
|
||||
for (auto const& it : in) {
|
||||
result.push_back(it.asString());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
cmServerRequest::cmServerRequest(cmServer* server, cmConnection* connection,
|
||||
std::string t, std::string c, Json::Value d)
|
||||
: Type(std::move(t))
|
||||
, Cookie(std::move(c))
|
||||
, Data(std::move(d))
|
||||
, Connection(connection)
|
||||
, m_Server(server)
|
||||
{
|
||||
}
|
||||
|
||||
void cmServerRequest::ReportProgress(int min, int current, int max,
|
||||
const std::string& message) const
|
||||
{
|
||||
this->m_Server->WriteProgress(*this, min, current, max, message);
|
||||
}
|
||||
|
||||
void cmServerRequest::ReportMessage(const std::string& message,
|
||||
const std::string& title) const
|
||||
{
|
||||
m_Server->WriteMessage(*this, message, title);
|
||||
}
|
||||
|
||||
cmServerResponse cmServerRequest::Reply(const Json::Value& data) const
|
||||
{
|
||||
cmServerResponse response(*this);
|
||||
response.SetData(data);
|
||||
return response;
|
||||
}
|
||||
|
||||
cmServerResponse cmServerRequest::ReportError(const std::string& message) const
|
||||
{
|
||||
cmServerResponse response(*this);
|
||||
response.SetError(message);
|
||||
return response;
|
||||
}
|
||||
|
||||
cmServerResponse::cmServerResponse(const cmServerRequest& request)
|
||||
: Type(request.Type)
|
||||
, Cookie(request.Cookie)
|
||||
{
|
||||
}
|
||||
|
||||
void cmServerResponse::SetData(const Json::Value& data)
|
||||
{
|
||||
assert(this->m_Payload == PAYLOAD_UNKNOWN);
|
||||
if (!data[kCOOKIE_KEY].isNull() || !data[kTYPE_KEY].isNull()) {
|
||||
this->SetError("Response contains cookie or type field.");
|
||||
return;
|
||||
}
|
||||
this->m_Payload = PAYLOAD_DATA;
|
||||
this->m_Data = data;
|
||||
}
|
||||
|
||||
void cmServerResponse::SetError(const std::string& message)
|
||||
{
|
||||
assert(this->m_Payload == PAYLOAD_UNKNOWN);
|
||||
this->m_Payload = PAYLOAD_ERROR;
|
||||
this->m_ErrorMessage = message;
|
||||
}
|
||||
|
||||
bool cmServerResponse::IsComplete() const
|
||||
{
|
||||
return this->m_Payload != PAYLOAD_UNKNOWN;
|
||||
}
|
||||
|
||||
bool cmServerResponse::IsError() const
|
||||
{
|
||||
assert(this->m_Payload != PAYLOAD_UNKNOWN);
|
||||
return this->m_Payload == PAYLOAD_ERROR;
|
||||
}
|
||||
|
||||
std::string cmServerResponse::ErrorMessage() const
|
||||
{
|
||||
if (this->m_Payload == PAYLOAD_ERROR) {
|
||||
return this->m_ErrorMessage;
|
||||
}
|
||||
return std::string();
|
||||
}
|
||||
|
||||
Json::Value cmServerResponse::Data() const
|
||||
{
|
||||
assert(this->m_Payload != PAYLOAD_UNKNOWN);
|
||||
return this->m_Data;
|
||||
}
|
||||
|
||||
bool cmServerProtocol::Activate(cmServer* server,
|
||||
const cmServerRequest& request,
|
||||
std::string* errorMessage)
|
||||
{
|
||||
assert(server);
|
||||
this->m_Server = server;
|
||||
this->m_CMakeInstance =
|
||||
cm::make_unique<cmake>(cmake::RoleProject, cmState::Project);
|
||||
this->m_WarnUnused = false;
|
||||
const bool result = this->DoActivate(request, errorMessage);
|
||||
if (!result) {
|
||||
this->m_CMakeInstance = nullptr;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
cmFileMonitor* cmServerProtocol::FileMonitor() const
|
||||
{
|
||||
return this->m_Server ? this->m_Server->FileMonitor() : nullptr;
|
||||
}
|
||||
|
||||
void cmServerProtocol::SendSignal(const std::string& name,
|
||||
const Json::Value& data) const
|
||||
{
|
||||
if (this->m_Server) {
|
||||
this->m_Server->WriteSignal(name, data);
|
||||
}
|
||||
}
|
||||
|
||||
cmake* cmServerProtocol::CMakeInstance() const
|
||||
{
|
||||
return this->m_CMakeInstance.get();
|
||||
}
|
||||
|
||||
bool cmServerProtocol::DoActivate(const cmServerRequest& /*request*/,
|
||||
std::string* /*errorMessage*/)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
std::pair<int, int> cmServerProtocol1::ProtocolVersion() const
|
||||
{
|
||||
return { 1, 2 };
|
||||
}
|
||||
|
||||
static void setErrorMessage(std::string* errorMessage, const std::string& text)
|
||||
{
|
||||
if (errorMessage) {
|
||||
*errorMessage = text;
|
||||
}
|
||||
}
|
||||
|
||||
static bool getOrTestHomeDirectory(cmState* state, std::string& value,
|
||||
std::string* errorMessage)
|
||||
{
|
||||
const std::string cachedValue =
|
||||
*state->GetCacheEntryValue("CMAKE_HOME_DIRECTORY");
|
||||
if (value.empty()) {
|
||||
value = cachedValue;
|
||||
return true;
|
||||
}
|
||||
const std::string suffix = "/CMakeLists.txt";
|
||||
const std::string cachedValueCML = cachedValue + suffix;
|
||||
const std::string valueCML = value + suffix;
|
||||
if (!cmSystemTools::SameFile(valueCML, cachedValueCML)) {
|
||||
setErrorMessage(errorMessage,
|
||||
std::string("\"CMAKE_HOME_DIRECTORY\" is set but "
|
||||
"incompatible with configured "
|
||||
"source directory value."));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool getOrTestValue(cmState* state, const std::string& key,
|
||||
std::string& value,
|
||||
const std::string& keyDescription,
|
||||
std::string* errorMessage)
|
||||
{
|
||||
const std::string cachedValue = state->GetSafeCacheEntryValue(key);
|
||||
if (value.empty()) {
|
||||
value = cachedValue;
|
||||
}
|
||||
if (!cachedValue.empty() && cachedValue != value) {
|
||||
setErrorMessage(errorMessage,
|
||||
std::string("\"") + key +
|
||||
"\" is set but incompatible with configured " +
|
||||
keyDescription + " value.");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool cmServerProtocol1::DoActivate(const cmServerRequest& request,
|
||||
std::string* errorMessage)
|
||||
{
|
||||
std::string sourceDirectory = request.Data[kSOURCE_DIRECTORY_KEY].asString();
|
||||
std::string buildDirectory = request.Data[kBUILD_DIRECTORY_KEY].asString();
|
||||
std::string generator = request.Data[kGENERATOR_KEY].asString();
|
||||
std::string extraGenerator = request.Data[kEXTRA_GENERATOR_KEY].asString();
|
||||
std::string toolset = request.Data[kTOOLSET_KEY].asString();
|
||||
std::string platform = request.Data[kPLATFORM_KEY].asString();
|
||||
|
||||
// normalize source and build directory
|
||||
if (!sourceDirectory.empty()) {
|
||||
sourceDirectory = cmSystemTools::CollapseFullPath(sourceDirectory);
|
||||
cmSystemTools::ConvertToUnixSlashes(sourceDirectory);
|
||||
}
|
||||
if (!buildDirectory.empty()) {
|
||||
buildDirectory = cmSystemTools::CollapseFullPath(buildDirectory);
|
||||
cmSystemTools::ConvertToUnixSlashes(buildDirectory);
|
||||
}
|
||||
|
||||
if (buildDirectory.empty()) {
|
||||
setErrorMessage(errorMessage,
|
||||
std::string("\"") + kBUILD_DIRECTORY_KEY +
|
||||
"\" is missing.");
|
||||
return false;
|
||||
}
|
||||
|
||||
cmake* cm = CMakeInstance();
|
||||
if (cmSystemTools::PathExists(buildDirectory)) {
|
||||
if (!cmSystemTools::FileIsDirectory(buildDirectory)) {
|
||||
setErrorMessage(errorMessage,
|
||||
std::string("\"") + kBUILD_DIRECTORY_KEY +
|
||||
"\" exists but is not a directory.");
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::string cachePath = cmake::FindCacheFile(buildDirectory);
|
||||
if (cm->LoadCache(cachePath)) {
|
||||
cmState* state = cm->GetState();
|
||||
|
||||
// Check generator:
|
||||
if (!getOrTestValue(state, "CMAKE_GENERATOR", generator, "generator",
|
||||
errorMessage)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// check extra generator:
|
||||
if (!getOrTestValue(state, "CMAKE_EXTRA_GENERATOR", extraGenerator,
|
||||
"extra generator", errorMessage)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// check sourcedir:
|
||||
if (!getOrTestHomeDirectory(state, sourceDirectory, errorMessage)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// check toolset:
|
||||
if (!getOrTestValue(state, "CMAKE_GENERATOR_TOOLSET", toolset, "toolset",
|
||||
errorMessage)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// check platform:
|
||||
if (!getOrTestValue(state, "CMAKE_GENERATOR_PLATFORM", platform,
|
||||
"platform", errorMessage)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (sourceDirectory.empty()) {
|
||||
setErrorMessage(errorMessage,
|
||||
std::string("\"") + kSOURCE_DIRECTORY_KEY +
|
||||
"\" is unset but required.");
|
||||
return false;
|
||||
}
|
||||
if (!cmSystemTools::FileIsDirectory(sourceDirectory)) {
|
||||
setErrorMessage(errorMessage,
|
||||
std::string("\"") + kSOURCE_DIRECTORY_KEY +
|
||||
"\" is not a directory.");
|
||||
return false;
|
||||
}
|
||||
if (generator.empty()) {
|
||||
setErrorMessage(errorMessage,
|
||||
std::string("\"") + kGENERATOR_KEY +
|
||||
"\" is unset but required.");
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<cmake::GeneratorInfo> generators;
|
||||
cm->GetRegisteredGenerators(generators);
|
||||
auto baseIt = std::find_if(generators.begin(), generators.end(),
|
||||
[&generator](const cmake::GeneratorInfo& info) {
|
||||
return info.name == generator;
|
||||
});
|
||||
if (baseIt == generators.end()) {
|
||||
setErrorMessage(errorMessage,
|
||||
std::string("Generator \"") + generator +
|
||||
"\" not supported.");
|
||||
return false;
|
||||
}
|
||||
auto extraIt = std::find_if(
|
||||
generators.begin(), generators.end(),
|
||||
[&generator, &extraGenerator](const cmake::GeneratorInfo& info) {
|
||||
return info.baseName == generator && info.extraName == extraGenerator;
|
||||
});
|
||||
if (extraIt == generators.end()) {
|
||||
setErrorMessage(errorMessage,
|
||||
std::string("The combination of generator \"" + generator +
|
||||
"\" and extra generator \"" + extraGenerator +
|
||||
"\" is not supported."));
|
||||
return false;
|
||||
}
|
||||
if (!extraIt->supportsToolset && !toolset.empty()) {
|
||||
setErrorMessage(errorMessage,
|
||||
std::string("Toolset was provided but is not supported by "
|
||||
"the requested generator."));
|
||||
return false;
|
||||
}
|
||||
if (!extraIt->supportsPlatform && !platform.empty()) {
|
||||
setErrorMessage(errorMessage,
|
||||
std::string("Platform was provided but is not supported "
|
||||
"by the requested generator."));
|
||||
return false;
|
||||
}
|
||||
|
||||
this->GeneratorInfo =
|
||||
GeneratorInformation(generator, extraGenerator, toolset, platform,
|
||||
sourceDirectory, buildDirectory);
|
||||
|
||||
this->m_State = STATE_ACTIVE;
|
||||
return true;
|
||||
}
|
||||
|
||||
void cmServerProtocol1::HandleCMakeFileChanges(const std::string& path,
|
||||
int event, int status)
|
||||
{
|
||||
assert(status == 0);
|
||||
static_cast<void>(status);
|
||||
|
||||
if (!m_isDirty) {
|
||||
m_isDirty = true;
|
||||
SendSignal(kDIRTY_SIGNAL, Json::objectValue);
|
||||
}
|
||||
Json::Value obj = Json::objectValue;
|
||||
obj[kPATH_KEY] = path;
|
||||
Json::Value properties = Json::arrayValue;
|
||||
if (event & UV_RENAME) {
|
||||
properties.append(kRENAME_PROPERTY_VALUE);
|
||||
}
|
||||
if (event & UV_CHANGE) {
|
||||
properties.append(kCHANGE_PROPERTY_VALUE);
|
||||
}
|
||||
|
||||
obj[kPROPERTIES_KEY] = properties;
|
||||
SendSignal(kFILE_CHANGE_SIGNAL, obj);
|
||||
}
|
||||
|
||||
cmServerResponse cmServerProtocol1::Process(const cmServerRequest& request)
|
||||
{
|
||||
assert(this->m_State >= STATE_ACTIVE);
|
||||
|
||||
if (request.Type == kCACHE_TYPE) {
|
||||
return this->ProcessCache(request);
|
||||
}
|
||||
if (request.Type == kCMAKE_INPUTS_TYPE) {
|
||||
return this->ProcessCMakeInputs(request);
|
||||
}
|
||||
if (request.Type == kCODE_MODEL_TYPE) {
|
||||
return this->ProcessCodeModel(request);
|
||||
}
|
||||
if (request.Type == kCOMPUTE_TYPE) {
|
||||
return this->ProcessCompute(request);
|
||||
}
|
||||
if (request.Type == kCONFIGURE_TYPE) {
|
||||
return this->ProcessConfigure(request);
|
||||
}
|
||||
if (request.Type == kFILESYSTEM_WATCHERS_TYPE) {
|
||||
return this->ProcessFileSystemWatchers(request);
|
||||
}
|
||||
if (request.Type == kGLOBAL_SETTINGS_TYPE) {
|
||||
return this->ProcessGlobalSettings(request);
|
||||
}
|
||||
if (request.Type == kSET_GLOBAL_SETTINGS_TYPE) {
|
||||
return this->ProcessSetGlobalSettings(request);
|
||||
}
|
||||
if (request.Type == kCTEST_INFO_TYPE) {
|
||||
return this->ProcessCTests(request);
|
||||
}
|
||||
|
||||
return request.ReportError("Unknown command!");
|
||||
}
|
||||
|
||||
bool cmServerProtocol1::IsExperimental() const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
cmServerResponse cmServerProtocol1::ProcessCache(
|
||||
const cmServerRequest& request)
|
||||
{
|
||||
cmState* state = this->CMakeInstance()->GetState();
|
||||
|
||||
Json::Value result = Json::objectValue;
|
||||
|
||||
std::vector<std::string> allKeys = state->GetCacheEntryKeys();
|
||||
|
||||
Json::Value list = Json::arrayValue;
|
||||
std::vector<std::string> keys = toStringList(request.Data[kKEYS_KEY]);
|
||||
if (keys.empty()) {
|
||||
keys = allKeys;
|
||||
} else {
|
||||
for (auto const& i : keys) {
|
||||
if (!cm::contains(allKeys, i)) {
|
||||
return request.ReportError("Key \"" + i + "\" not found in cache.");
|
||||
}
|
||||
}
|
||||
}
|
||||
std::sort(keys.begin(), keys.end());
|
||||
for (auto const& key : keys) {
|
||||
Json::Value entry = Json::objectValue;
|
||||
entry[kKEY_KEY] = key;
|
||||
entry[kTYPE_KEY] =
|
||||
cmState::CacheEntryTypeToString(state->GetCacheEntryType(key));
|
||||
entry[kVALUE_KEY] = *state->GetCacheEntryValue(key);
|
||||
|
||||
Json::Value props = Json::objectValue;
|
||||
bool haveProperties = false;
|
||||
for (auto const& prop : state->GetCacheEntryPropertyList(key)) {
|
||||
haveProperties = true;
|
||||
props[prop] = *state->GetCacheEntryProperty(key, prop);
|
||||
}
|
||||
if (haveProperties) {
|
||||
entry[kPROPERTIES_KEY] = props;
|
||||
}
|
||||
|
||||
list.append(entry);
|
||||
}
|
||||
|
||||
result[kCACHE_KEY] = list;
|
||||
return request.Reply(result);
|
||||
}
|
||||
|
||||
cmServerResponse cmServerProtocol1::ProcessCMakeInputs(
|
||||
const cmServerRequest& request)
|
||||
{
|
||||
if (this->m_State < STATE_CONFIGURED) {
|
||||
return request.ReportError("This instance was not yet configured.");
|
||||
}
|
||||
|
||||
const cmake* cm = this->CMakeInstance();
|
||||
const std::string cmakeRootDir = cmSystemTools::GetCMakeRoot();
|
||||
const std::string& sourceDir = cm->GetHomeDirectory();
|
||||
|
||||
Json::Value result = Json::objectValue;
|
||||
result[kSOURCE_DIRECTORY_KEY] = sourceDir;
|
||||
result[kCMAKE_ROOT_DIRECTORY_KEY] = cmakeRootDir;
|
||||
result[kBUILD_FILES_KEY] = cmDumpCMakeInputs(cm);
|
||||
return request.Reply(result);
|
||||
}
|
||||
|
||||
cmServerResponse cmServerProtocol1::ProcessCodeModel(
|
||||
const cmServerRequest& request)
|
||||
{
|
||||
if (this->m_State != STATE_COMPUTED) {
|
||||
return request.ReportError("No build system was generated yet.");
|
||||
}
|
||||
|
||||
return request.Reply(cmDumpCodeModel(this->CMakeInstance()));
|
||||
}
|
||||
|
||||
cmServerResponse cmServerProtocol1::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::ProcessConfigure(
|
||||
const cmServerRequest& request)
|
||||
{
|
||||
if (this->m_State == STATE_INACTIVE) {
|
||||
return request.ReportError("This instance is inactive.");
|
||||
}
|
||||
|
||||
FileMonitor()->StopMonitoring();
|
||||
|
||||
std::string errorMessage;
|
||||
cmake* cm = this->CMakeInstance();
|
||||
this->GeneratorInfo.SetupGenerator(cm, &errorMessage);
|
||||
if (!errorMessage.empty()) {
|
||||
return request.ReportError(errorMessage);
|
||||
}
|
||||
|
||||
// Make sure the types of cacheArguments matches (if given):
|
||||
std::vector<std::string> cacheArgs = { "unused" };
|
||||
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 const& arg : passedArgs) {
|
||||
if (!arg.isString()) {
|
||||
cacheArgumentsError = true;
|
||||
break;
|
||||
}
|
||||
cacheArgs.push_back(arg.asString());
|
||||
}
|
||||
} else {
|
||||
cacheArgumentsError = true;
|
||||
}
|
||||
}
|
||||
if (cacheArgumentsError) {
|
||||
request.ReportError(
|
||||
"cacheArguments must be unset, a string or an array of strings.");
|
||||
}
|
||||
|
||||
std::string sourceDir = cm->GetHomeDirectory();
|
||||
const std::string buildDir = cm->GetHomeOutputDirectory();
|
||||
|
||||
cmGlobalGenerator* gg = cm->GetGlobalGenerator();
|
||||
|
||||
if (buildDir.empty()) {
|
||||
return request.ReportError("No build directory set via Handshake.");
|
||||
}
|
||||
|
||||
if (cm->LoadCache(buildDir)) {
|
||||
// build directory has been set up before
|
||||
cmProp cachedSourceDir =
|
||||
cm->GetState()->GetInitializedCacheValue("CMAKE_HOME_DIRECTORY");
|
||||
if (!cachedSourceDir) {
|
||||
return request.ReportError("No CMAKE_HOME_DIRECTORY found in cache.");
|
||||
}
|
||||
if (sourceDir.empty()) {
|
||||
sourceDir = *cachedSourceDir;
|
||||
cm->SetHomeDirectory(sourceDir);
|
||||
}
|
||||
|
||||
cmProp cachedGenerator =
|
||||
cm->GetState()->GetInitializedCacheValue("CMAKE_GENERATOR");
|
||||
if (cachedGenerator) {
|
||||
if (gg && gg->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.");
|
||||
}
|
||||
}
|
||||
|
||||
cmSystemTools::ResetErrorOccuredFlag(); // Reset error state
|
||||
|
||||
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();
|
||||
cm->IssueMessage(
|
||||
MessageType::DEPRECATION_WARNING,
|
||||
"The 'cmake-server(7)' is deprecated. "
|
||||
"Please port clients to use the 'cmake-file-api(7)' instead.");
|
||||
if (ret < 0) {
|
||||
return request.ReportError("Configuration failed.");
|
||||
}
|
||||
|
||||
std::vector<std::string> toWatchList;
|
||||
cmGetCMakeInputs(gg, std::string(), buildDir, nullptr, &toWatchList,
|
||||
nullptr);
|
||||
|
||||
FileMonitor()->MonitorPaths(toWatchList,
|
||||
[this](const std::string& p, int e, int s) {
|
||||
this->HandleCMakeFileChanges(p, e, s);
|
||||
});
|
||||
|
||||
m_State = STATE_CONFIGURED;
|
||||
m_isDirty = false;
|
||||
return request.Reply(Json::Value());
|
||||
}
|
||||
|
||||
cmServerResponse cmServerProtocol1::ProcessGlobalSettings(
|
||||
const cmServerRequest& request)
|
||||
{
|
||||
cmake* cm = this->CMakeInstance();
|
||||
Json::Value obj = Json::objectValue;
|
||||
|
||||
// Capabilities information:
|
||||
obj[kCAPABILITIES_KEY] = cm->ReportCapabilitiesJson();
|
||||
|
||||
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] = m_WarnUnused;
|
||||
obj[kWARN_UNUSED_CLI_KEY] = cm->GetWarnUnusedCli();
|
||||
obj[kCHECK_SYSTEM_VARS_KEY] = cm->GetCheckSystemVars();
|
||||
|
||||
obj[kSOURCE_DIRECTORY_KEY] = this->GeneratorInfo.SourceDirectory;
|
||||
obj[kBUILD_DIRECTORY_KEY] = this->GeneratorInfo.BuildDirectory;
|
||||
|
||||
// Currently used generator:
|
||||
obj[kGENERATOR_KEY] = this->GeneratorInfo.GeneratorName;
|
||||
obj[kEXTRA_GENERATOR_KEY] = this->GeneratorInfo.ExtraGeneratorName;
|
||||
|
||||
return request.Reply(obj);
|
||||
}
|
||||
|
||||
static void setBool(const cmServerRequest& request, const std::string& key,
|
||||
std::function<void(bool)> const& setter)
|
||||
{
|
||||
if (request.Data[key].isNull()) {
|
||||
return;
|
||||
}
|
||||
setter(request.Data[key].asBool());
|
||||
}
|
||||
|
||||
cmServerResponse cmServerProtocol1::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 (std::string const& 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, [this](bool e) { m_WarnUnused = 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());
|
||||
}
|
||||
|
||||
cmServerResponse cmServerProtocol1::ProcessFileSystemWatchers(
|
||||
const cmServerRequest& request)
|
||||
{
|
||||
const cmFileMonitor* const fm = FileMonitor();
|
||||
Json::Value result = Json::objectValue;
|
||||
Json::Value files = Json::arrayValue;
|
||||
for (auto const& f : fm->WatchedFiles()) {
|
||||
files.append(f);
|
||||
}
|
||||
Json::Value directories = Json::arrayValue;
|
||||
for (auto const& d : fm->WatchedDirectories()) {
|
||||
directories.append(d);
|
||||
}
|
||||
result[kWATCHED_FILES_KEY] = files;
|
||||
result[kWATCHED_DIRECTORIES_KEY] = directories;
|
||||
|
||||
return request.Reply(result);
|
||||
}
|
||||
|
||||
cmServerResponse cmServerProtocol1::ProcessCTests(
|
||||
const cmServerRequest& request)
|
||||
{
|
||||
if (this->m_State < STATE_COMPUTED) {
|
||||
return request.ReportError("This instance was not yet computed.");
|
||||
}
|
||||
|
||||
return request.Reply(cmDumpCTestInfo(this->CMakeInstance()));
|
||||
}
|
||||
|
||||
cmServerProtocol1::GeneratorInformation::GeneratorInformation(
|
||||
std::string generatorName, std::string extraGeneratorName,
|
||||
std::string toolset, std::string platform, std::string sourceDirectory,
|
||||
std::string buildDirectory)
|
||||
: GeneratorName(std::move(generatorName))
|
||||
, ExtraGeneratorName(std::move(extraGeneratorName))
|
||||
, Toolset(std::move(toolset))
|
||||
, Platform(std::move(platform))
|
||||
, SourceDirectory(std::move(sourceDirectory))
|
||||
, BuildDirectory(std::move(buildDirectory))
|
||||
{
|
||||
}
|
||||
|
||||
void cmServerProtocol1::GeneratorInformation::SetupGenerator(
|
||||
cmake* cm, std::string* errorMessage)
|
||||
{
|
||||
const std::string fullGeneratorName =
|
||||
cmExternalMakefileProjectGenerator::CreateFullGeneratorName(
|
||||
GeneratorName, ExtraGeneratorName);
|
||||
|
||||
cm->SetHomeDirectory(SourceDirectory);
|
||||
cm->SetHomeOutputDirectory(BuildDirectory);
|
||||
|
||||
auto gg = cm->CreateGlobalGenerator(fullGeneratorName);
|
||||
if (!gg) {
|
||||
setErrorMessage(
|
||||
errorMessage,
|
||||
std::string("Could not set up the requested combination of \"") +
|
||||
kGENERATOR_KEY + "\" and \"" + kEXTRA_GENERATOR_KEY + "\"");
|
||||
return;
|
||||
}
|
||||
|
||||
cm->SetGlobalGenerator(std::move(gg));
|
||||
|
||||
cm->SetGeneratorToolset(Toolset);
|
||||
cm->SetGeneratorPlatform(Platform);
|
||||
}
|
@@ -1,162 +0,0 @@
|
||||
/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
|
||||
file Copyright.txt or https://cmake.org/licensing for details. */
|
||||
#pragma once
|
||||
|
||||
#include "cmConfigure.h" // IWYU pragma: keep
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#include <cm3p/json/value.h>
|
||||
|
||||
#include "cmake.h"
|
||||
|
||||
class cmConnection;
|
||||
class cmFileMonitor;
|
||||
class cmServer;
|
||||
class cmServerRequest;
|
||||
|
||||
class cmServerResponse
|
||||
{
|
||||
public:
|
||||
explicit cmServerResponse(const cmServerRequest& request);
|
||||
|
||||
void SetData(const Json::Value& data);
|
||||
void SetError(const std::string& message);
|
||||
|
||||
bool IsComplete() const;
|
||||
bool IsError() const;
|
||||
std::string ErrorMessage() const;
|
||||
Json::Value Data() const;
|
||||
|
||||
const std::string Type;
|
||||
const std::string Cookie;
|
||||
|
||||
private:
|
||||
enum PayLoad
|
||||
{
|
||||
PAYLOAD_UNKNOWN,
|
||||
PAYLOAD_ERROR,
|
||||
PAYLOAD_DATA
|
||||
};
|
||||
PayLoad m_Payload = PAYLOAD_UNKNOWN;
|
||||
std::string m_ErrorMessage;
|
||||
Json::Value m_Data;
|
||||
};
|
||||
|
||||
class cmServerRequest
|
||||
{
|
||||
public:
|
||||
cmServerResponse Reply(const Json::Value& data) const;
|
||||
cmServerResponse ReportError(const std::string& message) const;
|
||||
|
||||
const std::string Type;
|
||||
const std::string Cookie;
|
||||
const Json::Value Data;
|
||||
cmConnection* Connection;
|
||||
|
||||
private:
|
||||
cmServerRequest(cmServer* server, cmConnection* connection, std::string t,
|
||||
std::string c, Json::Value d);
|
||||
|
||||
void ReportProgress(int min, int current, int max,
|
||||
const std::string& message) const;
|
||||
void ReportMessage(const std::string& message,
|
||||
const std::string& title) const;
|
||||
|
||||
cmServer* m_Server;
|
||||
|
||||
friend class cmServer;
|
||||
};
|
||||
|
||||
class cmServerProtocol
|
||||
{
|
||||
public:
|
||||
cmServerProtocol() = default;
|
||||
virtual ~cmServerProtocol() = default;
|
||||
|
||||
cmServerProtocol(cmServerProtocol const&) = delete;
|
||||
cmServerProtocol& operator=(cmServerProtocol const&) = delete;
|
||||
|
||||
virtual std::pair<int, int> ProtocolVersion() const = 0;
|
||||
virtual bool IsExperimental() const = 0;
|
||||
virtual cmServerResponse Process(const cmServerRequest& request) = 0;
|
||||
|
||||
bool Activate(cmServer* server, const cmServerRequest& request,
|
||||
std::string* errorMessage);
|
||||
|
||||
cmFileMonitor* FileMonitor() const;
|
||||
void SendSignal(const std::string& name, const Json::Value& data) const;
|
||||
|
||||
protected:
|
||||
cmake* CMakeInstance() const;
|
||||
// Implement protocol specific activation tasks here. Called from Activate().
|
||||
virtual bool DoActivate(const cmServerRequest& request,
|
||||
std::string* errorMessage);
|
||||
bool m_WarnUnused = false; // storage for legacy option
|
||||
|
||||
private:
|
||||
std::unique_ptr<cmake> m_CMakeInstance;
|
||||
cmServer* m_Server = nullptr; // not owned!
|
||||
|
||||
friend class cmServer;
|
||||
};
|
||||
|
||||
class cmServerProtocol1 : public cmServerProtocol
|
||||
{
|
||||
public:
|
||||
std::pair<int, int> ProtocolVersion() const override;
|
||||
bool IsExperimental() const override;
|
||||
cmServerResponse Process(const cmServerRequest& request) override;
|
||||
|
||||
private:
|
||||
bool DoActivate(const cmServerRequest& request,
|
||||
std::string* errorMessage) override;
|
||||
|
||||
void HandleCMakeFileChanges(const std::string& path, int event, int status);
|
||||
|
||||
// Handle requests:
|
||||
cmServerResponse ProcessCache(const cmServerRequest& request);
|
||||
cmServerResponse ProcessCMakeInputs(const cmServerRequest& request);
|
||||
cmServerResponse ProcessCodeModel(const cmServerRequest& request);
|
||||
cmServerResponse ProcessCompute(const cmServerRequest& request);
|
||||
cmServerResponse ProcessConfigure(const cmServerRequest& request);
|
||||
cmServerResponse ProcessGlobalSettings(const cmServerRequest& request);
|
||||
cmServerResponse ProcessSetGlobalSettings(const cmServerRequest& request);
|
||||
cmServerResponse ProcessFileSystemWatchers(const cmServerRequest& request);
|
||||
cmServerResponse ProcessCTests(const cmServerRequest& request);
|
||||
|
||||
enum State
|
||||
{
|
||||
STATE_INACTIVE,
|
||||
STATE_ACTIVE,
|
||||
STATE_CONFIGURED,
|
||||
STATE_COMPUTED
|
||||
};
|
||||
State m_State = STATE_INACTIVE;
|
||||
|
||||
bool m_isDirty = false;
|
||||
|
||||
struct GeneratorInformation
|
||||
{
|
||||
public:
|
||||
GeneratorInformation() = default;
|
||||
GeneratorInformation(std::string generatorName,
|
||||
std::string extraGeneratorName, std::string toolset,
|
||||
std::string platform, std::string sourceDirectory,
|
||||
std::string buildDirectory);
|
||||
|
||||
void SetupGenerator(cmake* cm, std::string* errorMessage);
|
||||
|
||||
std::string GeneratorName;
|
||||
std::string ExtraGeneratorName;
|
||||
std::string Toolset;
|
||||
std::string Platform;
|
||||
|
||||
std::string SourceDirectory;
|
||||
std::string BuildDirectory;
|
||||
};
|
||||
|
||||
GeneratorInformation GeneratorInfo;
|
||||
};
|
@@ -263,7 +263,7 @@ Json::Value cmake::ReportCapabilitiesJson() const
|
||||
}
|
||||
obj["generators"] = generators;
|
||||
obj["fileApi"] = cmFileAPI::ReportCapabilities();
|
||||
obj["serverMode"] = true;
|
||||
obj["serverMode"] = false;
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
@@ -28,8 +28,6 @@
|
||||
#if !defined(CMAKE_BOOTSTRAP)
|
||||
# include "cmDependsFortran.h" // For -E cmake_copy_f90_mod callback.
|
||||
# include "cmFileTime.h"
|
||||
# include "cmServer.h"
|
||||
# include "cmServerConnection.h"
|
||||
|
||||
# include "bindexplib.h"
|
||||
#endif
|
||||
@@ -59,8 +57,6 @@
|
||||
#include "cmsys/Process.h"
|
||||
#include "cmsys/Terminal.h"
|
||||
|
||||
class cmConnection;
|
||||
|
||||
int cmcmd_cmake_ninja_depends(std::vector<std::string>::const_iterator argBeg,
|
||||
std::vector<std::string>::const_iterator argEnd);
|
||||
int cmcmd_cmake_ninja_dyndep(std::vector<std::string>::const_iterator argBeg,
|
||||
@@ -119,7 +115,6 @@ void CMakeCommandUsage(const char* program)
|
||||
"(on one volume)\n"
|
||||
<< " rm [-rRf] <file/dir>... - remove files or directories, use -f to "
|
||||
"force it, r or R to remove directories and their contents recursively\n"
|
||||
<< " server - start cmake in server mode\n"
|
||||
<< " sleep <number>... - sleep for given number of seconds\n"
|
||||
<< " tar [cxt][vf][zjJ] file.tar [file/dir1 file/dir2 ...]\n"
|
||||
<< " - create or extract a tar or zip archive\n"
|
||||
@@ -1351,47 +1346,8 @@ int cmcmd::ExecuteCMakeCommand(std::vector<std::string> const& args)
|
||||
}
|
||||
|
||||
if (args[1] == "server") {
|
||||
const std::string pipePrefix = "--pipe=";
|
||||
bool supportExperimental = false;
|
||||
bool isDebug = false;
|
||||
std::string pipe;
|
||||
|
||||
for (auto const& arg : cmMakeRange(args).advance(2)) {
|
||||
if (arg == "--experimental") {
|
||||
supportExperimental = true;
|
||||
} else if (arg == "--debug") {
|
||||
pipe.clear();
|
||||
isDebug = true;
|
||||
} else if (cmHasPrefix(arg, pipePrefix)) {
|
||||
isDebug = false;
|
||||
pipe = arg.substr(pipePrefix.size());
|
||||
if (pipe.empty()) {
|
||||
cmSystemTools::Error("No pipe given after --pipe=");
|
||||
return 2;
|
||||
}
|
||||
} else {
|
||||
cmSystemTools::Error("Unknown argument for server mode");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
#if !defined(CMAKE_BOOTSTRAP)
|
||||
cmConnection* conn;
|
||||
if (isDebug) {
|
||||
conn = new cmServerStdIoConnection;
|
||||
} else {
|
||||
conn = new cmServerPipeConnection(pipe);
|
||||
}
|
||||
cmServer server(conn, supportExperimental);
|
||||
std::string errorMessage;
|
||||
if (server.Serve(&errorMessage)) {
|
||||
return 0;
|
||||
}
|
||||
cmSystemTools::Error(errorMessage);
|
||||
#else
|
||||
static_cast<void>(supportExperimental);
|
||||
static_cast<void>(isDebug);
|
||||
cmSystemTools::Error("CMake was not built with server mode enabled");
|
||||
#endif
|
||||
cmSystemTools::Error(
|
||||
"CMake server mode has been removed in favor of the file-api.");
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
@@ -240,8 +240,6 @@ if(BUILD_TESTING)
|
||||
|
||||
if(NOT CMake_TEST_EXTERNAL_CMAKE)
|
||||
add_subdirectory(CMakeLib)
|
||||
|
||||
add_subdirectory(CMakeServerLib)
|
||||
endif()
|
||||
add_subdirectory(CMakeOnly)
|
||||
add_subdirectory(RunCMake)
|
||||
@@ -2911,13 +2909,6 @@ if(BUILD_TESTING)
|
||||
ADD_TEST_MACRO(CMakeCommands.link_directories)
|
||||
ADD_TEST_MACRO(CMakeCommands.target_link_directories)
|
||||
|
||||
# The cmake server-mode test requires python for a simple client.
|
||||
find_package(PythonInterp QUIET)
|
||||
if(PYTHON_EXECUTABLE)
|
||||
set(Server_BUILD_OPTIONS -DPYTHON_EXECUTABLE:FILEPATH=${PYTHON_EXECUTABLE})
|
||||
ADD_TEST_MACRO(Server Server)
|
||||
endif()
|
||||
|
||||
configure_file(
|
||||
"${CMake_SOURCE_DIR}/Tests/CTestTestCrash/test.cmake.in"
|
||||
"${CMake_BINARY_DIR}/Tests/CTestTestCrash/test.cmake"
|
||||
|
@@ -1,21 +0,0 @@
|
||||
include_directories(
|
||||
${CMAKE_CURRENT_BINARY_DIR}
|
||||
${CMake_BINARY_DIR}/Source
|
||||
${CMake_SOURCE_DIR}/Source
|
||||
)
|
||||
|
||||
set(CMakeServerLib_TESTS
|
||||
testServerBuffering.cpp
|
||||
)
|
||||
|
||||
create_test_sourcelist(CMakeLib_TEST_SRCS CMakeServerLibTests.cxx ${CMakeServerLib_TESTS})
|
||||
add_executable(CMakeServerLibTests ${CMakeLib_TEST_SRCS})
|
||||
target_link_libraries(CMakeServerLibTests CMakeLib CMakeServerLib)
|
||||
|
||||
SET_PROPERTY(TARGET CMakeServerLibTests PROPERTY C_CLANG_TIDY "")
|
||||
SET_PROPERTY(TARGET CMakeServerLibTests PROPERTY CXX_CLANG_TIDY "")
|
||||
|
||||
foreach(testfile ${CMakeServerLib_TESTS})
|
||||
get_filename_component(test "${testfile}" NAME_WE)
|
||||
add_test(CMakeServerLib.${test} CMakeServerLibTests ${test} ${${test}_ARGS})
|
||||
endforeach()
|
@@ -1,87 +0,0 @@
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "cmConnection.h"
|
||||
#include "cmServerConnection.h"
|
||||
|
||||
void print_error(const std::vector<std::string>& input,
|
||||
const std::vector<std::string>& output)
|
||||
{
|
||||
std::cerr << "Responses don't equal input messages input." << std::endl;
|
||||
std::cerr << "Responses: " << std::endl;
|
||||
|
||||
for (auto& msg : output) {
|
||||
std::cerr << "'" << msg << "'" << std::endl;
|
||||
}
|
||||
|
||||
std::cerr << "Input messages" << std::endl;
|
||||
for (auto& msg : input) {
|
||||
std::cerr << "'" << msg << "'" << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
std::string trim_newline(const std::string& _buffer)
|
||||
{
|
||||
auto buffer = _buffer;
|
||||
while (!buffer.empty() && (buffer.back() == '\n' || buffer.back() == '\r')) {
|
||||
buffer.pop_back();
|
||||
}
|
||||
return buffer;
|
||||
}
|
||||
|
||||
int testServerBuffering(int, char** const)
|
||||
{
|
||||
std::vector<std::string> messages = {
|
||||
"{ \"test\": 10}", "{ \"test\": { \"test2\": false} }",
|
||||
"{ \"test\": [1, 2, 3] }",
|
||||
"{ \"a\": { \"1\": {}, \n\n\n \"2\":[] \t\t\t\t}}"
|
||||
};
|
||||
|
||||
std::string fullMessage;
|
||||
for (auto& msg : messages) {
|
||||
fullMessage += "[== \"CMake Server\" ==[\n";
|
||||
fullMessage += msg;
|
||||
fullMessage += "\n]== \"CMake Server\" ==]\n";
|
||||
}
|
||||
|
||||
// The buffering strategy should cope with any fragmentation, including
|
||||
// just getting the characters one at a time.
|
||||
auto bufferingStrategy =
|
||||
std::unique_ptr<cmConnectionBufferStrategy>(new cmServerBufferStrategy);
|
||||
std::vector<std::string> response;
|
||||
std::string rawBuffer;
|
||||
for (auto& messageChar : fullMessage) {
|
||||
rawBuffer += messageChar;
|
||||
std::string packet = bufferingStrategy->BufferMessage(rawBuffer);
|
||||
do {
|
||||
if (!packet.empty() && packet != "\r\n") {
|
||||
response.push_back(trim_newline(packet));
|
||||
}
|
||||
packet = bufferingStrategy->BufferMessage(rawBuffer);
|
||||
} while (!packet.empty());
|
||||
}
|
||||
|
||||
if (response != messages) {
|
||||
print_error(messages, response);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// We should also be able to deal with getting a bunch at once
|
||||
response.clear();
|
||||
std::string packet = bufferingStrategy->BufferMessage(fullMessage);
|
||||
do {
|
||||
if (!packet.empty() && packet != "\r\n") {
|
||||
response.push_back(trim_newline(packet));
|
||||
}
|
||||
packet = bufferingStrategy->BufferMessage(fullMessage);
|
||||
} while (!packet.empty());
|
||||
|
||||
if (response != messages) {
|
||||
print_error(messages, response);
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
@@ -1 +1 @@
|
||||
^{"fileApi":{"requests":\[{"kind":"codemodel","version":\[{"major":2,"minor":2}]},{"kind":"cache","version":\[{"major":2,"minor":0}]},{"kind":"cmakeFiles","version":\[{"major":1,"minor":0}]}]},"generators":\[.*\],"serverMode":true,"version":{.*}}$
|
||||
^{"fileApi":{"requests":\[{"kind":"codemodel","version":\[{"major":2,"minor":2}]},{"kind":"cache","version":\[{"major":2,"minor":0}]},{"kind":"cmakeFiles","version":\[{"major":1,"minor":0}]}]},"generators":\[.*\],"serverMode":false,"version":{.*}}$
|
||||
|
@@ -1 +0,0 @@
|
||||
^CMake Error: Unknown argument for server mode$
|
@@ -1 +0,0 @@
|
||||
2
|
@@ -1 +0,0 @@
|
||||
^CMake Error: No pipe given after --pipe=$
|
1
Tests/RunCMake/CommandLine/E_server-stderr.txt
Normal file
1
Tests/RunCMake/CommandLine/E_server-stderr.txt
Normal file
@@ -0,0 +1 @@
|
||||
^CMake Error: CMake server mode has been removed in favor of the file-api\.$
|
@@ -25,8 +25,7 @@ run_cmake_command(E_compare_files-ignore-eol-nonexistent ${CMAKE_COMMAND} -E com
|
||||
run_cmake_command(E_compare_files-invalid-arguments ${CMAKE_COMMAND} -E compare_files file1.txt file2.txt file3.txt)
|
||||
run_cmake_command(E_echo_append ${CMAKE_COMMAND} -E echo_append)
|
||||
run_cmake_command(E_rename-no-arg ${CMAKE_COMMAND} -E rename)
|
||||
run_cmake_command(E_server-arg ${CMAKE_COMMAND} -E server --extra-arg)
|
||||
run_cmake_command(E_server-pipe ${CMAKE_COMMAND} -E server --pipe=)
|
||||
run_cmake_command(E_server ${CMAKE_COMMAND} -E server)
|
||||
run_cmake_command(E_true ${CMAKE_COMMAND} -E true)
|
||||
run_cmake_command(E_true-extraargs ${CMAKE_COMMAND} -E true ignored)
|
||||
run_cmake_command(E_false ${CMAKE_COMMAND} -E false)
|
||||
|
@@ -1,28 +0,0 @@
|
||||
cmake_minimum_required(VERSION 3.4)
|
||||
project(Server CXX)
|
||||
|
||||
find_package(Python REQUIRED)
|
||||
|
||||
macro(do_test bsname file type)
|
||||
execute_process(COMMAND ${Python_EXECUTABLE}
|
||||
-B # no .pyc files
|
||||
"${CMAKE_SOURCE_DIR}/${type}-test.py"
|
||||
"${CMAKE_COMMAND}"
|
||||
"${CMAKE_SOURCE_DIR}/${file}"
|
||||
"${CMAKE_SOURCE_DIR}"
|
||||
"${CMAKE_BINARY_DIR}"
|
||||
"${CMAKE_GENERATOR}"
|
||||
RESULT_VARIABLE test_result
|
||||
)
|
||||
|
||||
if (NOT test_result EQUAL 0)
|
||||
message(SEND_ERROR "TEST FAILED: ${test_result}")
|
||||
endif()
|
||||
endmacro()
|
||||
|
||||
do_test("test_cache" "tc_cache.json" "server")
|
||||
do_test("test_handshake" "tc_handshake.json" "server")
|
||||
do_test("test_globalSettings" "tc_globalSettings.json" "server")
|
||||
do_test("test_buildsystem1" "tc_buildsystem1.json" "server")
|
||||
|
||||
add_executable(Server empty.cpp)
|
@@ -1,22 +0,0 @@
|
||||
cmake_minimum_required(VERSION 3.4)
|
||||
|
||||
project(buildsystem2)
|
||||
|
||||
set(var1 123)
|
||||
|
||||
set(var2 345)
|
||||
|
||||
add_executable(main main.cpp)
|
||||
|
||||
add_executable(m_other main.cpp)
|
||||
|
||||
add_library(foo foo.cpp)
|
||||
|
||||
function(f1)
|
||||
endfunction()
|
||||
|
||||
set(var3 345)
|
||||
|
||||
add_library(someImportedLib UNKNOWN IMPORTED)
|
||||
|
||||
add_subdirectory(subdir)
|
@@ -1,5 +0,0 @@
|
||||
|
||||
int foo()
|
||||
{
|
||||
return 0;
|
||||
}
|
@@ -1,5 +0,0 @@
|
||||
|
||||
int main()
|
||||
{
|
||||
return 0;
|
||||
}
|
@@ -1,5 +0,0 @@
|
||||
set(bar4 something)
|
||||
|
||||
set(bar5 more)
|
||||
|
||||
add_executable(ooo empty.cpp)
|
@@ -1,5 +0,0 @@
|
||||
|
||||
int foo()
|
||||
{
|
||||
return 0;
|
||||
}
|
@@ -1,380 +0,0 @@
|
||||
from __future__ import print_function
|
||||
import sys, subprocess, json, os, select, shutil, time, socket
|
||||
|
||||
termwidth = 150
|
||||
|
||||
print_communication = True
|
||||
|
||||
def ordered(obj):
|
||||
if isinstance(obj, dict):
|
||||
return sorted((k, ordered(v)) for k, v in obj.items())
|
||||
if isinstance(obj, list):
|
||||
return sorted(ordered(x) for x in obj)
|
||||
else:
|
||||
return obj
|
||||
|
||||
def col_print(title, array):
|
||||
print()
|
||||
print()
|
||||
print(title)
|
||||
|
||||
indentwidth = 4
|
||||
indent = " " * indentwidth
|
||||
|
||||
if not array:
|
||||
print(indent + "<None>")
|
||||
return
|
||||
|
||||
padwidth = 2
|
||||
|
||||
maxitemwidth = len(max(array, key=len))
|
||||
|
||||
numCols = max(1, int((termwidth - indentwidth + padwidth) / (maxitemwidth + padwidth)))
|
||||
|
||||
numRows = len(array) // numCols + 1
|
||||
|
||||
pad = " " * padwidth
|
||||
|
||||
for index in range(numRows):
|
||||
print(indent + pad.join(item.ljust(maxitemwidth) for item in array[index::numRows]))
|
||||
|
||||
filterPacket = lambda x: x
|
||||
|
||||
STDIN = 0
|
||||
PIPE = 1
|
||||
|
||||
communicationMethods = [STDIN]
|
||||
|
||||
if hasattr(socket, 'AF_UNIX'):
|
||||
communicationMethods.append(PIPE)
|
||||
|
||||
def defaultExitWithError(proc):
|
||||
data = ""
|
||||
try:
|
||||
while select.select([proc.outPipe], [], [], 3.)[0]:
|
||||
data = data + proc.outPipe.read(1)
|
||||
if len(data):
|
||||
print("Rest of raw buffer from server:")
|
||||
printServer(data)
|
||||
except:
|
||||
pass
|
||||
proc.outPipe.close()
|
||||
proc.inPipe.close()
|
||||
proc.kill()
|
||||
sys.exit(1)
|
||||
|
||||
exitWithError = lambda proc: defaultExitWithError(proc)
|
||||
|
||||
serverTag = "SERVER"
|
||||
|
||||
def printServer(*args):
|
||||
print(serverTag + ">", *args)
|
||||
print()
|
||||
sys.stdout.flush()
|
||||
|
||||
def printClient(*args):
|
||||
print("CLIENT>", *args)
|
||||
print()
|
||||
sys.stdout.flush()
|
||||
|
||||
def waitForRawMessage(cmakeCommand):
|
||||
stdoutdata = ""
|
||||
payload = ""
|
||||
while not cmakeCommand.poll():
|
||||
stdoutdataLine = cmakeCommand.outPipe.readline()
|
||||
if stdoutdataLine:
|
||||
stdoutdata += stdoutdataLine.decode('utf-8')
|
||||
else:
|
||||
break
|
||||
begin = stdoutdata.find('[== "CMake Server" ==[\n')
|
||||
end = stdoutdata.find(']== "CMake Server" ==]')
|
||||
|
||||
if begin != -1 and end != -1:
|
||||
begin += len('[== "CMake Server" ==[\n')
|
||||
payload = stdoutdata[begin:end]
|
||||
jsonPayload = json.loads(payload)
|
||||
filteredPayload = filterPacket(jsonPayload)
|
||||
if print_communication and filteredPayload:
|
||||
printServer(filteredPayload)
|
||||
if filteredPayload is not None or jsonPayload is None:
|
||||
return jsonPayload
|
||||
stdoutdata = stdoutdata[(end+len(']== "CMake Server" ==]')):]
|
||||
|
||||
# Python2 has no problem writing the output of encodes directly,
|
||||
# but Python3 returns only 'int's for encode and so must be turned
|
||||
# into bytes. We use the existence of 'to_bytes' on an int to
|
||||
# determine which behavior is appropriate. It might be more clear
|
||||
# to do this in the code which uses the flag, but introducing
|
||||
# this lookup cost at every byte sent isn't ideal.
|
||||
has_to_bytes = "to_bytes" in dir(10)
|
||||
|
||||
def writeRawData(cmakeCommand, content):
|
||||
writeRawData.counter += 1
|
||||
payload = """
|
||||
[== "CMake Server" ==[
|
||||
%s
|
||||
]== "CMake Server" ==]
|
||||
""" % content
|
||||
|
||||
rn = ( writeRawData.counter % 2 ) == 0
|
||||
|
||||
if rn:
|
||||
payload = payload.replace('\n', '\r\n')
|
||||
|
||||
if print_communication:
|
||||
printClient(content, "(Use \\r\\n:", rn, ")")
|
||||
|
||||
# To stress test how cmake deals with fragmentation in the
|
||||
# communication channel, we send only one byte at a time.
|
||||
# Certain communication methods / platforms might still buffer
|
||||
# it all into one message since its so close together, but in
|
||||
# general this will catch places where we assume full buffers
|
||||
# come in all at once.
|
||||
encoded_payload = payload.encode('utf-8')
|
||||
|
||||
# Python version 3+ can't write ints directly; but 'to_bytes'
|
||||
# for int was only added in python 3.2. If this is a 3+ version
|
||||
# of python without that conversion function; just write the whole
|
||||
# thing out at once.
|
||||
if sys.version_info[0] > 2 and not has_to_bytes:
|
||||
cmakeCommand.write(encoded_payload)
|
||||
else:
|
||||
for c in encoded_payload:
|
||||
if has_to_bytes:
|
||||
c = c.to_bytes(1, byteorder='big')
|
||||
cmakeCommand.write(c)
|
||||
|
||||
writeRawData.counter = 0
|
||||
|
||||
def writePayload(cmakeCommand, obj):
|
||||
writeRawData(cmakeCommand, json.dumps(obj))
|
||||
|
||||
def getPipeName():
|
||||
return "/tmp/server-test-socket"
|
||||
|
||||
def attachPipe(cmakeCommand, pipeName):
|
||||
time.sleep(1)
|
||||
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
||||
sock.connect(pipeName)
|
||||
global serverTag
|
||||
serverTag = "SERVER(PIPE)"
|
||||
cmakeCommand.outPipe = sock.makefile()
|
||||
cmakeCommand.inPipe = sock
|
||||
cmakeCommand.write = cmakeCommand.inPipe.sendall
|
||||
|
||||
def writeAndFlush(pipe, val):
|
||||
pipe.write(val)
|
||||
pipe.flush()
|
||||
|
||||
def initServerProc(cmakeCommand, comm):
|
||||
if comm == PIPE:
|
||||
pipeName = getPipeName()
|
||||
cmakeCommand = subprocess.Popen([cmakeCommand, "-E", "server", "--experimental", "--pipe=" + pipeName])
|
||||
attachPipe(cmakeCommand, pipeName)
|
||||
else:
|
||||
cmakeCommand = subprocess.Popen([cmakeCommand, "-E", "server", "--experimental", "--debug"],
|
||||
stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE)
|
||||
cmakeCommand.outPipe = cmakeCommand.stdout
|
||||
cmakeCommand.inPipe = cmakeCommand.stdin
|
||||
cmakeCommand.write = lambda val: writeAndFlush(cmakeCommand.inPipe, val)
|
||||
|
||||
packet = waitForRawMessage(cmakeCommand)
|
||||
if packet == None:
|
||||
print("Not in server mode")
|
||||
sys.exit(2)
|
||||
|
||||
if packet['type'] != 'hello':
|
||||
print("No hello message")
|
||||
sys.exit(3)
|
||||
|
||||
return cmakeCommand
|
||||
|
||||
def exitProc(cmakeCommand):
|
||||
# Tell the server to exit.
|
||||
cmakeCommand.stdin.close()
|
||||
cmakeCommand.stdout.close()
|
||||
|
||||
# Wait for the server to exit.
|
||||
# If this version of python supports it, terminate the server after a timeout.
|
||||
try:
|
||||
cmakeCommand.wait(timeout=5)
|
||||
except TypeError:
|
||||
cmakeCommand.wait()
|
||||
except:
|
||||
cmakeCommand.terminate()
|
||||
raise
|
||||
|
||||
def waitForMessage(cmakeCommand, expected):
|
||||
data = ordered(expected)
|
||||
packet = ordered(waitForRawMessage(cmakeCommand))
|
||||
|
||||
if packet != data:
|
||||
print ("Received unexpected message; test failed")
|
||||
exitWithError(cmakeCommand)
|
||||
return packet
|
||||
|
||||
def waitForReply(cmakeCommand, originalType, cookie, skipProgress):
|
||||
gotResult = False
|
||||
while True:
|
||||
packet = waitForRawMessage(cmakeCommand)
|
||||
t = packet['type']
|
||||
if packet['cookie'] != cookie or packet['inReplyTo'] != originalType:
|
||||
print("cookie or inReplyTo mismatch")
|
||||
sys.exit(4)
|
||||
if t == 'message' or t == 'progress':
|
||||
if skipProgress:
|
||||
continue
|
||||
if t == 'reply':
|
||||
break
|
||||
print("Unrecognized message", packet)
|
||||
sys.exit(5)
|
||||
|
||||
return packet
|
||||
|
||||
def waitForError(cmakeCommand, originalType, cookie, message):
|
||||
packet = waitForRawMessage(cmakeCommand)
|
||||
if packet['cookie'] != cookie or packet['type'] != 'error' or packet['inReplyTo'] != originalType or packet['errorMessage'] != message:
|
||||
sys.exit(6)
|
||||
|
||||
def waitForProgress(cmakeCommand, originalType, cookie, current, message):
|
||||
packet = waitForRawMessage(cmakeCommand)
|
||||
if packet['cookie'] != cookie or packet['type'] != 'progress' or packet['inReplyTo'] != originalType or packet['progressCurrent'] != current or packet['progressMessage'] != message:
|
||||
sys.exit(7)
|
||||
|
||||
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', 'sourceDirectory': source, 'buildDirectory': build,
|
||||
'generator': generator, 'extraGenerator': extraGenerator })
|
||||
waitForReply(cmakeCommand, 'handshake', 'TEST_HANDSHAKE', False)
|
||||
|
||||
def validateGlobalSettings(cmakeCommand, cmakeCommandPath, data):
|
||||
packet = waitForReply(cmakeCommand, 'globalSettings', '', False)
|
||||
|
||||
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(8)
|
||||
if (versionString != cmakeVersion):
|
||||
sys.exit(9)
|
||||
|
||||
# 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:
|
||||
continue
|
||||
if line[0] == '*': # default generator marker
|
||||
line = ' ' + line[1:]
|
||||
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):
|
||||
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(10)
|
||||
|
||||
gen = packet['generator']
|
||||
if (gen != '' and not (gen in generators)):
|
||||
sys.exit(11)
|
||||
|
||||
for i in data:
|
||||
print("Validating", i)
|
||||
if (packet[i] != data[i]):
|
||||
sys.exit(12)
|
||||
|
||||
def validateCache(cmakeCommand, data):
|
||||
packet = waitForReply(cmakeCommand, 'cache', '', False)
|
||||
|
||||
cache = packet['cache']
|
||||
|
||||
if (data['isEmpty']):
|
||||
if (cache != []):
|
||||
print('Expected empty cache, but got data.\n')
|
||||
sys.exit(1)
|
||||
return;
|
||||
|
||||
if (cache == []):
|
||||
print('Expected cache contents, but got none.\n')
|
||||
sys.exit(1)
|
||||
|
||||
hadHomeDir = False
|
||||
for value in cache:
|
||||
if (value['key'] == 'CMAKE_HOME_DIRECTORY'):
|
||||
hadHomeDir = True
|
||||
|
||||
if (not hadHomeDir):
|
||||
print('No CMAKE_HOME_DIRECTORY found in cache.')
|
||||
sys.exit(1)
|
||||
|
||||
def handleBasicMessage(proc, obj, debug):
|
||||
if 'sendRaw' in obj:
|
||||
data = obj['sendRaw']
|
||||
if debug: print("Sending raw:", data)
|
||||
writeRawData(proc, data)
|
||||
return True
|
||||
elif 'send' in obj:
|
||||
data = obj['send']
|
||||
if debug: print("Sending:", json.dumps(data))
|
||||
writePayload(proc, data)
|
||||
return True
|
||||
elif 'recv' in obj:
|
||||
data = obj['recv']
|
||||
if debug: print("Waiting for:", json.dumps(data))
|
||||
waitForMessage(proc, data)
|
||||
return True
|
||||
elif 'message' in obj:
|
||||
print("MESSAGE:", obj["message"])
|
||||
sys.stdout.flush()
|
||||
return True
|
||||
return False
|
||||
|
||||
def shutdownProc(proc):
|
||||
# Tell the server to exit.
|
||||
proc.inPipe.close()
|
||||
proc.outPipe.close()
|
||||
|
||||
# Wait for the server to exit.
|
||||
# If this version of python supports it, terminate the server after a timeout.
|
||||
try:
|
||||
proc.wait(timeout=5)
|
||||
except TypeError:
|
||||
proc.wait()
|
||||
except:
|
||||
proc.terminate()
|
||||
raise
|
||||
|
||||
print('cmake-server exited: %d' % proc.returncode)
|
||||
sys.exit(proc.returncode)
|
@@ -1,5 +0,0 @@
|
||||
|
||||
int main()
|
||||
{
|
||||
return 0;
|
||||
}
|
@@ -1,105 +0,0 @@
|
||||
from __future__ import print_function
|
||||
import sys, cmakelib, json, os, shutil
|
||||
|
||||
debug = True
|
||||
|
||||
cmakeCommand = sys.argv[1]
|
||||
testFile = sys.argv[2]
|
||||
sourceDir = sys.argv[3]
|
||||
buildDir = sys.argv[4] + "/" + os.path.splitext(os.path.basename(testFile))[0]
|
||||
cmakeGenerator = sys.argv[5]
|
||||
|
||||
print("Server Test:", testFile,
|
||||
"\n-- SourceDir:", sourceDir,
|
||||
"\n-- BuildDir:", buildDir,
|
||||
"\n-- Generator:", cmakeGenerator)
|
||||
|
||||
if os.path.exists(buildDir):
|
||||
shutil.rmtree(buildDir)
|
||||
|
||||
cmakelib.filterBase = sourceDir
|
||||
|
||||
with open(testFile) as f:
|
||||
testData = json.loads(f.read())
|
||||
|
||||
for communicationMethod in cmakelib.communicationMethods:
|
||||
proc = cmakelib.initServerProc(cmakeCommand, communicationMethod)
|
||||
if proc is None:
|
||||
continue
|
||||
|
||||
for obj in testData:
|
||||
if cmakelib.handleBasicMessage(proc, obj, debug):
|
||||
pass
|
||||
elif 'reply' in obj:
|
||||
data = obj['reply']
|
||||
if debug: print("Waiting for reply:", json.dumps(data))
|
||||
originalType = ""
|
||||
cookie = ""
|
||||
skipProgress = False;
|
||||
if 'cookie' in data: cookie = data['cookie']
|
||||
if 'type' in data: originalType = data['type']
|
||||
if 'skipProgress' in data: skipProgress = data['skipProgress']
|
||||
cmakelib.waitForReply(proc, originalType, cookie, skipProgress)
|
||||
elif 'error' in obj:
|
||||
data = obj['error']
|
||||
if debug: print("Waiting for error:", json.dumps(data))
|
||||
originalType = ""
|
||||
cookie = ""
|
||||
message = ""
|
||||
if 'cookie' in data: cookie = data['cookie']
|
||||
if 'type' in data: originalType = data['type']
|
||||
if 'message' in data: message = data['message']
|
||||
cmakelib.waitForError(proc, originalType, cookie, message)
|
||||
elif 'progress' in obj:
|
||||
data = obj['progress']
|
||||
if debug: print("Waiting for progress:", json.dumps(data))
|
||||
originalType = ''
|
||||
cookie = ""
|
||||
current = 0
|
||||
message = ""
|
||||
if 'cookie' in data: cookie = data['cookie']
|
||||
if 'type' in data: originalType = data['type']
|
||||
if 'current' in data: current = data['current']
|
||||
if 'message' in data: message = data['message']
|
||||
cmakelib.waitForProgress(proc, originalType, cookie, current, message)
|
||||
elif 'handshake' in obj:
|
||||
data = obj['handshake']
|
||||
if debug: print("Doing handshake:", json.dumps(data))
|
||||
major = -1
|
||||
minor = -1
|
||||
generator = cmakeGenerator
|
||||
extraGenerator = ''
|
||||
sourceDirectory = sourceDir
|
||||
buildDirectory = buildDir
|
||||
if 'major' in data: major = data['major']
|
||||
if 'minor' in data: minor = data['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']
|
||||
|
||||
if not os.path.isabs(buildDirectory):
|
||||
buildDirectory = buildDir + "/" + buildDirectory
|
||||
if sourceDirectory != '' and not os.path.isabs(sourceDirectory):
|
||||
sourceDirectory = sourceDir + "/" + sourceDirectory
|
||||
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'] = cmakeGenerator
|
||||
if not 'extraGenerator' in data: data['extraGenerator'] = ''
|
||||
cmakelib.validateGlobalSettings(proc, cmakeCommand, data)
|
||||
elif 'validateCache' in obj:
|
||||
data = obj['validateCache']
|
||||
if not 'isEmpty' in data: data['isEmpty'] = false
|
||||
cmakelib.validateCache(proc, data)
|
||||
elif 'reconnect' in obj:
|
||||
cmakelib.exitProc(proc)
|
||||
proc = cmakelib.initServerProc(cmakeCommand, communicationMethod)
|
||||
else:
|
||||
print("Unknown command:", json.dumps(obj))
|
||||
sys.exit(2)
|
||||
cmakelib.shutdownProc(proc)
|
||||
print("Completed")
|
@@ -1,27 +0,0 @@
|
||||
[
|
||||
{ "message": "Testing globalSettings" },
|
||||
|
||||
{ "handshake": {"major": 1, "sourceDirectory":"buildsystem1","buildDirectory":"buildsystem1"} },
|
||||
|
||||
{ "message": "Configure:" },
|
||||
{ "send": { "type": "configure", "cookie":"CONFIG" } },
|
||||
{ "reply": { "type": "configure", "cookie":"CONFIG", "skipProgress":true } },
|
||||
|
||||
{ "message": "Compute:" },
|
||||
{ "send": { "type": "compute", "cookie":"COMPUTE" } },
|
||||
{ "reply": { "type": "compute", "cookie":"COMPUTE", "skipProgress":true } },
|
||||
|
||||
{ "message": "Codemodel:" },
|
||||
{ "send": { "type": "codemodel", "cookie":"CODEMODEL" } },
|
||||
{ "reply": { "type": "codemodel", "cookie":"CODEMODEL" } },
|
||||
|
||||
{ "message": "CMake Inputs:"},
|
||||
{ "send": { "type": "cmakeInputs", "cookie":"INPUTS" } },
|
||||
{ "reply": { "type": "cmakeInputs", "cookie":"INPUTS" } },
|
||||
|
||||
{ "message": "Cache:"},
|
||||
{ "send": { "type": "cache", "cookie":"CACHE" } },
|
||||
{ "reply": { "type": "cache", "cookie":"CACHE" } },
|
||||
|
||||
{ "message": "Everything ok." }
|
||||
]
|
@@ -1,24 +0,0 @@
|
||||
[
|
||||
{ "message": "Testing cache" },
|
||||
|
||||
{ "message": "Cache after first handshake is empty:" },
|
||||
{ "handshake": {"major": 1, "sourceDirectory": "buildsystem1", "buildDirectory": "buildsystem1"} },
|
||||
{ "send": { "type": "cache" } },
|
||||
{ "validateCache": { "isEmpty": true } },
|
||||
|
||||
{ "message": "Cache after configure is populated:" },
|
||||
{ "send": { "type": "configure" } },
|
||||
{ "reply": { "type": "configure", "skipProgress":true } },
|
||||
{ "send": { "type": "cache" } },
|
||||
{ "validateCache": { "isEmpty": false } },
|
||||
|
||||
{ "message": "Handshake for existing cache requires buildDirectory only:" },
|
||||
{ "reconnect": {} },
|
||||
{ "handshake": {"major": 1, "sourceDirectory": "", "buildDirectory": "buildsystem1"} },
|
||||
|
||||
{ "message": "Cache after reconnect is again populated:" },
|
||||
{ "send": { "type": "cache" } },
|
||||
{ "validateCache": { "isEmpty": false } },
|
||||
|
||||
{ "message": "Everything ok." }
|
||||
]
|
@@ -1,140 +0,0 @@
|
||||
[
|
||||
{ "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." }
|
||||
]
|
@@ -1,75 +0,0 @@
|
||||
[
|
||||
{ "message": "Testing basic message handling:" },
|
||||
|
||||
{ "sendRaw": "Sometext"},
|
||||
{ "recv": {"cookie":"","errorMessage":"Failed to parse JSON input.","inReplyTo":"","type":"error"} },
|
||||
|
||||
{ "message": "Testing invalid json input"},
|
||||
{ "send": { "test": "sometext" } },
|
||||
{ "recv": {"cookie":"","errorMessage":"No type given in request.","inReplyTo":"","type":"error"} },
|
||||
|
||||
{ "send": {"test": "sometext","cookie":"monster"} },
|
||||
{ "recv": {"cookie":"monster","errorMessage":"No type given in request.","inReplyTo":"","type":"error"} },
|
||||
|
||||
{ "message": "Testing commands before handshake" },
|
||||
{ "send": {"type": "cache","cookie":"monster"} },
|
||||
{ "recv": {"cookie":"monster","errorMessage":"Waiting for type \"handshake\".","inReplyTo":"cache","type":"error"} },
|
||||
|
||||
{ "message": "Testing handshake" },
|
||||
{ "send": {"type": "sometype","cookie":"monster2"} },
|
||||
{ "recv": {"cookie":"monster2","errorMessage":"Waiting for type \"handshake\".","inReplyTo":"sometype","type":"error"} },
|
||||
|
||||
{ "send": {"type": "handshake"} },
|
||||
{ "recv": {"cookie":"","errorMessage":"\"protocolVersion\" is required for \"handshake\".","inReplyTo":"handshake","type":"error"} },
|
||||
|
||||
{ "send": {"type": "handshake","foo":"bar"} },
|
||||
{ "recv": {"cookie":"","errorMessage":"\"protocolVersion\" is required for \"handshake\".","inReplyTo":"handshake","type":"error"} },
|
||||
|
||||
{ "send": {"type": "handshake","protocolVersion":"bar"} },
|
||||
{ "recv": {"cookie":"","errorMessage":"\"protocolVersion\" must be a JSON object.","inReplyTo":"handshake","type":"error"} },
|
||||
|
||||
{ "send": {"type": "handshake","protocolVersion":{}} },
|
||||
{ "recv": {"cookie":"","errorMessage":"\"major\" must be set and an integer.","inReplyTo":"handshake","type":"error"} },
|
||||
|
||||
{ "send": {"type": "handshake","protocolVersion":{"major":"foo"}} },
|
||||
{ "recv": {"cookie":"","errorMessage":"\"major\" must be set and an integer.","inReplyTo":"handshake","type":"error"} },
|
||||
|
||||
{ "send": {"type": "handshake","protocolVersion":{"major":1, "minor":"foo"}} },
|
||||
{ "recv": {"cookie":"","errorMessage":"\"minor\" must be unset or an integer.","inReplyTo":"handshake","type":"error"} },
|
||||
|
||||
{ "send": {"type": "handshake","protocolVersion":{"major":-1, "minor":-1}} },
|
||||
{ "recv": {"cookie":"","errorMessage":"\"major\" must be >= 0.","inReplyTo":"handshake","type":"error"} },
|
||||
|
||||
{ "send": {"type": "handshake","protocolVersion":{"major":10, "minor":-1}} },
|
||||
{ "recv": {"cookie":"","errorMessage":"\"minor\" must be >= 0 when set.","inReplyTo":"handshake","type":"error"} },
|
||||
|
||||
{ "send": {"type": "handshake","protocolVersion":{"major":10000}} },
|
||||
{ "recv": {"cookie":"","errorMessage":"Protocol version not supported.","inReplyTo":"handshake","type":"error"} },
|
||||
|
||||
{ "send": {"type": "handshake","protocolVersion":{"major":1, "minor":10000}} },
|
||||
{ "recv": {"cookie":"","errorMessage":"Protocol version not supported.","inReplyTo":"handshake","type":"error"} },
|
||||
|
||||
{ "send": {"cookie":"zimtstern","type": "handshake","protocolVersion":{"major":1}} },
|
||||
{ "recv": {"cookie":"zimtstern","inReplyTo":"handshake","type":"error","errorMessage":"Failed to activate protocol version: \"buildDirectory\" is missing."} },
|
||||
|
||||
{ "message": "Testing protocol version specific options (1.0):" },
|
||||
{ "send": {"cookie":"zimtstern","type": "handshake","protocolVersion":{"major":1},"sourceDirectory":"/tmp/src"} },
|
||||
{ "recv": {"cookie":"zimtstern","inReplyTo":"handshake","type":"error","errorMessage":"Failed to activate protocol version: \"buildDirectory\" is missing."} },
|
||||
|
||||
{ "send": {"cookie":"zimtstern","type": "handshake","protocolVersion":{"major":1},"sourceDirectory":"/tmp/src","buildDirectory":"/tmp/build"} },
|
||||
{ "recv": {"cookie":"zimtstern","inReplyTo":"handshake","type":"error","errorMessage":"Failed to activate protocol version: \"sourceDirectory\" is not a directory."} },
|
||||
|
||||
{ "send": {"cookie":"zimtstern","type": "handshake","protocolVersion":{"major":1},"sourceDirectory":".","buildDirectory":"/tmp/build","extraGenerator":"CodeBlocks"} },
|
||||
{ "recv": {"cookie":"zimtstern","inReplyTo":"handshake","type":"error","errorMessage":"Failed to activate protocol version: \"generator\" is unset but required."} },
|
||||
|
||||
{ "send": {"cookie":"zimtstern","type": "handshake","protocolVersion":{"major":1},"sourceDirectory":".","buildDirectory":"/tmp/build","generator":"XXXX","extraGenerator":"CodeBlocks"} },
|
||||
{ "recv": {"cookie":"zimtstern","inReplyTo":"handshake","type":"error","errorMessage":"Failed to activate protocol version: Generator \"XXXX\" not supported."} },
|
||||
|
||||
{ "send": {"cookie":"zimtstern","type": "handshake","protocolVersion":{"major":1},"sourceDirectory":".","buildDirectory":"/tmp/build","generator":"Ninja","extraGenerator":"XXXX"} },
|
||||
{ "recv": {"cookie":"zimtstern","inReplyTo":"handshake","type":"error","errorMessage":"Failed to activate protocol version: The combination of generator \"Ninja\" and extra generator \"XXXX\" is not supported."} },
|
||||
|
||||
{ "send": {"cookie":"zimtstern","type": "handshake","protocolVersion":{"major":1},"sourceDirectory":".","buildDirectory":"/tmp/build","generator":"Ninja","extraGenerator":"CodeBlocks"} },
|
||||
{ "recv": {"cookie":"zimtstern","inReplyTo":"handshake","type":"reply"} },
|
||||
|
||||
{ "message": "Everything ok." }
|
||||
]
|
Reference in New Issue
Block a user