diff options
68 files changed, 1597 insertions, 476 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/command/string.rst b/Help/command/string.rst index bc4399c..20f8094 100644 --- a/Help/command/string.rst +++ b/Help/command/string.rst @@ -96,7 +96,7 @@ random number generator. ``FIND`` will return the position where the given substring was found in the supplied string. If the ``REVERSE`` flag was used, the command will search for the position of the last occurrence of the specified -substring. +substring. If the substring is not found, a position of -1 is returned. The following characters have special meaning in regular expressions: 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/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/Modules/Compiler/Cray-DetermineCompiler.cmake b/Modules/Compiler/Cray-DetermineCompiler.cmake index 881b82c..6602294 100644 --- a/Modules/Compiler/Cray-DetermineCompiler.cmake +++ b/Modules/Compiler/Cray-DetermineCompiler.cmake @@ -2,5 +2,5 @@ set(_compiler_id_pp_test "defined(_CRAYC)") set(_compiler_id_version_compute " -# define @PREFIX@COMPILER_VERSION_MAJOR @MACRO_DEC@(_RELEASE) +# define @PREFIX@COMPILER_VERSION_MAJOR @MACRO_DEC@(_RELEASE_MAJOR) # define @PREFIX@COMPILER_VERSION_MINOR @MACRO_DEC@(_RELEASE_MINOR)") 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/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/FindOpenSSL.cmake b/Modules/FindOpenSSL.cmake index 3adc269..7545d94 100644 --- a/Modules/FindOpenSSL.cmake +++ b/Modules/FindOpenSSL.cmake @@ -99,15 +99,20 @@ if(WIN32 AND NOT CYGWIN) # We are using the libraries located in the VC subdir instead of the parent directory eventhough : # libeay32MD.lib is identical to ../libeay32.lib, and # ssleay32MD.lib is identical to ../ssleay32.lib + + set(_OPENSSL_PATH_SUFFIXES + "lib" + "VC" + "lib/VC" + ) + find_library(LIB_EAY_DEBUG NAMES libeay32MDd libeay32d ${_OPENSSL_ROOT_HINTS_AND_PATHS} PATH_SUFFIXES - "lib" - "VC" - "lib/VC" + ${_OPENSSL_PATH_SUFFIXES} ) find_library(LIB_EAY_RELEASE @@ -116,9 +121,7 @@ if(WIN32 AND NOT CYGWIN) libeay32 ${_OPENSSL_ROOT_HINTS_AND_PATHS} PATH_SUFFIXES - "lib" - "VC" - "lib/VC" + ${_OPENSSL_PATH_SUFFIXES} ) find_library(SSL_EAY_DEBUG @@ -127,9 +130,7 @@ if(WIN32 AND NOT CYGWIN) ssleay32d ${_OPENSSL_ROOT_HINTS_AND_PATHS} PATH_SUFFIXES - "lib" - "VC" - "lib/VC" + ${_OPENSSL_PATH_SUFFIXES} ) find_library(SSL_EAY_RELEASE @@ -139,9 +140,7 @@ if(WIN32 AND NOT CYGWIN) ssl ${_OPENSSL_ROOT_HINTS_AND_PATHS} PATH_SUFFIXES - "lib" - "VC" - "lib/VC" + ${_OPENSSL_PATH_SUFFIXES} ) set(LIB_EAY_LIBRARY_DEBUG "${LIB_EAY_DEBUG}") @@ -155,9 +154,9 @@ if(WIN32 AND NOT CYGWIN) mark_as_advanced(LIB_EAY_LIBRARY_DEBUG LIB_EAY_LIBRARY_RELEASE SSL_EAY_LIBRARY_DEBUG SSL_EAY_LIBRARY_RELEASE) - set( OPENSSL_SSL_LIBRARY ${SSL_EAY_LIBRARY} ) - set( OPENSSL_CRYPTO_LIBRARY ${LIB_EAY_LIBRARY} ) - set( OPENSSL_LIBRARIES ${SSL_EAY_LIBRARY} ${LIB_EAY_LIBRARY} ) + set(OPENSSL_SSL_LIBRARY ${SSL_EAY_LIBRARY} ) + set(OPENSSL_CRYPTO_LIBRARY ${LIB_EAY_LIBRARY} ) + set(OPENSSL_LIBRARIES ${SSL_EAY_LIBRARY} ${LIB_EAY_LIBRARY} ) elseif(MINGW) # same player, for MinGW set(LIB_EAY_NAMES libeay32) @@ -185,9 +184,9 @@ if(WIN32 AND NOT CYGWIN) ) mark_as_advanced(SSL_EAY LIB_EAY) - set( OPENSSL_SSL_LIBRARY ${SSL_EAY} ) - set( OPENSSL_CRYPTO_LIBRARY ${LIB_EAY} ) - set( OPENSSL_LIBRARIES ${SSL_EAY} ${LIB_EAY} ) + set(OPENSSL_SSL_LIBRARY ${SSL_EAY} ) + set(OPENSSL_CRYPTO_LIBRARY ${LIB_EAY} ) + set(OPENSSL_LIBRARIES ${SSL_EAY} ${LIB_EAY} ) unset(LIB_EAY_NAMES) unset(SSL_EAY_NAMES) else() @@ -213,9 +212,9 @@ if(WIN32 AND NOT CYGWIN) ) mark_as_advanced(SSL_EAY LIB_EAY) - set( OPENSSL_SSL_LIBRARY ${SSL_EAY} ) - set( OPENSSL_CRYPTO_LIBRARY ${LIB_EAY} ) - set( OPENSSL_LIBRARIES ${SSL_EAY} ${LIB_EAY} ) + set(OPENSSL_SSL_LIBRARY ${SSL_EAY} ) + set(OPENSSL_CRYPTO_LIBRARY ${LIB_EAY} ) + set(OPENSSL_LIBRARIES ${SSL_EAY} ${LIB_EAY} ) endif() else() 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/Platform/SunOS.cmake b/Modules/Platform/SunOS.cmake index aaa79c4..77946f2 100644 --- a/Modules/Platform/SunOS.cmake +++ b/Modules/Platform/SunOS.cmake @@ -7,14 +7,6 @@ if(CMAKE_SYSTEM MATCHES "SunOS-4") set(CMAKE_SHARED_LIBRARY_RUNTIME_C_FLAG_SEP ":") endif() -if(CMAKE_COMPILER_IS_GNUCXX) - if(CMAKE_COMPILER_IS_GNUCC) - set(CMAKE_CXX_CREATE_SHARED_LIBRARY - "<CMAKE_C_COMPILER> <CMAKE_SHARED_LIBRARY_CXX_FLAGS> <LINK_FLAGS> <CMAKE_SHARED_LIBRARY_CREATE_CXX_FLAGS> <SONAME_FLAG><TARGET_SONAME> -o <TARGET> <OBJECTS> <LINK_LIBRARIES>") - else() - # Take default rule from CMakeDefaultMakeRuleVariables.cmake. - endif() -endif() include(Platform/UnixPaths) # Add the compiler's implicit link directories. diff --git a/Source/CMakeVersion.cmake b/Source/CMakeVersion.cmake index 612ab7e..9a7ae8b 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 20150728) +set(CMake_VERSION_PATCH 20150804) #set(CMake_VERSION_RC 1) diff --git a/Source/cmCommonTargetGenerator.cxx b/Source/cmCommonTargetGenerator.cxx index 2225fdc..26ca375 100644 --- a/Source/cmCommonTargetGenerator.cxx +++ b/Source/cmCommonTargetGenerator.cxx @@ -20,8 +20,12 @@ #include "cmSystemTools.h" #include "cmTarget.h" -cmCommonTargetGenerator::cmCommonTargetGenerator(cmGeneratorTarget* gt) - : GeneratorTarget(gt) +cmCommonTargetGenerator::cmCommonTargetGenerator( + cmOutputConverter::RelativeRoot wd, + cmGeneratorTarget* gt + ) + : WorkingDirectory(wd) + , GeneratorTarget(gt) , Target(gt->Target) , Makefile(gt->Makefile) , LocalGenerator(static_cast<cmLocalCommonGenerator*>(gt->LocalGenerator)) @@ -101,47 +105,47 @@ void cmCommonTargetGenerator::AddModuleDefinitionFlag(std::string& flags) } //---------------------------------------------------------------------------- -const char* cmCommonTargetGenerator::GetFortranModuleDirectory() +std::string cmCommonTargetGenerator::ComputeFortranModuleDirectory() const { - // Compute the module directory. - if(!this->FortranModuleDirectoryComputed) + std::string mod_dir; + const char* target_mod_dir = + this->Target->GetProperty("Fortran_MODULE_DIRECTORY"); + const char* moddir_flag = + this->Makefile->GetDefinition("CMAKE_Fortran_MODDIR_FLAG"); + if(target_mod_dir && moddir_flag) { - const char* target_mod_dir = - this->Target->GetProperty("Fortran_MODULE_DIRECTORY"); - const char* moddir_flag = - this->Makefile->GetDefinition("CMAKE_Fortran_MODDIR_FLAG"); - if(target_mod_dir && moddir_flag) + // Compute the full path to the module directory. + if(cmSystemTools::FileIsFullPath(target_mod_dir)) { - // Compute the full path to the module directory. - if(cmSystemTools::FileIsFullPath(target_mod_dir)) - { - // Already a full path. - this->FortranModuleDirectory = target_mod_dir; - } - else - { - // Interpret relative to the current output directory. - this->FortranModuleDirectory = - this->Makefile->GetCurrentBinaryDirectory(); - this->FortranModuleDirectory += "/"; - this->FortranModuleDirectory += target_mod_dir; - } - - // Make sure the module output directory exists. - cmSystemTools::MakeDirectory(this->FortranModuleDirectory.c_str()); + // Already a full path. + mod_dir = target_mod_dir; + } + else + { + // Interpret relative to the current output directory. + mod_dir = this->Makefile->GetCurrentBinaryDirectory(); + mod_dir += "/"; + mod_dir += target_mod_dir; } - this->FortranModuleDirectoryComputed = true; - } - // Return the computed directory. - if(this->FortranModuleDirectory.empty()) - { - return 0; + // Make sure the module output directory exists. + cmSystemTools::MakeDirectory(mod_dir); } - else + return mod_dir; +} + +//---------------------------------------------------------------------------- +std::string cmCommonTargetGenerator::GetFortranModuleDirectory() +{ + // Compute the module directory. + if(!this->FortranModuleDirectoryComputed) { - return this->FortranModuleDirectory.c_str(); + this->FortranModuleDirectoryComputed = true; + this->FortranModuleDirectory = this->ComputeFortranModuleDirectory(); } + + // Return the computed directory. + return this->FortranModuleDirectory; } //---------------------------------------------------------------------------- @@ -155,19 +159,24 @@ void cmCommonTargetGenerator::AddFortranFlags(std::string& flags) } // Add a module output directory flag if necessary. - const char* mod_dir = this->GetFortranModuleDirectory(); - if(!mod_dir) + std::string mod_dir = this->GetFortranModuleDirectory(); + if (!mod_dir.empty()) + { + mod_dir = this->Convert(mod_dir, + this->WorkingDirectory, + cmLocalGenerator::SHELL); + } + else { - mod_dir = this->Makefile->GetDefinition("CMAKE_Fortran_MODDIR_DEFAULT"); + mod_dir = + this->Makefile->GetSafeDefinition("CMAKE_Fortran_MODDIR_DEFAULT"); } - if(mod_dir) + if (!mod_dir.empty()) { const char* moddir_flag = this->Makefile->GetRequiredDefinition("CMAKE_Fortran_MODDIR_FLAG"); std::string modflag = moddir_flag; - modflag += this->Convert(mod_dir, - cmLocalGenerator::START_OUTPUT, - cmLocalGenerator::SHELL); + modflag += mod_dir; this->LocalGenerator->AppendFlags(flags, modflag); } @@ -368,3 +377,37 @@ std::string cmCommonTargetGenerator::GetIncludes(std::string const& l) } return i->second; } + +std::vector<std::string> +cmCommonTargetGenerator::GetLinkedTargetDirectories() const +{ + std::vector<std::string> dirs; + std::set<cmTarget const*> emitted; + if (cmComputeLinkInformation* cli = + this->Target->GetLinkInformation(this->ConfigName)) + { + cmComputeLinkInformation::ItemVector const& items = cli->GetItems(); + for(cmComputeLinkInformation::ItemVector::const_iterator + i = items.begin(); i != items.end(); ++i) + { + cmTarget const* linkee = i->Target; + if(linkee && !linkee->IsImported() + // We can ignore the INTERFACE_LIBRARY items because + // Target->GetLinkInformation already processed their + // link interface and they don't have any output themselves. + && linkee->GetType() != cmTarget::INTERFACE_LIBRARY + && emitted.insert(linkee).second) + { + cmGeneratorTarget* gt = + this->GlobalGenerator->GetGeneratorTarget(linkee); + cmLocalGenerator* lg = gt->GetLocalGenerator(); + cmMakefile* mf = linkee->GetMakefile(); + std::string di = mf->GetCurrentBinaryDirectory(); + di += "/"; + di += lg->GetTargetDirectory(*linkee); + dirs.push_back(di); + } + } + } + return dirs; +} diff --git a/Source/cmCommonTargetGenerator.h b/Source/cmCommonTargetGenerator.h index 5451a5a..0a49e12 100644 --- a/Source/cmCommonTargetGenerator.h +++ b/Source/cmCommonTargetGenerator.h @@ -29,7 +29,8 @@ class cmTarget; class cmCommonTargetGenerator { public: - cmCommonTargetGenerator(cmGeneratorTarget* gt); + cmCommonTargetGenerator(cmOutputConverter::RelativeRoot wd, + cmGeneratorTarget* gt); virtual ~cmCommonTargetGenerator(); std::string const& GetConfigName() const; @@ -46,6 +47,7 @@ protected: // Helper to add flag for windows .def file. void AddModuleDefinitionFlag(std::string& flags); + cmOutputConverter::RelativeRoot WorkingDirectory; cmGeneratorTarget* GeneratorTarget; cmTarget* Target; cmMakefile* Makefile; @@ -59,7 +61,8 @@ protected: // Target-wide Fortran module output directory. bool FortranModuleDirectoryComputed; std::string FortranModuleDirectory; - const char* GetFortranModuleDirectory(); + std::string GetFortranModuleDirectory(); + virtual std::string ComputeFortranModuleDirectory() const; // Compute target-specific Fortran language flags. void AddFortranFlags(std::string& flags); @@ -85,6 +88,8 @@ protected: ByLanguageMap DefinesByLanguage; std::string GetIncludes(std::string const& l); ByLanguageMap IncludesByLanguage; + + std::vector<std::string> GetLinkedTargetDirectories() const; }; #endif 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/cmDependsFortran.cxx b/Source/cmDependsFortran.cxx index 13c6409..856dcd4 100644 --- a/Source/cmDependsFortran.cxx +++ b/Source/cmDependsFortran.cxx @@ -154,14 +154,10 @@ bool cmDependsFortran::Finalize(std::ostream& makeDepends, const char* stamp_dir = this->TargetDirectory.c_str(); // Get the directory in which module files will be created. - const char* mod_dir; cmMakefile* mf = this->LocalGenerator->GetMakefile(); - if(const char* target_mod_dir = - mf->GetDefinition("CMAKE_Fortran_TARGET_MODULE_DIR")) - { - mod_dir = target_mod_dir; - } - else + std::string mod_dir = + mf->GetSafeDefinition("CMAKE_Fortran_TARGET_MODULE_DIR"); + if (mod_dir.empty()) { mod_dir = this->LocalGenerator->GetMakefile()->GetCurrentBinaryDirectory(); @@ -356,7 +352,8 @@ bool cmDependsFortran ::WriteDependenciesReal(const char *obj, cmFortranSourceInfo const& info, - const char* mod_dir, const char* stamp_dir, + std::string const& mod_dir, + const char* stamp_dir, std::ostream& makeDepends, std::ostream& internalDepends) { diff --git a/Source/cmDependsFortran.h b/Source/cmDependsFortran.h index d6ec7d7..a8a4013 100644 --- a/Source/cmDependsFortran.h +++ b/Source/cmDependsFortran.h @@ -66,7 +66,8 @@ protected: // Actually write the depenencies to the streams. bool WriteDependenciesReal(const char *obj, cmFortranSourceInfo const& info, - const char* mod_dir, const char* stamp_dir, + std::string const& mod_dir, + const char* stamp_dir, std::ostream& makeDepends, std::ostream& internalDepends); diff --git a/Source/cmExportInstallFileGenerator.cxx b/Source/cmExportInstallFileGenerator.cxx index 6c7d97e..1d75f43 100644 --- a/Source/cmExportInstallFileGenerator.cxx +++ b/Source/cmExportInstallFileGenerator.cxx @@ -398,7 +398,7 @@ cmExportInstallFileGenerator } // Get the target to be installed. - cmTarget* target = itgen->GetTarget(); + cmTarget* target = itgen->GetTarget()->Target; // Construct the installed location of the target. std::string dest = itgen->GetDestination(config); diff --git a/Source/cmExportTryCompileFileGenerator.h b/Source/cmExportTryCompileFileGenerator.h index 0d84896..749f3ef 100644 --- a/Source/cmExportTryCompileFileGenerator.h +++ b/Source/cmExportTryCompileFileGenerator.h @@ -9,8 +9,8 @@ implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the License for more information. ============================================================================*/ -#ifndef cmExportInstallFileGenerator_h -#define cmExportInstallFileGenerator_h +#ifndef cmExportTryCompileFileGenerator_h +#define cmExportTryCompileFileGenerator_h #include "cmExportFileGenerator.h" diff --git a/Source/cmGeneratorExpressionNode.cxx b/Source/cmGeneratorExpressionNode.cxx index 3daa7b8..f3527ff 100644 --- a/Source/cmGeneratorExpressionNode.cxx +++ b/Source/cmGeneratorExpressionNode.cxx @@ -1369,8 +1369,6 @@ static const struct CompileFeaturesNode : public cmGeneratorExpressionNode bool evalLL = dagChecker && dagChecker->EvaluatingLinkLibraries(); - std::string result; - for (LangMap::const_iterator lit = testedFeatures.begin(); lit != testedFeatures.end(); ++lit) { diff --git a/Source/cmGeneratorTarget.cxx b/Source/cmGeneratorTarget.cxx index edd89e8..bd57b3d 100644 --- a/Source/cmGeneratorTarget.cxx +++ b/Source/cmGeneratorTarget.cxx @@ -1150,35 +1150,35 @@ void cmGeneratorTarget::GenerateTargetManifest( f = dir; f += "/"; f += name; - gg->AddToManifest(config, f); + gg->AddToManifest(f); } if(!soName.empty()) { f = dir; f += "/"; f += soName; - gg->AddToManifest(config, f); + gg->AddToManifest(f); } if(!realName.empty()) { f = dir; f += "/"; f += realName; - gg->AddToManifest(config, f); + gg->AddToManifest(f); } if(!pdbName.empty()) { f = dir; f += "/"; f += pdbName; - gg->AddToManifest(config, f); + gg->AddToManifest(f); } if(!impName.empty()) { f = this->Target->GetDirectory(config, true); f += "/"; f += impName; - gg->AddToManifest(config, f); + gg->AddToManifest(f); } } diff --git a/Source/cmGlobalGenerator.cxx b/Source/cmGlobalGenerator.cxx index 5599854..b437942 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" @@ -34,6 +48,7 @@ #include "cmExportBuildFileGenerator.h" #include "cmCPackPropertiesGenerator.h" #include "cmAlgorithms.h" +#include "cmInstallGenerator.h" #include <cmsys/Directory.hxx> #include <cmsys/FStream.hxx> @@ -432,23 +447,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"); @@ -1212,7 +1249,7 @@ bool cmGlobalGenerator::CheckALLOW_DUPLICATE_CUSTOM_TARGETS() const return false; } -void cmGlobalGenerator::DoGenerate() +bool cmGlobalGenerator::Compute() { // Some generators track files replaced during the Generate. // Start with an empty vector: @@ -1221,17 +1258,11 @@ void cmGlobalGenerator::DoGenerate() // clear targets to issue warning CMP0042 for this->CMP0042WarnTargets.clear(); - this->Generate(); -} - -void cmGlobalGenerator::Generate() -{ // Check whether this generator is allowed to run. if(!this->CheckALLOW_DUPLICATE_CUSTOM_TARGETS()) { - return; + return false; } - this->FinalizeTargetCompileInfo(); this->CreateGenerationObjects(); @@ -1266,6 +1297,24 @@ void cmGlobalGenerator::Generate() } #endif + for (i = 0; i < this->LocalGenerators.size(); ++i) + { + cmMakefile* mf = this->LocalGenerators[i]->GetMakefile(); + std::vector<cmInstallGenerator*>& gens = mf->GetInstallGenerators(); + for (std::vector<cmInstallGenerator*>::const_iterator git = gens.begin(); + git != gens.end(); ++git) + { + (*git)->Compute(this->LocalGenerators[i]); + } + } + + return true; +} + +void cmGlobalGenerator::Generate() +{ + unsigned int i; + // Trace the dependencies, after that no custom commands should be added // because their dependencies might not be handled correctly for (i = 0; i < this->LocalGenerators.size(); ++i) @@ -2698,12 +2747,8 @@ void cmGlobalGenerator::AddTargetDepends(cmGeneratorTarget const* target, //---------------------------------------------------------------------------- -void cmGlobalGenerator::AddToManifest(const std::string& config, - std::string const& f) +void cmGlobalGenerator::AddToManifest(std::string const& f) { - // Add to the main manifest for this configuration. - this->TargetManifest[config].insert(f); - // Add to the content listing for the file's directory. std::string dir = cmSystemTools::GetFilenamePath(f); std::string file = cmSystemTools::GetFilenameName(f); diff --git a/Source/cmGlobalGenerator.h b/Source/cmGlobalGenerator.h index d486003..9492372 100644 --- a/Source/cmGlobalGenerator.h +++ b/Source/cmGlobalGenerator.h @@ -86,6 +86,7 @@ public: */ virtual void Configure(); + virtual bool Compute(); enum TargetTypes { AllTargets, @@ -99,7 +100,7 @@ public: * basically creates a series of LocalGenerators for each directory and * requests that they Generate. */ - void DoGenerate(); + virtual void Generate(); /** * Set/Get and Clear the enabled languages. @@ -200,7 +201,7 @@ public: cmExportSetMap& GetExportSets() {return this->ExportSets;} /** Add a file to the manifest of generated targets for a configuration. */ - void AddToManifest(const std::string& config, std::string const& f); + void AddToManifest(std::string const& f); void EnableInstallTarget(); @@ -264,11 +265,6 @@ public: const std::string& suffix, std::string& dir); - /** Get the manifest of all targets that will be built for each - configuration. This is valid during generation only. */ - cmTargetManifest const& GetTargetManifest() const - { return this->TargetManifest; } - /** Get the content of a directory. Directory listings are cached and re-loaded from disk only when modified. During the generation step the content will include the target files to be built even if @@ -373,8 +369,6 @@ public: std::string MakeSilentFlag; protected: - virtual void Generate(); - typedef std::vector<cmLocalGenerator*> GeneratorVector; // for a project collect all its targets by following depend // information, and also collect all the targets @@ -429,10 +423,6 @@ protected: std::map<std::string, cmExportBuildFileGenerator*> BuildExportSets; std::map<std::string, cmExportBuildFileGenerator*> BuildExportExportSets; - // Manifest of all targets that will be built for each configuration. - // This is computed just before local generators generate. - cmTargetManifest TargetManifest; - // All targets in the entire project. #if defined(CMAKE_BUILD_WITH_CMAKE) #ifdef CMake_HAVE_CXX11_UNORDERED_MAP diff --git a/Source/cmGlobalVisualStudio10Generator.cxx b/Source/cmGlobalVisualStudio10Generator.cxx index 4e8ada4..8ec4fd9 100644 --- a/Source/cmGlobalVisualStudio10Generator.cxx +++ b/Source/cmGlobalVisualStudio10Generator.cxx @@ -314,9 +314,18 @@ cmGlobalVisualStudio10Generator::CreateLocalGenerator(cmLocalGenerator* parent, } //---------------------------------------------------------------------------- -void cmGlobalVisualStudio10Generator::Generate() +bool cmGlobalVisualStudio10Generator::Compute() { + if (!cmGlobalVisualStudio8Generator::Compute()) + { + return false; + } this->LongestSource = LongestSourcePath(); + return true; +} + +void cmGlobalVisualStudio10Generator::Generate() +{ this->cmGlobalVisualStudio8Generator::Generate(); if(this->LongestSource.Length > 0) { diff --git a/Source/cmGlobalVisualStudio10Generator.h b/Source/cmGlobalVisualStudio10Generator.h index 74d5022..3d34a3f 100644 --- a/Source/cmGlobalVisualStudio10Generator.h +++ b/Source/cmGlobalVisualStudio10Generator.h @@ -45,6 +45,8 @@ public: std::vector<std::string> const& makeOptions = std::vector<std::string>() ); + virtual bool Compute(); + ///! create the correct local generator virtual cmLocalGenerator *CreateLocalGenerator(cmLocalGenerator* parent, cmState::Snapshot snapshot); diff --git a/Source/cmGlobalVisualStudio8Generator.cxx b/Source/cmGlobalVisualStudio8Generator.cxx index f3cf36e..d5a5585 100644 --- a/Source/cmGlobalVisualStudio8Generator.cxx +++ b/Source/cmGlobalVisualStudio8Generator.cxx @@ -254,6 +254,8 @@ bool cmGlobalVisualStudio8Generator::AddCheckTarget() mf->AddUtilityCommand(CMAKE_CHECK_BUILD_SYSTEM_TARGET, false, no_working_directory, no_depends, noCommandLines); + cmGeneratorTarget* gt = new cmGeneratorTarget(tgt, lg); + mf->AddGeneratorTarget(tgt, gt); // Organize in the "predefined targets" folder: // @@ -345,8 +347,13 @@ bool cmGlobalVisualStudio8Generator::AddCheckTarget() } //---------------------------------------------------------------------------- -void cmGlobalVisualStudio8Generator::Generate() +bool cmGlobalVisualStudio8Generator::Compute() { + if (!cmGlobalVisualStudio7Generator::Compute()) + { + return false; + } + if(this->AddCheckTarget()) { // All targets depend on the build-system check target. @@ -360,9 +367,7 @@ void cmGlobalVisualStudio8Generator::Generate() } } } - - // Now perform the main generation. - this->cmGlobalVisualStudio7Generator::Generate(); + return true; } //---------------------------------------------------------------------------- diff --git a/Source/cmGlobalVisualStudio8Generator.h b/Source/cmGlobalVisualStudio8Generator.h index cc02b78..1c61103 100644 --- a/Source/cmGlobalVisualStudio8Generator.h +++ b/Source/cmGlobalVisualStudio8Generator.h @@ -67,7 +67,7 @@ public: return !this->WindowsCEVersion.empty(); } protected: - virtual void Generate(); + virtual bool Compute(); virtual const char* GetIDEVersion() { return "8.0"; } virtual std::string FindDevEnvCommand(); diff --git a/Source/cmGlobalVisualStudioGenerator.cxx b/Source/cmGlobalVisualStudioGenerator.cxx index 1d583eb..2f9d79a 100644 --- a/Source/cmGlobalVisualStudioGenerator.cxx +++ b/Source/cmGlobalVisualStudioGenerator.cxx @@ -64,8 +64,13 @@ std::string cmGlobalVisualStudioGenerator::GetRegistryBase( } //---------------------------------------------------------------------------- -void cmGlobalVisualStudioGenerator::Generate() +bool cmGlobalVisualStudioGenerator::Compute() { + if (!cmGlobalGenerator::Compute()) + { + return false; + } + // Add a special target that depends on ALL projects for easy build // of one configuration only. const char* no_working_dir = 0; @@ -85,6 +90,8 @@ void cmGlobalVisualStudioGenerator::Generate() AddUtilityCommand("ALL_BUILD", true, no_working_dir, no_depends, no_commands, false, "Build all projects"); + cmGeneratorTarget* gt = new cmGeneratorTarget(allBuild, gen[0]); + allBuild->GetMakefile()->AddGeneratorTarget(allBuild, gt); #if 0 // Can't activate this code because we want ALL_BUILD @@ -108,6 +115,10 @@ void cmGlobalVisualStudioGenerator::Generate() for(cmTargets::iterator t = targets.begin(); t != targets.end(); ++t) { + if (t->second.GetType() == cmTarget::GLOBAL_TARGET) + { + continue; + } if(!this->IsExcluded(gen[0], t->second)) { allBuild->AddUtility(t->second.GetName()); @@ -130,9 +141,7 @@ void cmGlobalVisualStudioGenerator::Generate() static_cast<cmLocalVisualStudioGenerator*>(*lgi); lg->AddCMakeListsRules(); } - - // Run all the local generators. - this->cmGlobalGenerator::Generate(); + return true; } //---------------------------------------------------------------------------- diff --git a/Source/cmGlobalVisualStudioGenerator.h b/Source/cmGlobalVisualStudioGenerator.h index 8e2d6a4..64440ad 100644 --- a/Source/cmGlobalVisualStudioGenerator.h +++ b/Source/cmGlobalVisualStudioGenerator.h @@ -108,7 +108,7 @@ public: cmGeneratorTarget*, std::vector<cmCustomCommand>& commands, std::string const& configName); protected: - virtual void Generate(); + virtual bool Compute(); // Does this VS version link targets to each other if there are // dependencies in the SLN file? This was done for VS versions diff --git a/Source/cmGlobalXCodeGenerator.cxx b/Source/cmGlobalXCodeGenerator.cxx index ba5ff30..7dea107 100644 --- a/Source/cmGlobalXCodeGenerator.cxx +++ b/Source/cmGlobalXCodeGenerator.cxx @@ -378,8 +378,13 @@ cmGlobalXCodeGenerator::CreateLocalGenerator(cmLocalGenerator* parent, } //---------------------------------------------------------------------------- -void cmGlobalXCodeGenerator::Generate() +bool cmGlobalXCodeGenerator::Compute() { + if (!cmGlobalGenerator::Compute()) + { + return false; + } + std::map<std::string, std::vector<cmLocalGenerator*> >::iterator it; // make sure extra targets are added before calling // the parent generate which will call trace depends @@ -390,11 +395,17 @@ void cmGlobalXCodeGenerator::Generate() // add ALL_BUILD, INSTALL, etc this->AddExtraTargets(root, it->second); } + return true; +} + +void cmGlobalXCodeGenerator::Generate() +{ this->cmGlobalGenerator::Generate(); if(cmSystemTools::GetErrorOccuredFlag()) { return; } + std::map<std::string, std::vector<cmLocalGenerator*> >::iterator it; for(it = this->ProjectMap.begin(); it!= this->ProjectMap.end(); ++it) { cmLocalGenerator* root = it->second[0]; @@ -449,10 +460,11 @@ cmGlobalXCodeGenerator::AddExtraTargets(cmLocalGenerator* root, // Add ALL_BUILD const char* no_working_directory = 0; std::vector<std::string> no_depends; - mf->AddUtilityCommand("ALL_BUILD", true, no_depends, + cmTarget* allbuild = mf->AddUtilityCommand("ALL_BUILD", true, no_depends, no_working_directory, "echo", "Build all projects"); - cmTarget* allbuild = mf->FindTarget("ALL_BUILD"); + cmGeneratorTarget* allBuildGt = new cmGeneratorTarget(allbuild, root); + mf->AddGeneratorTarget(allbuild, allBuildGt); // Refer to the main build configuration file for easy editing. std::string listfile = mf->GetCurrentSourceDirectory(); @@ -481,9 +493,12 @@ cmGlobalXCodeGenerator::AddExtraTargets(cmLocalGenerator* root, std::string file = this->ConvertToRelativeForMake( this->CurrentReRunCMakeMakefile.c_str()); cmSystemTools::ReplaceString(file, "\\ ", " "); - mf->AddUtilityCommand(CMAKE_CHECK_BUILD_SYSTEM_TARGET, true, no_depends, + cmTarget* check = mf->AddUtilityCommand(CMAKE_CHECK_BUILD_SYSTEM_TARGET, + true, no_depends, no_working_directory, "make", "-f", file.c_str()); + cmGeneratorTarget* checkGt = new cmGeneratorTarget(check, root); + mf->AddGeneratorTarget(check, checkGt); } // now make the allbuild depend on all the non-utility targets @@ -502,6 +517,11 @@ cmGlobalXCodeGenerator::AddExtraTargets(cmLocalGenerator* root, { cmTarget& target = l->second; + if (target.GetType() == cmTarget::GLOBAL_TARGET) + { + continue; + } + if (regenerate && (l->first != CMAKE_CHECK_BUILD_SYSTEM_TARGET)) { target.AddUtility(CMAKE_CHECK_BUILD_SYSTEM_TARGET); diff --git a/Source/cmGlobalXCodeGenerator.h b/Source/cmGlobalXCodeGenerator.h index c36e4af..ee8bf2c 100644 --- a/Source/cmGlobalXCodeGenerator.h +++ b/Source/cmGlobalXCodeGenerator.h @@ -88,6 +88,7 @@ public: virtual bool SetGeneratorToolset(std::string const& ts, cmMakefile* mf); void AppendFlag(std::string& flags, std::string const& flag); protected: + virtual bool Compute(); virtual void Generate(); private: cmXCodeObject* CreateOrGetPBXGroup(cmTarget& cmtarget, diff --git a/Source/cmInstallCommand.cxx b/Source/cmInstallCommand.cxx index 899b088..f548f5d 100644 --- a/Source/cmInstallCommand.cxx +++ b/Source/cmInstallCommand.cxx @@ -27,7 +27,8 @@ static cmInstallTargetGenerator* CreateInstallTargetGenerator(cmTarget& target, { cmInstallGenerator::MessageLevel message = cmInstallGenerator::SelectMessageLevel(target.GetMakefile()); - return new cmInstallTargetGenerator(target, args.GetDestination().c_str(), + return new cmInstallTargetGenerator(target.GetName(), + args.GetDestination().c_str(), impLib, args.GetPermissions().c_str(), args.GetConfigurations(), args.GetComponent().c_str(), message, @@ -752,6 +753,12 @@ bool cmInstallCommand::HandleTargetsMode(std::vector<std::string> const& args) installsPublicHeader = installsPublicHeader || publicHeaderGenerator != 0; installsResource = installsResource || resourceGenerator; + if (installsArchive || installsRuntime || installsFramework + || installsLibrary || installsBundle) + { + target.SetHaveInstallRule(true); + } + this->Makefile->AddInstallGenerator(archiveGenerator); this->Makefile->AddInstallGenerator(libraryGenerator); this->Makefile->AddInstallGenerator(runtimeGenerator); diff --git a/Source/cmInstallGenerator.h b/Source/cmInstallGenerator.h index c4191e4..b8e5b53 100644 --- a/Source/cmInstallGenerator.h +++ b/Source/cmInstallGenerator.h @@ -62,6 +62,8 @@ public: /** Select message level from CMAKE_INSTALL_MESSAGE or 'never'. */ static MessageLevel SelectMessageLevel(cmMakefile* mf, bool never = false); + virtual void Compute(cmLocalGenerator*) {} + protected: virtual void GenerateScript(std::ostream& os); diff --git a/Source/cmInstallTargetGenerator.cxx b/Source/cmInstallTargetGenerator.cxx index 5115788..01d4b77 100644 --- a/Source/cmInstallTargetGenerator.cxx +++ b/Source/cmInstallTargetGenerator.cxx @@ -16,26 +16,29 @@ #include "cmGlobalGenerator.h" #include "cmLocalGenerator.h" #include "cmMakefile.h" +#include "cmGeneratorTarget.h" #include "cmake.h" #include <assert.h> //---------------------------------------------------------------------------- cmInstallTargetGenerator -::cmInstallTargetGenerator(cmTarget& t, const char* dest, bool implib, +::cmInstallTargetGenerator(const std::string& targetName, + const char* dest, bool implib, const char* file_permissions, std::vector<std::string> const& configurations, const char* component, MessageLevel message, bool optional): - cmInstallGenerator(dest, configurations, component, message), Target(&t), + cmInstallGenerator(dest, configurations, component, message), + TargetName(targetName), + Target(0), FilePermissions(file_permissions), ImportLibrary(implib), Optional(optional) { this->ActionsPerConfig = true; this->NamelinkMode = NamelinkModeNone; - this->Target->SetHaveInstallRule(true); } //---------------------------------------------------------------------------- @@ -69,15 +72,17 @@ void cmInstallTargetGenerator::GenerateScriptForConfig(std::ostream& os, { // Compute the build tree directory from which to copy the target. std::string fromDirConfig; - if(this->Target->NeedRelinkBeforeInstall(config)) + if(this->Target->Target->NeedRelinkBeforeInstall(config)) { - fromDirConfig = this->Target->GetMakefile()->GetCurrentBinaryDirectory(); + fromDirConfig = + this->Target->Target->GetMakefile()->GetCurrentBinaryDirectory(); fromDirConfig += cmake::GetCMakeFilesDirectory(); fromDirConfig += "/CMakeRelink.dir/"; } else { - fromDirConfig = this->Target->GetDirectory(config, this->ImportLibrary); + fromDirConfig = + this->Target->Target->GetDirectory(config, this->ImportLibrary); fromDirConfig += "/"; } std::string toDir = @@ -88,7 +93,8 @@ void cmInstallTargetGenerator::GenerateScriptForConfig(std::ostream& os, std::vector<std::string> filesFrom; std::vector<std::string> filesTo; std::string literal_args; - cmTarget::TargetType targetType = this->Target->GetType(); + cmTarget::TargetType targetType = + static_cast<cmTarget::TargetType>(this->Target->GetType()); cmInstallType type = cmInstallType(); switch(targetType) { @@ -105,7 +111,7 @@ void cmInstallTargetGenerator::GenerateScriptForConfig(std::ostream& os, case cmTarget::UTILITY: case cmTarget::GLOBAL_TARGET: case cmTarget::UNKNOWN_LIBRARY: - this->Target->GetMakefile()->IssueMessage(cmake::INTERNAL_ERROR, + this->Target->Target->GetMakefile()->IssueMessage(cmake::INTERNAL_ERROR, "cmInstallTargetGenerator created with non-installable target."); return; } @@ -118,7 +124,7 @@ void cmInstallTargetGenerator::GenerateScriptForConfig(std::ostream& os, std::string targetNameReal; std::string targetNameImport; std::string targetNamePDB; - this->Target->GetExecutableNames(targetName, targetNameReal, + this->Target->Target->GetExecutableNames(targetName, targetNameReal, targetNameImport, targetNamePDB, config); if(this->ImportLibrary) @@ -128,7 +134,7 @@ void cmInstallTargetGenerator::GenerateScriptForConfig(std::ostream& os, filesFrom.push_back(from1); filesTo.push_back(to1); std::string targetNameImportLib; - if(this->Target->GetImplibGNUtoMS(targetNameImport, + if(this->Target->Target->GetImplibGNUtoMS(targetNameImport, targetNameImportLib)) { filesFrom.push_back(fromDirConfig + targetNameImportLib); @@ -144,7 +150,7 @@ void cmInstallTargetGenerator::GenerateScriptForConfig(std::ostream& os, std::string to1 = toDir + targetName; // Handle OSX Bundles. - if(this->Target->IsAppBundleOnApple()) + if(this->Target->Target->IsAppBundleOnApple()) { // Install the whole app bundle directory. type = cmInstallType_DIRECTORY; @@ -178,7 +184,8 @@ void cmInstallTargetGenerator::GenerateScriptForConfig(std::ostream& os, std::string targetNameReal; std::string targetNameImport; std::string targetNamePDB; - this->Target->GetLibraryNames(targetName, targetNameSO, targetNameReal, + this->Target->Target->GetLibraryNames(targetName, targetNameSO, + targetNameReal, targetNameImport, targetNamePDB, config); if(this->ImportLibrary) @@ -191,7 +198,7 @@ void cmInstallTargetGenerator::GenerateScriptForConfig(std::ostream& os, filesFrom.push_back(from1); filesTo.push_back(to1); std::string targetNameImportLib; - if(this->Target->GetImplibGNUtoMS(targetNameImport, + if(this->Target->Target->GetImplibGNUtoMS(targetNameImport, targetNameImportLib)) { filesFrom.push_back(fromDirConfig + targetNameImportLib); @@ -201,7 +208,7 @@ void cmInstallTargetGenerator::GenerateScriptForConfig(std::ostream& os, // An import library looks like a static library. type = cmInstallType_STATIC_LIBRARY; } - else if(this->Target->IsFrameworkOnApple()) + else if(this->Target->Target->IsFrameworkOnApple()) { // There is a bug in cmInstallCommand if this fails. assert(this->NamelinkMode == NamelinkModeNone); @@ -219,7 +226,7 @@ void cmInstallTargetGenerator::GenerateScriptForConfig(std::ostream& os, filesFrom.push_back(from1); filesTo.push_back(to1); } - else if(this->Target->IsCFBundleOnApple()) + else if(this->Target->Target->IsCFBundleOnApple()) { // Install the whole app bundle directory. type = cmInstallType_DIRECTORY; @@ -343,7 +350,7 @@ cmInstallTargetGenerator::GetDestination(std::string const& config) const { cmGeneratorExpression ge; return ge.Parse(this->Destination) - ->Evaluate(this->Target->GetMakefile(), config); + ->Evaluate(this->Target->Target->GetMakefile(), config); } //---------------------------------------------------------------------------- @@ -352,7 +359,7 @@ cmInstallTargetGenerator::GetInstallFilename(const std::string& config) const { NameType nameType = this->ImportLibrary? NameImplib : NameNormal; return - cmInstallTargetGenerator::GetInstallFilename(this->Target, config, + cmInstallTargetGenerator::GetInstallFilename(this->Target->Target, config, nameType); } @@ -431,6 +438,12 @@ cmInstallTargetGenerator::GetInstallFilename(cmTarget const* target, return fname; } +void cmInstallTargetGenerator::Compute(cmLocalGenerator* lg) +{ + this->Target = lg->GetGlobalGenerator()->GetGeneratorTarget( + lg->GetMakefile()->FindTarget(this->TargetName)); +} + //---------------------------------------------------------------------------- void cmInstallTargetGenerator @@ -533,8 +546,8 @@ cmInstallTargetGenerator } // Fix the install_name settings in installed binaries. - std::string installNameTool = - this->Target->GetMakefile()->GetSafeDefinition("CMAKE_INSTALL_NAME_TOOL"); + std::string installNameTool = this->Target->Target->GetMakefile() + ->GetSafeDefinition("CMAKE_INSTALL_NAME_TOOL"); if(installNameTool.empty()) { @@ -544,7 +557,8 @@ cmInstallTargetGenerator // Build a map of build-tree install_name to install-tree install_name for // shared libraries linked to this target. std::map<std::string, std::string> install_name_remap; - if(cmComputeLinkInformation* cli = this->Target->GetLinkInformation(config)) + if(cmComputeLinkInformation* cli = + this->Target->Target->GetLinkInformation(config)) { std::set<cmTarget const*> const& sharedLibs = cli->GetSharedLibrariesLinked(); @@ -588,11 +602,11 @@ cmInstallTargetGenerator if(this->Target->GetType() == cmTarget::SHARED_LIBRARY) { std::string for_build = - this->Target->GetInstallNameDirForBuildTree(config); + this->Target->Target->GetInstallNameDirForBuildTree(config); std::string for_install = - this->Target->GetInstallNameDirForInstallTree(); + this->Target->Target->GetInstallNameDirForInstallTree(); - if(this->Target->IsFrameworkOnApple() && for_install.empty()) + if(this->Target->Target->IsFrameworkOnApple() && for_install.empty()) { // Frameworks seem to have an id corresponding to their own full // path. @@ -606,7 +620,7 @@ cmInstallTargetGenerator { // Prepare to refer to the install-tree install_name. new_id = for_install; - new_id += this->GetInstallFilename(this->Target, config, NameSO); + new_id += this->GetInstallFilename(this->Target->Target, config, NameSO); } } @@ -639,20 +653,22 @@ cmInstallTargetGenerator std::string const& toDestDirPath) { // Skip the chrpath if the target does not need it. - if(this->ImportLibrary || !this->Target->IsChrpathUsed(config)) + if(this->ImportLibrary || !this->Target->Target->IsChrpathUsed(config)) { return; } // Skip if on Apple - if(this->Target->GetMakefile()->IsOn("CMAKE_PLATFORM_HAS_INSTALLNAME")) + if(this->Target->Target->GetMakefile() + ->IsOn("CMAKE_PLATFORM_HAS_INSTALLNAME")) { return; } // Get the link information for this target. // It can provide the RPATH. - cmComputeLinkInformation* cli = this->Target->GetLinkInformation(config); + cmComputeLinkInformation* cli = + this->Target->Target->GetLinkInformation(config); if(!cli) { return; @@ -677,20 +693,21 @@ cmInstallTargetGenerator std::string const& toDestDirPath) { // Skip the chrpath if the target does not need it. - if(this->ImportLibrary || !this->Target->IsChrpathUsed(config)) + if(this->ImportLibrary || !this->Target->Target->IsChrpathUsed(config)) { return; } // Get the link information for this target. // It can provide the RPATH. - cmComputeLinkInformation* cli = this->Target->GetLinkInformation(config); + cmComputeLinkInformation* cli = + this->Target->Target->GetLinkInformation(config); if(!cli) { return; } - cmMakefile* mf = this->Target->GetMakefile(); + cmMakefile* mf = this->Target->Target->GetMakefile(); if(mf->IsOn("CMAKE_PLATFORM_HAS_INSTALLNAME")) { @@ -802,20 +819,20 @@ cmInstallTargetGenerator::AddStripRule(std::ostream& os, } // Don't handle OSX Bundles. - if(this->Target->GetMakefile()->IsOn("APPLE") && - this->Target->GetPropertyAsBool("MACOSX_BUNDLE")) + if(this->Target->Target->GetMakefile()->IsOn("APPLE") && + this->Target->Target->GetPropertyAsBool("MACOSX_BUNDLE")) { return; } - if(! this->Target->GetMakefile()->IsSet("CMAKE_STRIP")) + if(! this->Target->Target->GetMakefile()->IsSet("CMAKE_STRIP")) { return; } os << indent << "if(CMAKE_INSTALL_DO_STRIP)\n"; os << indent << " execute_process(COMMAND \"" - << this->Target->GetMakefile()->GetDefinition("CMAKE_STRIP") + << this->Target->Target->GetMakefile()->GetDefinition("CMAKE_STRIP") << "\" \"" << toDestDirPath << "\")\n"; os << indent << "endif()\n"; } @@ -834,13 +851,13 @@ cmInstallTargetGenerator::AddRanlibRule(std::ostream& os, // Perform post-installation processing on the file depending // on its type. - if(!this->Target->GetMakefile()->IsOn("APPLE")) + if(!this->Target->Target->GetMakefile()->IsOn("APPLE")) { return; } std::string ranlib = - this->Target->GetMakefile()->GetRequiredDefinition("CMAKE_RANLIB"); + this->Target->Target->GetMakefile()->GetRequiredDefinition("CMAKE_RANLIB"); if(ranlib.empty()) { return; diff --git a/Source/cmInstallTargetGenerator.h b/Source/cmInstallTargetGenerator.h index db69220..a8f4a75 100644 --- a/Source/cmInstallTargetGenerator.h +++ b/Source/cmInstallTargetGenerator.h @@ -13,7 +13,9 @@ #define cmInstallTargetGenerator_h #include "cmInstallGenerator.h" -#include "cmTarget.h" + +class cmTarget; +class cmGeneratorTarget; /** \class cmInstallTargetGenerator * \brief Generate target installation rules. @@ -22,7 +24,7 @@ class cmInstallTargetGenerator: public cmInstallGenerator { public: cmInstallTargetGenerator( - cmTarget& t, const char* dest, bool implib, + std::string const& targetName, const char* dest, bool implib, const char* file_permissions, std::vector<std::string> const& configurations, const char* component, @@ -56,7 +58,10 @@ public: const std::string& config, NameType nameType = NameNormal); - cmTarget* GetTarget() const { return this->Target; } + void Compute(cmLocalGenerator* lg); + + cmGeneratorTarget* GetTarget() const { return this->Target; } + bool IsImportLibrary() const { return this->ImportLibrary; } std::string GetDestination(std::string const& config) const; @@ -98,7 +103,8 @@ protected: void AddRanlibRule(std::ostream& os, Indent const& indent, const std::string& toDestDirPath); - cmTarget* Target; + std::string TargetName; + cmGeneratorTarget* Target; std::string FilePermissions; NamelinkModeType NamelinkMode; bool ImportLibrary; diff --git a/Source/cmLocalGenerator.cxx b/Source/cmLocalGenerator.cxx index 2e20ee2..6b48a44 100644 --- a/Source/cmLocalGenerator.cxx +++ b/Source/cmLocalGenerator.cxx @@ -2375,11 +2375,15 @@ cmLocalGenerator::ConstructComment(cmCustomCommandGenerator const& ccg, class cmInstallTargetGeneratorLocal: public cmInstallTargetGenerator { public: - cmInstallTargetGeneratorLocal(cmTarget& t, const char* dest, bool implib): + cmInstallTargetGeneratorLocal(cmLocalGenerator* lg, std::string const& t, + const char* dest, bool implib): cmInstallTargetGenerator( t, dest, implib, "", std::vector<std::string>(), "Unspecified", - cmInstallGenerator::SelectMessageLevel(t.GetMakefile()), - false) {} + cmInstallGenerator::SelectMessageLevel(lg->GetMakefile()), + false) + { + this->Compute(lg); + } }; //---------------------------------------------------------------------------- @@ -2428,7 +2432,7 @@ cmLocalGenerator { // Use a target install generator. cmInstallTargetGeneratorLocal - g(l->second, destination.c_str(), false); + g(this, l->first, destination.c_str(), false); g.Generate(os, config, configurationTypes); } break; @@ -2439,18 +2443,18 @@ cmLocalGenerator // to the normal destination and the DLL to the runtime // destination. cmInstallTargetGeneratorLocal - g1(l->second, destination.c_str(), true); + g1(this, l->first, destination.c_str(), true); g1.Generate(os, config, configurationTypes); // We also skip over the leading slash given by the user. destination = l->second.GetRuntimeInstallPath().substr(1); cmSystemTools::ConvertToUnixSlashes(destination); cmInstallTargetGeneratorLocal - g2(l->second, destination.c_str(), false); + g2(this, l->first, destination.c_str(), false); g2.Generate(os, config, configurationTypes); #else // Use a target install generator. cmInstallTargetGeneratorLocal - g(l->second, destination.c_str(), false); + g(this, l->first, destination.c_str(), false); g.Generate(os, config, configurationTypes); #endif } diff --git a/Source/cmLocalVisualStudio6Generator.cxx b/Source/cmLocalVisualStudio6Generator.cxx index f1c8def..cab5a47 100644 --- a/Source/cmLocalVisualStudio6Generator.cxx +++ b/Source/cmLocalVisualStudio6Generator.cxx @@ -88,7 +88,8 @@ void cmLocalVisualStudio6Generator::AddCMakeListsRules() for(cmTargets::iterator l = tgts.begin(); l != tgts.end(); l++) { - if (l->second.GetType() == cmTarget::INTERFACE_LIBRARY) + if (l->second.GetType() == cmTarget::INTERFACE_LIBRARY + || l->second.GetType() == cmTarget::GLOBAL_TARGET) { continue; } diff --git a/Source/cmLocalVisualStudio7Generator.cxx b/Source/cmLocalVisualStudio7Generator.cxx index f199a41..572dbde 100644 --- a/Source/cmLocalVisualStudio7Generator.cxx +++ b/Source/cmLocalVisualStudio7Generator.cxx @@ -108,6 +108,10 @@ void cmLocalVisualStudio7Generator::AddCMakeListsRules() // Add the rule to targets that need it. for(cmTargets::iterator l = tgts.begin(); l != tgts.end(); ++l) { + if (l->second.GetType() == cmTarget::GLOBAL_TARGET) + { + continue; + } if(l->first != CMAKE_CHECK_BUILD_SYSTEM_TARGET) { l->second.AddSource(sf->GetFullPath()); diff --git a/Source/cmMakefile.cxx b/Source/cmMakefile.cxx index 57e33df..4bdc9d5 100644 --- a/Source/cmMakefile.cxx +++ b/Source/cmMakefile.cxx @@ -1205,15 +1205,16 @@ cmMakefile::AddCustomCommandOldStyle(const std::string& target, } //---------------------------------------------------------------------------- -void cmMakefile::AddUtilityCommand(const std::string& utilityName, - bool excludeFromAll, - const std::vector<std::string>& depends, - const char* workingDirectory, - const char* command, - const char* arg1, - const char* arg2, - const char* arg3, - const char* arg4) +cmTarget* +cmMakefile::AddUtilityCommand(const std::string& utilityName, + bool excludeFromAll, + const std::vector<std::string>& depends, + const char* workingDirectory, + const char* command, + const char* arg1, + const char* arg2, + const char* arg3, + const char* arg4) { // Construct the command line for the custom command. cmCustomCommandLine commandLine; @@ -1238,8 +1239,8 @@ void cmMakefile::AddUtilityCommand(const std::string& utilityName, commandLines.push_back(commandLine); // Call the real signature of this method. - this->AddUtilityCommand(utilityName, excludeFromAll, workingDirectory, - depends, commandLines); + return this->AddUtilityCommand(utilityName, excludeFromAll, workingDirectory, + depends, commandLines); } //---------------------------------------------------------------------------- @@ -1905,6 +1906,12 @@ const char* cmMakefile::GetCurrentBinaryDirectory() const return this->StateSnapshot.GetDirectory().GetCurrentBinary(); } +void cmMakefile::AddGeneratorTarget(cmTarget* t, cmGeneratorTarget* gt) +{ + this->GeneratorTargets[t] = gt; + this->GetGlobalGenerator()->AddGeneratorTarget(t, gt); +} + //---------------------------------------------------------------------------- void cmMakefile::AddIncludeDirectories(const std::vector<std::string> &incs, bool before) diff --git a/Source/cmMakefile.h b/Source/cmMakefile.h index 7938fcc..1e5c301 100644 --- a/Source/cmMakefile.h +++ b/Source/cmMakefile.h @@ -194,14 +194,15 @@ public: * Add a utility to the build. A utiltity target is a command that * is run every time the target is built. */ - void AddUtilityCommand(const std::string& utilityName, bool excludeFromAll, - const std::vector<std::string>& depends, - const char* workingDirectory, - const char* command, - const char* arg1=0, - const char* arg2=0, - const char* arg3=0, - const char* arg4=0); + cmTarget* AddUtilityCommand(const std::string& utilityName, + bool excludeFromAll, + const std::vector<std::string>& depends, + const char* workingDirectory, + const char* command, + const char* arg1=0, + const char* arg2=0, + const char* arg3=0, + const char* arg4=0); cmTarget* AddUtilityCommand(const std::string& utilityName, bool excludeFromAll, const char* workingDirectory, @@ -416,10 +417,7 @@ public: { this->GeneratorTargets = targets; } - void AddGeneratorTarget(cmTarget* t, cmGeneratorTarget* gt) - { - this->GeneratorTargets[t] = gt; - } + void AddGeneratorTarget(cmTarget* t, cmGeneratorTarget* gt); cmTarget* FindTarget(const std::string& name, bool excludeAliases = false) const; diff --git a/Source/cmMakefileTargetGenerator.cxx b/Source/cmMakefileTargetGenerator.cxx index 0f82fb3..5edc0f5 100644 --- a/Source/cmMakefileTargetGenerator.cxx +++ b/Source/cmMakefileTargetGenerator.cxx @@ -33,7 +33,7 @@ #include <ctype.h> cmMakefileTargetGenerator::cmMakefileTargetGenerator(cmGeneratorTarget* target) - : cmCommonTargetGenerator(target) + : cmCommonTargetGenerator(cmOutputConverter::START_OUTPUT, target) , OSXBundleGenerator(0) , MacOSXContentGenerator(0) { @@ -414,7 +414,6 @@ void cmMakefileTargetGenerator // we compute some depends when writing the depend.make that we will also // use in the build.make, same with depMakeFile std::vector<std::string> depends; - std::string depMakeFile; // generate the build rule file this->WriteObjectBuildFile(obj, lang, source, depends); @@ -1059,46 +1058,21 @@ void cmMakefileTargetGenerator::WriteTargetDependRules() << "\n" << "# Targets to which this target links.\n" << "set(CMAKE_TARGET_LINKED_INFO_FILES\n"; - std::set<cmTarget const*> emitted; - const char* cfg = this->LocalGenerator->GetConfigName().c_str(); - if(cmComputeLinkInformation* cli = this->Target->GetLinkInformation(cfg)) + std::vector<std::string> dirs = this->GetLinkedTargetDirectories(); + for (std::vector<std::string>::iterator i = dirs.begin(); + i != dirs.end(); ++i) { - cmComputeLinkInformation::ItemVector const& items = cli->GetItems(); - for(cmComputeLinkInformation::ItemVector::const_iterator - i = items.begin(); i != items.end(); ++i) - { - cmTarget const* linkee = i->Target; - if(linkee && !linkee->IsImported() - // We can ignore the INTERFACE_LIBRARY items because - // Target->GetLinkInformation already processed their - // link interface and they don't have any output themselves. - && linkee->GetType() != cmTarget::INTERFACE_LIBRARY - && emitted.insert(linkee).second) - { - cmGeneratorTarget* gt = - this->GlobalGenerator->GetGeneratorTarget(linkee); - cmLocalGenerator* lg = gt->GetLocalGenerator(); - cmMakefile* mf = linkee->GetMakefile(); - std::string di = mf->GetCurrentBinaryDirectory(); - di += "/"; - di += lg->GetTargetDirectory(*linkee); - di += "/DependInfo.cmake"; - *this->InfoFileStream << " \"" << di << "\"\n"; - } - } + *this->InfoFileStream << " \"" << *i << "/DependInfo.cmake\"\n"; } *this->InfoFileStream << " )\n"; } - // Check for a target-specific module output directory. - if(const char* mdir = this->GetFortranModuleDirectory()) - { - *this->InfoFileStream - << "\n" - << "# Fortran module output directory.\n" - << "set(CMAKE_Fortran_TARGET_MODULE_DIR \"" << mdir << "\")\n"; - } + *this->InfoFileStream + << "\n" + << "# Fortran module output directory.\n" + << "set(CMAKE_Fortran_TARGET_MODULE_DIR \"" + << this->GetFortranModuleDirectory() << "\")\n"; // and now write the rule to use it std::vector<std::string> depends; diff --git a/Source/cmNinjaNormalTargetGenerator.cxx b/Source/cmNinjaNormalTargetGenerator.cxx index 8ec6f5e..c9fa7c0 100644 --- a/Source/cmNinjaNormalTargetGenerator.cxx +++ b/Source/cmNinjaNormalTargetGenerator.cxx @@ -547,6 +547,8 @@ void cmNinjaNormalTargetGenerator::WriteLinkStatement() } } + cmNinjaDeps byproducts; + if (!this->TargetNameImport.empty()) { const std::string impLibPath = localGen.ConvertToOutputFormat( @@ -556,7 +558,7 @@ void cmNinjaNormalTargetGenerator::WriteLinkStatement() EnsureParentDirectoryExists(impLibPath); if(target.HasImportLibrary()) { - outputs.push_back(targetOutputImplib); + byproducts.push_back(targetOutputImplib); } } @@ -602,7 +604,6 @@ void cmNinjaNormalTargetGenerator::WriteLinkStatement() &postBuildCmdLines }; - cmNinjaDeps byproducts; for (unsigned i = 0; i != 3; ++i) { for (std::vector<cmCustomCommand>::const_iterator diff --git a/Source/cmNinjaTargetGenerator.cxx b/Source/cmNinjaTargetGenerator.cxx index addf292..4e4dc3f 100644 --- a/Source/cmNinjaTargetGenerator.cxx +++ b/Source/cmNinjaTargetGenerator.cxx @@ -58,7 +58,7 @@ cmNinjaTargetGenerator::New(cmGeneratorTarget* target) } cmNinjaTargetGenerator::cmNinjaTargetGenerator(cmGeneratorTarget* target) - : cmCommonTargetGenerator(target), + : cmCommonTargetGenerator(cmOutputConverter::HOME_OUTPUT, target), MacOSXContentGenerator(0), OSXBundleGenerator(0), MacContentFolders(), 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/cmQtAutoGenerators.cxx b/Source/cmQtAutoGenerators.cxx index 979db91..1322dea 100644 --- a/Source/cmQtAutoGenerators.cxx +++ b/Source/cmQtAutoGenerators.cxx @@ -476,7 +476,6 @@ bool cmQtAutoGenerators::InitializeAutogenTarget(cmLocalGenerator* lg, commandLines, false, autogenComment.c_str()); cmGeneratorTarget* gt = new cmGeneratorTarget(autogenTarget, lg); - lg->GetGlobalGenerator()->AddGeneratorTarget(autogenTarget, gt); makefile->AddGeneratorTarget(autogenTarget, gt); // Set target folder diff --git a/Source/cmScriptGenerator.h b/Source/cmScriptGenerator.h index 9ab04f1..bc9372f 100644 --- a/Source/cmScriptGenerator.h +++ b/Source/cmScriptGenerator.h @@ -54,9 +54,6 @@ public: void Generate(std::ostream& os, const std::string& config, std::vector<std::string> const& configurationTypes); - const std::vector<std::string>& GetConfigurations() const - { return this->Configurations; } - protected: typedef cmScriptGeneratorIndent Indent; virtual void GenerateScript(std::ostream& os); diff --git a/Source/cmXCodeObject.cxx b/Source/cmXCodeObject.cxx index e72d315..ba6e395 100644 --- a/Source/cmXCodeObject.cxx +++ b/Source/cmXCodeObject.cxx @@ -243,7 +243,7 @@ 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_of(" <>+-*=@[](){},~") != 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 e28f385..8c455b2 100644 --- a/Source/cmake.cxx +++ b/Source/cmake.cxx @@ -1764,7 +1764,11 @@ int cmake::Generate() { return -1; } - this->GlobalGenerator->DoGenerate(); + if (!this->GlobalGenerator->Compute()) + { + return -1; + } + this->GlobalGenerator->Generate(); if ( !this->GraphVizFile.empty() ) { std::cout << "Generate graphviz: " << this->GraphVizFile << std::endl; 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/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/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/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/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..20d47b2 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() 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() |