summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--CTestCustom.cmake.in4
-rw-r--r--Help/command/if.rst4
-rw-r--r--Help/manual/cmake-policies.7.rst1
-rw-r--r--Help/policy/CMP0064.rst17
-rw-r--r--Help/prop_gbl/RULE_LAUNCH_COMPILE.rst10
-rw-r--r--Help/prop_gbl/RULE_LAUNCH_CUSTOM.rst10
-rw-r--r--Help/prop_gbl/RULE_LAUNCH_LINK.rst10
-rw-r--r--Help/release/dev/if-TEST.rst5
-rw-r--r--Help/release/dev/java-updates.rst13
-rw-r--r--Modules/CMakeFortranCompiler.cmake.in1
-rw-r--r--Modules/ExternalProject.cmake6
-rw-r--r--Modules/FindJava.cmake94
-rw-r--r--Modules/FindMPI.cmake8
-rw-r--r--Modules/GetPrerequisites.cmake38
-rw-r--r--Modules/UseJava.cmake88
-rw-r--r--Source/CMakeVersion.cmake2
-rw-r--r--Source/cmConditionEvaluator.cxx26
-rw-r--r--Source/cmConditionEvaluator.h1
-rw-r--r--Source/cmGlobalGenerator.cxx56
-rw-r--r--Source/cmMakefile.cxx4
-rw-r--r--Source/cmMessageCommand.cxx2
-rw-r--r--Source/cmPolicies.h3
-rw-r--r--Source/cmXCodeObject.cxx6
-rw-r--r--Source/cmake.cxx10
-rw-r--r--Source/kwsys/CMakeLists.txt6
-rw-r--r--Source/kwsys/CTestCustom.cmake.in15
-rw-r--r--Source/kwsys/EncodingC.c14
-rw-r--r--Source/kwsys/Process.h.in131
-rw-r--r--Source/kwsys/ProcessUNIX.c357
-rw-r--r--Source/kwsys/ProcessWin32.c462
-rw-r--r--Source/kwsys/SystemTools.cxx18
-rw-r--r--Source/kwsys/testProcess.c267
-rw-r--r--Source/kwsys/testSystemTools.cxx22
-rw-r--r--Tests/CMakeLists.txt21
-rw-r--r--Tests/CTestTest2/test.cmake.in13
-rw-r--r--Tests/Java/CMakeLists.txt4
-rw-r--r--Tests/RunCMake/CMP0064/CMP0064-NEW.cmake5
-rw-r--r--Tests/RunCMake/CMP0064/CMP0064-OLD.cmake7
-rw-r--r--Tests/RunCMake/CMP0064/CMP0064-WARN.cmake7
-rw-r--r--Tests/RunCMake/CMP0064/CMakeLists.txt3
-rw-r--r--Tests/RunCMake/CMP0064/RunCMakeTest.cmake5
-rw-r--r--Tests/RunCMake/CMakeLists.txt2
-rw-r--r--Tests/RunCMake/add_subdirectory/CMakeLists.txt3
-rw-r--r--Tests/RunCMake/add_subdirectory/DoesNotExist-result.txt1
-rw-r--r--Tests/RunCMake/add_subdirectory/DoesNotExist-stderr.txt5
-rw-r--r--Tests/RunCMake/add_subdirectory/DoesNotExist.cmake1
-rw-r--r--Tests/RunCMake/add_subdirectory/Missing-result.txt1
-rw-r--r--Tests/RunCMake/add_subdirectory/Missing-stderr.txt8
-rw-r--r--Tests/RunCMake/add_subdirectory/Missing.cmake1
-rw-r--r--Tests/RunCMake/add_subdirectory/Missing/Missing.txt0
-rw-r--r--Tests/RunCMake/add_subdirectory/RunCMakeTest.cmake4
-rw-r--r--Tests/RunCMake/if/RunCMakeTest.cmake3
-rw-r--r--Tests/RunCMake/if/TestNameThatDoesNotExist-stdout.txt1
-rw-r--r--Tests/RunCMake/if/TestNameThatDoesNotExist.cmake6
-rw-r--r--Tests/RunCMake/if/TestNameThatExists-stdout.txt1
-rw-r--r--Tests/RunCMake/if/TestNameThatExists.cmake7
56 files changed, 1527 insertions, 293 deletions
diff --git a/CTestCustom.cmake.in b/CTestCustom.cmake.in
index 7f20d10..f29ac70 100644
--- a/CTestCustom.cmake.in
+++ b/CTestCustom.cmake.in
@@ -109,3 +109,7 @@ set(CTEST_CUSTOM_COVERAGE_EXCLUDE
# Exclude Qt source files from coverage results:
"[A-Za-z]./[Qq]t/qt-.+-opensource-src"
)
+
+list(APPEND CTEST_CUSTOM_MEMCHECK_IGNORE
+ kwsys.testProcess-10 # See Source/kwsys/CTestCustom.cmake.in
+ )
diff --git a/Help/command/if.rst b/Help/command/if.rst
index 396becf..2465bde 100644
--- a/Help/command/if.rst
+++ b/Help/command/if.rst
@@ -71,6 +71,10 @@ Possible expressions are:
created by the :command:`add_executable`, :command:`add_library`, or
:command:`add_custom_target` commands.
+``if(TEST test-name)``
+ True if the given name is an existing test name created by the
+ :command:`add_test` command.
+
``if(EXISTS path-to-file-or-directory)``
True if the named file or directory exists. Behavior is well-defined
only for full paths.
diff --git a/Help/manual/cmake-policies.7.rst b/Help/manual/cmake-policies.7.rst
index 0a313cd..590f10d 100644
--- a/Help/manual/cmake-policies.7.rst
+++ b/Help/manual/cmake-policies.7.rst
@@ -121,3 +121,4 @@ All Policies
/policy/CMP0061
/policy/CMP0062
/policy/CMP0063
+ /policy/CMP0064
diff --git a/Help/policy/CMP0064.rst b/Help/policy/CMP0064.rst
new file mode 100644
index 0000000..e9a061b
--- /dev/null
+++ b/Help/policy/CMP0064.rst
@@ -0,0 +1,17 @@
+CMP0064
+-------
+
+Recognize ``TEST`` as a operator for the :command:`if` command.
+
+The ``TEST`` operator was added to the :command:`if` command to determine if a
+given test name was created by the :command:`add_test` command.
+
+The ``OLD`` behavior for this policy is to ignore the ``TEST`` operator.
+The ``NEW`` behavior is to interpret the ``TEST`` operator.
+
+This policy was introduced in CMake version 3.4. CMake version
+|release| warns when the policy is not set and uses ``OLD`` behavior. Use
+the :command:`cmake_policy()` command to set it to ``OLD`` or ``NEW``
+explicitly.
+
+.. include:: DEPRECATED.txt
diff --git a/Help/prop_gbl/RULE_LAUNCH_COMPILE.rst b/Help/prop_gbl/RULE_LAUNCH_COMPILE.rst
index 980843b..e0df878 100644
--- a/Help/prop_gbl/RULE_LAUNCH_COMPILE.rst
+++ b/Help/prop_gbl/RULE_LAUNCH_COMPILE.rst
@@ -3,7 +3,9 @@ RULE_LAUNCH_COMPILE
Specify a launcher for compile rules.
-Makefile generators prefix compiler commands with the given launcher
-command line. This is intended to allow launchers to intercept build
-problems with high granularity. Non-Makefile generators currently
-ignore this property.
+:ref:`Makefile Generators` and the :generator:`Ninja` generator prefix
+compiler commands with the given launcher command line.
+This is intended to allow launchers to intercept build problems
+with high granularity. Other generators ignore this property
+because their underlying build systems provide no hook to wrap
+individual commands with a launcher.
diff --git a/Help/prop_gbl/RULE_LAUNCH_CUSTOM.rst b/Help/prop_gbl/RULE_LAUNCH_CUSTOM.rst
index 9d4a25c..b20c59b 100644
--- a/Help/prop_gbl/RULE_LAUNCH_CUSTOM.rst
+++ b/Help/prop_gbl/RULE_LAUNCH_CUSTOM.rst
@@ -3,7 +3,9 @@ RULE_LAUNCH_CUSTOM
Specify a launcher for custom rules.
-Makefile generators prefix custom commands with the given launcher
-command line. This is intended to allow launchers to intercept build
-problems with high granularity. Non-Makefile generators currently
-ignore this property.
+:ref:`Makefile Generators` and the :generator:`Ninja` generator prefix
+custom commands with the given launcher command line.
+This is intended to allow launchers to intercept build problems
+with high granularity. Other generators ignore this property
+because their underlying build systems provide no hook to wrap
+individual commands with a launcher.
diff --git a/Help/prop_gbl/RULE_LAUNCH_LINK.rst b/Help/prop_gbl/RULE_LAUNCH_LINK.rst
index 191f1d5..567bb68 100644
--- a/Help/prop_gbl/RULE_LAUNCH_LINK.rst
+++ b/Help/prop_gbl/RULE_LAUNCH_LINK.rst
@@ -3,7 +3,9 @@ RULE_LAUNCH_LINK
Specify a launcher for link rules.
-Makefile generators prefix link and archive commands with the given
-launcher command line. This is intended to allow launchers to
-intercept build problems with high granularity. Non-Makefile
-generators currently ignore this property.
+:ref:`Makefile Generators` and the :generator:`Ninja` generator prefix
+link and archive commands with the given launcher command line.
+This is intended to allow launchers to intercept build problems
+with high granularity. Other generators ignore this property
+because their underlying build systems provide no hook to wrap
+individual commands with a launcher.
diff --git a/Help/release/dev/if-TEST.rst b/Help/release/dev/if-TEST.rst
new file mode 100644
index 0000000..05bf71c
--- /dev/null
+++ b/Help/release/dev/if-TEST.rst
@@ -0,0 +1,5 @@
+if-TEST
+-------
+
+* Add a new TEST operator to if() that evaluates to true
+ if a given test name has been defined.
diff --git a/Help/release/dev/java-updates.rst b/Help/release/dev/java-updates.rst
new file mode 100644
index 0000000..b777807
--- /dev/null
+++ b/Help/release/dev/java-updates.rst
@@ -0,0 +1,13 @@
+java-updates
+------------
+
+* The :module:`FindJava` module learned to optionally find
+ the ``idlj`` and ``jarsigner`` tools.
+
+* The :module:`UseJava` module ``add_jar`` function learned
+ to support response files (e.g. ``@srcs.txt``) for source
+ specification.
+
+* The :module:`UseJava` module ``install_jar`` function learned
+ new ``DESTINATION`` and ``COMPONENT`` options to specify
+ the corresponding :command:`install` command options.
diff --git a/Modules/CMakeFortranCompiler.cmake.in b/Modules/CMakeFortranCompiler.cmake.in
index e4c7618..14fdd60 100644
--- a/Modules/CMakeFortranCompiler.cmake.in
+++ b/Modules/CMakeFortranCompiler.cmake.in
@@ -1,6 +1,7 @@
set(CMAKE_Fortran_COMPILER "@CMAKE_Fortran_COMPILER@")
set(CMAKE_Fortran_COMPILER_ARG1 "@CMAKE_Fortran_COMPILER_ARG1@")
set(CMAKE_Fortran_COMPILER_ID "@CMAKE_Fortran_COMPILER_ID@")
+set(CMAKE_Fortran_COMPILER_VERSION "@CMAKE_Fortran_COMPILER_VERSION@")
set(CMAKE_Fortran_PLATFORM_ID "@CMAKE_Fortran_PLATFORM_ID@")
set(CMAKE_Fortran_SIMULATE_ID "@CMAKE_Fortran_SIMULATE_ID@")
set(CMAKE_Fortran_SIMULATE_VERSION "@CMAKE_Fortran_SIMULATE_VERSION@")
diff --git a/Modules/ExternalProject.cmake b/Modules/ExternalProject.cmake
index f6844be..fdb146a 100644
--- a/Modules/ExternalProject.cmake
+++ b/Modules/ExternalProject.cmake
@@ -1211,7 +1211,7 @@ function(_ep_get_build_command name step cmd_var)
if(step STREQUAL "INSTALL")
set(args install)
endif()
- if(step STREQUAL "TEST")
+ if("x${step}x" STREQUAL "xTESTx")
set(args test)
endif()
else()
@@ -1230,7 +1230,7 @@ function(_ep_get_build_command name step cmd_var)
list(APPEND args --target install)
endif()
# But for "TEST" drive the project with corresponding "ctest".
- if(step STREQUAL "TEST")
+ if("x${step}x" STREQUAL "xTESTx")
string(REGEX REPLACE "^(.*/)cmake([^/]*)$" "\\1ctest\\2" cmd "${cmd}")
set(args "")
endif()
@@ -1246,7 +1246,7 @@ function(_ep_get_build_command name step cmd_var)
if(step STREQUAL "INSTALL")
set(args install)
endif()
- if(step STREQUAL "TEST")
+ if("x${step}x" STREQUAL "xTESTx")
set(args test)
endif()
endif()
diff --git a/Modules/FindJava.cmake b/Modules/FindJava.cmake
index 9e43174..9f87997 100644
--- a/Modules/FindJava.cmake
+++ b/Modules/FindJava.cmake
@@ -8,21 +8,34 @@
# include files and libraries are. The caller may set variable JAVA_HOME
# to specify a Java installation prefix explicitly.
#
+#
+# Specify one or more of the following components as you call this find module. See example below.
+#
+# ::
+#
+# Runtime = User just want to execute some Java byte-compiled
+# Development = Development tools (java, javac, javah and javadoc), includes Runtime component
+# IdlJ = idl compiler for Java
+# JarSigner = signer tool for jar
+#
+#
# This module sets the following result variables:
#
# ::
#
-# Java_JAVA_EXECUTABLE = the full path to the Java runtime
-# Java_JAVAC_EXECUTABLE = the full path to the Java compiler
-# Java_JAVAH_EXECUTABLE = the full path to the Java header generator
-# Java_JAVADOC_EXECUTABLE = the full path to the Java documention generator
-# Java_JAR_EXECUTABLE = the full path to the Java archiver
-# Java_VERSION_STRING = Version of java found, eg. 1.6.0_12
-# Java_VERSION_MAJOR = The major version of the package found.
-# Java_VERSION_MINOR = The minor version of the package found.
-# Java_VERSION_PATCH = The patch version of the package found.
-# Java_VERSION_TWEAK = The tweak version of the package found (after '_')
-# Java_VERSION = This is set to: $major.$minor.$patch(.$tweak)
+# Java_JAVA_EXECUTABLE = the full path to the Java runtime
+# Java_JAVAC_EXECUTABLE = the full path to the Java compiler
+# Java_JAVAH_EXECUTABLE = the full path to the Java header generator
+# Java_JAVADOC_EXECUTABLE = the full path to the Java documention generator
+# Java_IDLJ_EXECUTABLE = the full path to the Java idl compiler
+# Java_JAR_EXECUTABLE = the full path to the Java archiver
+# Java_JARSIGNER_EXECUTABLE = the full path to the Java jar signer
+# Java_VERSION_STRING = Version of java found, eg. 1.6.0_12
+# Java_VERSION_MAJOR = The major version of the package found.
+# Java_VERSION_MINOR = The minor version of the package found.
+# Java_VERSION_PATCH = The patch version of the package found.
+# Java_VERSION_TWEAK = The tweak version of the package found (after '_')
+# Java_VERSION = This is set to: $major.$minor.$patch(.$tweak)
#
#
#
@@ -184,28 +197,61 @@ find_program(Java_JAVADOC_EXECUTABLE
PATHS ${_JAVA_PATHS}
)
+find_program(Java_IDLJ_EXECUTABLE
+ NAMES idlj
+ HINTS ${_JAVA_HINTS}
+ PATHS ${_JAVA_PATHS}
+)
+
+find_program(Java_JARSIGNER_EXECUTABLE
+ NAMES jarsigner
+ HINTS ${_JAVA_HINTS}
+ PATHS ${_JAVA_PATHS}
+)
+
include(${CMAKE_CURRENT_LIST_DIR}/FindPackageHandleStandardArgs.cmake)
if(Java_FIND_COMPONENTS)
+ set(_JAVA_REQUIRED_VARS)
foreach(component ${Java_FIND_COMPONENTS})
# User just want to execute some Java byte-compiled
- if(component STREQUAL "Runtime")
- find_package_handle_standard_args(Java
- REQUIRED_VARS Java_JAVA_EXECUTABLE
- VERSION_VAR Java_VERSION
- )
+ If(component STREQUAL "Runtime")
+ list(APPEND _JAVA_REQUIRED_VARS Java_JAVA_EXECUTABLE)
+ if(Java_JAVA_EXECUTABLE)
+ set(Java_Runtime_FOUND TRUE)
+ endif()
elseif(component STREQUAL "Development")
- find_package_handle_standard_args(Java
- REQUIRED_VARS Java_JAVA_EXECUTABLE Java_JAR_EXECUTABLE Java_JAVAC_EXECUTABLE
- Java_JAVAH_EXECUTABLE Java_JAVADOC_EXECUTABLE
- VERSION_VAR Java_VERSION
- )
+ list(APPEND _JAVA_REQUIRED_VARS Java_JAVA_EXECUTABLE Java_JAVAC_EXECUTABLE
+ Java_JAVAH_EXECUTABLE Java_JAVADOC_EXECUTABLE)
+ if(Java_JAVA_EXECUTABLE AND Java_JAVAC_EXECUTABLE
+ AND Java_JAVAH_EXECUTABLE AND Java_JAVADOC_EXECUTABLE)
+ set(Java_Development_FOUND TRUE)
+ endif()
+ elseif(component STREQUAL "IdlJ")
+ list(APPEND _JAVA_REQUIRED_VARS Java_IDLJ_EXECUTABLE)
+ if(Java_IdlJ_EXECUTABLE)
+ set(Java_Extra_FOUND TRUE)
+ endif()
+ elseif(component STREQUAL "JarSigner")
+ list(APPEND _JAVA_REQUIRED_VARS Java_JARSIGNER_EXECUTABLE)
+ if(Java_IDLJ_EXECUTABLE)
+ set(Java_JarSigner_FOUND TRUE)
+ endif()
else()
message(FATAL_ERROR "Comp: ${component} is not handled")
endif()
- set(Java_${component}_FOUND TRUE)
endforeach()
+ list (REMOVE_DUPLICATES _JAVA_REQUIRED_VARS)
+ find_package_handle_standard_args(Java
+ REQUIRED_VARS ${_JAVA_REQUIRED_VARS} HANDLE_COMPONENTS
+ VERSION_VAR Java_VERSION
+ )
+ if(Java_FOUND)
+ foreach(component ${Java_FIND_COMPONENTS})
+ set(Java_${component}_FOUND TRUE)
+ endforeach()
+ endif()
else()
- # Check for everything
+ # Check for Development
find_package_handle_standard_args(Java
REQUIRED_VARS Java_JAVA_EXECUTABLE Java_JAR_EXECUTABLE Java_JAVAC_EXECUTABLE
Java_JAVAH_EXECUTABLE Java_JAVADOC_EXECUTABLE
@@ -220,6 +266,8 @@ mark_as_advanced(
Java_JAVAC_EXECUTABLE
Java_JAVAH_EXECUTABLE
Java_JAVADOC_EXECUTABLE
+ Java_IDLJ_EXECUTABLE
+ Java_JARSIGNER_EXECUTABLE
)
# LEGACY
diff --git a/Modules/FindMPI.cmake b/Modules/FindMPI.cmake
index 06ecfaa..48adf3c 100644
--- a/Modules/FindMPI.cmake
+++ b/Modules/FindMPI.cmake
@@ -102,7 +102,6 @@
# include this to handle the QUIETLY and REQUIRED arguments
include(${CMAKE_CURRENT_LIST_DIR}/FindPackageHandleStandardArgs.cmake)
-include(${CMAKE_CURRENT_LIST_DIR}/GetPrerequisites.cmake)
#
# This part detects MPI compilers, attempting to wade through the mess of compiler names in
@@ -578,14 +577,13 @@ foreach (lang C CXX Fortran)
if (CMAKE_${lang}_COMPILER_WORKS)
# If the user supplies a compiler *name* instead of an absolute path, assume that we need to find THAT compiler.
if (MPI_${lang}_COMPILER)
- is_file_executable(MPI_${lang}_COMPILER MPI_COMPILER_IS_EXECUTABLE)
- if (NOT MPI_COMPILER_IS_EXECUTABLE)
+ if (NOT IS_ABSOLUTE "${MPI_${lang}_COMPILER}")
# Get rid of our default list of names and just search for the name the user wants.
set(_MPI_${lang}_COMPILER_NAMES ${MPI_${lang}_COMPILER})
set(MPI_${lang}_COMPILER "MPI_${lang}_COMPILER-NOTFOUND" CACHE FILEPATH "Cleared" FORCE)
- # If the user specifies a compiler, we don't want to try to search libraries either.
- set(try_libs FALSE)
endif()
+ # If the user specifies a compiler, we don't want to try to search libraries either.
+ set(try_libs FALSE)
else()
set(try_libs TRUE)
endif()
diff --git a/Modules/GetPrerequisites.cmake b/Modules/GetPrerequisites.cmake
index 23d486e..e4018b6 100644
--- a/Modules/GetPrerequisites.cmake
+++ b/Modules/GetPrerequisites.cmake
@@ -229,9 +229,14 @@ function(is_file_executable file result_var)
if(file_cmd)
execute_process(COMMAND "${file_cmd}" "${file_full}"
+ RESULT_VARIABLE file_rv
OUTPUT_VARIABLE file_ov
+ ERROR_VARIABLE file_ev
OUTPUT_STRIP_TRAILING_WHITESPACE
)
+ if(NOT file_rv STREQUAL "0")
+ message(FATAL_ERROR "${file_cmd} failed: ${file_rv}\n${file_ev}")
+ endif()
# Replace the name of the file in the output with a placeholder token
# (the string " _file_full_ ") so that just in case the path name of
@@ -543,11 +548,21 @@ function(gp_resolved_file_type original_file file exepath dirs type_var)
if(CYGPATH_EXECUTABLE)
execute_process(COMMAND ${CYGPATH_EXECUTABLE} -W
+ RESULT_VARIABLE env_rv
OUTPUT_VARIABLE env_windir
+ ERROR_VARIABLE env_ev
OUTPUT_STRIP_TRAILING_WHITESPACE)
+ if(NOT env_rv STREQUAL "0")
+ message(FATAL_ERROR "${CYGPATH_EXECUTABLE} -W failed: ${env_rv}\n${env_ev}")
+ endif()
execute_process(COMMAND ${CYGPATH_EXECUTABLE} -S
+ RESULT_VARIABLE env_rv
OUTPUT_VARIABLE env_sysdir
+ ERROR_VARIABLE env_ev
OUTPUT_STRIP_TRAILING_WHITESPACE)
+ if(NOT env_rv STREQUAL "0")
+ message(FATAL_ERROR "${CYGPATH_EXECUTABLE} -S failed: ${env_rv}\n${env_ev}")
+ endif()
string(TOLOWER "${env_windir}" windir)
string(TOLOWER "${env_sysdir}" sysroot)
@@ -685,6 +700,8 @@ function(get_prerequisites target prerequisites_var exclude_system recurse exepa
return()
endif()
+ set(gp_cmd_maybe_filter) # optional command to pre-filter gp_tool results
+
if(gp_tool STREQUAL "ldd")
set(gp_cmd_args "")
set(gp_regex "^[\t ]*[^\t ]+ => ([^\t\(]+) .*${eol_char}$")
@@ -709,6 +726,11 @@ function(get_prerequisites target prerequisites_var exclude_system recurse exepa
set(gp_regex_error "")
set(gp_regex_fallback "")
set(gp_regex_cmp_count 1)
+ # objdump generaates copious output so we create a grep filter to pre-filter results
+ find_program(gp_grep_cmd grep)
+ if(gp_grep_cmd)
+ set(gp_cmd_maybe_filter COMMAND ${gp_grep_cmd} "^[[:blank:]]*DLL Name: ")
+ endif()
else()
message(STATUS "warning: gp_tool='${gp_tool}' is an unknown tool...")
message(STATUS "CMake function get_prerequisites needs more code to handle '${gp_tool}'")
@@ -765,8 +787,19 @@ function(get_prerequisites target prerequisites_var exclude_system recurse exepa
#
execute_process(
COMMAND ${gp_cmd} ${gp_cmd_args} ${target}
+ ${gp_cmd_maybe_filter}
+ RESULT_VARIABLE gp_rv
OUTPUT_VARIABLE gp_cmd_ov
+ ERROR_VARIABLE gp_ev
)
+ if(NOT gp_rv STREQUAL "0")
+ if(gp_tool STREQUAL "dumpbin")
+ # dumpbin error messages seem to go to stdout
+ message(FATAL_ERROR "${gp_cmd} failed: ${gp_rv}\n${gp_ev}\n${gp_cmd_ov}")
+ else()
+ message(FATAL_ERROR "${gp_cmd} failed: ${gp_rv}\n${gp_ev}")
+ endif()
+ endif()
if(gp_tool STREQUAL "ldd")
set(ENV{LD_LIBRARY_PATH} "${old_ld_env}")
@@ -791,8 +824,13 @@ function(get_prerequisites target prerequisites_var exclude_system recurse exepa
if(gp_tool STREQUAL "otool")
execute_process(
COMMAND otool -D ${target}
+ RESULT_VARIABLE otool_rv
OUTPUT_VARIABLE gp_install_id_ov
+ ERROR_VARIABLE otool_ev
)
+ if(NOT otool_rv STREQUAL "0")
+ message(FATAL_ERROR "otool -D failed: ${otool_rv}\n${otool_ev}")
+ endif()
# second line is install name
string(REGEX REPLACE ".*:\n" "" gp_install_id "${gp_install_id_ov}")
if(gp_install_id)
diff --git a/Modules/UseJava.cmake b/Modules/UseJava.cmake
index 5eb0ca8..c61591d 100644
--- a/Modules/UseJava.cmake
+++ b/Modules/UseJava.cmake
@@ -21,7 +21,8 @@
#
# This command creates a <target_name>.jar. It compiles the given
# source files (source) and adds the given resource files (resource) to
-# the jar file. If only resource files are given then just a jar file
+# the jar file. Source files can be java files or listing files
+# (prefixed by '@'). If only resource files are given then just a jar file
# is created. The list of include jars are added to the classpath when
# compiling the java sources and also to the dependencies of the target.
# INCLUDE_JARS also accepts other target names created by add_jar. For
@@ -210,14 +211,16 @@
#
# ::
#
-# install_jar(TARGET_NAME DESTINATION)
+# install_jar(target_name destination)
+# install_jar(target_name DESTINATION destination [COMPONENT component])
#
# This command installs the TARGET_NAME files to the given DESTINATION.
# It should be called in the same scope as add_jar() or it will fail.
#
# ::
#
-# install_jni_symlink(TARGET_NAME DESTINATION)
+# install_jni_symlink(target_name destination)
+# install_jni_symlink(target_name DESTINATION destination [COMPONENT component])
#
# This command installs the TARGET_NAME JNI symlinks to the given
# DESTINATION. It should be called in the same scope as add_jar() or it
@@ -423,6 +426,7 @@ function(add_jar _TARGET_NAME)
set(_JAVA_CLASS_FILES)
set(_JAVA_COMPILE_FILES)
+ set(_JAVA_COMPILE_FILELISTS)
set(_JAVA_DEPENDS)
set(_JAVA_COMPILE_DEPENDS)
set(_JAVA_RESOURCE_FILES)
@@ -433,7 +437,11 @@ function(add_jar _TARGET_NAME)
get_filename_component(_JAVA_PATH ${_JAVA_SOURCE_FILE} PATH)
get_filename_component(_JAVA_FULL ${_JAVA_SOURCE_FILE} ABSOLUTE)
- if (_JAVA_EXT MATCHES ".java")
+ if (_JAVA_SOURCE_FILE MATCHES "^@(.+)$")
+ get_filename_component(_JAVA_FULL ${CMAKE_MATCH_1} ABSOLUTE)
+ list(APPEND _JAVA_COMPILE_FILELISTS ${_JAVA_FULL})
+
+ elseif (_JAVA_EXT MATCHES ".java")
file(RELATIVE_PATH _JAVA_REL_BINARY_PATH ${_add_jar_OUTPUT_DIR} ${_JAVA_FULL})
file(RELATIVE_PATH _JAVA_REL_SOURCE_PATH ${CMAKE_CURRENT_SOURCE_DIR} ${_JAVA_FULL})
string(LENGTH ${_JAVA_REL_BINARY_PATH} _BIN_LEN)
@@ -492,11 +500,21 @@ function(add_jar _TARGET_NAME)
file(WRITE ${CMAKE_JAVA_CLASS_OUTPUT_PATH}/java_class_filelist "")
endif()
- if (_JAVA_COMPILE_FILES)
- # Create the list of files to compile.
- set(_JAVA_SOURCES_FILE ${CMAKE_JAVA_CLASS_OUTPUT_PATH}/java_sources)
- string(REPLACE ";" "\"\n\"" _JAVA_COMPILE_STRING "\"${_JAVA_COMPILE_FILES}\"")
- file(WRITE ${_JAVA_SOURCES_FILE} ${_JAVA_COMPILE_STRING})
+ if (_JAVA_COMPILE_FILES OR _JAVA_COMPILE_FILELISTS)
+ set (_JAVA_SOURCES_FILELISTS)
+
+ if (_JAVA_COMPILE_FILES)
+ # Create the list of files to compile.
+ set(_JAVA_SOURCES_FILE ${CMAKE_JAVA_CLASS_OUTPUT_PATH}/java_sources)
+ string(REPLACE ";" "\"\n\"" _JAVA_COMPILE_STRING "\"${_JAVA_COMPILE_FILES}\"")
+ file(WRITE ${_JAVA_SOURCES_FILE} ${_JAVA_COMPILE_STRING})
+ list (APPEND _JAVA_SOURCES_FILELISTS "@${_JAVA_SOURCES_FILE}")
+ endif()
+ if (_JAVA_COMPILE_FILELISTS)
+ foreach (_JAVA_FILELIST IN LISTS _JAVA_COMPILE_FILELISTS)
+ list (APPEND _JAVA_SOURCES_FILELISTS "@${_JAVA_FILELIST}")
+ endforeach()
+ endif()
# Compile the java files and create a list of class files
add_custom_command(
@@ -506,9 +524,9 @@ function(add_jar _TARGET_NAME)
${CMAKE_JAVA_COMPILE_FLAGS}
-classpath "${CMAKE_JAVA_INCLUDE_PATH_FINAL}"
-d ${CMAKE_JAVA_CLASS_OUTPUT_PATH}
- @${_JAVA_SOURCES_FILE}
+ ${_JAVA_SOURCES_FILELISTS}
COMMAND ${CMAKE_COMMAND} -E touch ${CMAKE_JAVA_CLASS_OUTPUT_PATH}/java_compiled_${_TARGET_NAME}
- DEPENDS ${_JAVA_COMPILE_FILES} ${_JAVA_COMPILE_DEPENDS}
+ DEPENDS ${_JAVA_COMPILE_FILES} ${_JAVA_COMPILE_FILELISTS} ${_JAVA_COMPILE_DEPENDS}
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
COMMENT "Building Java objects for ${_TARGET_NAME}.jar"
)
@@ -613,7 +631,26 @@ function(add_jar _TARGET_NAME)
endfunction()
-function(INSTALL_JAR _TARGET_NAME _DESTINATION)
+function(INSTALL_JAR _TARGET_NAME)
+ if (ARGC EQUAL 2)
+ set (_DESTINATION ${ARGV1})
+ else()
+ cmake_parse_arguments(_install_jar
+ ""
+ "DESTINATION;COMPONENT"
+ ""
+ ${ARGN})
+ if (_install_jar_DESTINATION)
+ set (_DESTINATION ${_install_jar_DESTINATION})
+ else()
+ message(SEND_ERROR "install_jar: ${_TARGET_NAME}: DESTINATION must be specified.")
+ endif()
+
+ if (_install_jar_COMPONENT)
+ set (_COMPONENT COMPONENT ${_install_jar_COMPONENT})
+ endif()
+ endif()
+
get_property(__FILES
TARGET
${_TARGET_NAME}
@@ -627,13 +664,33 @@ function(INSTALL_JAR _TARGET_NAME _DESTINATION)
${__FILES}
DESTINATION
${_DESTINATION}
+ ${_COMPONENT}
)
else ()
- message(SEND_ERROR "The target ${_TARGET_NAME} is not known in this scope.")
+ message(SEND_ERROR "install_jar: The target ${_TARGET_NAME} is not known in this scope.")
endif ()
endfunction()
-function(INSTALL_JNI_SYMLINK _TARGET_NAME _DESTINATION)
+function(INSTALL_JNI_SYMLINK _TARGET_NAME)
+ if (ARGC EQUAL 2)
+ set (_DESTINATION ${ARGV1})
+ else()
+ cmake_parse_arguments(_install_jni_symlink
+ ""
+ "DESTINATION;COMPONENT"
+ ""
+ ${ARGN})
+ if (_install_jni_symlink_DESTINATION)
+ set (_DESTINATION ${_install_jni_symlink_DESTINATION})
+ else()
+ message(SEND_ERROR "install_jni_symlink: ${_TARGET_NAME}: DESTINATION must be specified.")
+ endif()
+
+ if (_install_jni_symlink_COMPONENT)
+ set (_COMPONENT COMPONENT ${_install_jni_symlink_COMPONENT})
+ endif()
+ endif()
+
get_property(__SYMLINK
TARGET
${_TARGET_NAME}
@@ -647,9 +704,10 @@ function(INSTALL_JNI_SYMLINK _TARGET_NAME _DESTINATION)
${__SYMLINK}
DESTINATION
${_DESTINATION}
+ ${_COMPONENT}
)
else ()
- message(SEND_ERROR "The target ${_TARGET_NAME} is not known in this scope.")
+ message(SEND_ERROR "install_jni_symlink: The target ${_TARGET_NAME} is not known in this scope.")
endif ()
endfunction()
diff --git a/Source/CMakeVersion.cmake b/Source/CMakeVersion.cmake
index 5d53072..c9fec35 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 3)
-set(CMake_VERSION_PATCH 20150801)
+set(CMake_VERSION_PATCH 20150806)
#set(CMake_VERSION_RC 1)
diff --git a/Source/cmConditionEvaluator.cxx b/Source/cmConditionEvaluator.cxx
index 420bfdf..7874803 100644
--- a/Source/cmConditionEvaluator.cxx
+++ b/Source/cmConditionEvaluator.cxx
@@ -16,7 +16,8 @@ cmConditionEvaluator::cmConditionEvaluator(cmMakefile& makefile):
Makefile(makefile),
Policy12Status(makefile.GetPolicyStatus(cmPolicies::CMP0012)),
Policy54Status(makefile.GetPolicyStatus(cmPolicies::CMP0054)),
- Policy57Status(makefile.GetPolicyStatus(cmPolicies::CMP0057))
+ Policy57Status(makefile.GetPolicyStatus(cmPolicies::CMP0057)),
+ Policy64Status(makefile.GetPolicyStatus(cmPolicies::CMP0064))
{
}
@@ -493,6 +494,29 @@ bool cmConditionEvaluator::HandleLevel1(cmArgumentList &newArgs,
this->Makefile.FindTargetToUse(argP1->GetValue())?true:false,
reducible, arg, newArgs, argP1, argP2);
}
+ // does a test exist
+ if(this->Policy64Status != cmPolicies::OLD &&
+ this->Policy64Status != cmPolicies::WARN)
+ {
+ if (this->IsKeyword("TEST", *arg) && argP1 != newArgs.end())
+ {
+ const cmTest* haveTest = this->Makefile.GetTest(argP1->c_str());
+ this->HandlePredicate(
+ haveTest?true:false,
+ reducible, arg, newArgs, argP1, argP2);
+ }
+ }
+ else if(this->Policy64Status == cmPolicies::WARN &&
+ this->IsKeyword("TEST", *arg))
+ {
+ std::ostringstream e;
+ e << cmPolicies::GetPolicyWarning(cmPolicies::CMP0064) << "\n";
+ e << "TEST will be interpreted as an operator "
+ "when the policy is set to NEW. "
+ "Since the policy is not set the OLD behavior will be used.";
+
+ this->Makefile.IssueMessage(cmake::AUTHOR_WARNING, e.str());
+ }
// is a variable defined
if (this->IsKeyword("DEFINED", *arg) && argP1 != newArgs.end())
{
diff --git a/Source/cmConditionEvaluator.h b/Source/cmConditionEvaluator.h
index c923d76..c4e2d11 100644
--- a/Source/cmConditionEvaluator.h
+++ b/Source/cmConditionEvaluator.h
@@ -94,6 +94,7 @@ private:
cmPolicies::PolicyStatus Policy12Status;
cmPolicies::PolicyStatus Policy54Status;
cmPolicies::PolicyStatus Policy57Status;
+ cmPolicies::PolicyStatus Policy64Status;
};
#endif
diff --git a/Source/cmGlobalGenerator.cxx b/Source/cmGlobalGenerator.cxx
index d6d36d4..3c818ff 100644
--- a/Source/cmGlobalGenerator.cxx
+++ b/Source/cmGlobalGenerator.cxx
@@ -14,6 +14,20 @@
#if defined(_MSC_VER) && _MSC_VER >= 1800
# define KWSYS_WINDOWS_DEPRECATED_GetVersionEx
#endif
+typedef struct {
+ ULONG dwOSVersionInfoSize;
+ ULONG dwMajorVersion;
+ ULONG dwMinorVersion;
+ ULONG dwBuildNumber;
+ ULONG dwPlatformId;
+ WCHAR szCSDVersion[128];
+ USHORT wServicePackMajor;
+ USHORT wServicePackMinor;
+ USHORT wSuiteMask;
+ UCHAR wProductType;
+ UCHAR wReserved;
+} CMRTL_OSVERSIONINFOEXW;
+
#endif
#include "cmGlobalGenerator.h"
@@ -432,23 +446,45 @@ cmGlobalGenerator::EnableLanguage(std::vector<std::string>const& languages,
if (!mf->GetDefinition("CMAKE_SYSTEM"))
{
#if defined(_WIN32) && !defined(__CYGWIN__)
- /* Windows version number data. */
- OSVERSIONINFO osvi;
- ZeroMemory(&osvi, sizeof(osvi));
- osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
+ CMRTL_OSVERSIONINFOEXW osviex;
+ ZeroMemory(&osviex, sizeof(osviex));
+ osviex.dwOSVersionInfoSize = sizeof(osviex);
+
+ typedef LONG (FAR WINAPI *cmRtlGetVersion)(CMRTL_OSVERSIONINFOEXW*);
+ cmRtlGetVersion rtlGetVersion = reinterpret_cast<cmRtlGetVersion>(
+ GetProcAddress(GetModuleHandleW(L"ntdll.dll"), "RtlGetVersion"));
+ if (rtlGetVersion && rtlGetVersion(&osviex) == 0)
+ {
+ std::ostringstream windowsVersionString;
+ windowsVersionString << osviex.dwMajorVersion << "."
+ << osviex.dwMinorVersion << "."
+ << osviex.dwBuildNumber;
+ windowsVersionString.str();
+ mf->AddDefinition("CMAKE_HOST_SYSTEM_VERSION",
+ windowsVersionString.str().c_str());
+ }
+ else
+ {
+ // RtlGetVersion failed, so use the deprecated GetVersionEx function.
+ /* Windows version number data. */
+ OSVERSIONINFO osvi;
+ ZeroMemory(&osvi, sizeof(osvi));
+ osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
#ifdef KWSYS_WINDOWS_DEPRECATED_GetVersionEx
# pragma warning (push)
# pragma warning (disable:4996)
#endif
- GetVersionEx (&osvi);
+ GetVersionEx (&osvi);
#ifdef KWSYS_WINDOWS_DEPRECATED_GetVersionEx
# pragma warning (pop)
#endif
- std::ostringstream windowsVersionString;
- windowsVersionString << osvi.dwMajorVersion << "." << osvi.dwMinorVersion;
- windowsVersionString.str();
- mf->AddDefinition("CMAKE_HOST_SYSTEM_VERSION",
- windowsVersionString.str().c_str());
+ std::ostringstream windowsVersionString;
+ windowsVersionString << osvi.dwMajorVersion << "."
+ << osvi.dwMinorVersion;
+ windowsVersionString.str();
+ mf->AddDefinition("CMAKE_HOST_SYSTEM_VERSION",
+ windowsVersionString.str().c_str());
+ }
#endif
// Read the DetermineSystem file
std::string systemFile = mf->GetModulesFile("CMakeDetermineSystem.cmake");
diff --git a/Source/cmMakefile.cxx b/Source/cmMakefile.cxx
index 3e8abbc..165e271 100644
--- a/Source/cmMakefile.cxx
+++ b/Source/cmMakefile.cxx
@@ -1817,8 +1817,8 @@ void cmMakefile::ConfigureSubDirectory(cmMakefile *mf)
cmSystemTools::Message(msg.c_str());
}
- currentStart += "/CMakeLists.txt";
- if(!cmSystemTools::FileExists(currentStart.c_str(), true))
+ std::string const currentStartFile = currentStart + "/CMakeLists.txt";
+ if (!cmSystemTools::FileExists(currentStartFile, true))
{
// The file is missing. Check policy CMP0014.
std::ostringstream e;
diff --git a/Source/cmMessageCommand.cxx b/Source/cmMessageCommand.cxx
index e09ba75..467555f 100644
--- a/Source/cmMessageCommand.cxx
+++ b/Source/cmMessageCommand.cxx
@@ -43,7 +43,7 @@ bool cmMessageCommand
}
else if (*i == "AUTHOR_WARNING")
{
- if (!this->Makefile->IsOn("CMAKE_SUPPRESS_DEVELOPER_ERRORS"))
+ if (this->Makefile->IsOn("CMAKE_ERROR_DEVELOPER_WARNINGS"))
{
fatal = true;
type = cmake::AUTHOR_ERROR;
diff --git a/Source/cmPolicies.h b/Source/cmPolicies.h
index b783701..a791b89 100644
--- a/Source/cmPolicies.h
+++ b/Source/cmPolicies.h
@@ -217,6 +217,9 @@ class cmPolicy;
3, 3, 0, cmPolicies::WARN) \
SELECT(POLICY, CMP0063, \
"Honor visibility properties for all target types.", \
+ 3, 3, 0, cmPolicies::WARN) \
+ SELECT(POLICY, CMP0064, \
+ "Support new TEST if() operator.", \
3, 3, 0, cmPolicies::WARN)
#define CM_SELECT_ID(F, A1, A2, A3, A4, A5, A6) F(A1)
diff --git a/Source/cmXCodeObject.cxx b/Source/cmXCodeObject.cxx
index e72d315..c59c360 100644
--- a/Source/cmXCodeObject.cxx
+++ b/Source/cmXCodeObject.cxx
@@ -243,7 +243,11 @@ void cmXCodeObject::PrintString(std::ostream& os,std::string String)
bool needQuote =
(String.empty() ||
String.find("//") != String.npos ||
- String.find_first_of(" <>+-*=@[](){},") != String.npos);
+ String.find_first_not_of(
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ "abcdefghijklmnopqrstuvwxyz"
+ "0123456789"
+ "$_./") != String.npos);
const char* quote = needQuote? "\"" : "";
// Print the string, quoted and escaped as necessary.
diff --git a/Source/cmake.cxx b/Source/cmake.cxx
index d4bca9a..8c455b2 100644
--- a/Source/cmake.cxx
+++ b/Source/cmake.cxx
@@ -1289,7 +1289,7 @@ int cmake::Configure()
" the author of the CMakeLists.txt files.",
cmState::INTERNAL);
this->CacheManager->
- AddCacheEntry("CMAKE_SUPPRESS_DEVELOPER_ERRORS", "TRUE",
+ AddCacheEntry("CMAKE_ERROR_DEVELOPER_WARNINGS", "FALSE",
"Suppress errors that are meant for"
" the author of the CMakeLists.txt files.",
cmState::INTERNAL);
@@ -1328,7 +1328,7 @@ int cmake::Configure()
else if (warningLevel == ERROR_LEVEL)
{
this->CacheManager->
- AddCacheEntry("CMAKE_SUPPRESS_DEVELOPER_ERRORS", "FALSE",
+ AddCacheEntry("CMAKE_ERROR_DEVELOPER_WARNINGS", "TRUE",
"Suppress errors that are meant for"
" the author of the CMakeLists.txt files.",
cmState::INTERNAL);
@@ -1676,11 +1676,11 @@ int cmake::Run(const std::vector<std::string>& args, bool noconfigure)
}
// don't turn dev warnings into errors by default, if no value has been
- // specified for the flag, enable it
- if (!this->State->GetCacheEntryValue("CMAKE_SUPPRESS_DEVELOPER_ERRORS"))
+ // specified for the flag, disable it
+ if (!this->State->GetCacheEntryValue("CMAKE_ERROR_DEVELOPER_WARNINGS"))
{
this->CacheManager->
- AddCacheEntry("CMAKE_SUPPRESS_DEVELOPER_ERRORS", "TRUE",
+ AddCacheEntry("CMAKE_ERROR_DEVELOPER_WARNINGS", "FALSE",
"Suppress errors that are meant for"
" the author of the CMakeLists.txt files.",
cmState::INTERNAL);
diff --git a/Source/kwsys/CMakeLists.txt b/Source/kwsys/CMakeLists.txt
index 7eb678b..017d619 100644
--- a/Source/kwsys/CMakeLists.txt
+++ b/Source/kwsys/CMakeLists.txt
@@ -1237,7 +1237,7 @@ IF(KWSYS_STANDALONE OR CMake_SOURCE_DIR)
IF(NOT CYGWIN)
SET(KWSYS_TEST_PROCESS_7 7)
ENDIF()
- FOREACH(n 1 2 3 4 5 6 ${KWSYS_TEST_PROCESS_7})
+ FOREACH(n 1 2 3 4 5 6 ${KWSYS_TEST_PROCESS_7} 9 10)
ADD_TEST(kwsys.testProcess-${n} ${EXEC_DIR}/${KWSYS_NAMESPACE}TestProcess ${n})
SET_PROPERTY(TEST kwsys.testProcess-${n} PROPERTY LABELS ${KWSYS_LABELS_TEST})
SET_TESTS_PROPERTIES(kwsys.testProcess-${n} PROPERTIES TIMEOUT 120)
@@ -1270,6 +1270,10 @@ IF(KWSYS_STANDALONE OR CMake_SOURCE_DIR)
MESSAGE(STATUS "GET_TEST_PROPERTY returned: ${wfv}")
ENDIF()
+ # Set up ctest custom configuration file.
+ CONFIGURE_FILE(${PROJECT_SOURCE_DIR}/CTestCustom.cmake.in
+ ${PROJECT_BINARY_DIR}/CTestCustom.cmake @ONLY)
+
# Suppress known consistent failures on buggy systems.
IF(KWSYS_TEST_BOGUS_FAILURES)
SET_TESTS_PROPERTIES(${KWSYS_TEST_BOGUS_FAILURES} PROPERTIES WILL_FAIL ON)
diff --git a/Source/kwsys/CTestCustom.cmake.in b/Source/kwsys/CTestCustom.cmake.in
new file mode 100644
index 0000000..d6f802e
--- /dev/null
+++ b/Source/kwsys/CTestCustom.cmake.in
@@ -0,0 +1,15 @@
+# kwsys.testProcess-10 involves sending SIGINT to a child process, which then
+# exits abnormally via a call to _exit(). (On Windows, a call to ExitProcess).
+# Naturally, this results in plenty of memory being "leaked" by this child
+# process - the memory check results are not meaningful in this case.
+#
+# kwsys.testProcess-9 also tests sending SIGINT to a child process. However,
+# normal operation of that test involves the child process timing out, and the
+# host process kills (SIGKILL) it as a result. Since it was SIGKILL'ed, the
+# resulting memory leaks are not logged by valgrind anyway. Therefore, we
+# don't have to exclude it.
+
+set(CTEST_CUSTOM_MEMCHECK_IGNORE
+ ${CTEST_CUSTOM_MEMCHECK_IGNORE}
+ kwsys.testProcess-10
+ )
diff --git a/Source/kwsys/EncodingC.c b/Source/kwsys/EncodingC.c
index ba2cec2..32b9bff 100644
--- a/Source/kwsys/EncodingC.c
+++ b/Source/kwsys/EncodingC.c
@@ -45,8 +45,11 @@ wchar_t* kwsysEncoding_DupToWide(const char* str)
if(length > 0)
{
ret = (wchar_t*)malloc((length)*sizeof(wchar_t));
- ret[0] = 0;
- kwsysEncoding_mbstowcs(ret, str, length);
+ if(ret)
+ {
+ ret[0] = 0;
+ kwsysEncoding_mbstowcs(ret, str, length);
+ }
}
return ret;
}
@@ -72,8 +75,11 @@ char* kwsysEncoding_DupToNarrow(const wchar_t* str)
if(length > 0)
{
ret = (char*)malloc(length);
- ret[0] = 0;
- kwsysEncoding_wcstombs(ret, str, length);
+ if(ret)
+ {
+ ret[0] = 0;
+ kwsysEncoding_wcstombs(ret, str, length);
+ }
}
return ret;
}
diff --git a/Source/kwsys/Process.h.in b/Source/kwsys/Process.h.in
index e35939f..c5ebc97 100644
--- a/Source/kwsys/Process.h.in
+++ b/Source/kwsys/Process.h.in
@@ -23,58 +23,60 @@
# define kwsysEXPORT @KWSYS_NAMESPACE@_EXPORT
#endif
#if !@KWSYS_NAMESPACE@_NAME_IS_KWSYS
-# define kwsysProcess kwsys_ns(Process)
-# define kwsysProcess_s kwsys_ns(Process_s)
-# define kwsysProcess_New kwsys_ns(Process_New)
-# define kwsysProcess_Delete kwsys_ns(Process_Delete)
-# define kwsysProcess_SetCommand kwsys_ns(Process_SetCommand)
-# define kwsysProcess_AddCommand kwsys_ns(Process_AddCommand)
-# define kwsysProcess_SetTimeout kwsys_ns(Process_SetTimeout)
-# define kwsysProcess_SetWorkingDirectory kwsys_ns(Process_SetWorkingDirectory)
-# define kwsysProcess_SetPipeFile kwsys_ns(Process_SetPipeFile)
-# define kwsysProcess_SetPipeNative kwsys_ns(Process_SetPipeNative)
-# define kwsysProcess_SetPipeShared kwsys_ns(Process_SetPipeShared)
-# define kwsysProcess_Option_Detach kwsys_ns(Process_Option_Detach)
-# define kwsysProcess_Option_HideWindow kwsys_ns(Process_Option_HideWindow)
-# define kwsysProcess_Option_MergeOutput kwsys_ns(Process_Option_MergeOutput)
-# define kwsysProcess_Option_Verbatim kwsys_ns(Process_Option_Verbatim)
-# define kwsysProcess_GetOption kwsys_ns(Process_GetOption)
-# define kwsysProcess_SetOption kwsys_ns(Process_SetOption)
-# define kwsysProcess_Option_e kwsys_ns(Process_Option_e)
-# define kwsysProcess_State_Starting kwsys_ns(Process_State_Starting)
-# define kwsysProcess_State_Error kwsys_ns(Process_State_Error)
-# define kwsysProcess_State_Exception kwsys_ns(Process_State_Exception)
-# define kwsysProcess_State_Executing kwsys_ns(Process_State_Executing)
-# define kwsysProcess_State_Exited kwsys_ns(Process_State_Exited)
-# define kwsysProcess_State_Expired kwsys_ns(Process_State_Expired)
-# define kwsysProcess_State_Killed kwsys_ns(Process_State_Killed)
-# define kwsysProcess_State_Disowned kwsys_ns(Process_State_Disowned)
-# define kwsysProcess_GetState kwsys_ns(Process_GetState)
-# define kwsysProcess_State_e kwsys_ns(Process_State_e)
-# define kwsysProcess_Exception_None kwsys_ns(Process_Exception_None)
-# define kwsysProcess_Exception_Fault kwsys_ns(Process_Exception_Fault)
-# define kwsysProcess_Exception_Illegal kwsys_ns(Process_Exception_Illegal)
-# define kwsysProcess_Exception_Interrupt kwsys_ns(Process_Exception_Interrupt)
-# define kwsysProcess_Exception_Numerical kwsys_ns(Process_Exception_Numerical)
-# define kwsysProcess_Exception_Other kwsys_ns(Process_Exception_Other)
-# define kwsysProcess_GetExitException kwsys_ns(Process_GetExitException)
-# define kwsysProcess_Exception_e kwsys_ns(Process_Exception_e)
-# define kwsysProcess_GetExitCode kwsys_ns(Process_GetExitCode)
-# define kwsysProcess_GetExitValue kwsys_ns(Process_GetExitValue)
-# define kwsysProcess_GetErrorString kwsys_ns(Process_GetErrorString)
-# define kwsysProcess_GetExceptionString kwsys_ns(Process_GetExceptionString)
-# define kwsysProcess_Execute kwsys_ns(Process_Execute)
-# define kwsysProcess_Disown kwsys_ns(Process_Disown)
-# define kwsysProcess_WaitForData kwsys_ns(Process_WaitForData)
-# define kwsysProcess_Pipes_e kwsys_ns(Process_Pipes_e)
-# define kwsysProcess_Pipe_None kwsys_ns(Process_Pipe_None)
-# define kwsysProcess_Pipe_STDIN kwsys_ns(Process_Pipe_STDIN)
-# define kwsysProcess_Pipe_STDOUT kwsys_ns(Process_Pipe_STDOUT)
-# define kwsysProcess_Pipe_STDERR kwsys_ns(Process_Pipe_STDERR)
-# define kwsysProcess_Pipe_Timeout kwsys_ns(Process_Pipe_Timeout)
-# define kwsysProcess_Pipe_Handle kwsys_ns(Process_Pipe_Handle)
-# define kwsysProcess_WaitForExit kwsys_ns(Process_WaitForExit)
-# define kwsysProcess_Kill kwsys_ns(Process_Kill)
+# define kwsysProcess kwsys_ns(Process)
+# define kwsysProcess_s kwsys_ns(Process_s)
+# define kwsysProcess_New kwsys_ns(Process_New)
+# define kwsysProcess_Delete kwsys_ns(Process_Delete)
+# define kwsysProcess_SetCommand kwsys_ns(Process_SetCommand)
+# define kwsysProcess_AddCommand kwsys_ns(Process_AddCommand)
+# define kwsysProcess_SetTimeout kwsys_ns(Process_SetTimeout)
+# define kwsysProcess_SetWorkingDirectory kwsys_ns(Process_SetWorkingDirectory)
+# define kwsysProcess_SetPipeFile kwsys_ns(Process_SetPipeFile)
+# define kwsysProcess_SetPipeNative kwsys_ns(Process_SetPipeNative)
+# define kwsysProcess_SetPipeShared kwsys_ns(Process_SetPipeShared)
+# define kwsysProcess_Option_Detach kwsys_ns(Process_Option_Detach)
+# define kwsysProcess_Option_HideWindow kwsys_ns(Process_Option_HideWindow)
+# define kwsysProcess_Option_MergeOutput kwsys_ns(Process_Option_MergeOutput)
+# define kwsysProcess_Option_Verbatim kwsys_ns(Process_Option_Verbatim)
+# define kwsysProcess_Option_CreateProcessGroup kwsys_ns(Process_Option_CreateProcessGroup)
+# define kwsysProcess_GetOption kwsys_ns(Process_GetOption)
+# define kwsysProcess_SetOption kwsys_ns(Process_SetOption)
+# define kwsysProcess_Option_e kwsys_ns(Process_Option_e)
+# define kwsysProcess_State_Starting kwsys_ns(Process_State_Starting)
+# define kwsysProcess_State_Error kwsys_ns(Process_State_Error)
+# define kwsysProcess_State_Exception kwsys_ns(Process_State_Exception)
+# define kwsysProcess_State_Executing kwsys_ns(Process_State_Executing)
+# define kwsysProcess_State_Exited kwsys_ns(Process_State_Exited)
+# define kwsysProcess_State_Expired kwsys_ns(Process_State_Expired)
+# define kwsysProcess_State_Killed kwsys_ns(Process_State_Killed)
+# define kwsysProcess_State_Disowned kwsys_ns(Process_State_Disowned)
+# define kwsysProcess_GetState kwsys_ns(Process_GetState)
+# define kwsysProcess_State_e kwsys_ns(Process_State_e)
+# define kwsysProcess_Exception_None kwsys_ns(Process_Exception_None)
+# define kwsysProcess_Exception_Fault kwsys_ns(Process_Exception_Fault)
+# define kwsysProcess_Exception_Illegal kwsys_ns(Process_Exception_Illegal)
+# define kwsysProcess_Exception_Interrupt kwsys_ns(Process_Exception_Interrupt)
+# define kwsysProcess_Exception_Numerical kwsys_ns(Process_Exception_Numerical)
+# define kwsysProcess_Exception_Other kwsys_ns(Process_Exception_Other)
+# define kwsysProcess_GetExitException kwsys_ns(Process_GetExitException)
+# define kwsysProcess_Exception_e kwsys_ns(Process_Exception_e)
+# define kwsysProcess_GetExitCode kwsys_ns(Process_GetExitCode)
+# define kwsysProcess_GetExitValue kwsys_ns(Process_GetExitValue)
+# define kwsysProcess_GetErrorString kwsys_ns(Process_GetErrorString)
+# define kwsysProcess_GetExceptionString kwsys_ns(Process_GetExceptionString)
+# define kwsysProcess_Execute kwsys_ns(Process_Execute)
+# define kwsysProcess_Disown kwsys_ns(Process_Disown)
+# define kwsysProcess_WaitForData kwsys_ns(Process_WaitForData)
+# define kwsysProcess_Pipes_e kwsys_ns(Process_Pipes_e)
+# define kwsysProcess_Pipe_None kwsys_ns(Process_Pipe_None)
+# define kwsysProcess_Pipe_STDIN kwsys_ns(Process_Pipe_STDIN)
+# define kwsysProcess_Pipe_STDOUT kwsys_ns(Process_Pipe_STDOUT)
+# define kwsysProcess_Pipe_STDERR kwsys_ns(Process_Pipe_STDERR)
+# define kwsysProcess_Pipe_Timeout kwsys_ns(Process_Pipe_Timeout)
+# define kwsysProcess_Pipe_Handle kwsys_ns(Process_Pipe_Handle)
+# define kwsysProcess_WaitForExit kwsys_ns(Process_WaitForExit)
+# define kwsysProcess_Interrupt kwsys_ns(Process_Interrupt)
+# define kwsysProcess_Kill kwsys_ns(Process_Kill)
#endif
#if defined(__cplusplus)
@@ -199,6 +201,15 @@ kwsysEXPORT void kwsysProcess_SetPipeNative(kwsysProcess* cp, int pipe,
* and ignore the rest of the arguments.
* 0 = No (default)
* 1 = Yes
+ *
+ * kwsysProcess_Option_CreateProcessGroup = Whether to place the process in a
+ * new process group. This is
+ * useful if you want to send Ctrl+C
+ * to the process. On UNIX, also
+ * places the process in a new
+ * session.
+ * 0 = No (default)
+ * 1 = Yes
*/
kwsysEXPORT int kwsysProcess_GetOption(kwsysProcess* cp, int optionId);
kwsysEXPORT void kwsysProcess_SetOption(kwsysProcess* cp, int optionId,
@@ -208,7 +219,8 @@ enum kwsysProcess_Option_e
kwsysProcess_Option_HideWindow,
kwsysProcess_Option_Detach,
kwsysProcess_Option_MergeOutput,
- kwsysProcess_Option_Verbatim
+ kwsysProcess_Option_Verbatim,
+ kwsysProcess_Option_CreateProcessGroup
};
/**
@@ -363,6 +375,17 @@ enum kwsysProcess_Pipes_e
kwsysEXPORT int kwsysProcess_WaitForExit(kwsysProcess* cp, double* timeout);
/**
+ * Interrupt the process group for the child process that is currently
+ * running by sending it the appropriate operating-system specific signal.
+ * The caller should call WaitForExit after this returns to wait for the
+ * child to terminate.
+ *
+ * WARNING: If you didn't specify kwsysProcess_Option_CreateProcessGroup,
+ * you will interrupt your own process group.
+ */
+kwsysEXPORT void kwsysProcess_Interrupt(kwsysProcess* cp);
+
+/**
* Forcefully terminate the child process that is currently running.
* The caller should call WaitForExit after this returns to wait for
* the child to terminate.
@@ -394,6 +417,7 @@ kwsysEXPORT void kwsysProcess_Kill(kwsysProcess* cp);
# undef kwsysProcess_Option_HideWindow
# undef kwsysProcess_Option_MergeOutput
# undef kwsysProcess_Option_Verbatim
+# undef kwsysProcess_Option_CreateProcessGroup
# undef kwsysProcess_GetOption
# undef kwsysProcess_SetOption
# undef kwsysProcess_Option_e
@@ -430,6 +454,7 @@ kwsysEXPORT void kwsysProcess_Kill(kwsysProcess* cp);
# undef kwsysProcess_Pipe_Timeout
# undef kwsysProcess_Pipe_Handle
# undef kwsysProcess_WaitForExit
+# undef kwsysProcess_Interrupt
# undef kwsysProcess_Kill
# endif
#endif
diff --git a/Source/kwsys/ProcessUNIX.c b/Source/kwsys/ProcessUNIX.c
index 0393a6d..6d9b109 100644
--- a/Source/kwsys/ProcessUNIX.c
+++ b/Source/kwsys/ProcessUNIX.c
@@ -88,7 +88,7 @@ typedef ssize_t kwsysProcess_ssize_t;
typedef int kwsysProcess_ssize_t;
#endif
-#if defined(__BEOS__) && !defined(__ZETA__)
+#if defined(__BEOS__) && !defined(__ZETA__)
/* BeOS 5 doesn't have usleep(), but it has snooze(), which is identical. */
# include <be/kernel/OS.h>
static inline void kwsysProcess_usleep(unsigned int msec)
@@ -151,6 +151,7 @@ typedef struct kwsysProcessCreateInformation_s
} kwsysProcessCreateInformation;
/*--------------------------------------------------------------------------*/
+static void kwsysProcessVolatileFree(volatile void* p);
static int kwsysProcessInitialize(kwsysProcess* cp);
static void kwsysProcessCleanup(kwsysProcess* cp, int error);
static void kwsysProcessCleanupDescriptor(int* pfd);
@@ -197,7 +198,7 @@ struct kwsysProcess_s
{
/* The command lines to execute. */
char*** Commands;
- int NumberOfCommands;
+ volatile int NumberOfCommands;
/* Descriptors for the read ends of the child's output pipes and
the signal pipe. */
@@ -213,8 +214,10 @@ struct kwsysProcess_s
/* Buffer for pipe data. */
char PipeBuffer[KWSYSPE_PIPE_BUFFER_SIZE];
- /* Process IDs returned by the calls to fork. */
- pid_t* ForkPIDs;
+ /* Process IDs returned by the calls to fork. Everything is volatile
+ because the signal handler accesses them. You must be very careful
+ when reaping PIDs or modifying this array to avoid race conditions. */
+ volatile pid_t* volatile ForkPIDs;
/* Flag for whether the children were terminated by a faild select. */
int SelectError;
@@ -237,6 +240,9 @@ struct kwsysProcess_s
/* Whether to merge stdout/stderr of the child. */
int MergeOutput;
+ /* Whether to create the process in a new process group. */
+ volatile sig_atomic_t CreateProcessGroup;
+
/* Time at which the child started. Negative for no timeout. */
kwsysProcessTime StartTime;
@@ -257,8 +263,9 @@ struct kwsysProcess_s
/* The number of children still executing. */
int CommandsLeft;
- /* The current status of the child process. */
- int State;
+ /* The current status of the child process. Must be atomic because
+ the signal handler checks this to avoid a race. */
+ volatile sig_atomic_t State;
/* The exceptional behavior that terminated the child process, if
* any. */
@@ -271,7 +278,7 @@ struct kwsysProcess_s
int ExitValue;
/* Whether the process was killed. */
- int Killed;
+ volatile sig_atomic_t Killed;
/* Buffer for error message in case of failure. */
char ErrorMessage[KWSYSPE_PIPE_BUFFER_SIZE+1];
@@ -649,6 +656,8 @@ int kwsysProcess_GetOption(kwsysProcess* cp, int optionId)
case kwsysProcess_Option_Detach: return cp->OptionDetach;
case kwsysProcess_Option_MergeOutput: return cp->MergeOutput;
case kwsysProcess_Option_Verbatim: return cp->Verbatim;
+ case kwsysProcess_Option_CreateProcessGroup:
+ return cp->CreateProcessGroup;
default: return 0;
}
}
@@ -666,6 +675,8 @@ void kwsysProcess_SetOption(kwsysProcess* cp, int optionId, int value)
case kwsysProcess_Option_Detach: cp->OptionDetach = value; break;
case kwsysProcess_Option_MergeOutput: cp->MergeOutput = value; break;
case kwsysProcess_Option_Verbatim: cp->Verbatim = value; break;
+ case kwsysProcess_Option_CreateProcessGroup:
+ cp->CreateProcessGroup = value; break;
default: break;
}
}
@@ -1490,6 +1501,45 @@ int kwsysProcess_WaitForExit(kwsysProcess* cp, double* userTimeout)
}
/*--------------------------------------------------------------------------*/
+void kwsysProcess_Interrupt(kwsysProcess* cp)
+{
+ int i;
+ /* Make sure we are executing a process. */
+ if(!cp || cp->State != kwsysProcess_State_Executing || cp->TimeoutExpired ||
+ cp->Killed)
+ {
+ return;
+ }
+
+ /* Interrupt the children. */
+ if (cp->CreateProcessGroup)
+ {
+ if(cp->ForkPIDs)
+ {
+ for(i=0; i < cp->NumberOfCommands; ++i)
+ {
+ /* Make sure the PID is still valid. */
+ if(cp->ForkPIDs[i])
+ {
+ /* The user created a process group for this process. The group ID
+ is the process ID for the original process in the group. */
+ kill(-cp->ForkPIDs[i], SIGINT);
+ }
+ }
+ }
+ }
+ else
+ {
+ /* No process group was created. Kill our own process group.
+ NOTE: While one could argue that we could call kill(cp->ForkPIDs[i],
+ SIGINT) as a way to still interrupt the process even though it's not in
+ a special group, this is not an option on Windows. Therefore, we kill
+ the current process group for consistency with Windows. */
+ kill(0, SIGINT);
+ }
+}
+
+/*--------------------------------------------------------------------------*/
void kwsysProcess_Kill(kwsysProcess* cp)
{
int i;
@@ -1539,10 +1589,28 @@ void kwsysProcess_Kill(kwsysProcess* cp)
}
/*--------------------------------------------------------------------------*/
+/* Call the free() function with a pointer to volatile without causing
+ compiler warnings. */
+static void kwsysProcessVolatileFree(volatile void* p)
+{
+ /* clang has made it impossible to free memory that points to volatile
+ without first using special pragmas to disable a warning... */
+#if defined(__clang__)
+# pragma clang diagnostic push
+# pragma clang diagnostic ignored "-Wcast-qual"
+#endif
+ free((void*)p); /* The cast will silence most compilers, but not clang. */
+#if defined(__clang__)
+# pragma clang diagnostic pop
+#endif
+}
+
+/*--------------------------------------------------------------------------*/
/* Initialize a process control structure for kwsysProcess_Execute. */
static int kwsysProcessInitialize(kwsysProcess* cp)
{
int i;
+ volatile pid_t* oldForkPIDs;
for(i=0; i < KWSYSPE_PIPE_COUNT; ++i)
{
cp->PipeReadEnds[i] = -1;
@@ -1571,16 +1639,21 @@ static int kwsysProcessInitialize(kwsysProcess* cp)
cp->ErrorMessage[0] = 0;
strcpy(cp->ExitExceptionString, "No exception");
- if(cp->ForkPIDs)
+ oldForkPIDs = cp->ForkPIDs;
+ cp->ForkPIDs = (volatile pid_t*)malloc(
+ sizeof(volatile pid_t)*(size_t)(cp->NumberOfCommands));
+ if(oldForkPIDs)
{
- free(cp->ForkPIDs);
+ kwsysProcessVolatileFree(oldForkPIDs);
}
- cp->ForkPIDs = (pid_t*)malloc(sizeof(pid_t)*(size_t)(cp->NumberOfCommands));
if(!cp->ForkPIDs)
{
return 0;
}
- memset(cp->ForkPIDs, 0, sizeof(pid_t)*(size_t)(cp->NumberOfCommands));
+ for(i=0; i < cp->NumberOfCommands; ++i)
+ {
+ cp->ForkPIDs[i] = 0; /* can't use memset due to volatile */
+ }
if(cp->CommandExitCodes)
{
@@ -1671,7 +1744,7 @@ static void kwsysProcessCleanup(kwsysProcess* cp, int error)
/* Free memory. */
if(cp->ForkPIDs)
{
- free(cp->ForkPIDs);
+ kwsysProcessVolatileFree(cp->ForkPIDs);
cp->ForkPIDs = 0;
}
if(cp->RealWorkingDirectory)
@@ -1758,15 +1831,49 @@ int decc$set_child_standard_streams(int fd1, int fd2, int fd3);
static int kwsysProcessCreate(kwsysProcess* cp, int prIndex,
kwsysProcessCreateInformation* si)
{
+ sigset_t mask, old_mask;
+ int pgidPipe[2];
+ char tmp;
+ ssize_t readRes;
+
/* Create the error reporting pipe. */
if(pipe(si->ErrorPipe) < 0)
{
return 0;
}
- /* Set close-on-exec flag on the error pipe's write end. */
- if(fcntl(si->ErrorPipe[1], F_SETFD, FD_CLOEXEC) < 0)
+ /* Create a pipe for detecting that the child process has created a process
+ group and session. */
+ if(pipe(pgidPipe) < 0)
{
+ kwsysProcessCleanupDescriptor(&si->ErrorPipe[0]);
+ kwsysProcessCleanupDescriptor(&si->ErrorPipe[1]);
+ return 0;
+ }
+
+ /* Set close-on-exec flag on the pipe's write end. */
+ if(fcntl(si->ErrorPipe[1], F_SETFD, FD_CLOEXEC) < 0 ||
+ fcntl(pgidPipe[1], F_SETFD, FD_CLOEXEC) < 0)
+ {
+ kwsysProcessCleanupDescriptor(&si->ErrorPipe[0]);
+ kwsysProcessCleanupDescriptor(&si->ErrorPipe[1]);
+ kwsysProcessCleanupDescriptor(&pgidPipe[0]);
+ kwsysProcessCleanupDescriptor(&pgidPipe[1]);
+ return 0;
+ }
+
+ /* Block SIGINT / SIGTERM while we start. The purpose is so that our signal
+ handler doesn't get called from the child process after the fork and
+ before the exec, and subsequently start kill()'ing PIDs from ForkPIDs. */
+ sigemptyset(&mask);
+ sigaddset(&mask, SIGINT);
+ sigaddset(&mask, SIGTERM);
+ if(sigprocmask(SIG_BLOCK, &mask, &old_mask) < 0)
+ {
+ kwsysProcessCleanupDescriptor(&si->ErrorPipe[0]);
+ kwsysProcessCleanupDescriptor(&si->ErrorPipe[1]);
+ kwsysProcessCleanupDescriptor(&pgidPipe[0]);
+ kwsysProcessCleanupDescriptor(&pgidPipe[1]);
return 0;
}
@@ -1774,13 +1881,19 @@ static int kwsysProcessCreate(kwsysProcess* cp, int prIndex,
#if defined(__VMS)
/* VMS needs vfork and execvp to be in the same function because
they use setjmp/longjmp to run the child startup code in the
- parent! TODO: OptionDetach. */
+ parent! TODO: OptionDetach. Also
+ TODO: CreateProcessGroup. */
cp->ForkPIDs[prIndex] = vfork();
#else
cp->ForkPIDs[prIndex] = kwsysProcessFork(cp, si);
#endif
if(cp->ForkPIDs[prIndex] < 0)
{
+ sigprocmask(SIG_SETMASK, &old_mask, 0);
+ kwsysProcessCleanupDescriptor(&si->ErrorPipe[0]);
+ kwsysProcessCleanupDescriptor(&si->ErrorPipe[1]);
+ kwsysProcessCleanupDescriptor(&pgidPipe[0]);
+ kwsysProcessCleanupDescriptor(&pgidPipe[1]);
return 0;
}
@@ -1790,8 +1903,10 @@ static int kwsysProcessCreate(kwsysProcess* cp, int prIndex,
/* Specify standard pipes for child process. */
decc$set_child_standard_streams(si->StdIn, si->StdOut, si->StdErr);
#else
- /* Close the read end of the error reporting pipe. */
+ /* Close the read end of the error reporting / process group
+ setup pipe. */
close(si->ErrorPipe[0]);
+ close(pgidPipe[0]);
/* Setup the stdin, stdout, and stderr pipes. */
if(si->StdIn > 0)
@@ -1819,11 +1934,25 @@ static int kwsysProcessCreate(kwsysProcess* cp, int prIndex,
/* Restore all default signal handlers. */
kwsysProcessRestoreDefaultSignalHandlers();
+
+ /* Now that we have restored default signal handling and created the
+ process group, restore mask. */
+ sigprocmask(SIG_SETMASK, &old_mask, 0);
+
+ /* Create new process group. We use setsid instead of setpgid to avoid
+ the child getting hung up on signals like SIGTTOU. (In the real world,
+ this has been observed where "git svn" ends up calling the "resize"
+ program which opens /dev/tty. */
+ if(cp->CreateProcessGroup && setsid() < 0)
+ {
+ kwsysProcessChildErrorExit(si->ErrorPipe[1]);
+ }
#endif
/* Execute the real process. If successful, this does not return. */
execvp(cp->Commands[prIndex][0], cp->Commands[prIndex]);
/* TODO: What does VMS do if the child fails to start? */
+ /* TODO: On VMS, how do we put the process in a new group? */
/* Failure. Report error to parent and terminate. */
kwsysProcessChildErrorExit(si->ErrorPipe[1]);
@@ -1834,12 +1963,34 @@ static int kwsysProcessCreate(kwsysProcess* cp, int prIndex,
decc$set_child_standard_streams(0, 1, 2);
#endif
+ /* We are done with the error reporting pipe and process group setup pipe
+ write end. */
+ kwsysProcessCleanupDescriptor(&si->ErrorPipe[1]);
+ kwsysProcessCleanupDescriptor(&pgidPipe[1]);
+
+ /* Make sure the child is in the process group before we proceed. This
+ avoids race conditions with calls to the kill function that we make for
+ signalling process groups. */
+ while((readRes = read(pgidPipe[0], &tmp, 1)) > 0);
+ if(readRes < 0)
+ {
+ sigprocmask(SIG_SETMASK, &old_mask, 0);
+ kwsysProcessCleanupDescriptor(&si->ErrorPipe[0]);
+ kwsysProcessCleanupDescriptor(&pgidPipe[0]);
+ return 0;
+ }
+ kwsysProcessCleanupDescriptor(&pgidPipe[0]);
+
+ /* Unmask signals. */
+ if(sigprocmask(SIG_SETMASK, &old_mask, 0) < 0)
+ {
+ kwsysProcessCleanupDescriptor(&si->ErrorPipe[0]);
+ return 0;
+ }
+
/* A child has been created. */
++cp->CommandsLeft;
- /* We are done with the error reporting pipe write end. */
- kwsysProcessCleanupDescriptor(&si->ErrorPipe[1]);
-
/* Block until the child's exec call succeeds and closes the error
pipe or writes data to the pipe to report an error. */
{
@@ -1877,6 +2028,17 @@ static void kwsysProcessDestroy(kwsysProcess* cp)
/* A child process has terminated. Reap it if it is one handled by
this object. */
int i;
+ /* Temporarily disable signals that access ForkPIDs. We don't want them to
+ read a reaped PID, and writes to ForkPIDs are not atomic. */
+ sigset_t mask, old_mask;
+ sigemptyset(&mask);
+ sigaddset(&mask, SIGINT);
+ sigaddset(&mask, SIGTERM);
+ if(sigprocmask(SIG_BLOCK, &mask, &old_mask) < 0)
+ {
+ return;
+ }
+
for(i=0; i < cp->NumberOfCommands; ++i)
{
if(cp->ForkPIDs[i])
@@ -1910,6 +2072,9 @@ static void kwsysProcessDestroy(kwsysProcess* cp)
}
}
}
+
+ /* Re-enable signals. */
+ sigprocmask(SIG_SETMASK, &old_mask, 0);
}
/*--------------------------------------------------------------------------*/
@@ -1938,7 +2103,7 @@ static int kwsysProcessSetupOutputPipeFile(int* p, const char* name)
/* Assign the replacement descriptor. */
*p = fout;
- return 1;
+ return 1;
}
/*--------------------------------------------------------------------------*/
@@ -2582,19 +2747,23 @@ typedef struct kwsysProcessInstances_s
} kwsysProcessInstances;
static kwsysProcessInstances kwsysProcesses;
-/* The old SIGCHLD handler. */
+/* The old SIGCHLD / SIGINT / SIGTERM handlers. */
static struct sigaction kwsysProcessesOldSigChldAction;
+static struct sigaction kwsysProcessesOldSigIntAction;
+static struct sigaction kwsysProcessesOldSigTermAction;
/*--------------------------------------------------------------------------*/
static void kwsysProcessesUpdate(kwsysProcessInstances* newProcesses)
{
- /* Block SIGCHLD while we update the set of pipes to check.
+ /* Block signals while we update the set of pipes to check.
TODO: sigprocmask is undefined for threaded apps. See
pthread_sigmask. */
sigset_t newset;
sigset_t oldset;
sigemptyset(&newset);
sigaddset(&newset, SIGCHLD);
+ sigaddset(&newset, SIGINT);
+ sigaddset(&newset, SIGTERM);
sigprocmask(SIG_BLOCK, &newset, &oldset);
/* Store the new set in that seen by the signal handler. */
@@ -2686,21 +2855,36 @@ static int kwsysProcessesAdd(kwsysProcess* cp)
{
/* Install our handler for SIGCHLD. Repeat call until it is not
interrupted. */
- struct sigaction newSigChldAction;
- memset(&newSigChldAction, 0, sizeof(struct sigaction));
+ struct sigaction newSigAction;
+ memset(&newSigAction, 0, sizeof(struct sigaction));
#if KWSYSPE_USE_SIGINFO
- newSigChldAction.sa_sigaction = kwsysProcessesSignalHandler;
- newSigChldAction.sa_flags = SA_NOCLDSTOP | SA_SIGINFO;
+ newSigAction.sa_sigaction = kwsysProcessesSignalHandler;
+ newSigAction.sa_flags = SA_NOCLDSTOP | SA_SIGINFO;
# ifdef SA_RESTART
- newSigChldAction.sa_flags |= SA_RESTART;
+ newSigAction.sa_flags |= SA_RESTART;
# endif
#else
- newSigChldAction.sa_handler = kwsysProcessesSignalHandler;
- newSigChldAction.sa_flags = SA_NOCLDSTOP;
+ newSigAction.sa_handler = kwsysProcessesSignalHandler;
+ newSigAction.sa_flags = SA_NOCLDSTOP;
#endif
- while((sigaction(SIGCHLD, &newSigChldAction,
+ sigemptyset(&newSigAction.sa_mask);
+ while((sigaction(SIGCHLD, &newSigAction,
&kwsysProcessesOldSigChldAction) < 0) &&
(errno == EINTR));
+
+ /* Install our handler for SIGINT / SIGTERM. Repeat call until
+ it is not interrupted. */
+ sigemptyset(&newSigAction.sa_mask);
+ sigaddset(&newSigAction.sa_mask, SIGTERM);
+ while((sigaction(SIGINT, &newSigAction,
+ &kwsysProcessesOldSigIntAction) < 0) &&
+ (errno == EINTR));
+
+ sigemptyset(&newSigAction.sa_mask);
+ sigaddset(&newSigAction.sa_mask, SIGINT);
+ while((sigaction(SIGTERM, &newSigAction,
+ &kwsysProcessesOldSigIntAction) < 0) &&
+ (errno == EINTR));
}
}
@@ -2734,10 +2918,14 @@ static void kwsysProcessesRemove(kwsysProcess* cp)
/* If this was the last process, disable the signal handler. */
if(newProcesses.Count == 0)
{
- /* Restore the SIGCHLD handler. Repeat call until it is not
+ /* Restore the signal handlers. Repeat call until it is not
interrupted. */
while((sigaction(SIGCHLD, &kwsysProcessesOldSigChldAction, 0) < 0) &&
(errno == EINTR));
+ while((sigaction(SIGINT, &kwsysProcessesOldSigIntAction, 0) < 0) &&
+ (errno == EINTR));
+ while((sigaction(SIGTERM, &kwsysProcessesOldSigTermAction, 0) < 0) &&
+ (errno == EINTR));
/* Free the table of process pointers since it is now empty.
This is safe because the signal handler has been removed. */
@@ -2763,39 +2951,108 @@ static void kwsysProcessesSignalHandler(int signum
#endif
)
{
- (void)signum;
+ int i, j, procStatus, old_errno = errno;
#if KWSYSPE_USE_SIGINFO
(void)info;
(void)ucontext;
#endif
/* Signal all process objects that a child has terminated. */
- {
- int i;
- for(i=0; i < kwsysProcesses.Count; ++i)
+ switch(signum)
{
- /* Set the pipe in a signalled state. */
- char buf = 1;
- kwsysProcess* cp = kwsysProcesses.Processes[i];
- kwsysProcess_ssize_t status=
- read(cp->PipeReadEnds[KWSYSPE_PIPE_SIGNAL], &buf, 1);
- (void)status;
- status=write(cp->SignalPipe, &buf, 1);
- (void)status;
+ case SIGCHLD:
+ for(i=0; i < kwsysProcesses.Count; ++i)
+ {
+ /* Set the pipe in a signalled state. */
+ char buf = 1;
+ kwsysProcess* cp = kwsysProcesses.Processes[i];
+ kwsysProcess_ssize_t pipeStatus=
+ read(cp->PipeReadEnds[KWSYSPE_PIPE_SIGNAL], &buf, 1);
+ (void)pipeStatus;
+ pipeStatus=write(cp->SignalPipe, &buf, 1);
+ (void)pipeStatus;
+ }
+ break;
+ case SIGINT:
+ case SIGTERM:
+ /* Signal child processes that are running in new process groups. */
+ for(i=0; i < kwsysProcesses.Count; ++i)
+ {
+ kwsysProcess* cp = kwsysProcesses.Processes[i];
+ /* Check Killed to avoid data race condition when killing.
+ Check State to avoid data race condition in kwsysProcessCleanup
+ when there is an error (it leaves a reaped PID). */
+ if(cp->CreateProcessGroup && !cp->Killed &&
+ cp->State != kwsysProcess_State_Error && cp->ForkPIDs)
+ {
+ for(j=0; j < cp->NumberOfCommands; ++j)
+ {
+ /* Make sure the PID is still valid. */
+ if(cp->ForkPIDs[j])
+ {
+ /* The user created a process group for this process. The group ID
+ is the process ID for the original process in the group. */
+ kill(-cp->ForkPIDs[j], SIGINT);
+ }
+ }
+ }
+ }
+
+ /* Wait for all processes to terminate. */
+ while(wait(&procStatus) >= 0 || errno != ECHILD)
+ {
+ }
+
+ /* Terminate the process, which is now in an inconsistent state
+ because we reaped all the PIDs that it may have been reaping
+ or may have reaped in the future. Reraise the signal so that
+ the proper exit code is returned. */
+ {
+ /* Install default signal handler. */
+ struct sigaction defSigAction;
+ sigset_t unblockSet;
+ memset(&defSigAction, 0, sizeof(defSigAction));
+ defSigAction.sa_handler = SIG_DFL;
+ sigemptyset(&defSigAction.sa_mask);
+ while((sigaction(signum, &defSigAction, 0) < 0) &&
+ (errno == EINTR));
+ /* Unmask the signal. */
+ sigemptyset(&unblockSet);
+ sigaddset(&unblockSet, signum);
+ sigprocmask(SIG_UNBLOCK, &unblockSet, 0);
+ /* Raise the signal again. */
+ raise(signum);
+ /* We shouldn't get here... but if we do... */
+ _exit(1);
+ }
+ /* break omitted to silence unreachable code clang compiler warning. */
}
- }
#if !KWSYSPE_USE_SIGINFO
- /* Re-Install our handler for SIGCHLD. Repeat call until it is not
- interrupted. */
+ /* Re-Install our handler. Repeat call until it is not interrupted. */
{
- struct sigaction newSigChldAction;
- memset(&newSigChldAction, 0, sizeof(struct sigaction));
+ struct sigaction newSigAction;
+ struct sigaction &oldSigAction;
+ memset(&newSigAction, 0, sizeof(struct sigaction));
newSigChldAction.sa_handler = kwsysProcessesSignalHandler;
newSigChldAction.sa_flags = SA_NOCLDSTOP;
- while((sigaction(SIGCHLD, &newSigChldAction,
- &kwsysProcessesOldSigChldAction) < 0) &&
+ sigemptyset(&newSigAction.sa_mask);
+ switch(signum)
+ {
+ case SIGCHLD: oldSigAction = &kwsysProcessesOldSigChldAction; break;
+ case SIGINT:
+ sigaddset(&newSigAction.sa_mask, SIGTERM);
+ oldSigAction = &kwsysProcessesOldSigIntAction; break;
+ case SIGTERM:
+ sigaddset(&newSigAction.sa_mask, SIGINT);
+ oldSigAction = &kwsysProcessesOldSigTermAction; break;
+ default: return 0;
+ }
+ while((sigaction(signum, &newSigAction,
+ oldSigAction) < 0) &&
(errno == EINTR));
}
#endif
+
+ errno = old_errno;
}
diff --git a/Source/kwsys/ProcessWin32.c b/Source/kwsys/ProcessWin32.c
index a7dd2ca..1f8749f 100644
--- a/Source/kwsys/ProcessWin32.c
+++ b/Source/kwsys/ProcessWin32.c
@@ -109,14 +109,15 @@ static DWORD WINAPI kwsysProcessPipeThreadWake(LPVOID ptd);
static void kwsysProcessPipeThreadWakePipe(kwsysProcess* cp,
kwsysProcessPipeData* td);
static int kwsysProcessInitialize(kwsysProcess* cp);
-static int kwsysProcessCreate(kwsysProcess* cp, int index,
- kwsysProcessCreateInformation* si);
+static DWORD kwsysProcessCreate(kwsysProcess* cp, int index,
+ kwsysProcessCreateInformation* si);
static void kwsysProcessDestroy(kwsysProcess* cp, int event);
-static int kwsysProcessSetupOutputPipeFile(PHANDLE handle, const char* name);
+static DWORD kwsysProcessSetupOutputPipeFile(PHANDLE handle,
+ const char* name);
static void kwsysProcessSetupSharedPipe(DWORD nStdHandle, PHANDLE handle);
static void kwsysProcessSetupPipeNative(HANDLE native, PHANDLE handle);
static void kwsysProcessCleanupHandle(PHANDLE h);
-static void kwsysProcessCleanup(kwsysProcess* cp, int error);
+static void kwsysProcessCleanup(kwsysProcess* cp, DWORD error);
static void kwsysProcessCleanErrorMessage(kwsysProcess* cp);
static int kwsysProcessGetTimeoutTime(kwsysProcess* cp, double* userTimeout,
kwsysProcessTime* timeoutTime);
@@ -133,6 +134,13 @@ static kwsysProcessTime kwsysProcessTimeSubtract(kwsysProcessTime in1, kwsysProc
static void kwsysProcessSetExitException(kwsysProcess* cp, int code);
static void kwsysProcessKillTree(int pid);
static void kwsysProcessDisablePipeThreads(kwsysProcess* cp);
+static int kwsysProcessesInitialize(void);
+static int kwsysTryEnterCreateProcessSection(void);
+static void kwsysLeaveCreateProcessSection(void);
+static int kwsysProcessesAdd(HANDLE hProcess, DWORD dwProcessId,
+ int newProcessGroup);
+static void kwsysProcessesRemove(HANDLE hProcess);
+static BOOL WINAPI kwsysCtrlHandler(DWORD dwCtrlType);
/*--------------------------------------------------------------------------*/
/* A structure containing synchronization data for each thread. */
@@ -222,6 +230,9 @@ struct kwsysProcess_s
/* Whether to merge stdout/stderr of the child. */
int MergeOutput;
+ /* Whether to create the process in a new process group. */
+ int CreateProcessGroup;
+
/* Mutex to protect the shared index used by threads to report data. */
HANDLE SharedIndexMutex;
@@ -321,6 +332,16 @@ kwsysProcess* kwsysProcess_New(void)
/* Windows version number data. */
OSVERSIONINFO osv;
+ /* Initialize list of processes before we get any farther. It's especially
+ important that the console Ctrl handler be added BEFORE starting the
+ first process. This prevents the risk of an orphaned process being
+ started by the main thread while the default Ctrl handler is in
+ progress. */
+ if(!kwsysProcessesInitialize())
+ {
+ return 0;
+ }
+
/* Allocate a process control structure. */
cp = (kwsysProcess*)malloc(sizeof(kwsysProcess));
if(!cp)
@@ -836,6 +857,8 @@ int kwsysProcess_GetOption(kwsysProcess* cp, int optionId)
case kwsysProcess_Option_HideWindow: return cp->HideWindow;
case kwsysProcess_Option_MergeOutput: return cp->MergeOutput;
case kwsysProcess_Option_Verbatim: return cp->Verbatim;
+ case kwsysProcess_Option_CreateProcessGroup:
+ return cp->CreateProcessGroup;
default: return 0;
}
}
@@ -854,6 +877,8 @@ void kwsysProcess_SetOption(kwsysProcess* cp, int optionId, int value)
case kwsysProcess_Option_HideWindow: cp->HideWindow = value; break;
case kwsysProcess_Option_MergeOutput: cp->MergeOutput = value; break;
case kwsysProcess_Option_Verbatim: cp->Verbatim = value; break;
+ case kwsysProcess_Option_CreateProcessGroup:
+ cp->CreateProcessGroup = value; break;
default: break;
}
}
@@ -945,7 +970,7 @@ void kwsysProcess_Execute(kwsysProcess* cp)
if(!GetCurrentDirectoryW(cp->RealWorkingDirectoryLength,
cp->RealWorkingDirectory))
{
- kwsysProcessCleanup(cp, 1);
+ kwsysProcessCleanup(cp, GetLastError());
return;
}
SetCurrentDirectoryW(cp->WorkingDirectory);
@@ -957,14 +982,16 @@ void kwsysProcess_Execute(kwsysProcess* cp)
{
/* Create a handle to read a file for stdin. */
wchar_t* wstdin = kwsysEncoding_DupToWide(cp->PipeFileSTDIN);
+ DWORD error;
cp->PipeChildStd[0] =
CreateFileW(wstdin, GENERIC_READ|GENERIC_WRITE,
FILE_SHARE_READ|FILE_SHARE_WRITE,
0, OPEN_EXISTING, 0, 0);
+ error = GetLastError(); /* Check now in case free changes this. */
free(wstdin);
if(cp->PipeChildStd[0] == INVALID_HANDLE_VALUE)
{
- kwsysProcessCleanup(cp, 1);
+ kwsysProcessCleanup(cp, error);
return;
}
}
@@ -990,17 +1017,18 @@ void kwsysProcess_Execute(kwsysProcess* cp)
if(!CreatePipe(&cp->Pipe[KWSYSPE_PIPE_STDOUT].Read,
&cp->Pipe[KWSYSPE_PIPE_STDOUT].Write, 0, 0))
{
- kwsysProcessCleanup(cp, 1);
+ kwsysProcessCleanup(cp, GetLastError());
return;
}
if(cp->PipeFileSTDOUT)
{
/* Use a file for stdout. */
- if(!kwsysProcessSetupOutputPipeFile(&cp->PipeChildStd[1],
- cp->PipeFileSTDOUT))
+ DWORD error = kwsysProcessSetupOutputPipeFile(&cp->PipeChildStd[1],
+ cp->PipeFileSTDOUT);
+ if(error)
{
- kwsysProcessCleanup(cp, 1);
+ kwsysProcessCleanup(cp, error);
return;
}
}
@@ -1023,7 +1051,7 @@ void kwsysProcess_Execute(kwsysProcess* cp)
GetCurrentProcess(), &cp->PipeChildStd[1],
0, FALSE, DUPLICATE_SAME_ACCESS))
{
- kwsysProcessCleanup(cp, 1);
+ kwsysProcessCleanup(cp, GetLastError());
return;
}
}
@@ -1034,17 +1062,18 @@ void kwsysProcess_Execute(kwsysProcess* cp)
if(!CreatePipe(&cp->Pipe[KWSYSPE_PIPE_STDERR].Read,
&cp->Pipe[KWSYSPE_PIPE_STDERR].Write, 0, 0))
{
- kwsysProcessCleanup(cp, 1);
+ kwsysProcessCleanup(cp, GetLastError());
return;
}
if(cp->PipeFileSTDERR)
{
/* Use a file for stderr. */
- if(!kwsysProcessSetupOutputPipeFile(&cp->PipeChildStd[2],
- cp->PipeFileSTDERR))
+ DWORD error = kwsysProcessSetupOutputPipeFile(&cp->PipeChildStd[2],
+ cp->PipeFileSTDERR);
+ if(error)
{
- kwsysProcessCleanup(cp, 1);
+ kwsysProcessCleanup(cp, error);
return;
}
}
@@ -1067,7 +1096,7 @@ void kwsysProcess_Execute(kwsysProcess* cp)
GetCurrentProcess(), &cp->PipeChildStd[2],
0, FALSE, DUPLICATE_SAME_ACCESS))
{
- kwsysProcessCleanup(cp, 1);
+ kwsysProcessCleanup(cp, GetLastError());
return;
}
}
@@ -1106,11 +1135,12 @@ void kwsysProcess_Execute(kwsysProcess* cp)
HANDLE p[2] = {INVALID_HANDLE_VALUE, INVALID_HANDLE_VALUE};
if (!CreatePipe(&p[0], &p[1], 0, 0))
{
+ DWORD error = GetLastError();
if (nextStdInput != cp->PipeChildStd[0])
{
kwsysProcessCleanupHandle(&nextStdInput);
}
- kwsysProcessCleanup(cp, 1);
+ kwsysProcessCleanup(cp, error);
return;
}
nextStdInput = p[0];
@@ -1119,7 +1149,7 @@ void kwsysProcess_Execute(kwsysProcess* cp)
si.hStdError = cp->MergeOutput? cp->PipeChildStd[1] : cp->PipeChildStd[2];
{
- int res = kwsysProcessCreate(cp, i, &si);
+ DWORD error = kwsysProcessCreate(cp, i, &si);
/* Close our copies of pipes used between children. */
if (si.hStdInput != cp->PipeChildStd[0])
@@ -1134,7 +1164,7 @@ void kwsysProcess_Execute(kwsysProcess* cp)
{
kwsysProcessCleanupHandle(&si.hStdError);
}
- if (res)
+ if (!error)
{
cp->ProcessEvents[i+1] = cp->ProcessInformation[i].hProcess;
}
@@ -1144,7 +1174,7 @@ void kwsysProcess_Execute(kwsysProcess* cp)
{
kwsysProcessCleanupHandle(&nextStdInput);
}
- kwsysProcessCleanup(cp, 1);
+ kwsysProcessCleanup(cp, error);
return;
}
}
@@ -1460,6 +1490,52 @@ int kwsysProcess_WaitForExit(kwsysProcess* cp, double* userTimeout)
}
/*--------------------------------------------------------------------------*/
+void kwsysProcess_Interrupt(kwsysProcess* cp)
+{
+ int i;
+ /* Make sure we are executing a process. */
+ if(!cp || cp->State != kwsysProcess_State_Executing || cp->TimeoutExpired ||
+ cp->Killed)
+ {
+ KWSYSPE_DEBUG((stderr, "interrupt: child not executing\n"));
+ return;
+ }
+
+ /* Skip actually interrupting the child if it has already terminated. */
+ if(cp->Terminated)
+ {
+ KWSYSPE_DEBUG((stderr, "interrupt: child already terminated\n"));
+ return;
+ }
+
+ /* Interrupt the children. */
+ if (cp->CreateProcessGroup)
+ {
+ if(cp->ProcessInformation)
+ {
+ for(i=0; i < cp->NumberOfCommands; ++i)
+ {
+ /* Make sure the process handle isn't closed (e.g. from disowning). */
+ if(cp->ProcessInformation[i].hProcess)
+ {
+ /* The user created a process group for this process. The group ID
+ is the process ID for the original process in the group. Note
+ that we have to use Ctrl+Break: Ctrl+C is not allowed for process
+ groups. */
+ GenerateConsoleCtrlEvent(CTRL_BREAK_EVENT,
+ cp->ProcessInformation[i].dwProcessId);
+ }
+ }
+ }
+ }
+ else
+ {
+ /* No process group was created. Kill our own process group... */
+ GenerateConsoleCtrlEvent(CTRL_BREAK_EVENT, 0);
+ }
+}
+
+/*--------------------------------------------------------------------------*/
void kwsysProcess_Kill(kwsysProcess* cp)
{
int i;
@@ -1487,7 +1563,8 @@ void kwsysProcess_Kill(kwsysProcess* cp)
for(i=0; i < cp->NumberOfCommands; ++i)
{
kwsysProcessKillTree(cp->ProcessInformation[i].dwProcessId);
- // close the handle if we kill it
+ /* Remove from global list of processes and close handles. */
+ kwsysProcessesRemove(cp->ProcessInformation[i].hProcess);
kwsysProcessCleanupHandle(&cp->ProcessInformation[i].hThread);
kwsysProcessCleanupHandle(&cp->ProcessInformation[i].hProcess);
}
@@ -1686,7 +1763,7 @@ int kwsysProcessInitialize(kwsysProcess* cp)
}
/*--------------------------------------------------------------------------*/
-static int kwsysProcessCreateChildHandle(PHANDLE out, HANDLE in, int isStdIn)
+static DWORD kwsysProcessCreateChildHandle(PHANDLE out, HANDLE in, int isStdIn)
{
DWORD flags;
@@ -1697,13 +1774,19 @@ static int kwsysProcessCreateChildHandle(PHANDLE out, HANDLE in, int isStdIn)
if (flags & HANDLE_FLAG_INHERIT)
{
*out = in;
- return 1;
+ return ERROR_SUCCESS;
}
/* Create an inherited copy of this handle. */
- return DuplicateHandle(GetCurrentProcess(), in,
- GetCurrentProcess(), out,
- 0, TRUE, DUPLICATE_SAME_ACCESS);
+ if (DuplicateHandle(GetCurrentProcess(), in, GetCurrentProcess(), out,
+ 0, TRUE, DUPLICATE_SAME_ACCESS))
+ {
+ return ERROR_SUCCESS;
+ }
+ else
+ {
+ return GetLastError();
+ }
}
else
{
@@ -1719,29 +1802,46 @@ static int kwsysProcessCreateChildHandle(PHANDLE out, HANDLE in, int isStdIn)
(GENERIC_WRITE | FILE_READ_ATTRIBUTES)),
FILE_SHARE_READ|FILE_SHARE_WRITE,
&sa, OPEN_EXISTING, 0, 0);
- return *out != INVALID_HANDLE_VALUE;
+ return (*out != INVALID_HANDLE_VALUE) ? ERROR_SUCCESS : GetLastError();
}
-
}
/*--------------------------------------------------------------------------*/
-int kwsysProcessCreate(kwsysProcess* cp, int index,
- kwsysProcessCreateInformation* si)
+DWORD kwsysProcessCreate(kwsysProcess* cp, int index,
+ kwsysProcessCreateInformation* si)
{
- int res =
+ DWORD creationFlags;
+ DWORD error = ERROR_SUCCESS;
+
+ /* Check if we are currently exiting. */
+ if (!kwsysTryEnterCreateProcessSection())
+ {
+ /* The Ctrl handler is currently working on exiting our process. Rather
+ than return an error code, which could cause incorrect conclusions to be
+ reached by the caller, we simply hang. (For example, a CMake try_run
+ configure step might cause the project to configure wrong.) */
+ Sleep(INFINITE);
+ }
- /* Create inherited copies the handles. */
- kwsysProcessCreateChildHandle(&si->StartupInfo.hStdInput,
- si->hStdInput, 1) &&
- kwsysProcessCreateChildHandle(&si->StartupInfo.hStdOutput,
- si->hStdOutput, 0) &&
- kwsysProcessCreateChildHandle(&si->StartupInfo.hStdError,
- si->hStdError, 0) &&
+ /* Create the child in a suspended state so we can wait until all
+ children have been created before running any one. */
+ creationFlags = CREATE_SUSPENDED;
+ if (cp->CreateProcessGroup)
+ {
+ creationFlags |= CREATE_NEW_PROCESS_GROUP;
+ }
- /* Create the child in a suspended state so we can wait until all
- children have been created before running any one. */
- CreateProcessW(0, cp->Commands[index], 0, 0, TRUE, CREATE_SUSPENDED, 0,
- 0, &si->StartupInfo, &cp->ProcessInformation[index]);
+ /* Create inherited copies of the handles. */
+ (error = kwsysProcessCreateChildHandle(&si->StartupInfo.hStdInput,
+ si->hStdInput, 1)) ||
+ (error = kwsysProcessCreateChildHandle(&si->StartupInfo.hStdOutput,
+ si->hStdOutput, 0)) ||
+ (error = kwsysProcessCreateChildHandle(&si->StartupInfo.hStdError,
+ si->hStdError, 0)) ||
+ /* Create the process. */
+ (!CreateProcessW(0, cp->Commands[index], 0, 0, TRUE, creationFlags, 0,
+ 0, &si->StartupInfo, &cp->ProcessInformation[index]) &&
+ (error = GetLastError()));
/* Close the inherited copies of the handles. */
if (si->StartupInfo.hStdInput != si->hStdInput)
@@ -1757,7 +1857,23 @@ int kwsysProcessCreate(kwsysProcess* cp, int index,
kwsysProcessCleanupHandle(&si->StartupInfo.hStdError);
}
- return res;
+ /* Add the process to the global list of processes. */
+ if (!error &&
+ !kwsysProcessesAdd(cp->ProcessInformation[index].hProcess,
+ cp->ProcessInformation[index].dwProcessId, cp->CreateProcessGroup))
+ {
+ /* This failed for some reason. Kill the suspended process. */
+ TerminateProcess(cp->ProcessInformation[index].hProcess, 1);
+ /* And clean up... */
+ kwsysProcessCleanupHandle(&cp->ProcessInformation[index].hProcess);
+ kwsysProcessCleanupHandle(&cp->ProcessInformation[index].hThread);
+ strcpy(cp->ErrorMessage, "kwsysProcessesAdd function failed");
+ error = ERROR_NOT_ENOUGH_MEMORY; /* Most likely reason. */
+ }
+
+ /* If the console Ctrl handler is waiting for us, this will release it... */
+ kwsysLeaveCreateProcessSection();
+ return error;
}
/*--------------------------------------------------------------------------*/
@@ -1779,6 +1895,9 @@ void kwsysProcessDestroy(kwsysProcess* cp, int event)
GetExitCodeProcess(cp->ProcessInformation[index].hProcess,
&cp->CommandExitCodes[index]);
+ /* Remove from global list of processes. */
+ kwsysProcessesRemove(cp->ProcessInformation[index].hProcess);
+
/* Close the process handle for the terminated process. */
kwsysProcessCleanupHandle(&cp->ProcessInformation[index].hProcess);
@@ -1813,13 +1932,14 @@ void kwsysProcessDestroy(kwsysProcess* cp, int event)
}
/*--------------------------------------------------------------------------*/
-int kwsysProcessSetupOutputPipeFile(PHANDLE phandle, const char* name)
+DWORD kwsysProcessSetupOutputPipeFile(PHANDLE phandle, const char* name)
{
HANDLE fout;
wchar_t* wname;
+ DWORD error;
if(!name)
{
- return 1;
+ return ERROR_INVALID_PARAMETER;
}
/* Close the existing handle. */
@@ -1829,15 +1949,16 @@ int kwsysProcessSetupOutputPipeFile(PHANDLE phandle, const char* name)
wname = kwsysEncoding_DupToWide(name);
fout = CreateFileW(wname, GENERIC_WRITE, FILE_SHARE_READ, 0,
CREATE_ALWAYS, 0, 0);
+ error = GetLastError();
free(wname);
if(fout == INVALID_HANDLE_VALUE)
{
- return 0;
+ return error;
}
/* Assign the replacement handle. */
*phandle = fout;
- return 1;
+ return ERROR_SUCCESS;
}
/*--------------------------------------------------------------------------*/
@@ -1876,7 +1997,7 @@ void kwsysProcessCleanupHandle(PHANDLE h)
/*--------------------------------------------------------------------------*/
/* Close all handles created by kwsysProcess_Execute. */
-void kwsysProcessCleanup(kwsysProcess* cp, int error)
+void kwsysProcessCleanup(kwsysProcess* cp, DWORD error)
{
int i;
/* If this is an error case, report the error. */
@@ -1886,21 +2007,27 @@ void kwsysProcessCleanup(kwsysProcess* cp, int error)
if(cp->ErrorMessage[0] == 0)
{
/* Format the error message. */
- DWORD original = GetLastError();
wchar_t err_msg[KWSYSPE_PIPE_BUFFER_SIZE];
DWORD length = FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM |
- FORMAT_MESSAGE_IGNORE_INSERTS, 0, original,
+ FORMAT_MESSAGE_IGNORE_INSERTS, 0, error,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
err_msg, KWSYSPE_PIPE_BUFFER_SIZE, 0);
- WideCharToMultiByte(CP_UTF8, 0, err_msg, -1, cp->ErrorMessage,
- KWSYSPE_PIPE_BUFFER_SIZE, NULL, NULL);
if(length < 1)
{
/* FormatMessage failed. Use a default message. */
_snprintf(cp->ErrorMessage, KWSYSPE_PIPE_BUFFER_SIZE,
"Process execution failed with error 0x%X. "
"FormatMessage failed with error 0x%X",
- original, GetLastError());
+ error, GetLastError());
+ }
+ if(!WideCharToMultiByte(CP_UTF8, 0, err_msg, -1, cp->ErrorMessage,
+ KWSYSPE_PIPE_BUFFER_SIZE, NULL, NULL))
+ {
+ /* WideCharToMultiByte failed. Use a default message. */
+ _snprintf(cp->ErrorMessage, KWSYSPE_PIPE_BUFFER_SIZE,
+ "Process execution failed with error 0x%X. "
+ "WideCharToMultiByte failed with error 0x%X",
+ error, GetLastError());
}
}
@@ -1923,6 +2050,8 @@ void kwsysProcessCleanup(kwsysProcess* cp, int error)
}
for(i=0; i < cp->NumberOfCommands; ++i)
{
+ /* Remove from global list of processes and close handles. */
+ kwsysProcessesRemove(cp->ProcessInformation[i].hProcess);
kwsysProcessCleanupHandle(&cp->ProcessInformation[i].hThread);
kwsysProcessCleanupHandle(&cp->ProcessInformation[i].hProcess);
}
@@ -2659,3 +2788,230 @@ static void kwsysProcessDisablePipeThreads(kwsysProcess* cp)
ReleaseSemaphore(cp->Pipe[cp->CurrentIndex].Reader.Go, 1, 0);
}
}
+
+/*--------------------------------------------------------------------------*/
+/* Global set of executing processes for use by the Ctrl handler.
+ This global instance will be zero-initialized by the compiler.
+
+ Note that the console Ctrl handler runs on a background thread and so
+ everything it does must be thread safe. Here, we track the hProcess
+ HANDLEs directly instead of kwsysProcess instances, so that we don't have
+ to make kwsysProcess thread safe. */
+typedef struct kwsysProcessInstance_s
+{
+ HANDLE hProcess;
+ DWORD dwProcessId;
+ int NewProcessGroup; /* Whether the process was created in a new group. */
+} kwsysProcessInstance;
+
+typedef struct kwsysProcessInstances_s
+{
+ /* Whether we have initialized key fields below, like critical sections. */
+ int Initialized;
+
+ /* Ctrl handler runs on a different thread, so we must sync access. */
+ CRITICAL_SECTION Lock;
+
+ int Exiting;
+ size_t Count;
+ size_t Size;
+ kwsysProcessInstance* Processes;
+} kwsysProcessInstances;
+static kwsysProcessInstances kwsysProcesses;
+
+/*--------------------------------------------------------------------------*/
+/* Initialize critial section and set up console Ctrl handler. You MUST call
+ this before using any other kwsysProcesses* functions below. */
+static int kwsysProcessesInitialize(void)
+{
+ /* Initialize everything if not done already. */
+ if(!kwsysProcesses.Initialized)
+ {
+ InitializeCriticalSection(&kwsysProcesses.Lock);
+
+ /* Set up console ctrl handler. */
+ if(!SetConsoleCtrlHandler(kwsysCtrlHandler, TRUE))
+ {
+ return 0;
+ }
+
+ kwsysProcesses.Initialized = 1;
+ }
+ return 1;
+}
+
+/*--------------------------------------------------------------------------*/
+/* The Ctrl handler waits on the global list of processes. To prevent an
+ orphaned process, do not create a new process if the Ctrl handler is
+ already running. Do so by using this function to check if it is ok to
+ create a process. */
+static int kwsysTryEnterCreateProcessSection(void)
+{
+ /* Enter main critical section; this means creating a process and the Ctrl
+ handler are mutually exclusive. */
+ EnterCriticalSection(&kwsysProcesses.Lock);
+ /* Indicate to the caller if they can create a process. */
+ if(kwsysProcesses.Exiting)
+ {
+ LeaveCriticalSection(&kwsysProcesses.Lock);
+ return 0;
+ }
+ else
+ {
+ return 1;
+ }
+}
+
+/*--------------------------------------------------------------------------*/
+/* Matching function on successful kwsysTryEnterCreateProcessSection return.
+ Make sure you called kwsysProcessesAdd if applicable before calling this.*/
+static void kwsysLeaveCreateProcessSection(void)
+{
+ LeaveCriticalSection(&kwsysProcesses.Lock);
+}
+
+/*--------------------------------------------------------------------------*/
+/* Add new process to global process list. The Ctrl handler will wait for
+ the process to exit before it returns. Do not close the process handle
+ until after calling kwsysProcessesRemove. The newProcessGroup parameter
+ must be set if the process was created with CREATE_NEW_PROCESS_GROUP. */
+static int kwsysProcessesAdd(HANDLE hProcess, DWORD dwProcessid,
+ int newProcessGroup)
+{
+ if(!kwsysProcessesInitialize() || !hProcess ||
+ hProcess == INVALID_HANDLE_VALUE)
+ {
+ return 0;
+ }
+
+ /* Enter the critical section. */
+ EnterCriticalSection(&kwsysProcesses.Lock);
+
+ /* Make sure there is enough space for the new process handle. */
+ if(kwsysProcesses.Count == kwsysProcesses.Size)
+ {
+ size_t newSize;
+ kwsysProcessInstance *newArray;
+ /* Start with enough space for a small number of process handles
+ and double the size each time more is needed. */
+ newSize = kwsysProcesses.Size? kwsysProcesses.Size*2 : 4;
+
+ /* Try allocating the new block of memory. */
+ if(newArray = (kwsysProcessInstance*)malloc(
+ newSize*sizeof(kwsysProcessInstance)))
+ {
+ /* Copy the old process handles to the new memory. */
+ if(kwsysProcesses.Count > 0)
+ {
+ memcpy(newArray, kwsysProcesses.Processes,
+ kwsysProcesses.Count * sizeof(kwsysProcessInstance));
+ }
+ }
+ else
+ {
+ /* Failed to allocate memory for the new process handle set. */
+ LeaveCriticalSection(&kwsysProcesses.Lock);
+ return 0;
+ }
+
+ /* Free original array. */
+ free(kwsysProcesses.Processes);
+
+ /* Update original structure with new allocation. */
+ kwsysProcesses.Size = newSize;
+ kwsysProcesses.Processes = newArray;
+ }
+
+ /* Append the new process information to the set. */
+ kwsysProcesses.Processes[kwsysProcesses.Count].hProcess = hProcess;
+ kwsysProcesses.Processes[kwsysProcesses.Count].dwProcessId = dwProcessid;
+ kwsysProcesses.Processes[kwsysProcesses.Count++].NewProcessGroup =
+ newProcessGroup;
+
+ /* Leave critical section and return success. */
+ LeaveCriticalSection(&kwsysProcesses.Lock);
+
+ return 1;
+}
+
+/*--------------------------------------------------------------------------*/
+/* Removes process to global process list. */
+static void kwsysProcessesRemove(HANDLE hProcess)
+{
+ size_t i;
+
+ if (!hProcess || hProcess == INVALID_HANDLE_VALUE)
+ {
+ return;
+ }
+
+ EnterCriticalSection(&kwsysProcesses.Lock);
+
+ /* Find the given process in the set. */
+ for(i=0; i < kwsysProcesses.Count; ++i)
+ {
+ if(kwsysProcesses.Processes[i].hProcess == hProcess)
+ {
+ break;
+ }
+ }
+ if(i < kwsysProcesses.Count)
+ {
+ /* Found it! Remove the process from the set. */
+ --kwsysProcesses.Count;
+ for(; i < kwsysProcesses.Count; ++i)
+ {
+ kwsysProcesses.Processes[i] = kwsysProcesses.Processes[i+1];
+ }
+
+ /* If this was the last process, free the array. */
+ if(kwsysProcesses.Count == 0)
+ {
+ kwsysProcesses.Size = 0;
+ free(kwsysProcesses.Processes);
+ kwsysProcesses.Processes = 0;
+ }
+ }
+
+ LeaveCriticalSection(&kwsysProcesses.Lock);
+}
+
+/*--------------------------------------------------------------------------*/
+static BOOL WINAPI kwsysCtrlHandler(DWORD dwCtrlType)
+{
+ size_t i;
+ (void)dwCtrlType;
+ /* Enter critical section. */
+ EnterCriticalSection(&kwsysProcesses.Lock);
+
+ /* Set flag indicating that we are exiting. */
+ kwsysProcesses.Exiting = 1;
+
+ /* If some of our processes were created in a new process group, we must
+ manually interrupt them. They won't otherwise receive a Ctrl+C/Break. */
+ for(i=0; i < kwsysProcesses.Count; ++i)
+ {
+ if(kwsysProcesses.Processes[i].NewProcessGroup)
+ {
+ DWORD groupId = kwsysProcesses.Processes[i].dwProcessId;
+ if(groupId)
+ {
+ GenerateConsoleCtrlEvent(CTRL_BREAK_EVENT, groupId);
+ }
+ }
+ }
+
+ /* Wait for each child process to exit. This is the key step that prevents
+ us from leaving several orphaned children processes running in the
+ background when the user presses Ctrl+C. */
+ for(i=0; i < kwsysProcesses.Count; ++i)
+ {
+ WaitForSingleObject(kwsysProcesses.Processes[i].hProcess, INFINITE);
+ }
+
+ /* Leave critical section. */
+ LeaveCriticalSection(&kwsysProcesses.Lock);
+
+ /* Continue on to default Ctrl handler (which calls ExitProcess). */
+ return FALSE;
+}
diff --git a/Source/kwsys/SystemTools.cxx b/Source/kwsys/SystemTools.cxx
index 3452259..0714344 100644
--- a/Source/kwsys/SystemTools.cxx
+++ b/Source/kwsys/SystemTools.cxx
@@ -3198,8 +3198,16 @@ bool SystemTools::FileIsDirectory(const kwsys_stl::string& inName)
bool SystemTools::FileIsSymlink(const kwsys_stl::string& name)
{
#if defined( _WIN32 )
- (void)name;
- return false;
+ DWORD attr = GetFileAttributesW(
+ SystemTools::ConvertToWindowsExtendedPath(name).c_str());
+ if (attr != INVALID_FILE_ATTRIBUTES)
+ {
+ return (attr & FILE_ATTRIBUTE_REPARSE_POINT) != 0;
+ }
+ else
+ {
+ return false;
+ }
#else
struct stat fs;
if(lstat(name.c_str(), &fs) == 0)
@@ -4230,6 +4238,11 @@ SystemTools::DetectFileType(const char *filename,
return SystemTools::FileTypeUnknown;
}
+ if (SystemTools::FileIsDirectory(filename))
+ {
+ return SystemTools::FileTypeUnknown;
+ }
+
FILE *fp = Fopen(filename, "rb");
if (!fp)
{
@@ -4243,6 +4256,7 @@ SystemTools::DetectFileType(const char *filename,
fclose(fp);
if (read_length == 0)
{
+ delete [] buffer;
return SystemTools::FileTypeUnknown;
}
diff --git a/Source/kwsys/testProcess.c b/Source/kwsys/testProcess.c
index 47c3fb0..d0e20c1 100644
--- a/Source/kwsys/testProcess.c
+++ b/Source/kwsys/testProcess.c
@@ -29,26 +29,48 @@
# include <windows.h>
#else
# include <unistd.h>
+# include <signal.h>
#endif
#if defined(__BORLANDC__)
# pragma warn -8060 /* possibly incorrect assignment */
#endif
+/* Platform-specific sleep functions. */
+
#if defined(__BEOS__) && !defined(__ZETA__)
/* BeOS 5 doesn't have usleep(), but it has snooze(), which is identical. */
# include <be/kernel/OS.h>
-static inline void testProcess_usleep(unsigned int msec)
+static inline void testProcess_usleep(unsigned int usec)
+{
+ snooze(usec);
+}
+#elif defined(_WIN32)
+/* Windows can only sleep in millisecond intervals. */
+static void testProcess_usleep(unsigned int usec)
{
- snooze(msec);
+ Sleep(usec / 1000);
}
#else
# define testProcess_usleep usleep
#endif
+#if defined(_WIN32)
+static void testProcess_sleep(unsigned int sec)
+{
+ Sleep(sec*1000);
+}
+#else
+static void testProcess_sleep(unsigned int sec)
+{
+ sleep(sec);
+}
+#endif
+
int runChild(const char* cmd[], int state, int exception, int value,
int share, int output, int delay, double timeout, int poll,
- int repeat, int disown);
+ int repeat, int disown, int createNewGroup,
+ unsigned int interruptDelay);
static int test1(int argc, const char* argv[])
{
@@ -73,11 +95,7 @@ static int test3(int argc, const char* argv[])
fprintf(stderr, "Output before sleep on stderr from timeout test.\n");
fflush(stdout);
fflush(stderr);
-#if defined(_WIN32)
- Sleep(15000);
-#else
- sleep(15);
-#endif
+ testProcess_sleep(15);
fprintf(stdout, "Output after sleep on stdout from timeout test.\n");
fprintf(stderr, "Output after sleep on stderr from timeout test.\n");
return 0;
@@ -102,7 +120,7 @@ static int test4(int argc, const char* argv[])
#endif
(void)argc; (void)argv;
fprintf(stdout, "Output before crash on stdout from crash test.\n");
- fprintf(stderr, "Output before crash on stderr from crash test.\n");
+ fprintf(stderr, "Output before crash on stderr from crash test.\n");
fflush(stdout);
fflush(stderr);
assert(invalidAddress); /* Quiet Clang scan-build. */
@@ -127,7 +145,7 @@ static int test5(int argc, const char* argv[])
fflush(stdout);
fflush(stderr);
r = runChild(cmd, kwsysProcess_State_Exception,
- kwsysProcess_Exception_Fault, 1, 1, 1, 0, 15, 0, 1, 0);
+ kwsysProcess_Exception_Fault, 1, 1, 1, 0, 15, 0, 1, 0, 0, 0);
fprintf(stdout, "Output on stdout after recursive test.\n");
fprintf(stderr, "Output on stderr after recursive test.\n");
fflush(stdout);
@@ -168,11 +186,7 @@ static int test7(int argc, const char* argv[])
fflush(stdout);
fflush(stderr);
/* Sleep for 1 second. */
-#if defined(_WIN32)
- Sleep(1000);
-#else
- sleep(1);
-#endif
+ testProcess_sleep(1);
fprintf(stdout, "Output on stdout after sleep.\n");
fprintf(stderr, "Output on stderr after sleep.\n");
fflush(stdout);
@@ -196,7 +210,7 @@ static int test8(int argc, const char* argv[])
fflush(stdout);
fflush(stderr);
r = runChild(cmd, kwsysProcess_State_Disowned, kwsysProcess_Exception_None,
- 1, 1, 1, 0, 10, 0, 1, 1);
+ 1, 1, 1, 0, 10, 0, 1, 1, 0, 0);
fprintf(stdout, "Output on stdout after grandchild test.\n");
fprintf(stderr, "Output on stderr after grandchild test.\n");
fflush(stdout);
@@ -217,18 +231,137 @@ static int test8_grandchild(int argc, const char* argv[])
implemented. */
fclose(stdout);
fclose(stderr);
+ testProcess_sleep(15);
+ return 0;
+}
+
+static int test9(int argc, const char* argv[])
+{
+ /* Test Ctrl+C behavior: the root test program will send a Ctrl+C to this
+ process. Here, we start a child process that sleeps for a long time
+ while ignoring signals. The test is successful if this process waits
+ for the child to return before exiting from the Ctrl+C handler.
+
+ WARNING: This test will falsely pass if the share parameter of runChild
+ was set to 0 when invoking the test9 process. */
+ int r;
+ const char* cmd[4];
+ (void)argc;
+ cmd[0] = argv[0];
+ cmd[1] = "run";
+ cmd[2] = "109";
+ cmd[3] = 0;
+ fprintf(stdout, "Output on stdout before grandchild test.\n");
+ fprintf(stderr, "Output on stderr before grandchild test.\n");
+ fflush(stdout);
+ fflush(stderr);
+ r = runChild(cmd, kwsysProcess_State_Exited,
+ kwsysProcess_Exception_None,
+ 0, 1, 1, 0, 30, 0, 1, 0, 0, 0);
+ /* This sleep will avoid a race condition between this function exiting
+ normally and our Ctrl+C handler exiting abnormally after the process
+ exits. */
+ testProcess_sleep(1);
+ fprintf(stdout, "Output on stdout after grandchild test.\n");
+ fprintf(stderr, "Output on stderr after grandchild test.\n");
+ fflush(stdout);
+ fflush(stderr);
+ return r;
+}
+
#if defined(_WIN32)
- Sleep(15000);
+static BOOL WINAPI test9_grandchild_handler(DWORD dwCtrlType)
+{
+ /* Ignore all Ctrl+C/Break signals. We must use an actual handler function
+ instead of using SetConsoleCtrlHandler(NULL, TRUE) so that we can also
+ ignore Ctrl+Break in addition to Ctrl+C. */
+ (void)dwCtrlType;
+ return TRUE;
+}
+#endif
+
+static int test9_grandchild(int argc, const char* argv[])
+{
+ /* The grandchild just sleeps for a few seconds while ignoring signals. */
+ (void)argc; (void)argv;
+#if defined(_WIN32)
+ if(!SetConsoleCtrlHandler(test9_grandchild_handler, TRUE))
+ {
+ return 1;
+ }
#else
- sleep(15);
+ struct sigaction sa;
+ memset(&sa, 0, sizeof(sa));
+ sa.sa_handler = SIG_IGN;
+ sigemptyset(&sa.sa_mask);
+ if(sigaction(SIGINT, &sa, 0) < 0)
+ {
+ return 1;
+ }
#endif
+ fprintf(stdout, "Output on stdout from grandchild before sleep.\n");
+ fprintf(stderr, "Output on stderr from grandchild before sleep.\n");
+ fflush(stdout);
+ fflush(stderr);
+ /* Sleep for 9 seconds. */
+ testProcess_sleep(9);
+ fprintf(stdout, "Output on stdout from grandchild after sleep.\n");
+ fprintf(stderr, "Output on stderr from grandchild after sleep.\n");
+ fflush(stdout);
+ fflush(stderr);
+ return 0;
+}
+
+static int test10(int argc, const char* argv[])
+{
+ /* Test Ctrl+C behavior: the root test program will send a Ctrl+C to this
+ process. Here, we start a child process that sleeps for a long time and
+ processes signals normally. However, this grandchild is created in a new
+ process group - ensuring that Ctrl+C we receive is sent to our process
+ groups. We make sure it exits anyway. */
+ int r;
+ const char* cmd[4];
+ (void)argc;
+ cmd[0] = argv[0];
+ cmd[1] = "run";
+ cmd[2] = "110";
+ cmd[3] = 0;
+ fprintf(stdout, "Output on stdout before grandchild test.\n");
+ fprintf(stderr, "Output on stderr before grandchild test.\n");
+ fflush(stdout);
+ fflush(stderr);
+ r = runChild(cmd, kwsysProcess_State_Exception,
+ kwsysProcess_Exception_Interrupt,
+ 0, 1, 1, 0, 30, 0, 1, 0, 1, 0);
+ fprintf(stdout, "Output on stdout after grandchild test.\n");
+ fprintf(stderr, "Output on stderr after grandchild test.\n");
+ fflush(stdout);
+ fflush(stderr);
+ return r;
+}
+
+static int test10_grandchild(int argc, const char* argv[])
+{
+ /* The grandchild just sleeps for a few seconds and handles signals. */
+ (void)argc; (void)argv;
+ fprintf(stdout, "Output on stdout from grandchild before sleep.\n");
+ fprintf(stderr, "Output on stderr from grandchild before sleep.\n");
+ fflush(stdout);
+ fflush(stderr);
+ /* Sleep for 6 seconds. */
+ testProcess_sleep(6);
+ fprintf(stdout, "Output on stdout from grandchild after sleep.\n");
+ fprintf(stderr, "Output on stderr from grandchild after sleep.\n");
+ fflush(stdout);
+ fflush(stderr);
return 0;
}
static int runChild2(kwsysProcess* kp,
const char* cmd[], int state, int exception, int value,
int share, int output, int delay, double timeout,
- int poll, int disown)
+ int poll, int disown, int createNewGroup,
+ unsigned int interruptDelay)
{
int result = 0;
char* data = 0;
@@ -249,6 +382,10 @@ static int runChild2(kwsysProcess* kp,
{
kwsysProcess_SetOption(kp, kwsysProcess_Option_Detach, 1);
}
+ if(createNewGroup)
+ {
+ kwsysProcess_SetOption(kp, kwsysProcess_Option_CreateProcessGroup, 1);
+ }
kwsysProcess_Execute(kp);
if(poll)
@@ -256,6 +393,12 @@ static int runChild2(kwsysProcess* kp,
pUserTimeout = &userTimeout;
}
+ if(interruptDelay)
+ {
+ testProcess_sleep(interruptDelay);
+ kwsysProcess_Interrupt(kp);
+ }
+
if(!share && !disown)
{
int p;
@@ -286,17 +429,13 @@ static int runChild2(kwsysProcess* kp,
if(poll)
{
/* Delay to avoid busy loop during polling. */
-#if defined(_WIN32)
- Sleep(100);
-#else
testProcess_usleep(100000);
-#endif
}
if(delay)
{
/* Purposely sleeping only on Win32 to let pipe fill up. */
#if defined(_WIN32)
- Sleep(100);
+ testProcess_usleep(100000);
#endif
}
}
@@ -337,7 +476,7 @@ static int runChild2(kwsysProcess* kp,
printf("Error in administrating child process: [%s]\n",
kwsysProcess_GetErrorString(kp)); break;
};
-
+
if(result)
{
if(exception != kwsysProcess_GetExitException(kp))
@@ -353,7 +492,7 @@ static int runChild2(kwsysProcess* kp,
value, kwsysProcess_GetExitValue(kp));
}
}
-
+
if(kwsysProcess_GetState(kp) != state)
{
fprintf(stderr, "Mismatch in state. "
@@ -374,9 +513,37 @@ static int runChild2(kwsysProcess* kp,
return result;
}
+/**
+ * Runs a child process and blocks until it returns. Arguments as follows:
+ *
+ * cmd = Command line to run.
+ * state = Expected return value of kwsysProcess_GetState after exit.
+ * exception = Expected return value of kwsysProcess_GetExitException.
+ * value = Expected return value of kwsysProcess_GetExitValue.
+ * share = Whether to share stdout/stderr child pipes with our pipes
+ * by way of kwsysProcess_SetPipeShared. If false, new pipes
+ * are created.
+ * output = If !share && !disown, whether to write the child's stdout
+ * and stderr output to our stdout.
+ * delay = If !share && !disown, adds an additional short delay to
+ * the pipe loop to allow the pipes to fill up; Windows only.
+ * timeout = Non-zero to sets a timeout in seconds via
+ * kwsysProcess_SetTimeout.
+ * poll = If !share && !disown, we count the number of 0.1 second
+ * intervals where the child pipes had no new data. We fail
+ * if not in the bounds of MINPOLL/MAXPOLL.
+ * repeat = Number of times to run the process.
+ * disown = If set, the process is disowned.
+ * createNewGroup = If set, the process is created in a new process group.
+ * interruptDelay = If non-zero, number of seconds to delay before
+ * interrupting the process. Note that this delay will occur
+ * BEFORE any reading/polling of pipes occurs and before any
+ * detachment occurs.
+ */
int runChild(const char* cmd[], int state, int exception, int value,
int share, int output, int delay, double timeout,
- int poll, int repeat, int disown)
+ int poll, int repeat, int disown, int createNewGroup,
+ unsigned int interruptDelay)
{
int result = 1;
kwsysProcess* kp = kwsysProcess_New();
@@ -388,7 +555,8 @@ int runChild(const char* cmd[], int state, int exception, int value,
while(repeat-- > 0)
{
result = runChild2(kp, cmd, state, exception, value, share,
- output, delay, timeout, poll, disown);
+ output, delay, timeout, poll, disown, createNewGroup,
+ interruptDelay);
}
kwsysProcess_Delete(kp);
return result;
@@ -435,7 +603,7 @@ int main(int argc, const char* argv[])
n = atoi(argv[2]);
}
/* Check arguments. */
- if(((n >= 1 && n <= 8) || n == 108) && argc == 3)
+ if(((n >= 1 && n <= 10) || n == 108 || n == 109 || n == 110) && argc == 3)
{
/* This is the child process for a requested test number. */
switch (n)
@@ -448,15 +616,19 @@ int main(int argc, const char* argv[])
case 6: test6(argc, argv); return 0;
case 7: return test7(argc, argv);
case 8: return test8(argc, argv);
+ case 9: return test9(argc, argv);
+ case 10: return test10(argc, argv);
case 108: return test8_grandchild(argc, argv);
+ case 109: return test9_grandchild(argc, argv);
+ case 110: return test10_grandchild(argc, argv);
}
fprintf(stderr, "Invalid test number %d.\n", n);
return 1;
}
- else if(n >= 1 && n <= 8)
+ else if(n >= 1 && n <= 10)
{
/* This is the parent process for a requested test number. */
- int states[8] =
+ int states[10] =
{
kwsysProcess_State_Exited,
kwsysProcess_State_Exited,
@@ -465,9 +637,11 @@ int main(int argc, const char* argv[])
kwsysProcess_State_Exited,
kwsysProcess_State_Expired,
kwsysProcess_State_Exited,
- kwsysProcess_State_Exited
+ kwsysProcess_State_Exited,
+ kwsysProcess_State_Expired, /* Ctrl+C handler test */
+ kwsysProcess_State_Exception /* Process group test */
};
- int exceptions[8] =
+ int exceptions[10] =
{
kwsysProcess_Exception_None,
kwsysProcess_Exception_None,
@@ -476,14 +650,19 @@ int main(int argc, const char* argv[])
kwsysProcess_Exception_None,
kwsysProcess_Exception_None,
kwsysProcess_Exception_None,
- kwsysProcess_Exception_None
+ kwsysProcess_Exception_None,
+ kwsysProcess_Exception_None,
+ kwsysProcess_Exception_Interrupt
};
- int values[8] = {0, 123, 1, 1, 0, 0, 0, 0};
- int outputs[8] = {1, 1, 1, 1, 1, 0, 1, 1};
- int delays[8] = {0, 0, 0, 0, 0, 1, 0, 0};
- double timeouts[8] = {10, 10, 10, 30, 30, 10, -1, 10};
- int polls[8] = {0, 0, 0, 0, 0, 0, 1, 0};
- int repeat[8] = {2, 1, 1, 1, 1, 1, 1, 1};
+ int values[10] = {0, 123, 1, 1, 0, 0, 0, 0, 1, 1};
+ int shares[10] = {0, 0, 0, 0, 0, 0, 0, 0, 1, 1};
+ int outputs[10] = {1, 1, 1, 1, 1, 0, 1, 1, 1, 1};
+ int delays[10] = {0, 0, 0, 0, 0, 1, 0, 0, 0, 0};
+ double timeouts[10] = {10, 10, 10, 30, 30, 10, -1, 10, 6, 4};
+ int polls[10] = {0, 0, 0, 0, 0, 0, 1, 0, 0, 0};
+ int repeat[10] = {2, 1, 1, 1, 1, 1, 1, 1, 1, 1};
+ int createNewGroups[10] = {0, 0, 0, 0, 0, 0, 0, 0, 1, 1};
+ unsigned int interruptDelays[10] = {0, 0, 0, 0, 0, 0, 0, 0, 3, 2};
int r;
const char* cmd[4];
#ifdef _WIN32
@@ -515,9 +694,10 @@ int main(int argc, const char* argv[])
fprintf(stderr, "Output on stderr before test %d.\n", n);
fflush(stdout);
fflush(stderr);
- r = runChild(cmd, states[n-1], exceptions[n-1], values[n-1], 0,
+ r = runChild(cmd, states[n-1], exceptions[n-1], values[n-1], shares[n-1],
outputs[n-1], delays[n-1], timeouts[n-1],
- polls[n-1], repeat[n-1], 0);
+ polls[n-1], repeat[n-1], 0, createNewGroups[n-1],
+ interruptDelays[n-1]);
fprintf(stdout, "Output on stdout after test %d.\n", n);
fprintf(stderr, "Output on stderr after test %d.\n", n);
fflush(stdout);
@@ -536,7 +716,8 @@ int main(int argc, const char* argv[])
int exception = kwsysProcess_Exception_None;
int value = 0;
double timeout = 0;
- int r = runChild(cmd, state, exception, value, 0, 1, 0, timeout, 0, 1, 0);
+ int r = runChild(cmd, state, exception, value, 0, 1, 0, timeout,
+ 0, 1, 0, 0, 0);
return r;
}
else
diff --git a/Source/kwsys/testSystemTools.cxx b/Source/kwsys/testSystemTools.cxx
index 15d8eab..7b5c025 100644
--- a/Source/kwsys/testSystemTools.cxx
+++ b/Source/kwsys/testSystemTools.cxx
@@ -98,6 +98,10 @@ static bool CheckEscapeChars(kwsys_stl::string input,
static bool CheckFileOperations()
{
bool res = true;
+ const kwsys_stl::string testNonExistingFile(TEST_SYSTEMTOOLS_SOURCE_DIR
+ "/testSystemToolsNonExistingFile");
+ const kwsys_stl::string testDotFile(TEST_SYSTEMTOOLS_SOURCE_DIR
+ "/.");
const kwsys_stl::string testBinFile(TEST_SYSTEMTOOLS_SOURCE_DIR
"/testSystemTools.bin");
const kwsys_stl::string testTxtFile(TEST_SYSTEMTOOLS_SOURCE_DIR
@@ -106,6 +110,24 @@ static bool CheckFileOperations()
"/testSystemToolsNewDir");
const kwsys_stl::string testNewFile(testNewDir + "/testNewFile.txt");
+ if (kwsys::SystemTools::DetectFileType(testNonExistingFile.c_str()) !=
+ kwsys::SystemTools::FileTypeUnknown)
+ {
+ kwsys_ios::cerr
+ << "Problem with DetectFileType - failed to detect type of: "
+ << testNonExistingFile << kwsys_ios::endl;
+ res = false;
+ }
+
+ if (kwsys::SystemTools::DetectFileType(testDotFile.c_str()) !=
+ kwsys::SystemTools::FileTypeUnknown)
+ {
+ kwsys_ios::cerr
+ << "Problem with DetectFileType - failed to detect type of: "
+ << testDotFile << kwsys_ios::endl;
+ res = false;
+ }
+
if (kwsys::SystemTools::DetectFileType(testBinFile.c_str()) !=
kwsys::SystemTools::FileTypeBinary)
{
diff --git a/Tests/CMakeLists.txt b/Tests/CMakeLists.txt
index d95a5f4..5781a9e 100644
--- a/Tests/CMakeLists.txt
+++ b/Tests/CMakeLists.txt
@@ -3008,17 +3008,30 @@ ${CMake_BINARY_DIR}/bin/cmake -DDIR=dev -P ${CMake_SOURCE_DIR}/Utilities/Release
if(JNI_H AND EXISTS "${JNI_H}") # in case jni.h is a broken symlink
file(READ "${JNI_H}" JNI_FILE)
if("${JNI_FILE}" MATCHES "JDK1_2")
- add_test(Java ${CMAKE_CTEST_COMMAND}
+ add_test(Java.Jar ${CMAKE_CTEST_COMMAND}
--build-and-test
"${CMake_SOURCE_DIR}/Tests/Java"
- "${CMake_BINARY_DIR}/Tests/Java"
+ "${CMake_BINARY_DIR}/Tests/JavaJar"
${build_generator_args}
--build-project hello
+ --build-target hello
--build-two-config
- --build-run-dir "${CMake_BINARY_DIR}/Tests/Java/"
+ --build-run-dir "${CMake_BINARY_DIR}/Tests/JavaJar/"
--build-options ${build_options}
--test-command ${JAVA_RUNTIME} -classpath hello.jar HelloWorld)
- list(APPEND TEST_BUILD_DIRS "${CMake_BINARY_DIR}/Tests/Java")
+ list(APPEND TEST_BUILD_DIRS "${CMake_BINARY_DIR}/Tests/JavaJar")
+ add_test(Java.JarSourceList ${CMAKE_CTEST_COMMAND}
+ --build-and-test
+ "${CMake_SOURCE_DIR}/Tests/Java"
+ "${CMake_BINARY_DIR}/Tests/JavaJarSourceList"
+ ${build_generator_args}
+ --build-project hello
+ --build-target hello2
+ --build-two-config
+ --build-run-dir "${CMake_BINARY_DIR}/Tests/JavaJarSourceList/"
+ --build-options ${build_options}
+ --test-command ${JAVA_RUNTIME} -classpath hello2.jar HelloWorld)
+ list(APPEND TEST_BUILD_DIRS "${CMake_BINARY_DIR}/Tests/JavaJarSourceList")
endif()
endif()
endif()
diff --git a/Tests/CTestTest2/test.cmake.in b/Tests/CTestTest2/test.cmake.in
index 852bb6b..825b957 100644
--- a/Tests/CTestTest2/test.cmake.in
+++ b/Tests/CTestTest2/test.cmake.in
@@ -39,14 +39,19 @@ CMAKE_CXX_COMPILER_ARG1:STRING=@CMAKE_CXX_COMPILER_ARG1@
CTEST_TEST_KWSYS:BOOL=ON
")
+set(test_exclude
+ kwsys.testProcess-10
+ )
+
CTEST_START(Experimental)
#CTEST_UPDATE(SOURCE "${CTEST_SOURCE_DIRECTORY}" RETURN_VALUE res)
CTEST_CONFIGURE(BUILD "${CTEST_BINARY_DIRECTORY}" RETURN_VALUE res)
+CTEST_READ_CUSTOM_FILES(${CTEST_BINARY_DIRECTORY})
CTEST_BUILD(BUILD "${CTEST_BINARY_DIRECTORY}" RETURN_VALUE res)
-CTEST_TEST(BUILD "${CTEST_BINARY_DIRECTORY}" RETURN_VALUE res START 1 END 5 STRIDE 2)
-CTEST_TEST(BUILD "${CTEST_BINARY_DIRECTORY}" RETURN_VALUE res START 7 STRIDE 2 SUBMIT_INDEX 1)
-CTEST_TEST(BUILD "${CTEST_BINARY_DIRECTORY}" RETURN_VALUE res START 2 END 4 STRIDE 2 SUBMIT_INDEX 2)
-CTEST_TEST(BUILD "${CTEST_BINARY_DIRECTORY}" RETURN_VALUE res START 6 STRIDE 2 SUBMIT_INDEX 3)
+CTEST_TEST(BUILD "${CTEST_BINARY_DIRECTORY}" RETURN_VALUE res EXCLUDE ${test_exclude} START 1 END 5 STRIDE 2)
+CTEST_TEST(BUILD "${CTEST_BINARY_DIRECTORY}" RETURN_VALUE res EXCLUDE ${test_exclude} START 7 STRIDE 2 SUBMIT_INDEX 1)
+CTEST_TEST(BUILD "${CTEST_BINARY_DIRECTORY}" RETURN_VALUE res EXCLUDE ${test_exclude} START 2 END 4 STRIDE 2 SUBMIT_INDEX 2)
+CTEST_TEST(BUILD "${CTEST_BINARY_DIRECTORY}" RETURN_VALUE res EXCLUDE ${test_exclude} START 6 STRIDE 2 SUBMIT_INDEX 3)
CTEST_MEMCHECK(BUILD "${CTEST_BINARY_DIRECTORY}" RETURN_VALUE res STRIDE 1.5)
CTEST_COVERAGE(BUILD "${CTEST_BINARY_DIRECTORY}" RETURN_VALUE res)
diff --git a/Tests/Java/CMakeLists.txt b/Tests/Java/CMakeLists.txt
index 6a69a24..e1bcf3c 100644
--- a/Tests/Java/CMakeLists.txt
+++ b/Tests/Java/CMakeLists.txt
@@ -7,3 +7,7 @@ find_package(Java COMPONENTS Development)
include (UseJava)
add_jar(hello A.java HelloWorld.java)
+
+# use listing file to specify sources
+file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/java_fileslist "A.java\nHelloWorld.java\n")
+add_jar(hello2 @${CMAKE_CURRENT_BINARY_DIR}/java_fileslist)
diff --git a/Tests/RunCMake/CMP0064/CMP0064-NEW.cmake b/Tests/RunCMake/CMP0064/CMP0064-NEW.cmake
new file mode 100644
index 0000000..cdf50e9
--- /dev/null
+++ b/Tests/RunCMake/CMP0064/CMP0064-NEW.cmake
@@ -0,0 +1,5 @@
+cmake_policy(SET CMP0064 NEW)
+
+if(NOT TEST TestThatDoesNotExist)
+ message(STATUS "if NOT TestThatDoesNotExist is true")
+endif()
diff --git a/Tests/RunCMake/CMP0064/CMP0064-OLD.cmake b/Tests/RunCMake/CMP0064/CMP0064-OLD.cmake
new file mode 100644
index 0000000..bffd3f3
--- /dev/null
+++ b/Tests/RunCMake/CMP0064/CMP0064-OLD.cmake
@@ -0,0 +1,7 @@
+cmake_policy(SET CMP0064 OLD)
+
+if(TEST)
+ message(FATAL_ERROR "TEST was not recognized to be undefined")
+else()
+ message(STATUS "TEST was treated as a variable")
+endif()
diff --git a/Tests/RunCMake/CMP0064/CMP0064-WARN.cmake b/Tests/RunCMake/CMP0064/CMP0064-WARN.cmake
new file mode 100644
index 0000000..bffd3f3
--- /dev/null
+++ b/Tests/RunCMake/CMP0064/CMP0064-WARN.cmake
@@ -0,0 +1,7 @@
+cmake_policy(SET CMP0064 OLD)
+
+if(TEST)
+ message(FATAL_ERROR "TEST was not recognized to be undefined")
+else()
+ message(STATUS "TEST was treated as a variable")
+endif()
diff --git a/Tests/RunCMake/CMP0064/CMakeLists.txt b/Tests/RunCMake/CMP0064/CMakeLists.txt
new file mode 100644
index 0000000..74b3ff8
--- /dev/null
+++ b/Tests/RunCMake/CMP0064/CMakeLists.txt
@@ -0,0 +1,3 @@
+cmake_minimum_required(VERSION 3.3)
+project(${RunCMake_TEST} NONE)
+include(${RunCMake_TEST}.cmake)
diff --git a/Tests/RunCMake/CMP0064/RunCMakeTest.cmake b/Tests/RunCMake/CMP0064/RunCMakeTest.cmake
new file mode 100644
index 0000000..26e0a91
--- /dev/null
+++ b/Tests/RunCMake/CMP0064/RunCMakeTest.cmake
@@ -0,0 +1,5 @@
+include(RunCMake)
+
+run_cmake(CMP0064-OLD)
+run_cmake(CMP0064-WARN)
+run_cmake(CMP0064-NEW)
diff --git a/Tests/RunCMake/CMakeLists.txt b/Tests/RunCMake/CMakeLists.txt
index 607e799..78df603 100644
--- a/Tests/RunCMake/CMakeLists.txt
+++ b/Tests/RunCMake/CMakeLists.txt
@@ -103,6 +103,7 @@ add_RunCMake_test(CMP0055)
add_RunCMake_test(CMP0057)
add_RunCMake_test(CMP0059)
add_RunCMake_test(CMP0060)
+add_RunCMake_test(CMP0064)
if(CMAKE_GENERATOR MATCHES "Make")
add_RunCMake_test(Make)
endif()
@@ -161,6 +162,7 @@ add_RunCMake_test(Syntax)
add_RunCMake_test(add_custom_command)
add_RunCMake_test(add_custom_target)
add_RunCMake_test(add_dependencies)
+add_RunCMake_test(add_subdirectory)
add_RunCMake_test(build_command)
add_RunCMake_test(execute_process)
add_RunCMake_test(export)
diff --git a/Tests/RunCMake/add_subdirectory/CMakeLists.txt b/Tests/RunCMake/add_subdirectory/CMakeLists.txt
new file mode 100644
index 0000000..18dfd26
--- /dev/null
+++ b/Tests/RunCMake/add_subdirectory/CMakeLists.txt
@@ -0,0 +1,3 @@
+cmake_minimum_required(VERSION 3.2)
+project(${RunCMake_TEST} NONE)
+include(${RunCMake_TEST}.cmake)
diff --git a/Tests/RunCMake/add_subdirectory/DoesNotExist-result.txt b/Tests/RunCMake/add_subdirectory/DoesNotExist-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/add_subdirectory/DoesNotExist-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/add_subdirectory/DoesNotExist-stderr.txt b/Tests/RunCMake/add_subdirectory/DoesNotExist-stderr.txt
new file mode 100644
index 0000000..369a956
--- /dev/null
+++ b/Tests/RunCMake/add_subdirectory/DoesNotExist-stderr.txt
@@ -0,0 +1,5 @@
+^CMake Error at DoesNotExist.cmake:1 \(add_subdirectory\):
+ add_subdirectory given source "DoesNotExist" which is not an existing
+ directory.
+Call Stack \(most recent call first\):
+ CMakeLists.txt:3 \(include\)$
diff --git a/Tests/RunCMake/add_subdirectory/DoesNotExist.cmake b/Tests/RunCMake/add_subdirectory/DoesNotExist.cmake
new file mode 100644
index 0000000..fe2945c
--- /dev/null
+++ b/Tests/RunCMake/add_subdirectory/DoesNotExist.cmake
@@ -0,0 +1 @@
+add_subdirectory(DoesNotExist)
diff --git a/Tests/RunCMake/add_subdirectory/Missing-result.txt b/Tests/RunCMake/add_subdirectory/Missing-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/add_subdirectory/Missing-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/add_subdirectory/Missing-stderr.txt b/Tests/RunCMake/add_subdirectory/Missing-stderr.txt
new file mode 100644
index 0000000..aba0675
--- /dev/null
+++ b/Tests/RunCMake/add_subdirectory/Missing-stderr.txt
@@ -0,0 +1,8 @@
+^CMake Error at Missing.cmake:1 \(add_subdirectory\):
+ The source directory
+
+ .*/Tests/RunCMake/add_subdirectory/Missing
+
+ does not contain a CMakeLists.txt file.
+Call Stack \(most recent call first\):
+ CMakeLists.txt:3 \(include\)$
diff --git a/Tests/RunCMake/add_subdirectory/Missing.cmake b/Tests/RunCMake/add_subdirectory/Missing.cmake
new file mode 100644
index 0000000..0e68927
--- /dev/null
+++ b/Tests/RunCMake/add_subdirectory/Missing.cmake
@@ -0,0 +1 @@
+add_subdirectory(Missing)
diff --git a/Tests/RunCMake/add_subdirectory/Missing/Missing.txt b/Tests/RunCMake/add_subdirectory/Missing/Missing.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/Tests/RunCMake/add_subdirectory/Missing/Missing.txt
diff --git a/Tests/RunCMake/add_subdirectory/RunCMakeTest.cmake b/Tests/RunCMake/add_subdirectory/RunCMakeTest.cmake
new file mode 100644
index 0000000..a3ddec8
--- /dev/null
+++ b/Tests/RunCMake/add_subdirectory/RunCMakeTest.cmake
@@ -0,0 +1,4 @@
+include(RunCMake)
+
+run_cmake(DoesNotExist)
+run_cmake(Missing)
diff --git a/Tests/RunCMake/if/RunCMakeTest.cmake b/Tests/RunCMake/if/RunCMakeTest.cmake
index 2c0c4d7..3f4d2a2 100644
--- a/Tests/RunCMake/if/RunCMakeTest.cmake
+++ b/Tests/RunCMake/if/RunCMakeTest.cmake
@@ -4,3 +4,6 @@ run_cmake(InvalidArgument1)
run_cmake(IsDirectory)
run_cmake(IsDirectoryLong)
run_cmake(elseif-message)
+
+run_cmake(TestNameThatExists)
+run_cmake(TestNameThatDoesNotExist)
diff --git a/Tests/RunCMake/if/TestNameThatDoesNotExist-stdout.txt b/Tests/RunCMake/if/TestNameThatDoesNotExist-stdout.txt
new file mode 100644
index 0000000..8874ca8
--- /dev/null
+++ b/Tests/RunCMake/if/TestNameThatDoesNotExist-stdout.txt
@@ -0,0 +1 @@
+TestThatDoesNotExist is false
diff --git a/Tests/RunCMake/if/TestNameThatDoesNotExist.cmake b/Tests/RunCMake/if/TestNameThatDoesNotExist.cmake
new file mode 100644
index 0000000..74bc8b0
--- /dev/null
+++ b/Tests/RunCMake/if/TestNameThatDoesNotExist.cmake
@@ -0,0 +1,6 @@
+cmake_policy(SET CMP0064 NEW)
+if(TEST TestThatDoesNotExist)
+ message(FATAL_ERROR "if TestThatDoesNotExist is true")
+else()
+ message(STATUS "if TestThatDoesNotExist is false")
+endif()
diff --git a/Tests/RunCMake/if/TestNameThatExists-stdout.txt b/Tests/RunCMake/if/TestNameThatExists-stdout.txt
new file mode 100644
index 0000000..54911bc
--- /dev/null
+++ b/Tests/RunCMake/if/TestNameThatExists-stdout.txt
@@ -0,0 +1 @@
+TestThatExists is true
diff --git a/Tests/RunCMake/if/TestNameThatExists.cmake b/Tests/RunCMake/if/TestNameThatExists.cmake
new file mode 100644
index 0000000..65c2b46
--- /dev/null
+++ b/Tests/RunCMake/if/TestNameThatExists.cmake
@@ -0,0 +1,7 @@
+cmake_policy(SET CMP0064 NEW)
+add_test(NAME TestThatExists COMMAND ${CMAKE_COMMAND} -E echo "A CMake Test")
+if(TEST TestThatExists)
+ message(STATUS "if TestThatExists is true")
+else()
+ message(FATAL_ERROR "if TestThatExists is false")
+endif()