summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--CMakeLists.txt27
-rw-r--r--CONTRIBUTING.rst24
-rw-r--r--CTestCustom.cmake.in1
-rw-r--r--CompileFlags.cmake7
-rw-r--r--Help/manual/cmake-server.7.rst327
-rw-r--r--Modules/FindCxxTest.cmake11
-rw-r--r--Modules/Platform/Windows-MSVC.cmake4
-rw-r--r--Source/CMakeLists.txt3
-rw-r--r--Source/CMakeVersion.cmake2
-rw-r--r--Source/cmFileMonitor.cxx389
-rw-r--r--Source/cmFileMonitor.h28
-rw-r--r--Source/cmMakefile.cxx21
-rw-r--r--Source/cmParseArgumentsCommand.cxx25
-rw-r--r--Source/cmServer.cxx5
-rw-r--r--Source/cmServer.h3
-rw-r--r--Source/cmServerConnection.cxx9
-rw-r--r--Source/cmServerConnection.h4
-rw-r--r--Source/cmServerDictionary.h42
-rw-r--r--Source/cmServerProtocol.cxx613
-rw-r--r--Source/cmServerProtocol.h10
-rw-r--r--Source/cmVisualStudio10TargetGenerator.cxx3
-rw-r--r--Source/kwsys/SystemTools.cxx4
-rw-r--r--Tests/CMakeLists.txt25
-rw-r--r--Tests/RunCMake/CMakeLists.txt2
-rw-r--r--Tests/RunCMake/CPack/SOURCE_PACKAGE.cmake2
-rw-r--r--Tests/RunCMake/cmake_parse_arguments/ArgvN.cmake12
-rw-r--r--Tests/RunCMake/cmake_parse_arguments/CornerCasesArgvN.cmake53
-rw-r--r--Tests/RunCMake/cmake_parse_arguments/RunCMakeTest.cmake1
-rw-r--r--Tests/RunCMake/cmake_parse_arguments/Utils.cmake1
-rw-r--r--Tests/RunCMake/cmake_parse_arguments/test_utils.cmake44
-rw-r--r--Tests/RunCMake/project/ExplicitRC.cmake1
-rw-r--r--Tests/RunCMake/project/RunCMakeTest.cmake3
-rw-r--r--Tests/Server/CMakeLists.txt2
-rw-r--r--Tests/Server/buildsystem1/CMakeLists.txt22
-rw-r--r--Tests/Server/buildsystem1/foo.cpp5
-rw-r--r--Tests/Server/buildsystem1/main.cpp5
-rw-r--r--Tests/Server/buildsystem1/subdir/CMakeLists.txt5
-rw-r--r--Tests/Server/buildsystem1/subdir/empty.cpp5
-rw-r--r--Tests/Server/cmakelib.py20
-rw-r--r--Tests/Server/server-test.py51
-rw-r--r--Tests/Server/tc_buildsystem1.json27
-rw-r--r--Utilities/Release/linux64_release.cmake2
-rw-r--r--Utilities/Release/osx_release.cmake1
-rw-r--r--Utilities/Release/win32_release.cmake1
-rw-r--r--Utilities/Release/win64_release.cmake1
-rw-r--r--Utilities/cmlibuv/src/unix/fs.c17
-rw-r--r--Utilities/cmlibuv/src/win/fs.c5
-rw-r--r--Utilities/cmlibuv/src/win/tty.c94
-rw-r--r--Utilities/cmlibuv/src/win/util.c9
-rwxr-xr-xbootstrap11
50 files changed, 1866 insertions, 123 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 8e1aaf5..309e224 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -695,15 +695,28 @@ endif()
CMAKE_SETUP_TESTING()
# Check whether to build server mode or not:
-set(CMake_HAVE_SERVER_MODE 0)
-if(NOT CMake_TEST_EXTERNAL_CMAKE AND NOT CMAKE_BOOTSTRAP AND CMAKE_USE_LIBUV)
- list(FIND CMAKE_CXX_COMPILE_FEATURES cxx_auto_type CMake_HAVE_CXX_AUTO_TYPE)
- list(FIND CMAKE_CXX_COMPILE_FEATURES cxx_range_for CMake_HAVE_CXX_RANGE_FOR)
- if(CMake_HAVE_CXX_AUTO_TYPE AND CMake_HAVE_CXX_RANGE_FOR)
- if(CMake_HAVE_CXX_MAKE_UNIQUE)
- set(CMake_HAVE_SERVER_MODE 1)
+if(NOT CMake_TEST_EXTERNAL_CMAKE)
+ if(NOT DEFINED CMake_ENABLE_SERVER_MODE)
+ list(FIND CMAKE_CXX_COMPILE_FEATURES cxx_auto_type CMake_HAVE_CXX_AUTO_TYPE)
+ list(FIND CMAKE_CXX_COMPILE_FEATURES cxx_range_for CMake_HAVE_CXX_RANGE_FOR)
+ if(CMAKE_USE_LIBUV
+ AND CMake_HAVE_CXX_AUTO_TYPE
+ AND CMake_HAVE_CXX_MAKE_UNIQUE
+ AND CMake_HAVE_CXX_RANGE_FOR
+ )
+ set(CMake_ENABLE_SERVER_MODE 1)
+ else()
+ set(CMake_ENABLE_SERVER_MODE 0)
endif()
endif()
+ if(CMake_ENABLE_SERVER_MODE AND NOT CMAKE_USE_LIBUV)
+ message(FATAL_ERROR "The server mode requires libuv!")
+ endif()
+else()
+ set(CMake_ENABLE_SERVER_MODE 0)
+endif()
+if(NOT DEFINED CMake_TEST_SERVER_MODE)
+ set(CMake_TEST_SERVER_MODE ${CMake_ENABLE_SERVER_MODE})
endif()
if(NOT CMake_TEST_EXTERNAL_CMAKE)
diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst
index ddf8407..921ba7c 100644
--- a/CONTRIBUTING.rst
+++ b/CONTRIBUTING.rst
@@ -6,22 +6,26 @@ Community
CMake is maintained and supported by `Kitware`_ and developed in
collaboration with a productive community of contributors.
+Please subscribe and post to the `CMake Developers List`_ to raise
+discussion of development topics.
.. _`Kitware`: http://www.kitware.com/cmake
-
-The preferred entry point for new contributors is the mailing list.
-Please subscribe and post to the `CMake Developers List`_ to offer
-contributions. Regular and productive contributors may be invited
-to gain direct push access.
-
.. _`CMake Developers List`: https://cmake.org/mailman/listinfo/cmake-developers
Patches
=======
-Please base all new work on the ``master`` branch. Then use
-``git format-patch`` to produce patches suitable to post to
-the mailing list.
+CMake uses `Kitware's GitLab Instance`_ to manage development and code review.
+To contribute patches:
+
+#. Fork the upstream `CMake Repository`_ into a personal account.
+#. Base all new work on the upstream ``master`` branch.
+#. Create commits making incremental, distinct, logically complete changes.
+#. Push a topic branch to a personal repository fork on GitLab.
+#. Create a GitLab Merge Request targeting the upstream ``master`` branch.
+
+.. _`Kitware's GitLab Instance`: https://gitlab.kitware.com
+.. _`CMake Repository`: https://gitlab.kitware.com/cmake/cmake
Code Style
==========
@@ -29,9 +33,11 @@ Code Style
We use `clang-format`_ to define our style for C++ code in the CMake source
tree. See the `.clang-format`_ configuration file for our style settings.
Use ``clang-format`` version 3.8 or higher to format source files.
+See also the `Utilities/Scripts/clang-format.bash`_ script.
.. _`clang-format`: http://clang.llvm.org/docs/ClangFormat.html
.. _`.clang-format`: .clang-format
+.. _`Utilities/Scripts/clang-format.bash`: Utilities/Scripts/clang-format.bash
License
=======
diff --git a/CTestCustom.cmake.in b/CTestCustom.cmake.in
index 710681c..48dd3de 100644
--- a/CTestCustom.cmake.in
+++ b/CTestCustom.cmake.in
@@ -67,6 +67,7 @@ list(APPEND CTEST_CUSTOM_WARNING_EXCEPTION
"cm(StringCommand|CTestTestHandler)\\.cxx.*warning.*rand.*may return deterministic values"
"cm(StringCommand|CTestTestHandler)\\.cxx.*warning.*rand.*isn.*t random" # we do not do crypto
"cm(StringCommand|CTestTestHandler)\\.cxx.*warning.*srand.*seed choices are.*poor" # we do not do crypto
+ "IPA warning: function.*multiply defined in"
# Ignore clang's summary warning, assuming prior text has matched some
# other warning expression:
diff --git a/CompileFlags.cmake b/CompileFlags.cmake
index 382787c..c875e6f 100644
--- a/CompileFlags.cmake
+++ b/CompileFlags.cmake
@@ -71,6 +71,13 @@ if (CMAKE_CXX_COMPILER_ID STREQUAL SunPro)
endif()
endif()
+foreach(lang C CXX)
+ # Suppress warnings from PGI compiler.
+ if (CMAKE_${lang}_COMPILER_ID STREQUAL "PGI")
+ set(CMAKE_${lang}_FLAGS "${CMAKE_${lang}_FLAGS} -w")
+ endif()
+endforeach()
+
# use the ansi CXX compile flag for building cmake
if (CMAKE_ANSI_CXXFLAGS)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${CMAKE_ANSI_CXXFLAGS}")
diff --git a/Help/manual/cmake-server.7.rst b/Help/manual/cmake-server.7.rst
index b8a425c..afd4e2b 100644
--- a/Help/manual/cmake-server.7.rst
+++ b/Help/manual/cmake-server.7.rst
@@ -194,6 +194,49 @@ 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
----------------------
@@ -374,3 +417,287 @@ 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 "projects", which will contain a list of
+project objects, one for each (sub-)project defined in the cmake build system.
+
+Each project object can have the following keys:
+
+"name"
+ contains the (sub-)projects name.
+"sourceDirectory"
+ contains the current source directory
+"buildDirectory"
+ contains the current build directory.
+"configurations"
+ contains 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 support several.
+
+Each configuration object can have the following keys:
+
+"name"
+ contains the name of the configuration. The name may be empty.
+"targets"
+ contains a list of target objects, one for each build target.
+
+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.
+"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":"project"}
+ ]== CMake Server ==]
+
+CMake will reply::
+
+ [== CMake Server ==[
+ {
+ "cookie":"",
+ "type":"reply",
+ "inReplyTo":"project",
+
+ "projects":
+ [
+ {
+ "name":"CMAKE_FORM",
+ "sourceDirectory":"/home/code/src/cmake/Source/CursesDialog/form"
+ "buildDirectory":"/tmp/cmake-build-test/Source/CursesDialog/form",
+ "configurations":
+ [
+ {
+ "name":"",
+ "targets":
+ [
+ {
+ "artifactDirectory":"/tmp/cmake/Source/CursesDialog/form",
+ "fileGroups":
+ [
+ {
+ "compileFlags":" -std=gnu11",
+ "defines":
+ [
+ "SOMETHING=1",
+ "LIBARCHIVE_STATIC"
+ ],
+ "includePath":
+ [
+ { "path":"/tmp/cmake-build-test/Utilities" },
+ { "isSystem": true, "path":"/usr/include/something" },
+ ...
+ ]
+ "language":"C",
+ "sources":
+ [
+ "fld_arg.c",
+ ...
+ "fty_regex.c"
+ ]
+ }
+ ],
+ "fullName":"libcmForm.a",
+ "linkerLanguage":"C",
+ "name":"cmForm",
+ "type":"STATIC_LIBRARY"
+ }
+ ]
+ }
+ ],
+ },
+ ...
+ ]
+ }
+ ]== CMake Server ==]
+
+The output can be tailored to the specific needs via parameter passed when
+requesting "project" information.
+
+You can have a "depth" key, which accepts "project", "configuration" and
+"target" as string values. These cause the output to be trimmed at the
+appropriate depth of the output tree.
+
+You can also set "configurations" to an array of strings with configuration
+names to list. This will cause any configuration that is not listed to be
+trimmed from the output.
+
+Generated files can be included in the listing by setting "includeGeneratedFiles"
+to "true". This setting defaults to "false", so generated files are not
+listed by default.
+
+Finally you can limit the target types that are going to be listed. This is
+done by providing a list of target types as an array of strings to the
+"targetTypes" key.
+
+
+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 can be used once a project is configured and 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 ==]
diff --git a/Modules/FindCxxTest.cmake b/Modules/FindCxxTest.cmake
index 62489f9..9ba1ff3 100644
--- a/Modules/FindCxxTest.cmake
+++ b/Modules/FindCxxTest.cmake
@@ -58,7 +58,8 @@
# The test generator that is actually used (chosen using user preferences
# and interpreters found in the system)
# CXXTEST_TESTGEN_INTERPRETER (since CMake 2.8.3)
-# The full path to the Perl or Python executable on the system
+# The full path to the Perl or Python executable on the system, on
+# platforms where the script cannot be executed using its shebang line.
#
#
#
@@ -210,7 +211,13 @@ if(PYTHONINTERP_FOUND OR PERL_FOUND)
if(PYTHONINTERP_FOUND AND (CXXTEST_USE_PYTHON OR NOT PERL_FOUND OR NOT DEFINED CXXTEST_USE_PYTHON))
set(CXXTEST_TESTGEN_EXECUTABLE ${CXXTEST_PYTHON_TESTGEN_EXECUTABLE})
- set(CXXTEST_TESTGEN_INTERPRETER ${PYTHON_EXECUTABLE})
+ execute_process(COMMAND ${CXXTEST_PYTHON_TESTGEN_EXECUTABLE} --version
+ OUTPUT_VARIABLE _CXXTEST_OUT ERROR_VARIABLE _CXXTEST_OUT RESULT_VARIABLE _CXXTEST_RESULT)
+ if(_CXXTEST_RESULT EQUAL 0)
+ set(CXXTEST_TESTGEN_INTERPRETER "")
+ else()
+ set(CXXTEST_TESTGEN_INTERPRETER ${PYTHON_EXECUTABLE})
+ endif()
FIND_PACKAGE_HANDLE_STANDARD_ARGS(CxxTest DEFAULT_MSG
CXXTEST_INCLUDE_DIR CXXTEST_PYTHON_TESTGEN_EXECUTABLE)
diff --git a/Modules/Platform/Windows-MSVC.cmake b/Modules/Platform/Windows-MSVC.cmake
index 5055eef..d72ec66 100644
--- a/Modules/Platform/Windows-MSVC.cmake
+++ b/Modules/Platform/Windows-MSVC.cmake
@@ -182,7 +182,7 @@ else()
if(_MSVC_C_ARCHITECTURE_FAMILY STREQUAL "ARM" OR _MSVC_CXX_ARCHITECTURE_FAMILY STREQUAL "ARM")
set(CMAKE_C_STANDARD_LIBRARIES_INIT "kernel32.lib user32.lib")
elseif(MSVC_VERSION GREATER 1310)
- if(CMAKE_VS_PLATFORM_TOOLSET MATCHES "v[0-9]+_clang_.*")
+ if(CMAKE_VS_PLATFORM_TOOLSET MATCHES "(v[0-9]+_clang_.*|LLVM-vs[0-9]+.*)")
# Clang/C2 in MSVC14 Update 1 seems to not support -fsantinize (yet?)
# set(_RTC1 "-fsantinize=memory,safe-stack")
set(_FLAGS_CXX " -frtti -fexceptions")
@@ -281,7 +281,7 @@ macro(__windows_compiler_msvc lang)
if("x${lang}" STREQUAL "xC" OR
"x${lang}" STREQUAL "xCXX")
- if(CMAKE_VS_PLATFORM_TOOLSET MATCHES "v[0-9]+_clang_.*")
+ if(CMAKE_VS_PLATFORM_TOOLSET MATCHES "(v[0-9]+_clang_.*|LLVM-vs[0-9]+.*)")
# note: MSVC 14 2015 Update 1 sets -fno-ms-compatibility by default, but this does not allow one to compile many projects
# that include MS's own headers. CMake itself is affected project too.
string(APPEND CMAKE_${lang}_FLAGS_INIT " ${_PLATFORM_DEFINES}${_PLATFORM_DEFINES_${lang}} -fms-extensions -fms-compatibility -D_WINDOWS -Wall${_FLAGS_${lang}}")
diff --git a/Source/CMakeLists.txt b/Source/CMakeLists.txt
index b8f02e3..ec49481 100644
--- a/Source/CMakeLists.txt
+++ b/Source/CMakeLists.txt
@@ -778,8 +778,9 @@ add_executable(cmake cmakemain.cxx cmcmd.cxx cmcmd.h ${MANIFEST_FILE})
list(APPEND _tools cmake)
target_link_libraries(cmake CMakeLib)
-if(CMake_HAVE_SERVER_MODE)
+if(CMake_ENABLE_SERVER_MODE)
add_library(CMakeServerLib
+ cmFileMonitor.cxx cmFileMonitor.h
cmServer.cxx cmServer.h
cmServerConnection.cxx cmServerConnection.h
cmServerProtocol.cxx cmServerProtocol.h
diff --git a/Source/CMakeVersion.cmake b/Source/CMakeVersion.cmake
index e4f5e01..3e27338 100644
--- a/Source/CMakeVersion.cmake
+++ b/Source/CMakeVersion.cmake
@@ -1,5 +1,5 @@
# CMake version number components.
set(CMake_VERSION_MAJOR 3)
set(CMake_VERSION_MINOR 6)
-set(CMake_VERSION_PATCH 20160928)
+set(CMake_VERSION_PATCH 20161001)
#set(CMake_VERSION_RC 1)
diff --git a/Source/cmFileMonitor.cxx b/Source/cmFileMonitor.cxx
new file mode 100644
index 0000000..b97590b
--- /dev/null
+++ b/Source/cmFileMonitor.cxx
@@ -0,0 +1,389 @@
+/* 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 <cmsys/SystemTools.hxx>
+
+#include <cassert>
+#include <iostream>
+#include <set>
+#include <unordered_map>
+
+namespace {
+void on_directory_change(uv_fs_event_t* handle, const char* filename,
+ int events, int status);
+void on_handle_close(uv_handle_t* handle);
+} // namespace
+
+class cmIBaseWatcher
+{
+public:
+ cmIBaseWatcher() = default;
+ 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()
+ {
+ for (auto i : this->Children) {
+ delete i.second;
+ }
+ }
+
+ cmIBaseWatcher* Find(const std::string& ps)
+ {
+ const auto i = this->Children.find(ps);
+ return (i == this->Children.end()) ? nullptr : i->second;
+ }
+
+ void Trigger(const std::string& pathSegment, int events,
+ int status) const final
+ {
+ if (pathSegment.empty()) {
+ for (const auto& i : this->Children) {
+ i.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 (const auto& i : this->Children) {
+ i.second->StartWatching();
+ }
+ }
+
+ void StopWatching() override
+ {
+ for (const auto& i : this->Children) {
+ i.second->StopWatching();
+ }
+ }
+
+ std::vector<std::string> WatchedFiles() const final
+ {
+ std::vector<std::string> result;
+ for (const auto& i : this->Children) {
+ for (const auto& j : i.second->WatchedFiles()) {
+ result.push_back(j);
+ }
+ }
+ return result;
+ }
+
+ std::vector<std::string> WatchedDirectories() const override
+ {
+ std::vector<std::string> result;
+ for (const auto& i : this->Children) {
+ for (const auto& j : i.second->WatchedDirectories()) {
+ result.push_back(j);
+ }
+ }
+ return result;
+ }
+
+ void Reset()
+ {
+ for (auto c : this->Children) {
+ delete c.second;
+ }
+ 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(std::make_pair(ps, watcher));
+ }
+
+private:
+ std::unordered_map<std::string, 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);
+ }
+
+ ~cmRealDirectoryWatcher()
+ {
+ // Handle is freed via uv_handle_close callback!
+ }
+
+ 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);
+ uv_close(reinterpret_cast<uv_handle_t*>(this->Handle), &on_handle_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 (const auto& j : cmVirtualDirectoryWatcher::WatchedDirectories()) {
+ result.push_back(j);
+ }
+ 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({ cb })
+ {
+ assert(p);
+ assert(!ps.empty());
+ p->AddChildWatcher(ps, this);
+ }
+
+ void StartWatching() final {}
+
+ void StopWatching() final {}
+
+ void AppendCallback(cmFileMonitor::Callback cb) { 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 (const auto& 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);
+ watcher->Trigger(pathSegment, events, status);
+}
+
+void on_handle_close(uv_handle_t* handle)
+{
+ delete (reinterpret_cast<uv_fs_event_t*>(handle));
+}
+
+} // namespace
+
+cmFileMonitor::cmFileMonitor(uv_loop_t* l)
+ : Root(new cmRootWatcher(l))
+{
+}
+
+cmFileMonitor::~cmFileMonitor()
+{
+ delete this->Root;
+}
+
+void cmFileMonitor::MonitorPaths(const std::vector<std::string>& paths,
+ Callback cb)
+{
+ for (const auto& p : paths) {
+ std::vector<std::string> pathSegments;
+ cmsys::SystemTools::SplitPath(p, pathSegments, true);
+
+ const size_t segmentCount = pathSegments.size();
+ if (segmentCount < 2) { // Expect at least rootdir and filename
+ continue;
+ }
+ cmVirtualDirectoryWatcher* currentWatcher = this->Root;
+ for (size_t i = 0; i < segmentCount; ++i) {
+ assert(currentWatcher);
+
+ const bool fileSegment = (i == segmentCount - 1);
+ 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];
+
+ cmIBaseWatcher* nextWatcher = currentWatcher->Find(currentSegment);
+ if (!nextWatcher) {
+ if (rootSegment) { // Root part
+ assert(currentWatcher == this->Root);
+ nextWatcher = new cmRootDirectoryWatcher(this->Root, currentSegment);
+ assert(currentWatcher->Find(currentSegment) == nextWatcher);
+ } else if (fileSegment) { // File part
+ assert(currentWatcher != this->Root);
+ 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;
+}
diff --git a/Source/cmFileMonitor.h b/Source/cmFileMonitor.h
new file mode 100644
index 0000000..e05f48d
--- /dev/null
+++ b/Source/cmFileMonitor.h
@@ -0,0 +1,28 @@
+/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+ file Copyright.txt or https://cmake.org/licensing for details. */
+#pragma once
+
+#include <functional>
+#include <string>
+#include <vector>
+
+#include "cm_uv.h"
+
+class cmRootWatcher;
+
+class cmFileMonitor
+{
+public:
+ cmFileMonitor(uv_loop_t* l);
+ ~cmFileMonitor();
+
+ using Callback = std::function<void(const std::string&, int, int)>;
+ void MonitorPaths(const std::vector<std::string>& paths, Callback cb);
+ void StopMonitoring();
+
+ std::vector<std::string> WatchedFiles() const;
+ std::vector<std::string> WatchedDirectories() const;
+
+private:
+ cmRootWatcher* Root;
+};
diff --git a/Source/cmMakefile.cxx b/Source/cmMakefile.cxx
index 48e6c61..df993ce 100644
--- a/Source/cmMakefile.cxx
+++ b/Source/cmMakefile.cxx
@@ -3142,7 +3142,26 @@ void cmMakefile::EnableLanguage(std::vector<std::string> const& lang,
{
this->AddDefinition("CMAKE_CFG_INTDIR",
this->GetGlobalGenerator()->GetCMakeCFGIntDir());
- this->GetGlobalGenerator()->EnableLanguage(lang, this, optional);
+ // If RC is explicitly listed we need to do it after other languages.
+ // On some platforms we enable RC implicitly while enabling others.
+ // Do not let that look like recursive enable_language(RC).
+ std::vector<std::string> langs;
+ std::vector<std::string> langsRC;
+ langs.reserve(lang.size());
+ for (std::vector<std::string>::const_iterator i = lang.begin();
+ i != lang.end(); ++i) {
+ if (i->compare("RC") == 0) {
+ langsRC.push_back(*i);
+ } else {
+ langs.push_back(*i);
+ }
+ }
+ if (!langs.empty()) {
+ this->GetGlobalGenerator()->EnableLanguage(langs, this, optional);
+ }
+ if (!langsRC.empty()) {
+ this->GetGlobalGenerator()->EnableLanguage(langsRC, this, optional);
+ }
}
int cmMakefile::TryCompile(const std::string& srcdir,
diff --git a/Source/cmParseArgumentsCommand.cxx b/Source/cmParseArgumentsCommand.cxx
index e8de5b6..55d71ea 100644
--- a/Source/cmParseArgumentsCommand.cxx
+++ b/Source/cmParseArgumentsCommand.cxx
@@ -4,6 +4,19 @@
#include "cmAlgorithms.h"
+static std::string escape_arg(const std::string& arg)
+{
+ // replace ";" with "\;" so output argument lists will split correctly
+ std::string escapedArg;
+ for (size_t i = 0; i < arg.size(); ++i) {
+ if (arg[i] == ';') {
+ escapedArg += '\\';
+ }
+ escapedArg += arg[i];
+ }
+ return escapedArg;
+}
+
bool cmParseArgumentsCommand::InitialPass(std::vector<std::string> const& args,
cmExecutionStatus&)
{
@@ -165,10 +178,18 @@ bool cmParseArgumentsCommand::InitialPass(std::vector<std::string> const& args,
insideValues = NONE;
break;
case MULTI:
- multi[currentArgName].push_back(*argIter);
+ if (parseFromArgV) {
+ multi[currentArgName].push_back(escape_arg(*argIter));
+ } else {
+ multi[currentArgName].push_back(*argIter);
+ }
break;
default:
- unparsed.push_back(*argIter);
+ if (parseFromArgV) {
+ unparsed.push_back(escape_arg(*argIter));
+ } else {
+ unparsed.push_back(*argIter);
+ }
break;
}
}
diff --git a/Source/cmServer.cxx b/Source/cmServer.cxx
index ba1bd9d..51a363f 100644
--- a/Source/cmServer.cxx
+++ b/Source/cmServer.cxx
@@ -237,6 +237,11 @@ bool cmServer::Serve(std::string* errorMessage)
return Connection->ProcessEvents(errorMessage);
}
+cmFileMonitor* cmServer::FileMonitor() const
+{
+ return Connection->FileMonitor();
+}
+
void cmServer::WriteJsonObject(const Json::Value& jsonValue,
const DebugInfo* debug) const
{
diff --git a/Source/cmServer.h b/Source/cmServer.h
index 796db8e..7f29e32 100644
--- a/Source/cmServer.h
+++ b/Source/cmServer.h
@@ -13,6 +13,7 @@
#include <string>
#include <vector>
+class cmFileMonitor;
class cmServerConnection;
class cmServerProtocol;
class cmServerRequest;
@@ -28,6 +29,8 @@ public:
bool Serve(std::string* errorMessage);
+ cmFileMonitor* FileMonitor() const;
+
private:
void RegisterProtocol(cmServerProtocol* protocol);
diff --git a/Source/cmServerConnection.cxx b/Source/cmServerConnection.cxx
index 89ee6d8..c62ca3c 100644
--- a/Source/cmServerConnection.cxx
+++ b/Source/cmServerConnection.cxx
@@ -4,7 +4,8 @@
#include "cmServerDictionary.h"
-#include <cmServer.h>
+#include "cmFileMonitor.h"
+#include "cmServer.h"
#include <assert.h>
@@ -64,10 +65,16 @@ public:
: Connection(connection)
{
Connection->mLoop = uv_default_loop();
+ if (Connection->mLoop) {
+ Connection->mFileMonitor = new cmFileMonitor(Connection->mLoop);
+ }
}
~LoopGuard()
{
+ if (Connection->mFileMonitor) {
+ delete Connection->mFileMonitor;
+ }
uv_loop_close(Connection->mLoop);
Connection->mLoop = nullptr;
}
diff --git a/Source/cmServerConnection.h b/Source/cmServerConnection.h
index 16b1d5c..78842e7 100644
--- a/Source/cmServerConnection.h
+++ b/Source/cmServerConnection.h
@@ -10,6 +10,7 @@
#endif
class cmServer;
+class cmFileMonitor;
class LoopGuard;
class cmServerConnection
@@ -29,6 +30,8 @@ public:
virtual void Connect(uv_stream_t* server) { (void)(server); }
+ cmFileMonitor* FileMonitor() const { return this->mFileMonitor; }
+
protected:
virtual bool DoSetup(std::string* errorMessage) = 0;
virtual void TearDown() = 0;
@@ -46,6 +49,7 @@ protected:
private:
uv_loop_t* mLoop = nullptr;
+ cmFileMonitor* mFileMonitor = nullptr;
cmServer* Server = nullptr;
friend class LoopGuard;
diff --git a/Source/cmServerDictionary.h b/Source/cmServerDictionary.h
index e429571..c82274a 100644
--- a/Source/cmServerDictionary.h
+++ b/Source/cmServerDictionary.h
@@ -6,9 +6,16 @@
// 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";
@@ -17,36 +24,71 @@ 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 kARTIFACTS_KEY = "artifacts";
static const std::string kBUILD_DIRECTORY_KEY = "buildDirectory";
+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 kCOMPILE_FLAGS_KEY = "compileFlags";
+static const std::string kCONFIGURATIONS_KEY = "configurations";
static const std::string kCOOKIE_KEY = "cookie";
static const std::string kDEBUG_OUTPUT_KEY = "debugOutput";
+static const std::string kDEFINES_KEY = "defines";
static const std::string kERROR_MESSAGE_KEY = "errorMessage";
static const std::string kEXTRA_GENERATOR_KEY = "extraGenerator";
+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 kGENERATOR_KEY = "generator";
+static const std::string kINCLUDE_PATH_KEY = "includePath";
+static const std::string kIS_CMAKE_KEY = "isCMake";
static const std::string kIS_EXPERIMENTAL_KEY = "isExperimental";
+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 kKEYS_KEY = "keys";
+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 kMAJOR_KEY = "major";
static const std::string kMESSAGE_KEY = "message";
static const std::string kMINOR_KEY = "minor";
static const std::string kNAME_KEY = "name";
+static const std::string kPATH_KEY = "path";
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 kPROJECTS_KEY = "projects";
+static const std::string kPROPERTIES_KEY = "properties";
static const std::string kPROTOCOL_VERSION_KEY = "protocolVersion";
static const std::string kREPLY_TO_KEY = "inReplyTo";
static const std::string kSOURCE_DIRECTORY_KEY = "sourceDirectory";
+static const std::string kSOURCES_KEY = "sources";
static const std::string kSUPPORTED_PROTOCOL_VERSIONS =
"supportedProtocolVersions";
+static const std::string kSYSROOT_KEY = "sysroot";
+static const std::string kTARGETS_KEY = "targets";
static const std::string kTITLE_KEY = "title";
static const std::string kTRACE_EXPAND_KEY = "traceExpand";
static const std::string kTRACE_KEY = "trace";
static const std::string kTYPE_KEY = "type";
+static const std::string kVALUE_KEY = "value";
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";
diff --git a/Source/cmServerProtocol.cxx b/Source/cmServerProtocol.cxx
index c505568..a2bdf49 100644
--- a/Source/cmServerProtocol.cxx
+++ b/Source/cmServerProtocol.cxx
@@ -2,10 +2,17 @@
file Copyright.txt or https://cmake.org/licensing for details. */
#include "cmServerProtocol.h"
+#include "cmCacheManager.h"
#include "cmExternalMakefileProjectGenerator.h"
+#include "cmFileMonitor.h"
+#include "cmGeneratorTarget.h"
#include "cmGlobalGenerator.h"
+#include "cmListFileCache.h"
+#include "cmLocalGenerator.h"
+#include "cmMakefile.h"
#include "cmServer.h"
#include "cmServerDictionary.h"
+#include "cmSourceFile.h"
#include "cmSystemTools.h"
#include "cmake.h"
@@ -16,6 +23,100 @@
#include "cm_jsoncpp_value.h"
#endif
+#include <algorithm>
+#include <string>
+#include <vector>
+
+// Get rid of some windows macros:
+#undef max
+
+namespace {
+
+static std::vector<std::string> getConfigurations(const cmake* cm)
+{
+ std::vector<std::string> configurations;
+ auto makefiles = cm->GetGlobalGenerator()->GetMakefiles();
+ if (makefiles.empty()) {
+ return configurations;
+ }
+
+ makefiles[0]->GetConfigurations(configurations);
+ if (configurations.empty())
+ configurations.push_back("");
+ return configurations;
+}
+
+static bool hasString(const Json::Value& v, const std::string& s)
+{
+ return !v.isNull() &&
+ std::find_if(v.begin(), v.end(), [s](const Json::Value& i) {
+ return i.asString() == s;
+ }) != v.end();
+}
+
+template <class T>
+static Json::Value fromStringList(const T& in)
+{
+ Json::Value result = Json::arrayValue;
+ for (const std::string& i : in) {
+ result.append(i);
+ }
+ return result;
+}
+
+static std::vector<std::string> toStringList(const Json::Value& in)
+{
+ std::vector<std::string> result;
+ for (const auto& it : in) {
+ result.push_back(it.asString());
+ }
+ return result;
+}
+
+static void getCMakeInputs(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() + '/';
+ const std::vector<cmMakefile*> makefiles = gg->GetMakefiles();
+ for (auto it = makefiles.begin(); it != makefiles.end(); ++it) {
+ const std::vector<std::string> listFiles = (*it)->GetListFiles();
+
+ for (auto jt = listFiles.begin(); jt != listFiles.end(); ++jt) {
+
+ const std::string startOfFile = jt->substr(0, cmakeRootDir.size());
+ const bool isInternal = (startOfFile == cmakeRootDir);
+ const bool isTemporary = !isInternal && (jt->find(buildDir + '/') == 0);
+
+ std::string toAdd = *jt;
+ if (!sourceDir.empty()) {
+ const std::string& relative =
+ cmSystemTools::RelativePath(sourceDir.c_str(), jt->c_str());
+ if (toAdd.size() > relative.size())
+ toAdd = relative;
+ }
+
+ if (isInternal) {
+ if (internalFiles)
+ internalFiles->push_back(toAdd);
+ } else {
+ if (isTemporary) {
+ if (tmpFiles)
+ tmpFiles->push_back(toAdd);
+ } else {
+ if (explicitFiles)
+ explicitFiles->push_back(toAdd);
+ }
+ }
+ }
+ }
+}
+
+} // namespace
+
cmServerRequest::cmServerRequest(cmServer* server, const std::string& t,
const std::string& c, const Json::Value& d)
: Type(t)
@@ -114,6 +215,11 @@ bool cmServerProtocol::Activate(cmServer* server,
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
{
@@ -265,17 +371,53 @@ bool cmServerProtocol1_0::DoActivate(const cmServerRequest& request,
return true;
}
+void cmServerProtocol1_0::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);
+}
+
const cmServerResponse cmServerProtocol1_0::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);
}
@@ -291,6 +433,441 @@ bool cmServerProtocol1_0::IsExperimental() const
return true;
}
+cmServerResponse cmServerProtocol1_0::ProcessCache(
+ const cmServerRequest& request)
+{
+ if (this->m_State < STATE_CONFIGURED) {
+ return request.ReportError("This project was not configured yet.");
+ }
+
+ 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 i : keys) {
+ if (std::find_if(allKeys.begin(), allKeys.end(),
+ [i](const std::string& j) { return i == j; }) ==
+ allKeys.end()) {
+ return request.ReportError("Key \"" + i + "\" not found in cache.");
+ }
+ }
+ }
+ std::sort(keys.begin(), keys.end());
+ for (auto 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 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_0::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 cmGlobalGenerator* gg = cm->GetGlobalGenerator();
+ const std::string cmakeRootDir = cmSystemTools::GetCMakeRoot();
+ const std::string buildDir = cm->GetHomeOutputDirectory();
+ const std::string sourceDir = cm->GetHomeDirectory();
+
+ Json::Value result = Json::objectValue;
+ result[kSOURCE_DIRECTORY_KEY] = sourceDir;
+ result[kCMAKE_ROOT_DIRECTORY_KEY] = cmakeRootDir;
+
+ std::vector<std::string> internalFiles;
+ std::vector<std::string> explicitFiles;
+ std::vector<std::string> tmpFiles;
+ getCMakeInputs(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);
+
+ result[kBUILD_FILES_KEY] = array;
+
+ return request.Reply(result);
+}
+
+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;
+ for (auto i : defines) {
+ result.push_back(i);
+ }
+ std::sort(result.begin(), result.end());
+ Defines = 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 i : in.IncludePathList) {
+ result = result ^ (hash<std::string>()(i.first) ^
+ (i.second ? std::numeric_limits<size_t>::max() : 0));
+ }
+ for (auto 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 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 i : files) {
+ const std::string relPath =
+ cmSystemTools::RelativePath(baseDir.c_str(), i.c_str());
+ 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->GetLanguage();
+ if (!fileData.Language.empty()) {
+ const LanguageData& ld = languageDataMap.at(fileData.Language);
+ cmLocalGenerator* lg = target->GetLocalGenerator();
+
+ std::string compileFlags = ld.Flags;
+ lg->AppendFlags(compileFlags, file->GetProperty("COMPILE_FLAGS"));
+ fileData.Flags = compileFlags;
+
+ fileData.IncludePathList = ld.IncludePathList;
+
+ std::set<std::string> defines;
+ lg->AppendDefines(defines, file->GetProperty("COMPILE_DEFINITIONS"));
+ const std::string defPropName =
+ "COMPILE_DEFINITIONS_" + cmSystemTools::UpperCase(config);
+ lg->AppendDefines(defines, file->GetProperty(defPropName));
+ defines.insert(ld.Defines.begin(), ld.Defines.end());
+
+ fileData.SetDefines(defines);
+ }
+
+ fileData.IsGenerated = file->GetPropertyAsBool("GENERATED");
+ std::vector<std::string>& groupFileList = fileGroups[fileData];
+ groupFileList.push_back(file->GetFullPath());
+ }
+
+ const std::string baseDir = target->Makefile->GetCurrentSourceDirectory();
+ Json::Value result = Json::arrayValue;
+ for (auto it = fileGroups.begin(); it != fileGroups.end(); ++it) {
+ Json::Value group = DumpSourceFileGroup(it->first, it->second, baseDir);
+ if (!group.isNull())
+ result.append(group);
+ }
+
+ return result;
+}
+
+static Json::Value DumpTarget(cmGeneratorTarget* target,
+ const std::string& config)
+{
+ cmLocalGenerator* lg = target->GetLocalGenerator();
+ const cmState* state = lg->GetState();
+
+ const cmState::TargetType type = target->GetType();
+ const std::string typeName = state->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[kTYPE_KEY] = typeName;
+ result[kFULL_NAME_KEY] = target->GetFullName(config);
+ result[kSOURCE_DIRECTORY_KEY] = lg->GetCurrentSourceDirectory();
+ result[kBUILD_DIRECTORY_KEY] = lg->GetCurrentBinaryDirectory();
+
+ if (target->HaveWellDefinedOutputFiles()) {
+ Json::Value artifacts = Json::arrayValue;
+ artifacts.append(target->GetFullPath(config, false));
+ if (target->IsDLLPlatform()) {
+ artifacts.append(target->GetFullPath(config, true));
+ 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;
+ lg->GetTargetFlags(config, linkLibs, linkLanguageFlags, linkFlags,
+ frameworkPath, linkPath, target, false);
+
+ linkLibs = cmSystemTools::TrimWhitespace(linkLibs);
+ linkFlags = cmSystemTools::TrimWhitespace(linkFlags);
+ linkLanguageFlags = cmSystemTools::TrimWhitespace(linkLanguageFlags);
+ frameworkPath = cmSystemTools::TrimWhitespace(frameworkPath);
+ linkPath = cmSystemTools::TrimWhitespace(linkPath);
+
+ if (!cmSystemTools::TrimWhitespace(linkLibs).empty()) {
+ result[kLINK_LIBRARIES_KEY] = linkLibs;
+ }
+ if (!cmSystemTools::TrimWhitespace(linkFlags).empty()) {
+ result[kLINK_FLAGS_KEY] = linkFlags;
+ }
+ if (!cmSystemTools::TrimWhitespace(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 (auto 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, true);
+ for (auto i : includePathList) {
+ ld.IncludePathList.push_back(
+ std::make_pair(i, target->IsSystemIncludeDirectory(i, config)));
+ }
+ }
+
+ 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 (const auto& lgIt : generators) {
+ auto list = lgIt->GetGeneratorTargets();
+ targetList.insert(targetList.end(), list.begin(), list.end());
+ }
+ 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, const std::string 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;
+
+ assert(projectIt.second.size() >
+ 0); // All Projects must have at least one local generator
+ const cmLocalGenerator* lg = projectIt.second.at(0);
+
+ // Project structure information:
+ const cmMakefile* mf = lg->GetMakefile();
+ pObj[kSOURCE_DIRECTORY_KEY] = mf->GetCurrentSourceDirectory();
+ pObj[kBUILD_DIRECTORY_KEY] = mf->GetCurrentBinaryDirectory();
+ pObj[kTARGETS_KEY] = DumpTargetsList(projectIt.second, config);
+
+ 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 (const std::string& c : getConfigurations(cm)) {
+ result.append(DumpConfiguration(cm, c));
+ }
+
+ return result;
+}
+
+cmServerResponse cmServerProtocol1_0::ProcessCodeModel(
+ const cmServerRequest& request)
+{
+ if (this->m_State != STATE_COMPUTED) {
+ return request.ReportError("No build system was generated yet.");
+ }
+
+ Json::Value result = Json::objectValue;
+ result[kCONFIGURATIONS_KEY] = DumpConfigurationsList(this->CMakeInstance());
+ return request.Reply(result);
+}
+
cmServerResponse cmServerProtocol1_0::ProcessCompute(
const cmServerRequest& request)
{
@@ -318,6 +895,8 @@ cmServerResponse cmServerProtocol1_0::ProcessConfigure(
return request.ReportError("This instance is inactive.");
}
+ FileMonitor()->StopMonitoring();
+
// Make sure the types of cacheArguments matches (if given):
std::vector<std::string> cacheArgs;
bool cacheArgumentsError = false;
@@ -346,6 +925,8 @@ cmServerResponse cmServerProtocol1_0::ProcessConfigure(
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 setGlobalSettings.");
@@ -366,8 +947,7 @@ cmServerResponse cmServerProtocol1_0::ProcessConfigure(
const char* cachedGenerator =
cm->GetState()->GetInitializedCacheValue("CMAKE_GENERATOR");
if (cachedGenerator) {
- cmGlobalGenerator* gen = cm->GetGlobalGenerator();
- if (gen && gen->GetName() != cachedGenerator) {
+ if (gg && gg->GetName() != cachedGenerator) {
return request.ReportError("Configured generator does not match with "
"CMAKE_GENERATOR found in cache.");
}
@@ -393,7 +973,17 @@ cmServerResponse cmServerProtocol1_0::ProcessConfigure(
if (ret < 0) {
return request.ReportError("Configuration failed.");
}
+
+ std::vector<std::string> toWatchList;
+ getCMakeInputs(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());
}
@@ -466,3 +1056,22 @@ cmServerResponse cmServerProtocol1_0::ProcessSetGlobalSettings(
return request.Reply(Json::Value());
}
+
+cmServerResponse cmServerProtocol1_0::ProcessFileSystemWatchers(
+ const cmServerRequest& request)
+{
+ const cmFileMonitor* const fm = FileMonitor();
+ Json::Value result = Json::objectValue;
+ Json::Value files = Json::arrayValue;
+ for (const auto& f : fm->WatchedFiles()) {
+ files.append(f);
+ }
+ Json::Value directories = Json::arrayValue;
+ for (const auto& d : fm->WatchedDirectories()) {
+ directories.append(d);
+ }
+ result[kWATCHED_FILES_KEY] = files;
+ result[kWATCHED_DIRECTORIES_KEY] = directories;
+
+ return request.Reply(result);
+}
diff --git a/Source/cmServerProtocol.h b/Source/cmServerProtocol.h
index 63ef0be..5238d5d 100644
--- a/Source/cmServerProtocol.h
+++ b/Source/cmServerProtocol.h
@@ -13,6 +13,7 @@
#include <string>
class cmake;
+class cmFileMonitor;
class cmServer;
class cmServerRequest;
@@ -81,6 +82,7 @@ public:
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:
@@ -107,11 +109,17 @@ 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);
enum State
{
@@ -121,4 +129,6 @@ private:
STATE_COMPUTED
};
State m_State = STATE_INACTIVE;
+
+ bool m_isDirty = false;
};
diff --git a/Source/cmVisualStudio10TargetGenerator.cxx b/Source/cmVisualStudio10TargetGenerator.cxx
index b5fe754..d81f59d 100644
--- a/Source/cmVisualStudio10TargetGenerator.cxx
+++ b/Source/cmVisualStudio10TargetGenerator.cxx
@@ -1795,7 +1795,8 @@ void cmVisualStudio10TargetGenerator::WriteClOptions(
}
if (this->MSTools) {
- cmsys::RegularExpression clangToolset("v[0-9]+_clang_.*");
+ cmsys::RegularExpression clangToolset(
+ "(v[0-9]+_clang_.*|LLVM-vs[0-9]+.*)");
const char* toolset = this->GlobalGenerator->GetPlatformToolset();
if (toolset && clangToolset.find(toolset)) {
this->WriteString("<ObjectFileName>"
diff --git a/Source/kwsys/SystemTools.cxx b/Source/kwsys/SystemTools.cxx
index 4281c38..c97af25 100644
--- a/Source/kwsys/SystemTools.cxx
+++ b/Source/kwsys/SystemTools.cxx
@@ -66,6 +66,10 @@
#include <sys/stat.h>
#include <time.h>
+#if defined(_WIN32) && !defined(_MSC_VER) && defined(__GNUC__)
+# include <strings.h> /* for strcasecmp */
+#endif
+
#ifdef _MSC_VER
# define umask _umask // Note this is still umask on Borland
#endif
diff --git a/Tests/CMakeLists.txt b/Tests/CMakeLists.txt
index 3681843..31ed2eb 100644
--- a/Tests/CMakeLists.txt
+++ b/Tests/CMakeLists.txt
@@ -83,6 +83,17 @@ if(BUILD_TESTING)
set(MAKE_SUPPORTS_SPACES 0)
endif()
+ # assume no resources building to test
+ set(CMake_TEST_RESOURCES FALSE)
+ # for windows and cygwin assume we have resources
+ if(WIN32 OR CYGWIN)
+ set(CMake_TEST_RESOURCES TRUE)
+ endif()
+ # for borland and watcom there is no resource support
+ if(WATCOM OR BORLAND)
+ set(CMake_TEST_RESOURCES FALSE)
+ endif()
+
set(build_generator_args
--build-generator ${CMAKE_GENERATOR}
)
@@ -262,17 +273,7 @@ if(BUILD_TESTING)
ADD_TEST_MACRO(CompileFeatures CompileFeatures)
ADD_TEST_MACRO(CMakeCommands.target_compile_features target_compile_features)
- # assume no resources building to test
- set(TEST_RESOURCES FALSE)
- # for windows and cygwin assume we have resources
- if(WIN32 OR CYGWIN)
- set(TEST_RESOURCES TRUE)
- endif()
- # for borland and watcom there is no resource support
- if(WATCOM OR BORLAND)
- set(TEST_RESOURCES FALSE)
- endif()
- if(TEST_RESOURCES)
+ if(CMake_TEST_RESOURCES)
ADD_TEST_MACRO(VSResource VSResource)
if (CMAKE_GENERATOR MATCHES "Ninja")
add_test_macro(VSResourceNinjaForceRSP VSResourceNinjaForceRSP)
@@ -2722,7 +2723,7 @@ ${CMake_BINARY_DIR}/bin/cmake -DDIR=dev -P ${CMake_SOURCE_DIR}/Utilities/Release
ADD_TEST_MACRO(CMakeCommands.target_compile_definitions target_compile_definitions)
ADD_TEST_MACRO(CMakeCommands.target_compile_options target_compile_options)
- if(CMake_HAVE_SERVER_MODE)
+ if(CMake_TEST_SERVER_MODE)
# The cmake server-mode test requires python for a simple client.
find_package(PythonInterp QUIET)
if(PYTHON_EXECUTABLE)
diff --git a/Tests/RunCMake/CMakeLists.txt b/Tests/RunCMake/CMakeLists.txt
index 9e9903d..9dc540f 100644
--- a/Tests/RunCMake/CMakeLists.txt
+++ b/Tests/RunCMake/CMakeLists.txt
@@ -208,7 +208,7 @@ add_RunCMake_test(include)
add_RunCMake_test(include_directories)
add_RunCMake_test(list)
add_RunCMake_test(message)
-add_RunCMake_test(project)
+add_RunCMake_test(project -DCMake_TEST_RESOURCES=${CMake_TEST_RESOURCES})
add_RunCMake_test(return)
add_RunCMake_test(set_property)
add_RunCMake_test(string)
diff --git a/Tests/RunCMake/CPack/SOURCE_PACKAGE.cmake b/Tests/RunCMake/CPack/SOURCE_PACKAGE.cmake
index 9958c2a..946439d 100644
--- a/Tests/RunCMake/CPack/SOURCE_PACKAGE.cmake
+++ b/Tests/RunCMake/CPack/SOURCE_PACKAGE.cmake
@@ -1,5 +1,5 @@
file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/main.cpp"
- "int main() {return 0;}")
+ "int main() {return 0;}\n")
add_executable(test_prog "${CMAKE_CURRENT_BINARY_DIR}/main.cpp")
install(TARGETS test_prog DESTINATION foo COMPONENT applications)
diff --git a/Tests/RunCMake/cmake_parse_arguments/ArgvN.cmake b/Tests/RunCMake/cmake_parse_arguments/ArgvN.cmake
index 61bde03..96a373d 100644
--- a/Tests/RunCMake/cmake_parse_arguments/ArgvN.cmake
+++ b/Tests/RunCMake/cmake_parse_arguments/ArgvN.cmake
@@ -23,8 +23,16 @@ function(test2 arg1)
TEST(pref_OPT2 FALSE)
TEST(pref_SINGLE1 "foo;bar")
TEST(pref_SINGLE2 UNDEFINED)
- TEST(pref_MULTI1 bar foo bar)
+ TEST(pref_MULTI1 bar "foo;bar")
TEST(pref_MULTI2 UNDEFINED)
TEST(pref_UNPARSED_ARGUMENTS UNDEFINED)
endfunction()
-test2("first named" OPT1 SINGLE1 "foo;bar" MULTI1 bar foo bar)
+test2("first named" OPT1 SINGLE1 "foo;bar" MULTI1 bar "foo;bar")
+
+function(test3 arg1)
+ cmake_parse_arguments(PARSE_ARGV 0
+ pref "" "" "")
+
+ TEST(pref_UNPARSED_ARGUMENTS "foo;bar" dog cat)
+endfunction()
+test3("foo;bar" dog cat)
diff --git a/Tests/RunCMake/cmake_parse_arguments/CornerCasesArgvN.cmake b/Tests/RunCMake/cmake_parse_arguments/CornerCasesArgvN.cmake
new file mode 100644
index 0000000..807ed03
--- /dev/null
+++ b/Tests/RunCMake/cmake_parse_arguments/CornerCasesArgvN.cmake
@@ -0,0 +1,53 @@
+include(${CMAKE_CURRENT_LIST_DIR}/test_utils.cmake)
+
+function(test1)
+ cmake_parse_arguments(PARSE_ARGV 0
+ mpref "" "" "MULTI")
+
+ TEST(mpref_MULTI foo "foo\;bar")
+
+ cmake_parse_arguments(PARSE_ARGV 1
+ upref "" "" "MULTI")
+
+ TEST(upref_UNPARSED_ARGUMENTS foo "foo\;bar")
+endfunction()
+test1(MULTI foo "foo\;bar")
+
+function(test2)
+ cmake_parse_arguments(PARSE_ARGV 0
+ mpref "" "" "MULTI")
+
+ TEST(mpref_MULTI "foo;" "bar;")
+
+ cmake_parse_arguments(PARSE_ARGV 1
+ upref "" "" "MULTI")
+
+ TEST(upref_UNPARSED_ARGUMENTS "foo;" "bar;")
+endfunction()
+test2(MULTI "foo;" "bar;")
+
+function(test3)
+ cmake_parse_arguments(PARSE_ARGV 0
+ mpref "" "" "MULTI")
+
+ TEST(mpref_MULTI "[foo;]" "bar\\")
+
+ cmake_parse_arguments(PARSE_ARGV 1
+ upref "" "" "MULTI")
+
+ TEST(upref_UNPARSED_ARGUMENTS "[foo;]" "bar\\")
+endfunction()
+test3(MULTI "[foo;]" "bar\\")
+
+function(test4)
+ cmake_parse_arguments(PARSE_ARGV 0
+ mpref "" "" "MULTI")
+
+ TEST(mpref_MULTI foo "bar;none")
+
+ cmake_parse_arguments(PARSE_ARGV 1
+ upref "" "" "MULTI")
+
+ TEST(upref_UNPARSED_ARGUMENTS foo "bar;none")
+endfunction()
+test4(MULTI foo bar\\ none)
diff --git a/Tests/RunCMake/cmake_parse_arguments/RunCMakeTest.cmake b/Tests/RunCMake/cmake_parse_arguments/RunCMakeTest.cmake
index 22d7ee4..1e15b3b 100644
--- a/Tests/RunCMake/cmake_parse_arguments/RunCMakeTest.cmake
+++ b/Tests/RunCMake/cmake_parse_arguments/RunCMakeTest.cmake
@@ -10,3 +10,4 @@ run_cmake(BadArgvN1)
run_cmake(BadArgvN2)
run_cmake(BadArgvN3)
run_cmake(BadArgvN4)
+run_cmake(CornerCasesArgvN)
diff --git a/Tests/RunCMake/cmake_parse_arguments/Utils.cmake b/Tests/RunCMake/cmake_parse_arguments/Utils.cmake
index 3bbf115..f2001f2 100644
--- a/Tests/RunCMake/cmake_parse_arguments/Utils.cmake
+++ b/Tests/RunCMake/cmake_parse_arguments/Utils.cmake
@@ -17,4 +17,5 @@ SET (asdf "some value")
TEST(asdf "some value")
SET (asdf some list)
+TEST(asdf some list)
TEST(asdf "some;list")
diff --git a/Tests/RunCMake/cmake_parse_arguments/test_utils.cmake b/Tests/RunCMake/cmake_parse_arguments/test_utils.cmake
index f5425c2..9ce99b8 100644
--- a/Tests/RunCMake/cmake_parse_arguments/test_utils.cmake
+++ b/Tests/RunCMake/cmake_parse_arguments/test_utils.cmake
@@ -1,20 +1,30 @@
-macro(TEST variable)
- SET(expected "${ARGN}")
- if ( "${expected}" STREQUAL "UNDEFINED" )
- if (DEFINED ${variable})
- message(FATAL_ERROR "'${variable}' shall be undefined but has value '${${variable}}'")
- endif()
- elseif( "${expected}" STREQUAL "FALSE" )
- if (NOT ${variable} STREQUAL "FALSE")
- message(FATAL_ERROR "'${variable}' shall be FALSE")
- endif()
- elseif( "${expected}" STREQUAL "TRUE" )
- if (NOT ${variable} STREQUAL "TRUE")
- message(FATAL_ERROR "'${variable}' shall be TRUE")
- endif()
+function(TEST variable)
+ if(ARGC GREATER 2)
+ set(i 0)
+ foreach(value IN LISTS ${variable})
+ math(EXPR j "${i} + 1")
+ set(${variable}[${i}] "${value}")
+ TEST(${variable}[${i}] "${ARGV${j}}")
+ set(i ${j})
+ endforeach()
else()
- if (NOT ${variable} STREQUAL "${expected}")
- message(FATAL_ERROR "'${variable}' shall be '${expected}'")
+ set(expected "${ARGN}")
+ if("${expected}" STREQUAL "UNDEFINED")
+ if(DEFINED ${variable})
+ message(FATAL_ERROR "'${variable}' shall be undefined but has value '${${variable}}'")
+ endif()
+ elseif("${expected}" STREQUAL "FALSE")
+ if(NOT ${variable} STREQUAL "FALSE")
+ message(FATAL_ERROR "'${variable}' shall be FALSE")
+ endif()
+ elseif("${expected}" STREQUAL "TRUE")
+ if(NOT ${variable} STREQUAL "TRUE")
+ message(FATAL_ERROR "'${variable}' shall be TRUE")
+ endif()
+ else()
+ if(NOT ${variable} STREQUAL "${expected}")
+ message(FATAL_ERROR "'${variable}' shall be '${expected}'")
+ endif()
endif()
endif()
-endmacro()
+endfunction()
diff --git a/Tests/RunCMake/project/ExplicitRC.cmake b/Tests/RunCMake/project/ExplicitRC.cmake
new file mode 100644
index 0000000..b3feaa9
--- /dev/null
+++ b/Tests/RunCMake/project/ExplicitRC.cmake
@@ -0,0 +1 @@
+project(ExplicitRC C RC)
diff --git a/Tests/RunCMake/project/RunCMakeTest.cmake b/Tests/RunCMake/project/RunCMakeTest.cmake
index 6ab0fc9..dba97d2 100644
--- a/Tests/RunCMake/project/RunCMakeTest.cmake
+++ b/Tests/RunCMake/project/RunCMakeTest.cmake
@@ -1,5 +1,8 @@
include(RunCMake)
+if(CMake_TEST_RESOURCES)
+ run_cmake(ExplicitRC)
+endif()
run_cmake(LanguagesImplicit)
run_cmake(LanguagesEmpty)
run_cmake(LanguagesNONE)
diff --git a/Tests/Server/CMakeLists.txt b/Tests/Server/CMakeLists.txt
index 03f5042..8913406 100644
--- a/Tests/Server/CMakeLists.txt
+++ b/Tests/Server/CMakeLists.txt
@@ -10,6 +10,7 @@ macro(do_test bsname file)
"${CMAKE_SOURCE_DIR}/${file}"
"${CMAKE_SOURCE_DIR}"
"${CMAKE_BINARY_DIR}"
+ "${CMAKE_GENERATOR}"
RESULT_VARIABLE test_result
)
@@ -20,5 +21,6 @@ endmacro()
do_test("test_handshake" "tc_handshake.json")
do_test("test_globalSettings" "tc_globalSettings.json")
+do_test("test_buildsystem1" "tc_buildsystem1.json")
add_executable(Server empty.cpp)
diff --git a/Tests/Server/buildsystem1/CMakeLists.txt b/Tests/Server/buildsystem1/CMakeLists.txt
new file mode 100644
index 0000000..d690472
--- /dev/null
+++ b/Tests/Server/buildsystem1/CMakeLists.txt
@@ -0,0 +1,22 @@
+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)
diff --git a/Tests/Server/buildsystem1/foo.cpp b/Tests/Server/buildsystem1/foo.cpp
new file mode 100644
index 0000000..7f39d71
--- /dev/null
+++ b/Tests/Server/buildsystem1/foo.cpp
@@ -0,0 +1,5 @@
+
+int foo()
+{
+ return 0;
+}
diff --git a/Tests/Server/buildsystem1/main.cpp b/Tests/Server/buildsystem1/main.cpp
new file mode 100644
index 0000000..766b775
--- /dev/null
+++ b/Tests/Server/buildsystem1/main.cpp
@@ -0,0 +1,5 @@
+
+int main()
+{
+ return 0;
+}
diff --git a/Tests/Server/buildsystem1/subdir/CMakeLists.txt b/Tests/Server/buildsystem1/subdir/CMakeLists.txt
new file mode 100644
index 0000000..9157312
--- /dev/null
+++ b/Tests/Server/buildsystem1/subdir/CMakeLists.txt
@@ -0,0 +1,5 @@
+set(bar4 something)
+
+set(bar5 more)
+
+add_executable(ooo empty.cpp)
diff --git a/Tests/Server/buildsystem1/subdir/empty.cpp b/Tests/Server/buildsystem1/subdir/empty.cpp
new file mode 100644
index 0000000..7f39d71
--- /dev/null
+++ b/Tests/Server/buildsystem1/subdir/empty.cpp
@@ -0,0 +1,5 @@
+
+int foo()
+{
+ return 0;
+}
diff --git a/Tests/Server/cmakelib.py b/Tests/Server/cmakelib.py
index 8beaeef..94384eb 100644
--- a/Tests/Server/cmakelib.py
+++ b/Tests/Server/cmakelib.py
@@ -102,10 +102,20 @@ def waitForMessage(cmakeCommand, expected):
sys.exit(-1)
return packet
-def waitForReply(cmakeCommand, originalType, cookie):
- packet = waitForRawMessage(cmakeCommand)
- if packet['cookie'] != cookie or packet['type'] != 'reply' or packet['inReplyTo'] != originalType:
+def waitForReply(cmakeCommand, originalType, cookie, skipProgress):
+ gotResult = False
+ while True:
+ packet = waitForRawMessage(cmakeCommand)
+ t = packet['type']
+ if packet['cookie'] != cookie or packet['inReplyTo'] != originalType:
+ sys.exit(1)
+ if t == 'message' or t == 'progress':
+ if skipProgress:
+ continue
+ if t == 'reply':
+ break
sys.exit(1)
+
return packet
def waitForError(cmakeCommand, originalType, cookie, message):
@@ -126,10 +136,10 @@ def handshake(cmakeCommand, major, minor, source, build, generator, extraGenerat
writePayload(cmakeCommand, { 'type': 'handshake', 'protocolVersion': version,
'cookie': 'TEST_HANDSHAKE', 'sourceDirectory': source, 'buildDirectory': build,
'generator': generator, 'extraGenerator': extraGenerator })
- waitForReply(cmakeCommand, 'handshake', 'TEST_HANDSHAKE')
+ waitForReply(cmakeCommand, 'handshake', 'TEST_HANDSHAKE', False)
def validateGlobalSettings(cmakeCommand, cmakeCommandPath, data):
- packet = waitForReply(cmakeCommand, 'globalSettings', '')
+ packet = waitForReply(cmakeCommand, 'globalSettings', '', False)
capabilities = packet['capabilities']
diff --git a/Tests/Server/server-test.py b/Tests/Server/server-test.py
index d2bf92e..14767f4 100644
--- a/Tests/Server/server-test.py
+++ b/Tests/Server/server-test.py
@@ -1,24 +1,25 @@
-import sys, cmakelib, json
+import sys, cmakelib, json, os, shutil
debug = True
cmakeCommand = sys.argv[1]
testFile = sys.argv[2]
sourceDir = sys.argv[3]
-buildDir = sys.argv[4]
+buildDir = sys.argv[4] + "/" + os.path.splitext(os.path.basename(testFile))[0]
+cmakeGenerator = sys.argv[5]
-print("SourceDir: ", sourceDir, " -- BuildDir: ", buildDir)
+print("Test:", testFile,
+ "\n-- SourceDir:", sourceDir,
+ "\n-- BuildDir:", buildDir,
+ "\n-- Generator:", cmakeGenerator)
+
+if os.path.exists(buildDir):
+ shutil.rmtree(buildDir)
proc = cmakelib.initProc(cmakeCommand)
with open(testFile) as f:
- testText = f.read()
- testText = testText.replace('%BUILDDIR%', buildDir)
- testText = testText.replace('%SOURCEDIR%', sourceDir)
- testData = json.loads(testText)
-
-buildDir = sys.argv[3]
-sourceDir = sys.argv[4]
+ testData = json.loads(f.read())
for obj in testData:
if 'sendRaw' in obj:
@@ -38,9 +39,11 @@ for obj in testData:
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']
- cmakelib.waitForReply(proc, originalType, cookie)
+ 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))
@@ -68,8 +71,8 @@ for obj in testData:
if debug: print("Doing handshake:", json.dumps(data))
major = -1
minor = -1
- generator = 'Ninja'
- extraGenerator = 'CodeBlocks'
+ generator = cmakeGenerator
+ extraGenerator = ''
sourceDirectory = sourceDir
buildDirectory = buildDir
if 'major' in data: major = data['major']
@@ -78,14 +81,18 @@ for obj in testData:
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 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'] = 'Ninja'
- if not 'extraGenerator' in data: data['extraGenerator'] = 'CodeBlocks'
+ if not 'generator' in data: data['generator'] = cmakeGenerator
+ if not 'extraGenerator' in data: data['extraGenerator'] = ''
cmakelib.validateGlobalSettings(proc, cmakeCommand, data)
elif 'message' in obj:
print("MESSAGE:", obj["message"])
@@ -95,4 +102,18 @@ for obj in testData:
print("Completed")
+# Tell the server to exit.
+proc.stdin.close()
+proc.stdout.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
+
sys.exit(0)
diff --git a/Tests/Server/tc_buildsystem1.json b/Tests/Server/tc_buildsystem1.json
new file mode 100644
index 0000000..08831b7
--- /dev/null
+++ b/Tests/Server/tc_buildsystem1.json
@@ -0,0 +1,27 @@
+[
+{ "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." }
+]
diff --git a/Utilities/Release/linux64_release.cmake b/Utilities/Release/linux64_release.cmake
index e7e154e..b72fc12 100644
--- a/Utilities/Release/linux64_release.cmake
+++ b/Utilities/Release/linux64_release.cmake
@@ -31,8 +31,10 @@ CMAKE_USE_OPENSSL:BOOL=ON
OPENSSL_CRYPTO_LIBRARY:FILEPATH=/home/kitware/openssl-1.0.2h/lib/libcrypto.a
OPENSSL_INCLUDE_DIR:PATH=/home/kitware/openssl-1.0.2h/include
OPENSSL_SSL_LIBRARY:FILEPATH=/home/kitware/openssl-1.0.2h/lib/libssl.a
+PYTHON_EXECUTABLE:FILEPATH=/usr/bin/python3
CPACK_SYSTEM_NAME:STRING=Linux-x86_64
BUILD_QtDialog:BOOL:=TRUE
+CMake_ENABLE_SERVER_MODE:BOOL=TRUE
CMake_GUI_DISTRIBUTE_WITH_Qt_LGPL:STRING=3
CMake_INSTALL_DEPENDENCIES:BOOL=ON
CMAKE_PREFIX_PATH:STRING=${qt_prefix}
diff --git a/Utilities/Release/osx_release.cmake b/Utilities/Release/osx_release.cmake
index 12b12d7..27c820f 100644
--- a/Utilities/Release/osx_release.cmake
+++ b/Utilities/Release/osx_release.cmake
@@ -20,6 +20,7 @@ CMAKE_OSX_DEPLOYMENT_TARGET:STRING=10.7
CMAKE_SKIP_BOOTSTRAP_TEST:STRING=TRUE
CPACK_SYSTEM_NAME:STRING=Darwin-x86_64
BUILD_QtDialog:BOOL=TRUE
+CMake_ENABLE_SERVER_MODE:BOOL=TRUE
CMake_GUI_DISTRIBUTE_WITH_Qt_LGPL:STRING=3
CMake_INSTALL_DEPENDENCIES:BOOL=ON
CMAKE_SKIP_RPATH:BOOL=TRUE
diff --git a/Utilities/Release/win32_release.cmake b/Utilities/Release/win32_release.cmake
index f54a4ca..df9fe27 100644
--- a/Utilities/Release/win32_release.cmake
+++ b/Utilities/Release/win32_release.cmake
@@ -15,6 +15,7 @@ CMAKE_SKIP_BOOTSTRAP_TEST:STRING=TRUE
CMAKE_Fortran_COMPILER:FILEPATH=FALSE
CMAKE_GENERATOR:INTERNAL=Ninja
BUILD_QtDialog:BOOL:=TRUE
+CMake_ENABLE_SERVER_MODE:BOOL=TRUE
CMake_GUI_DISTRIBUTE_WITH_Qt_LGPL:STRING=3
CMake_INSTALL_DEPENDENCIES:BOOL=ON
CMAKE_EXE_LINKER_FLAGS:STRING=-machine:x86 -subsystem:console,5.01
diff --git a/Utilities/Release/win64_release.cmake b/Utilities/Release/win64_release.cmake
index bd2690f..ab52d79 100644
--- a/Utilities/Release/win64_release.cmake
+++ b/Utilities/Release/win64_release.cmake
@@ -16,6 +16,7 @@ CMAKE_SKIP_BOOTSTRAP_TEST:STRING=TRUE
CMAKE_Fortran_COMPILER:FILEPATH=FALSE
CMAKE_GENERATOR:INTERNAL=Ninja
BUILD_QtDialog:BOOL:=TRUE
+CMake_ENABLE_SERVER_MODE:BOOL=TRUE
CMake_GUI_DISTRIBUTE_WITH_Qt_LGPL:STRING=3
CMake_INSTALL_DEPENDENCIES:BOOL=ON
CMAKE_EXE_LINKER_FLAGS:STRING=-machine:x64 -subsystem:console,5.02
diff --git a/Utilities/cmlibuv/src/unix/fs.c b/Utilities/cmlibuv/src/unix/fs.c
index 216ef97..3d478b7 100644
--- a/Utilities/cmlibuv/src/unix/fs.c
+++ b/Utilities/cmlibuv/src/unix/fs.c
@@ -346,22 +346,30 @@ done:
}
-#if defined(__OpenBSD__) || (defined(__APPLE__) && !defined(MAC_OS_X_VERSION_10_8))
-static int uv__fs_scandir_filter(uv__dirent_t* dent) {
+#if defined(__APPLE__) && !defined(MAC_OS_X_VERSION_10_8)
+#define UV_CONST_DIRENT uv__dirent_t
#else
-static int uv__fs_scandir_filter(const uv__dirent_t* dent) {
+#define UV_CONST_DIRENT const uv__dirent_t
#endif
+
+
+static int uv__fs_scandir_filter(UV_CONST_DIRENT* dent) {
return strcmp(dent->d_name, ".") != 0 && strcmp(dent->d_name, "..") != 0;
}
+static int uv__fs_scandir_sort(UV_CONST_DIRENT** a, UV_CONST_DIRENT** b) {
+ return strcmp((*a)->d_name, (*b)->d_name);
+}
+
+
static ssize_t uv__fs_scandir(uv_fs_t* req) {
uv__dirent_t **dents;
int saved_errno;
int n;
dents = NULL;
- n = scandir(req->path, &dents, uv__fs_scandir_filter, alphasort);
+ n = scandir(req->path, &dents, uv__fs_scandir_filter, uv__fs_scandir_sort);
/* NOTE: We will use nbufs as an index field */
req->nbufs = 0;
@@ -790,6 +798,7 @@ static void uv__to_stat(struct stat* src, uv_stat_t* dst) {
dst->st_flags = 0;
dst->st_gen = 0;
#elif !defined(_AIX) && ( \
+ defined(_GNU_SOURCE) || \
defined(_BSD_SOURCE) || \
defined(_SVID_SOURCE) || \
defined(_XOPEN_SOURCE) || \
diff --git a/Utilities/cmlibuv/src/win/fs.c b/Utilities/cmlibuv/src/win/fs.c
index 6a4157b..f1711ac 100644
--- a/Utilities/cmlibuv/src/win/fs.c
+++ b/Utilities/cmlibuv/src/win/fs.c
@@ -230,6 +230,7 @@ INLINE static void uv_fs_req_init(uv_loop_t* loop, uv_fs_t* req,
req->ptr = NULL;
req->path = NULL;
req->cb = cb;
+ memset(&req->fs, 0, sizeof(req->fs));
}
@@ -1893,9 +1894,13 @@ void uv_fs_req_cleanup(uv_fs_t* req) {
uv__free(req->ptr);
}
+ if (req->fs.info.bufs != req->fs.info.bufsml)
+ uv__free(req->fs.info.bufs);
+
req->path = NULL;
req->file.pathw = NULL;
req->fs.info.new_pathw = NULL;
+ req->fs.info.bufs = NULL;
req->ptr = NULL;
req->flags |= UV_FS_CLEANEDUP;
diff --git a/Utilities/cmlibuv/src/win/tty.c b/Utilities/cmlibuv/src/win/tty.c
index 0975b33..18d68d0 100644
--- a/Utilities/cmlibuv/src/win/tty.c
+++ b/Utilities/cmlibuv/src/win/tty.c
@@ -111,7 +111,11 @@ static int uv_tty_virtual_offset = -1;
static int uv_tty_virtual_height = -1;
static int uv_tty_virtual_width = -1;
-static CRITICAL_SECTION uv_tty_output_lock;
+/* We use a semaphore rather than a mutex or critical section because in some
+ cases (uv__cancel_read_console) we need take the lock in the main thread and
+ release it in another thread. Using a semaphore ensures that in such
+ scenario the main thread will still block when trying to acquire the lock. */
+static uv_sem_t uv_tty_output_lock;
static HANDLE uv_tty_output_handle = INVALID_HANDLE_VALUE;
@@ -134,7 +138,8 @@ static uv_vtermstate_t uv__vterm_state = UV_UNCHECKED;
static void uv__determine_vterm_state(HANDLE handle);
void uv_console_init() {
- InitializeCriticalSection(&uv_tty_output_lock);
+ if (uv_sem_init(&uv_tty_output_lock, 1))
+ abort();
}
@@ -172,7 +177,7 @@ int uv_tty_init(uv_loop_t* loop, uv_tty_t* tty, uv_file fd, int readable) {
/* Obtain the the tty_output_lock because the virtual window state is */
/* shared between all uv_tty_t handles. */
- EnterCriticalSection(&uv_tty_output_lock);
+ uv_sem_wait(&uv_tty_output_lock);
if (uv__vterm_state == UV_UNCHECKED)
uv__determine_vterm_state(handle);
@@ -187,7 +192,7 @@ int uv_tty_init(uv_loop_t* loop, uv_tty_t* tty, uv_file fd, int readable) {
uv_tty_update_virtual_window(&screen_buffer_info);
- LeaveCriticalSection(&uv_tty_output_lock);
+ uv_sem_post(&uv_tty_output_lock);
}
@@ -315,10 +320,6 @@ int uv_tty_set_mode(uv_tty_t* tty, uv_tty_mode_t mode) {
return UV_EINVAL;
}
- if (!SetConsoleMode(tty->handle, flags)) {
- return uv_translate_sys_error(GetLastError());
- }
-
/* If currently reading, stop, and restart reading. */
if (tty->flags & UV_HANDLE_READING) {
was_reading = 1;
@@ -332,6 +333,14 @@ int uv_tty_set_mode(uv_tty_t* tty, uv_tty_mode_t mode) {
was_reading = 0;
}
+ uv_sem_wait(&uv_tty_output_lock);
+ if (!SetConsoleMode(tty->handle, flags)) {
+ err = uv_translate_sys_error(GetLastError());
+ uv_sem_post(&uv_tty_output_lock);
+ return err;
+ }
+ uv_sem_post(&uv_tty_output_lock);
+
/* Update flag. */
tty->flags &= ~UV_HANDLE_TTY_RAW;
tty->flags |= mode ? UV_HANDLE_TTY_RAW : 0;
@@ -361,9 +370,9 @@ int uv_tty_get_winsize(uv_tty_t* tty, int* width, int* height) {
return uv_translate_sys_error(GetLastError());
}
- EnterCriticalSection(&uv_tty_output_lock);
+ uv_sem_wait(&uv_tty_output_lock);
uv_tty_update_virtual_window(&info);
- LeaveCriticalSection(&uv_tty_output_lock);
+ uv_sem_post(&uv_tty_output_lock);
*width = uv_tty_virtual_width;
*height = uv_tty_virtual_height;
@@ -432,6 +441,7 @@ static DWORD CALLBACK uv_tty_line_read_thread(void* data) {
DWORD chars, read_chars;
LONG status;
COORD pos;
+ BOOL read_console_success;
assert(data);
@@ -461,11 +471,13 @@ static DWORD CALLBACK uv_tty_line_read_thread(void* data) {
return 0;
}
- if (ReadConsoleW(handle->handle,
- (void*) utf16,
- chars,
- &read_chars,
- NULL)) {
+ read_console_success = ReadConsoleW(handle->handle,
+ (void*) utf16,
+ chars,
+ &read_chars,
+ NULL);
+
+ if (read_console_success) {
read_bytes = WideCharToMultiByte(CP_UTF8,
0,
utf16,
@@ -480,33 +492,36 @@ static DWORD CALLBACK uv_tty_line_read_thread(void* data) {
SET_REQ_ERROR(req, GetLastError());
}
- InterlockedExchange(&uv__read_console_status, COMPLETED);
+ status = InterlockedExchange(&uv__read_console_status, COMPLETED);
- /* If we canceled the read by sending a VK_RETURN event, restore the screen
- state to undo the visual effect of the VK_RETURN*/
- if (InterlockedOr(&uv__restore_screen_state, 0)) {
- HANDLE active_screen_buffer = CreateFileA("conout$",
+ if (status == TRAP_REQUESTED) {
+ /* If we canceled the read by sending a VK_RETURN event, restore the
+ screen state to undo the visual effect of the VK_RETURN */
+ if (read_console_success && InterlockedOr(&uv__restore_screen_state, 0)) {
+ HANDLE active_screen_buffer;
+ active_screen_buffer = CreateFileA("conout$",
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL);
- if (active_screen_buffer != INVALID_HANDLE_VALUE) {
- pos = uv__saved_screen_state.dwCursorPosition;
-
- /* If the cursor was at the bottom line of the screen buffer, the
- VK_RETURN would have caused the buffer contents to scroll up by
- one line. The right position to reset the cursor to is therefore one
- line higher */
- if (pos.Y == uv__saved_screen_state.dwSize.Y - 1)
- pos.Y--;
-
- SetConsoleCursorPosition(active_screen_buffer, pos);
- CloseHandle(active_screen_buffer);
+ if (active_screen_buffer != INVALID_HANDLE_VALUE) {
+ pos = uv__saved_screen_state.dwCursorPosition;
+
+ /* If the cursor was at the bottom line of the screen buffer, the
+ VK_RETURN would have caused the buffer contents to scroll up by one
+ line. The right position to reset the cursor to is therefore one line
+ higher */
+ if (pos.Y == uv__saved_screen_state.dwSize.Y - 1)
+ pos.Y--;
+
+ SetConsoleCursorPosition(active_screen_buffer, pos);
+ CloseHandle(active_screen_buffer);
+ }
}
+ uv_sem_post(&uv_tty_output_lock);
}
-
POST_COMPLETION_FOR_REQ(loop, req);
return 0;
}
@@ -694,14 +709,14 @@ void uv_process_tty_read_raw_req(uv_loop_t* loop, uv_tty_t* handle,
if (handle->tty.rd.last_input_record.EventType == WINDOW_BUFFER_SIZE_EVENT) {
CONSOLE_SCREEN_BUFFER_INFO info;
- EnterCriticalSection(&uv_tty_output_lock);
+ uv_sem_wait(&uv_tty_output_lock);
if (uv_tty_output_handle != INVALID_HANDLE_VALUE &&
GetConsoleScreenBufferInfo(uv_tty_output_handle, &info)) {
uv_tty_update_virtual_window(&info);
}
- LeaveCriticalSection(&uv_tty_output_lock);
+ uv_sem_post(&uv_tty_output_lock);
continue;
}
@@ -1035,11 +1050,16 @@ static int uv__cancel_read_console(uv_tty_t* handle) {
assert(!(handle->flags & UV_HANDLE_CANCELLATION_PENDING));
+ /* Hold the output lock during the cancellation, to ensure that further
+ writes don't interfere with the screen state. It will be the ReadConsole
+ thread's responsibility to release the lock. */
+ uv_sem_wait(&uv_tty_output_lock);
status = InterlockedExchange(&uv__read_console_status, TRAP_REQUESTED);
if (status != IN_PROGRESS) {
/* Either we have managed to set a trap for the other thread before
ReadConsole is called, or ReadConsole has returned because the user
has pressed ENTER. In either case, there is nothing else to do. */
+ uv_sem_post(&uv_tty_output_lock);
return 0;
}
@@ -1624,7 +1644,7 @@ static int uv_tty_write_bufs(uv_tty_t* handle,
/* state. */
*error = ERROR_SUCCESS;
- EnterCriticalSection(&uv_tty_output_lock);
+ uv_sem_wait(&uv_tty_output_lock);
for (i = 0; i < nbufs; i++) {
uv_buf_t buf = bufs[i];
@@ -2061,7 +2081,7 @@ static int uv_tty_write_bufs(uv_tty_t* handle,
handle->tty.wr.previous_eol = previous_eol;
handle->tty.wr.ansi_parser_state = ansi_parser_state;
- LeaveCriticalSection(&uv_tty_output_lock);
+ uv_sem_post(&uv_tty_output_lock);
if (*error == STATUS_SUCCESS) {
return 0;
diff --git a/Utilities/cmlibuv/src/win/util.c b/Utilities/cmlibuv/src/win/util.c
index 4a2e501..050058a 100644
--- a/Utilities/cmlibuv/src/win/util.c
+++ b/Utilities/cmlibuv/src/win/util.c
@@ -1078,6 +1078,7 @@ int uv_getrusage(uv_rusage_t *uv_rusage) {
FILETIME createTime, exitTime, kernelTime, userTime;
SYSTEMTIME kernelSystemTime, userSystemTime;
PROCESS_MEMORY_COUNTERS memCounters;
+ IO_COUNTERS ioCounters;
int ret;
ret = GetProcessTimes(GetCurrentProcess(), &createTime, &exitTime, &kernelTime, &userTime);
@@ -1102,6 +1103,11 @@ int uv_getrusage(uv_rusage_t *uv_rusage) {
return uv_translate_sys_error(GetLastError());
}
+ ret = GetProcessIoCounters(GetCurrentProcess(), &ioCounters);
+ if (ret == 0) {
+ return uv_translate_sys_error(GetLastError());
+ }
+
memset(uv_rusage, 0, sizeof(*uv_rusage));
uv_rusage->ru_utime.tv_sec = userSystemTime.wHour * 3600 +
@@ -1117,6 +1123,9 @@ int uv_getrusage(uv_rusage_t *uv_rusage) {
uv_rusage->ru_majflt = (uint64_t) memCounters.PageFaultCount;
uv_rusage->ru_maxrss = (uint64_t) memCounters.PeakWorkingSetSize / 1024;
+ uv_rusage->ru_oublock = (uint64_t) ioCounters.WriteOperationCount;
+ uv_rusage->ru_inblock = (uint64_t) ioCounters.ReadOperationCount;
+
return 0;
}
diff --git a/bootstrap b/bootstrap
index e22570e..889cc33 100755
--- a/bootstrap
+++ b/bootstrap
@@ -65,6 +65,7 @@ cmake_init_file=""
cmake_bootstrap_system_libs=""
cmake_bootstrap_qt_gui=""
cmake_bootstrap_qt_qmake=""
+cmake_bootstrap_server=""
cmake_sphinx_man=""
cmake_sphinx_html=""
cmake_sphinx_qthelp=""
@@ -407,6 +408,9 @@ Configuration:
--no-qt-gui do not build the Qt-based GUI (default)
--qt-qmake=<qmake> use <qmake> as the qmake executable to find Qt
+ --server enable the server mode (default if supported)
+ --no-server disable the server mode
+
--sphinx-man build man pages with Sphinx
--sphinx-html build html help with Sphinx
--sphinx-qthelp build qch help with Sphinx
@@ -641,6 +645,8 @@ while test $# != 0; do
--qt-gui) cmake_bootstrap_qt_gui="1" ;;
--no-qt-gui) cmake_bootstrap_qt_gui="0" ;;
--qt-qmake=*) cmake_bootstrap_qt_qmake=`cmake_arg "$1"` ;;
+ --server) cmake_bootstrap_server="1" ;;
+ --no-server) cmake_bootstrap_server="0" ;;
--sphinx-man) cmake_sphinx_man="1" ;;
--sphinx-html) cmake_sphinx_html="1" ;;
--sphinx-qthelp) cmake_sphinx_qthelp="1" ;;
@@ -1401,6 +1407,11 @@ if [ "x${cmake_bootstrap_qt_qmake}" != "x" ]; then
set (QT_QMAKE_EXECUTABLE "'"${cmake_bootstrap_qt_qmake}"'" CACHE FILEPATH "Location of Qt qmake" FORCE)
' >> "${cmake_bootstrap_dir}/InitialCacheFlags.cmake"
fi
+if [ "x${cmake_bootstrap_server}" != "x" ]; then
+ echo '
+set (CMake_ENABLE_SERVER_MODE '"${cmake_bootstrap_server}"' CACHE BOOL "Enable server mode" FORCE)
+' >> "${cmake_bootstrap_dir}/InitialCacheFlags.cmake"
+fi
if [ "x${cmake_sphinx_man}" != "x" ]; then
echo '
set (SPHINX_MAN "'"${cmake_sphinx_man}"'" CACHE BOOL "Build man pages with Sphinx" FORCE)