diff --git a/Help/command/find_program.rst b/Help/command/find_program.rst index 2a5ce9a8d..d3430c054 100644 --- a/Help/command/find_program.rst +++ b/Help/command/find_program.rst @@ -2,7 +2,7 @@ find_program ------------ .. |FIND_XXX| replace:: find_program -.. |NAMES| replace:: NAMES name1 [name2 ...] +.. |NAMES| replace:: NAMES name1 [name2 ...] [NAMES_PER_DIR] .. |SEARCH_XXX| replace:: program .. |SEARCH_XXX_DESC| replace:: program .. |prefix_XXX_SUBDIR| replace:: ``/[s]bin`` @@ -26,3 +26,8 @@ find_program :variable:`CMAKE_FIND_ROOT_PATH_MODE_PROGRAM` .. include:: FIND_XXX.txt + +When more than one value is given to the ``NAMES`` option this command by +default will consider one name at a time and search every directory +for it. The ``NAMES_PER_DIR`` option tells this command to consider one +directory at a time and search for all names in it. diff --git a/Help/release/dev/find_program-NAMES_PER_DIR.rst b/Help/release/dev/find_program-NAMES_PER_DIR.rst new file mode 100644 index 000000000..04cd17045 --- /dev/null +++ b/Help/release/dev/find_program-NAMES_PER_DIR.rst @@ -0,0 +1,6 @@ +find_program-NAMES_PER_DIR +-------------------------- + +* The :command:`find_program` command learned a ``NAMES_PER_DIR`` + option to consdier all given ``NAMES`` in each directory before + moving on to the next directory. diff --git a/Source/cmFindLibraryCommand.cxx b/Source/cmFindLibraryCommand.cxx index e8d158ebb..e7696af40 100644 --- a/Source/cmFindLibraryCommand.cxx +++ b/Source/cmFindLibraryCommand.cxx @@ -203,6 +203,7 @@ struct cmFindLibraryHelper } bool HasValidSuffix(std::string const& name); void AddName(std::string const& name); + void SetName(std::string const& name); bool CheckDirectory(std::string const& path); bool CheckDirectoryForName(std::string const& path, Name& name); }; @@ -321,6 +322,13 @@ void cmFindLibraryHelper::AddName(std::string const& name) this->Names.push_back(entry); } +//---------------------------------------------------------------------------- +void cmFindLibraryHelper::SetName(std::string const& name) +{ + this->Names.clear(); + this->AddName(name); +} + //---------------------------------------------------------------------------- bool cmFindLibraryHelper::CheckDirectory(std::string const& path) { @@ -459,8 +467,7 @@ std::string cmFindLibraryCommand::FindNormalLibraryDirsPerName() ni != this->Names.end() ; ++ni) { // Switch to searching for this name. - std::string const& name = *ni; - helper.AddName(name); + helper.SetName(*ni); // Search every directory. for(std::vector::const_iterator diff --git a/Source/cmFindProgramCommand.cxx b/Source/cmFindProgramCommand.cxx index fbd9fd34e..e64ed87f2 100644 --- a/Source/cmFindProgramCommand.cxx +++ b/Source/cmFindProgramCommand.cxx @@ -16,6 +16,80 @@ #include #endif +//---------------------------------------------------------------------------- +struct cmFindProgramHelper +{ + cmFindProgramHelper() + { +#if defined (_WIN32) || defined(__CYGWIN__) || defined(__MINGW32__) + // Consider platform-specific extensions. + this->Extensions.push_back(".com"); + this->Extensions.push_back(".exe"); +#endif + // Consider original name with no extensions. + this->Extensions.push_back(""); + } + + // List of valid extensions. + std::vector Extensions; + + // Keep track of the best program file found so far. + std::string BestPath; + + // Current names under consideration. + std::vector Names; + + // Current full path under consideration. + std::string TestPath; + + void AddName(std::string const& name) + { + this->Names.push_back(name); + } + void SetName(std::string const& name) + { + this->Names.clear(); + this->AddName(name); + } + bool CheckDirectory(std::string const& path) + { + for (std::vector::iterator i = this->Names.begin(); + i != this->Names.end(); ++i) + { + if (this->CheckDirectoryForName(path, *i)) + { + return true; + } + } + return false; + } + bool CheckDirectoryForName(std::string const& path, std::string const& name) + { + for (std::vector::iterator ext = this->Extensions.begin(); + ext != this->Extensions.end(); ++ext) + { + this->TestPath = path; + this->TestPath += name; + if (!ext->empty() && cmSystemTools::StringEndsWith(name, ext->c_str())) + { + continue; + } + this->TestPath += *ext; + if (cmSystemTools::FileExists(this->TestPath, true)) + { + this->BestPath = cmSystemTools::CollapseFullPath(this->TestPath); + return true; + } + } + return false; + } +}; + +cmFindProgramCommand::cmFindProgramCommand() +{ + this->NamesPerDirAllowed = true; +} + // cmFindProgramCommand bool cmFindProgramCommand ::InitialPass(std::vector const& argsIn, cmExecutionStatus &) @@ -41,7 +115,7 @@ bool cmFindProgramCommand return true; } - std::string result = FindProgram(this->Names); + std::string result = FindProgram(); if(result != "") { // Save the value in the cache @@ -59,31 +133,92 @@ bool cmFindProgramCommand return true; } -std::string cmFindProgramCommand::FindProgram(std::vector names) +std::string cmFindProgramCommand::FindProgram() { std::string program = ""; if(this->SearchAppBundleFirst || this->SearchAppBundleOnly) { - program = FindAppBundle(names); + program = FindAppBundle(); } if(program.empty() && !this->SearchAppBundleOnly) { - program = cmSystemTools::FindProgram(names, this->SearchPaths, true); + program = this->FindNormalProgram(); } if(program.empty() && this->SearchAppBundleLast) { - program = this->FindAppBundle(names); + program = this->FindAppBundle(); } return program; } -std::string cmFindProgramCommand -::FindAppBundle(std::vector names) +//---------------------------------------------------------------------------- +std::string cmFindProgramCommand::FindNormalProgram() { - for(std::vector::const_iterator name = names.begin(); - name != names.end() ; ++name) + if(this->NamesPerDir) + { + return this->FindNormalProgramNamesPerDir(); + } + else + { + return this->FindNormalProgramDirsPerName(); + } +} + +//---------------------------------------------------------------------------- +std::string cmFindProgramCommand::FindNormalProgramNamesPerDir() +{ + // Search for all names in each directory. + cmFindProgramHelper helper; + for (std::vector::const_iterator ni = this->Names.begin(); + ni != this->Names.end() ; ++ni) + { + helper.AddName(*ni); + } + // Search every directory. + for (std::vector::const_iterator + p = this->SearchPaths.begin(); p != this->SearchPaths.end(); ++p) + { + if(helper.CheckDirectory(*p)) + { + return helper.BestPath; + } + } + // Couldn't find the program. + return ""; +} + +//---------------------------------------------------------------------------- +std::string cmFindProgramCommand::FindNormalProgramDirsPerName() +{ + // Search the entire path for each name. + cmFindProgramHelper helper; + for (std::vector::const_iterator ni = this->Names.begin(); + ni != this->Names.end() ; ++ni) + { + // Switch to searching for this name. + helper.SetName(*ni); + + // Search every directory. + for (std::vector::const_iterator + p = this->SearchPaths.begin(); + p != this->SearchPaths.end(); ++p) + { + if (helper.CheckDirectory(*p)) + { + return helper.BestPath; + } + } + } + // Couldn't find the program. + return ""; +} + +std::string cmFindProgramCommand::FindAppBundle() +{ + for(std::vector::const_iterator name = this->Names.begin(); + name != this->Names.end() ; ++name) { std::string appName = *name + std::string(".app"); diff --git a/Source/cmFindProgramCommand.h b/Source/cmFindProgramCommand.h index 70f758ff8..f88186b92 100644 --- a/Source/cmFindProgramCommand.h +++ b/Source/cmFindProgramCommand.h @@ -25,6 +25,7 @@ class cmFindProgramCommand : public cmFindBase { public: + cmFindProgramCommand(); /** * This is a virtual constructor for the command. */ @@ -52,11 +53,12 @@ public: cmTypeMacro(cmFindProgramCommand, cmFindBase); -protected: - std::string FindProgram(std::vector names); - private: - std::string FindAppBundle(std::vector names); + std::string FindProgram(); + std::string FindNormalProgram(); + std::string FindNormalProgramDirsPerName(); + std::string FindNormalProgramNamesPerDir(); + std::string FindAppBundle(); std::string GetBundleExecutable(std::string bundlePath); }; diff --git a/Source/cmSystemTools.cxx b/Source/cmSystemTools.cxx index 64eeed650..b1b7f4722 100644 --- a/Source/cmSystemTools.cxx +++ b/Source/cmSystemTools.cxx @@ -1409,15 +1409,6 @@ std::string cmSystemTools::ConvertToRunCommandPath(const char* path) #endif } -bool cmSystemTools::StringEndsWith(const char* str1, const char* str2) -{ - if ( !str1 || !str2 || strlen(str1) < strlen(str2) ) - { - return 0; - } - return !strncmp(str1 + (strlen(str1)-strlen(str2)), str2, strlen(str2)); -} - // compute the relative path from here to there std::string cmSystemTools::RelativePath(const char* local, const char* remote) { diff --git a/Source/cmSystemTools.h b/Source/cmSystemTools.h index c12a1db96..d14897f47 100644 --- a/Source/cmSystemTools.h +++ b/Source/cmSystemTools.h @@ -336,8 +336,6 @@ public: // be used when RunCommand is called from cmake, because the // running cmake needs paths to be in its format static std::string ConvertToRunCommandPath(const char* path); - //! Check if the first string ends with the second one. - static bool StringEndsWith(const char* str1, const char* str2); /** compute the relative path from local to remote. local must be a directory. remote can be a file or a directory. diff --git a/Tests/RunCMake/CMakeLists.txt b/Tests/RunCMake/CMakeLists.txt index 4d9686632..241cf90ce 100644 --- a/Tests/RunCMake/CMakeLists.txt +++ b/Tests/RunCMake/CMakeLists.txt @@ -184,6 +184,7 @@ add_RunCMake_test(find_file) add_RunCMake_test(find_library) add_RunCMake_test(find_package) add_RunCMake_test(find_path) +add_RunCMake_test(find_program -DCMAKE_SYSTEM_NAME=${CMAKE_SYSTEM_NAME}) add_RunCMake_test(get_filename_component) add_RunCMake_test(get_property) add_RunCMake_test(if) diff --git a/Tests/RunCMake/find_program/A/testA b/Tests/RunCMake/find_program/A/testA new file mode 100755 index 000000000..1a2485251 --- /dev/null +++ b/Tests/RunCMake/find_program/A/testA @@ -0,0 +1 @@ +#!/bin/sh diff --git a/Tests/RunCMake/find_program/B/testB b/Tests/RunCMake/find_program/B/testB new file mode 100755 index 000000000..1a2485251 --- /dev/null +++ b/Tests/RunCMake/find_program/B/testB @@ -0,0 +1 @@ +#!/bin/sh diff --git a/Tests/RunCMake/find_program/CMakeLists.txt b/Tests/RunCMake/find_program/CMakeLists.txt new file mode 100644 index 000000000..74b3ff8de --- /dev/null +++ b/Tests/RunCMake/find_program/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/find_program/DirsPerName-stdout.txt b/Tests/RunCMake/find_program/DirsPerName-stdout.txt new file mode 100644 index 000000000..f763bb086 --- /dev/null +++ b/Tests/RunCMake/find_program/DirsPerName-stdout.txt @@ -0,0 +1 @@ +-- PROG='[^']*/Tests/RunCMake/find_program/B/testB' diff --git a/Tests/RunCMake/find_program/DirsPerName.cmake b/Tests/RunCMake/find_program/DirsPerName.cmake new file mode 100644 index 000000000..54db6ddfb --- /dev/null +++ b/Tests/RunCMake/find_program/DirsPerName.cmake @@ -0,0 +1,6 @@ +find_program(PROG + NAMES testB testA + PATHS ${CMAKE_CURRENT_SOURCE_DIR}/A ${CMAKE_CURRENT_SOURCE_DIR}/B + NO_DEFAULT_PATH + ) +message(STATUS "PROG='${PROG}'") diff --git a/Tests/RunCMake/find_program/NamesPerDir-stdout.txt b/Tests/RunCMake/find_program/NamesPerDir-stdout.txt new file mode 100644 index 000000000..964e25971 --- /dev/null +++ b/Tests/RunCMake/find_program/NamesPerDir-stdout.txt @@ -0,0 +1 @@ +-- PROG='[^']*/Tests/RunCMake/find_program/A/testA' diff --git a/Tests/RunCMake/find_program/NamesPerDir.cmake b/Tests/RunCMake/find_program/NamesPerDir.cmake new file mode 100644 index 000000000..49ce49d84 --- /dev/null +++ b/Tests/RunCMake/find_program/NamesPerDir.cmake @@ -0,0 +1,6 @@ +find_program(PROG + NAMES testB testA NAMES_PER_DIR + PATHS ${CMAKE_CURRENT_SOURCE_DIR}/A ${CMAKE_CURRENT_SOURCE_DIR}/B + NO_DEFAULT_PATH + ) +message(STATUS "PROG='${PROG}'") diff --git a/Tests/RunCMake/find_program/RunCMakeTest.cmake b/Tests/RunCMake/find_program/RunCMakeTest.cmake new file mode 100644 index 000000000..2adec1100 --- /dev/null +++ b/Tests/RunCMake/find_program/RunCMakeTest.cmake @@ -0,0 +1,9 @@ +include(RunCMake) + +run_cmake(DirsPerName) +run_cmake(NamesPerDir) + +if(CMAKE_SYSTEM_NAME MATCHES "^(Windows|CYGWIN)$") + run_cmake(WindowsCom) + run_cmake(WindowsExe) +endif() diff --git a/Tests/RunCMake/find_program/Win/testCom.com b/Tests/RunCMake/find_program/Win/testCom.com new file mode 100755 index 000000000..e69de29bb diff --git a/Tests/RunCMake/find_program/Win/testCom.exe b/Tests/RunCMake/find_program/Win/testCom.exe new file mode 100755 index 000000000..e69de29bb diff --git a/Tests/RunCMake/find_program/Win/testExe.exe b/Tests/RunCMake/find_program/Win/testExe.exe new file mode 100755 index 000000000..e69de29bb diff --git a/Tests/RunCMake/find_program/WindowsCom-stdout.txt b/Tests/RunCMake/find_program/WindowsCom-stdout.txt new file mode 100644 index 000000000..e386fce50 --- /dev/null +++ b/Tests/RunCMake/find_program/WindowsCom-stdout.txt @@ -0,0 +1 @@ +-- PROG='[^']*/Tests/RunCMake/find_program/Win/testCom.com' diff --git a/Tests/RunCMake/find_program/WindowsCom.cmake b/Tests/RunCMake/find_program/WindowsCom.cmake new file mode 100644 index 000000000..b32d9e851 --- /dev/null +++ b/Tests/RunCMake/find_program/WindowsCom.cmake @@ -0,0 +1,6 @@ +find_program(PROG + NAMES testCom + PATHS ${CMAKE_CURRENT_SOURCE_DIR}/Win + NO_DEFAULT_PATH + ) +message(STATUS "PROG='${PROG}'") diff --git a/Tests/RunCMake/find_program/WindowsExe-stdout.txt b/Tests/RunCMake/find_program/WindowsExe-stdout.txt new file mode 100644 index 000000000..bdf48aadb --- /dev/null +++ b/Tests/RunCMake/find_program/WindowsExe-stdout.txt @@ -0,0 +1 @@ +-- PROG='[^']*/Tests/RunCMake/find_program/Win/testExe.exe' diff --git a/Tests/RunCMake/find_program/WindowsExe.cmake b/Tests/RunCMake/find_program/WindowsExe.cmake new file mode 100644 index 000000000..3a336ec61 --- /dev/null +++ b/Tests/RunCMake/find_program/WindowsExe.cmake @@ -0,0 +1,6 @@ +find_program(PROG + NAMES testExe + PATHS ${CMAKE_CURRENT_SOURCE_DIR}/Win + NO_DEFAULT_PATH + ) +message(STATUS "PROG='${PROG}'")