diff options
46 files changed, 1211 insertions, 52 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/Help/manual/cmake-modules.7.rst b/Help/manual/cmake-modules.7.rst index e905ef4..015e36e 100644 --- a/Help/manual/cmake-modules.7.rst +++ b/Help/manual/cmake-modules.7.rst @@ -14,6 +14,7 @@ All Modules :maxdepth: 1 /module/AddFileDependencies + /module/AndroidTestUtilities /module/BundleUtilities /module/CheckCCompilerFlag /module/CheckCSourceCompiles diff --git a/Help/manual/cmake-server.7.rst b/Help/manual/cmake-server.7.rst index f662125..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 ---------------------- @@ -635,3 +678,26 @@ CMake will respond with the following output:: 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/Help/module/AndroidTestUtilities.rst b/Help/module/AndroidTestUtilities.rst new file mode 100644 index 0000000..e7ec864 --- /dev/null +++ b/Help/module/AndroidTestUtilities.rst @@ -0,0 +1 @@ +.. cmake-module:: ../../Modules/AndroidTestUtilities.cmake diff --git a/Help/release/dev/add-android-test-utilities-module.rst b/Help/release/dev/add-android-test-utilities-module.rst new file mode 100644 index 0000000..998b3cd --- /dev/null +++ b/Help/release/dev/add-android-test-utilities-module.rst @@ -0,0 +1,5 @@ +add-android-test-utilities-module +--------------------------------- + +* A :module:`AndroidTestUtilities` module was added to manage transfer of + test data to an Android device. diff --git a/Modules/AndroidTestUtilities.cmake b/Modules/AndroidTestUtilities.cmake new file mode 100644 index 0000000..a0a74fa --- /dev/null +++ b/Modules/AndroidTestUtilities.cmake @@ -0,0 +1,157 @@ +# Distributed under the OSI-approved BSD 3-Clause License. See accompanying +# file Copyright.txt or https://cmake.org/licensing for details. + +#[======================================================================[.rst: +AndroidTestUtilities +------------------------ + +Create a test that automatically loads specified data onto an Android device. + +Introduction +^^^^^^^^^^^^ + +Use this module to push data needed for testing an Android device behavior +onto a connected Android device. The module will accept files and libraries as +well as separate destinations for each. It will create a test that loads the +files into a device object store and link to them from the specified +destination. The files are only uploaded if they are not already in the object +store. + +For example: + +.. code-block:: cmake + + include(AndroidTestUtilities) + android_add_test_data( + example_setup_test + FILES <files>... + LIBS <libs>... + DEVICE_TEST_DIR "/data/local/tests/example" + DEVICE_OBJECT_STORE "/sdcard/.ExternalData/SHA" + ) + + +At build time a test named "example_setup_test" will be created. Run this test +on the command line with :manual:`ctest(1)` to load the data onto the Android +device. + +Module Functions +^^^^^^^^^^^^^^^^ + +.. command:: android_add_test_data + + :: + + android_add_test_data(<test-name> + [FILES <files>...] [FILES_DEST <device-dir>] + [LIBS <libs>...] [LIBS_DEST <device-dir>] + [DEVICE_OBJECT_STORE <device-dir>] + [DEVICE_TEST_DIR <device-dir>] + [NO_LINK_REGEX <strings>...] + ) + + The ``android_add_test_data`` function is used to copy files and libraries + needed to run project-specific tests. On the host operating system, this is + done at build time. For on-device testing, the files are loaded onto the + device by the manufactured test at run time. + + This function accepts the following named parameters: + + ``FILES <files>...`` + zero or more files needed for testing + ``LIBS <libs>...`` + zero or more libraries needed for testing + ``FILES_DEST <device-dir>`` + absolute path where the data files are expected to be + ``LIBS_DEST <device-dir>`` + absolute path where the libraries are expected to be + ``DEVICE_OBJECT_STORE <device-dir>`` + absolute path to the location where the data is stored on-device + ``DEVICE_TEST_DIR <device-dir>`` + absolute path to the root directory of the on-device test location + ``NO_LINK_REGEX <strings>...`` + list of regex strings matching the names of files that should be + copied from the object store to the testing directory +#]======================================================================] + +include(${CMAKE_CURRENT_LIST_DIR}/ExternalData.cmake) + +set(_AndroidTestUtilities_SELF_DIR "${CMAKE_CURRENT_LIST_DIR}") + +# The parameters to this function should be set to the list of directories, +# files, and libraries that need to be installed prior to testing. +function(android_add_test_data test_name) + # As the names suggest, oneValueArgs lists the arguments that specify a + # single value, while multiValueArgs can contain one or more values. + set(keywordArgs) + set(oneValueArgs FILES_DEST LIBS_DEST DEVICE_OBJECT_STORE DEVICE_TEST_DIR) + set(multiValueArgs FILES LIBS NO_LINK_REGEX) + + # For example, if you called this function with FILES </path/to/file> + # then this path would be stored in the variable AST_FILES. + # The AST prefix stands for the name of this function (android_add_test_data). + cmake_parse_arguments(AST "${keywordArgs}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + if(NOT AST_DEVICE_TEST_DIR) + message(FATAL_ERROR "-- You must specify the location of the on device test directory.") + endif() + if(NOT AST_DEVICE_OBJECT_STORE) + message(FATAL_ERROR "-- You must specify the location of the on device object store.") + endif() + if(${AST_DEVICE_TEST_DIR} STREQUAL "/") + message(FATAL_ERROR "-- The device test directory cannot be '/'") + endif() + + # Copy all test data files into the binary directory, where tests are run. + # ExternalData will handle fetching DATA{...} references. + string(REPLACE "|" ";" hash_algs "${_ExternalData_REGEX_EXT}") + # Convert ExternalData placeholder file names to DATA{} syntax. + foreach(alg ${hash_algs}) + string(REGEX REPLACE "([^ ;]+)\\.${alg}" "DATA{\\1}" AST_FILES "${AST_FILES}") + endforeach() + + set(DATA_TARGET_NAME "${test_name}") + ExternalData_Expand_Arguments( + ${DATA_TARGET_NAME} + extern_data_output + ${AST_FILES}) + ExternalData_Add_Target(${DATA_TARGET_NAME}) + + # For regular files on Linux, just copy them directly. + foreach(path ${AST_FILES}) + foreach(output ${extern_data_output}) + if(${output} STREQUAL ${path}) + # Check if a destination was specified. If not, we copy by default + # into this project's binary directory, preserving its relative path. + if(AST_${VAR}_DEST) + set(DEST ${CMAKE_BINARY_DIR}/${parent_dir}/${AST_${VAR}_DEST}) + else() + get_filename_component(parent_dir ${path} DIRECTORY) + set(DEST "${CMAKE_BINARY_DIR}/${parent_dir}") + endif() + get_filename_component(extern_data_source ${output} REALPATH) + get_filename_component(extern_data_basename ${output} NAME) + add_custom_command( + TARGET ${DATA_TARGET_NAME} POST_BUILD + DEPENDS ${extern_data_source} + COMMAND ${CMAKE_COMMAND} -E copy_if_different ${extern_data_source} ${DEST}/${extern_data_basename} + ) + endif() + endforeach() + endforeach() + + if(ANDROID) + string(REGEX REPLACE "DATA{([^ ;]+)}" "\\1" processed_FILES "${AST_FILES}") + add_test( + NAME ${test_name} + COMMAND ${CMAKE_COMMAND} + "-Darg_files_dest=${AST_FILES_DEST}" + "-Darg_libs_dest=${AST_LIBS_DEST}" + "-Darg_dev_test_dir=${AST_DEVICE_TEST_DIR}" + "-Darg_dev_obj_store=${AST_DEVICE_OBJECT_STORE}" + "-Darg_no_link_regex=${AST_NO_LINK_REGEX}" + "-Darg_files=${processed_FILES}" + "-Darg_libs=${AST_LIBS}" + "-Darg_src_dir=${CMAKE_CURRENT_SOURCE_DIR}" + -P ${_AndroidTestUtilities_SELF_DIR}/AndroidTestUtilities/PushToAndroidDevice.cmake) + endif() +endfunction() diff --git a/Modules/AndroidTestUtilities/PushToAndroidDevice.cmake b/Modules/AndroidTestUtilities/PushToAndroidDevice.cmake new file mode 100644 index 0000000..f5f2564 --- /dev/null +++ b/Modules/AndroidTestUtilities/PushToAndroidDevice.cmake @@ -0,0 +1,174 @@ +# Distributed under the OSI-approved BSD 3-Clause License. See accompanying +# file Copyright.txt or https://cmake.org/licensing for details. + +# This function handles pushing all of the test files needed to the device. +# It places the data files in the object store and makes links to them from +# the appropriate directories. +# +# This function accepts the following named parameters: +# DIRS : one or more directories needed for testing. +# FILES : one or more files needed for testing. +# LIBS : one or more libraries needed for testing. +# DIRS_DEST : specify where the directories should be installed. +# FILES_DEST : specify where the files should be installed. +# LIBS_DEST : specify where the libraries should be installed. +# DEV_OBJ_STORE : specify where the actual data files should be placed. +# DEV_TEST_DIR : specify the root file for the module test directory. +# The DEV_OBJ_STORE and DEV_TEST_DIR variables are required. + +# The parameters to this function should be set to the list of directories, +# files, and libraries that need to be installed prior to testing. +function(android_push_test_files_to_device) + + # The functions in the module need the adb executable. + find_program(adb_executable adb) + if(NOT adb_executable) + message(FATAL_ERROR "could not find adb") + endif() + + function(execute_adb_command) + execute_process(COMMAND ${adb_executable} ${ARGN} RESULT_VARIABLE res_var OUTPUT_VARIABLE out_var ERROR_VARIABLE err_var) + set(out_var ${out_var} PARENT_SCOPE) + if(res_var) + string(REGEX REPLACE ";" " " com "${ARGN}") + message(FATAL_ERROR "Error occured during adb command: adb ${com}\nError: ${err_var}.") + endif() + endfunction() + + # Checks to make sure that a given file exists on the device. If it does, + # if(file_exists) will return true. + macro(check_device_file_exists device_file file_exists) + set(${file_exists} "") + execute_adb_command(shell ls ${device_file}) + if(NOT out_var) # when a directory exists but is empty the output is empty + set(${file_exists} "YES") + else() + string(FIND ${out_var} "No such file or directory" no_file_exists) + if(${no_file_exists} STREQUAL "-1") # -1 means the file exists + set(${file_exists} "YES") + endif() + endif() + endmacro() + + # Checks to see if a filename matches a regex. + function(filename_regex filename reg_ex) + string(REGEX MATCH ${reg_ex} filename_match ${filename}) + set(filename_match ${filename_match} PARENT_SCOPE) + endfunction() + + # If a file with given name exists in the CMAKE_BINARY_DIR then use that file. + # Otherwise use the file with root in CMAKE_CURRENT_SOURCE_DIR. + macro(set_absolute_path relative_path absolute_path) + set(${absolute_path} ${arg_src_dir}/${relative_path}) + if(EXISTS ${CMAKE_BINARY_DIR}/${relative_path}) + set(${absolute_path} ${CMAKE_BINARY_DIR}/${relative_path}) + endif() + if(NOT EXISTS ${${absolute_path}}) + if(EXISTS ${relative_path}) + set(${absolute_path} ${relative_path}) + else() + message(FATAL_ERROR "Cannot find file for specified path: ${relative_path}") + endif() + endif() + endmacro() + + # This function pushes the data into the device object store and + # creates a link to that data file in a specified location. + # + # This function requires the following un-named parameters: + # data_path : absolute path to data to load into dev obj store. + # dev_object_store : absolute path to the device object store directory. + # link_origin : absolute path to the origin of the link to the dev obj store data file. + function(push_and_link data_path dev_object_store link_origin) + FILE(SHA1 ${data_path} hash_val) + set(obj_store_dst ${dev_object_store}/${hash_val}) + check_device_file_exists(${obj_store_dst} obj_store_file_exists) + # TODO: Verify that the object store file is indeed hashed correctly. Could use md5. + if(NOT obj_store_file_exists) + execute_adb_command(push ${data_path} ${obj_store_dst}) + endif() + check_device_file_exists(${link_origin} link_exists) + if(link_exists) + execute_adb_command(shell rm -f ${link_origin}) + endif() + foreach(ex ${arg_no_link_regex}) + filename_regex(${data_path} ${ex}) + LIST(APPEND match_ex ${filename_match}) + endforeach() + if(match_ex) + execute_adb_command(shell cp ${obj_store_dst} ${link_origin}) + else() + execute_adb_command(shell ln -s ${obj_store_dst} ${link_origin}) + endif() + endfunction() + + #---------------------------------------------------------------------------- + #--------------------Beginning of actual function---------------------------- + #---------------------------------------------------------------------------- + set(oneValueArgs FILES_DEST LIBS_DEST DEV_TEST_DIR DEV_OBJ_STORE) + set(multiValueArgs FILES LIBS) + cmake_parse_arguments(_ptd "" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + # Setup of object store and test dir. + check_device_file_exists(${_ptd_DEV_OBJ_STORE} dev_obj_store_exists) + if(NOT dev_obj_store_exists) + execute_adb_command(shell mkdir -p ${_ptd_DEV_OBJ_STORE}) + endif() + check_device_file_exists(${_ptd_DEV_TEST_DIR} test_dir_exists) + if(test_dir_exists) + # This is protected in the SetupProjectTests module. + execute_adb_command(shell echo rm -r ${_ptd_DEV_TEST_DIR} | su) + endif() + execute_adb_command(shell mkdir -p ${_ptd_DEV_TEST_DIR}) + + # Looping over the various types of test data possible. + foreach(TYPE ${multiValueArgs}) + if(_ptd_${TYPE}) + + # determine if the data type destination has been explicitly specified. + if(_ptd_${TYPE}_DEST) + set(dest ${_ptd_${TYPE}_DEST}) + else() + if(${TYPE} STREQUAL LIBS) + set(dest ${_ptd_DEV_TEST_DIR}/lib) + else() + set(dest ${_ptd_DEV_TEST_DIR}) + endif() + endif() + execute_adb_command(shell mkdir -p ${dest}) + + # Loop over the files passed in + foreach(relative_path ${_ptd_${TYPE}}) + # The absolute path can be through the source directory or the build directory. + # If the file/dir exists in the build directory that version is chosen. + set_absolute_path(${relative_path} absolute_path) + # Need to transfer all data files in the data directories to the device + # except those explicitly ignored. + if(${TYPE} STREQUAL FILES) + get_filename_component(file_dir ${relative_path} DIRECTORY) + # dest was determined earlier, relative_path is a dir, file is path from relative path to a data + set(cur_dest ${dest}/${relative_path}) + set(on_dev_dir ${dest}/${file_dir}) + execute_adb_command(shell mkdir -p ${on_dev_dir}) + if(IS_SYMLINK ${absolute_path}) + get_filename_component(real_data_origin ${absolute_path} REALPATH) + push_and_link(${real_data_origin} ${_ptd_DEV_OBJ_STORE} ${cur_dest}) + else() + push_and_link(${absolute_path} ${_ptd_DEV_OBJ_STORE} ${cur_dest}) + endif() + else() # LIBS + execute_adb_command(push ${absolute_path} ${dest}) + endif() + endforeach() + endif() + endforeach() +endfunction() + +android_push_test_files_to_device( + FILES_DEST ${arg_files_dest} + LIBS_DEST ${arg_libs_dest} + DEV_TEST_DIR ${arg_dev_test_dir} + DEV_OBJ_STORE ${arg_dev_obj_store} + FILES ${arg_files} + LIBS ${arg_libs} + ) 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 dd36349..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 20160929) +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/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 c811b83..c82274a 100644 --- a/Source/cmServerDictionary.h +++ b/Source/cmServerDictionary.h @@ -6,12 +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"; @@ -80,6 +84,11 @@ 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 f083e49..a2bdf49 100644 --- a/Source/cmServerProtocol.cxx +++ b/Source/cmServerProtocol.cxx @@ -4,6 +4,7 @@ #include "cmCacheManager.h" #include "cmExternalMakefileProjectGenerator.h" +#include "cmFileMonitor.h" #include "cmGeneratorTarget.h" #include "cmGlobalGenerator.h" #include "cmListFileCache.h" @@ -214,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 { @@ -365,6 +371,30 @@ 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) { @@ -385,6 +415,9 @@ const cmServerResponse cmServerProtocol1_0::Process( 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); } @@ -862,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; @@ -938,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()); } @@ -1011,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 d672a60..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,6 +109,8 @@ 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); @@ -115,6 +119,7 @@ private: cmServerResponse ProcessConfigure(const cmServerRequest& request); cmServerResponse ProcessGlobalSettings(const cmServerRequest& request); cmServerResponse ProcessSetGlobalSettings(const cmServerRequest& request); + cmServerResponse ProcessFileSystemWatchers(const cmServerRequest& request); enum State { @@ -124,4 +129,6 @@ private: STATE_COMPUTED }; State m_State = STATE_INACTIVE; + + bool m_isDirty = false; }; 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 c056fb8..31ed2eb 100644 --- a/Tests/CMakeLists.txt +++ b/Tests/CMakeLists.txt @@ -2723,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/AndroidTestUtilities/CMakeLists.txt b/Tests/RunCMake/AndroidTestUtilities/CMakeLists.txt new file mode 100644 index 0000000..dc92486 --- /dev/null +++ b/Tests/RunCMake/AndroidTestUtilities/CMakeLists.txt @@ -0,0 +1,3 @@ +cmake_minimum_required(VERSION 3.6) +project(${RunCMake_TEST} NONE) +include(${RunCMake_TEST}.cmake) diff --git a/Tests/RunCMake/AndroidTestUtilities/RunCMakeTest.cmake b/Tests/RunCMake/AndroidTestUtilities/RunCMakeTest.cmake new file mode 100644 index 0000000..f0ae24b --- /dev/null +++ b/Tests/RunCMake/AndroidTestUtilities/RunCMakeTest.cmake @@ -0,0 +1,20 @@ +include(RunCMake) + +function(run_ATU case target) + # Use a single build tree for a few tests without cleaning. + set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/${case}-build) + set(RunCMake_TEST_NO_CLEAN 1) + file(REMOVE_RECURSE "${RunCMake_TEST_BINARY_DIR}") + file(MAKE_DIRECTORY "${RunCMake_TEST_BINARY_DIR}") + if(target) + set(build_args --target ${target}) + else() + set(build_args) + endif() + run_cmake(${case}) + run_cmake_command(${case}Build ${CMAKE_COMMAND} --build . --config Debug ${build_args}) +endfunction() + +run_ATU(SetupTest1 "") +run_ATU(SetupTest2 "tests") +run_ATU(SetupTest3 "tests") diff --git a/Tests/RunCMake/AndroidTestUtilities/SetupTest1.cmake b/Tests/RunCMake/AndroidTestUtilities/SetupTest1.cmake new file mode 100644 index 0000000..1c9098b --- /dev/null +++ b/Tests/RunCMake/AndroidTestUtilities/SetupTest1.cmake @@ -0,0 +1,17 @@ +enable_testing() +include(AndroidTestUtilities) + +find_program(adb_executable adb) + +set(ExternalData_URL_TEMPLATES + "https://data.kitware.com/api/v1/file/hashsum/%(algo)/%(hash)/download" + ) + +set(test_files "data/a.txt") + +set(ANDROID 1) + +android_add_test_data(setup_test + FILES ${test_files} + DEVICE_TEST_DIR "/data/local/tests/example1" + DEVICE_OBJECT_STORE "/sdcard/.ExternalData/SHA") diff --git a/Tests/RunCMake/AndroidTestUtilities/SetupTest1Build-check.cmake b/Tests/RunCMake/AndroidTestUtilities/SetupTest1Build-check.cmake new file mode 100644 index 0000000..ef7569d --- /dev/null +++ b/Tests/RunCMake/AndroidTestUtilities/SetupTest1Build-check.cmake @@ -0,0 +1,5 @@ +include(${CMAKE_CURRENT_LIST_DIR}/check.cmake) +compare_build_to_expected(FILES + "data/a.txt" + ) +check_for_setup_test() diff --git a/Tests/RunCMake/AndroidTestUtilities/SetupTest2.cmake b/Tests/RunCMake/AndroidTestUtilities/SetupTest2.cmake new file mode 100644 index 0000000..cf4c764 --- /dev/null +++ b/Tests/RunCMake/AndroidTestUtilities/SetupTest2.cmake @@ -0,0 +1,30 @@ +enable_testing() +include(AndroidTestUtilities) + +add_custom_target(tests) +find_program(adb_executable adb) + +set(ExternalData_URL_TEMPLATES + "https://data.kitware.com/api/v1/file/hashsum/%(algo)/%(hash)/download" + ) +set(test_files + "data/a.txt" + "data/subfolder/b.txt" + "data/subfolder/protobuffer.p" + ) + +set(test_libs "data/subfolder/exampleLib.txt") + +set(ANDROID 1) + +android_add_test_data(setup_test + FILES ${test_files} + LIBS ${test_libs} + DEVICE_TEST_DIR "/data/local/tests/example2" + DEVICE_OBJECT_STORE "/sdcard/.ExternalData/SHA" + NO_LINK_REGEX "\\.p$") + +set_property( + TARGET setup_test + PROPERTY EXCLUDE_FROM_ALL 1) +add_dependencies(tests setup_test) diff --git a/Tests/RunCMake/AndroidTestUtilities/SetupTest2Build-check.cmake b/Tests/RunCMake/AndroidTestUtilities/SetupTest2Build-check.cmake new file mode 100644 index 0000000..6adbd59 --- /dev/null +++ b/Tests/RunCMake/AndroidTestUtilities/SetupTest2Build-check.cmake @@ -0,0 +1,7 @@ +include(${CMAKE_CURRENT_LIST_DIR}/check.cmake) +compare_build_to_expected(FILES + "data/a.txt" + "data/subfolder/b.txt" + "data/subfolder/protobuffer.p" + ) +check_for_setup_test() diff --git a/Tests/RunCMake/AndroidTestUtilities/SetupTest3.cmake b/Tests/RunCMake/AndroidTestUtilities/SetupTest3.cmake new file mode 100644 index 0000000..b32b6b1 --- /dev/null +++ b/Tests/RunCMake/AndroidTestUtilities/SetupTest3.cmake @@ -0,0 +1,33 @@ +enable_testing() +include(AndroidTestUtilities) + +add_custom_target(tests) +find_program(adb_executable adb) + +set(ExternalData_URL_TEMPLATES + "https://data.kitware.com/api/v1/file/hashsum/%(algo)/%(hash)/download" + ) +set(test_dir "/data/local/tests/example3") +set(test_files + "data/a.txt" + "data/subfolder/b.txt" + ) +set(test_libs "libs/exampleLib.txt") +set(files_dest "${test_dir}/storage_folder") +set(libs_dest "${test_dir}/lib/lib/lib") + +set(ANDROID 1) + +android_add_test_data(setup_test + FILES ${test_files} + LIBS ${test_libs} + FILES_DEST ${files_dest} + LIBS_DEST ${libs_dest} + DEVICE_TEST_DIR "/data/local/tests/example3" + DEVICE_OBJECT_STORE "/sdcard/.ExternalData/SHA" + NO_LINK_REGEX "\\.p$") + +set_property( + TARGET setup_test + PROPERTY EXCLUDE_FROM_ALL 1) +add_dependencies(tests setup_test) diff --git a/Tests/RunCMake/AndroidTestUtilities/SetupTest3Build-check.cmake b/Tests/RunCMake/AndroidTestUtilities/SetupTest3Build-check.cmake new file mode 100644 index 0000000..3062cdc --- /dev/null +++ b/Tests/RunCMake/AndroidTestUtilities/SetupTest3Build-check.cmake @@ -0,0 +1,6 @@ +include(${CMAKE_CURRENT_LIST_DIR}/check.cmake) +compare_build_to_expected(FILES + "data/a.txt" + "data/subfolder/b.txt" + ) +check_for_setup_test() diff --git a/Tests/RunCMake/AndroidTestUtilities/check.cmake b/Tests/RunCMake/AndroidTestUtilities/check.cmake new file mode 100644 index 0000000..ccd4d74 --- /dev/null +++ b/Tests/RunCMake/AndroidTestUtilities/check.cmake @@ -0,0 +1,20 @@ +function(compare_build_to_expected) + cmake_parse_arguments(_comp "" "" "FILES" ${ARGN}) + set(missing) + foreach(file ${_comp_FILES}) + if(NOT EXISTS "${RunCMake_TEST_BINARY_DIR}/${file}") + list(APPEND missing "${file}") + endif() + endforeach() + if(missing) + string(APPEND RunCMake_TEST_FAILED "Missing files:\n ${missing}") + set(RunCMake_TEST_FAILED "${RunCMake_TEST_FAILED}" PARENT_SCOPE) + endif() +endfunction() + +function(check_for_setup_test) + file(STRINGS "${RunCMake_TEST_BINARY_DIR}/CTestTestfile.cmake" output_var REGEX "add_test\\(setup_test.*") + if(NOT output_var) + set(RunCMake_TEST_FAILED "Could not find the test: setup_test" PARENT_SCOPE) + endif() +endfunction() diff --git a/Tests/RunCMake/AndroidTestUtilities/data/a.txt b/Tests/RunCMake/AndroidTestUtilities/data/a.txt new file mode 100644 index 0000000..9d454fb --- /dev/null +++ b/Tests/RunCMake/AndroidTestUtilities/data/a.txt @@ -0,0 +1 @@ +Here is a file to test. diff --git a/Tests/RunCMake/AndroidTestUtilities/data/proto.proto b/Tests/RunCMake/AndroidTestUtilities/data/proto.proto new file mode 100644 index 0000000..7402a3a --- /dev/null +++ b/Tests/RunCMake/AndroidTestUtilities/data/proto.proto @@ -0,0 +1 @@ +proto.proto diff --git a/Tests/RunCMake/AndroidTestUtilities/data/subfolder/b.txt b/Tests/RunCMake/AndroidTestUtilities/data/subfolder/b.txt new file mode 100644 index 0000000..c8c6a89 --- /dev/null +++ b/Tests/RunCMake/AndroidTestUtilities/data/subfolder/b.txt @@ -0,0 +1 @@ +SetupTest2.cmake diff --git a/Tests/RunCMake/AndroidTestUtilities/data/subfolder/protobuffer.p b/Tests/RunCMake/AndroidTestUtilities/data/subfolder/protobuffer.p new file mode 100644 index 0000000..a5dc7d2 --- /dev/null +++ b/Tests/RunCMake/AndroidTestUtilities/data/subfolder/protobuffer.p @@ -0,0 +1 @@ +protobuffer.p diff --git a/Tests/RunCMake/AndroidTestUtilities/libs/exampleLib.so b/Tests/RunCMake/AndroidTestUtilities/libs/exampleLib.so new file mode 100644 index 0000000..f4cdf82 --- /dev/null +++ b/Tests/RunCMake/AndroidTestUtilities/libs/exampleLib.so @@ -0,0 +1 @@ +here is a fake lib. diff --git a/Tests/RunCMake/AndroidTestUtilities/libs/exampleLib.txt b/Tests/RunCMake/AndroidTestUtilities/libs/exampleLib.txt new file mode 100644 index 0000000..308921a --- /dev/null +++ b/Tests/RunCMake/AndroidTestUtilities/libs/exampleLib.txt @@ -0,0 +1 @@ +here is an example lib! diff --git a/Tests/RunCMake/CMakeLists.txt b/Tests/RunCMake/CMakeLists.txt index 0eafbef..9dc540f 100644 --- a/Tests/RunCMake/CMakeLists.txt +++ b/Tests/RunCMake/CMakeLists.txt @@ -131,6 +131,7 @@ if(NOT CMake_TEST_EXTERNAL_CMAKE) ) endif() +add_RunCMake_test(AndroidTestUtilities) add_RunCMake_test(BuildDepends) if(UNIX AND "${CMAKE_GENERATOR}" MATCHES "Unix Makefiles|Ninja") add_RunCMake_test(CompilerChange) diff --git a/Tests/Server/server-test.py b/Tests/Server/server-test.py index 72f82ba..14767f4 100644 --- a/Tests/Server/server-test.py +++ b/Tests/Server/server-test.py @@ -102,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/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; } @@ -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) |