Index: include/llvm/Support/Program.h =================================================================== --- include/llvm/Support/Program.h +++ include/llvm/Support/Program.h @@ -15,6 +15,7 @@ #define LLVM_SUPPORT_PROGRAM_H #include "llvm/ADT/ArrayRef.h" +#include "llvm/Support/ErrorOr.h" #include "llvm/Support/Path.h" #include @@ -63,6 +64,23 @@ /// the program could not be found. std::string FindProgramByName(const std::string& name); + /// \brief Find the first executable file \p Name in \p Paths. + /// + /// This does not perform hashing as a shell would but instead stats each PATH + /// entry individually so should generally be avoided. Core LLVM library + /// functions and options should instead require fully specified paths. + /// + /// \param Name name of the executable to find. If it contains any system + /// slashes, it will be returned as is. + /// \param Paths optional list of paths to search for \p Name. If empty it + /// will use the system PATH environment instead. + /// + /// \returns The fully qualified path to the first \p Name in \p Paths if it + /// exists. \p Name if \p Name has slashes in it. Otherwise an error. + ErrorOr + findProgramByName(StringRef Name, + ArrayRef Paths = ArrayRef()); + // These functions change the specified standard stream (stdin or stdout) to // binary mode. They return errc::success if the specified stream // was changed. Otherwise a platform dependent error is returned. Index: lib/Support/Unix/Program.inc =================================================================== --- lib/Support/Unix/Program.inc +++ lib/Support/Unix/Program.inc @@ -17,6 +17,7 @@ //===----------------------------------------------------------------------===// #include "Unix.h" +#include "llvm/ADT/StringExtras.h" #include "llvm/Support/Compiler.h" #include "llvm/Support/FileSystem.h" #include "llvm/Support/raw_ostream.h" @@ -100,6 +101,33 @@ return ""; } +ErrorOr sys::findProgramByName(StringRef Name, + ArrayRef Paths) { + assert(!Name.empty() && "Must have a name!"); + // Use the given path verbatim if it contains any slashes; this matches + // the behavior of sh(1) and friends. + if (Name.find('/') != StringRef::npos) + return std::string(Name); + + if (Paths.empty()) { + SmallVector SearchPaths; + SplitString(std::getenv("PATH"), SearchPaths, ":"); + return findProgramByName(Name, SearchPaths); + } + + for (auto Path : Paths) { + if (Path.empty()) + continue; + + // Check to see if this first directory contains the executable... + SmallString<128> FilePath(Path); + sys::path::append(FilePath, Name); + if (sys::fs::can_execute(FilePath.c_str())) + return std::string(FilePath.str()); // Found the executable! + } + return std::errc::no_such_file_or_directory; +} + static bool RedirectIO(const StringRef *Path, int FD, std::string* ErrMsg) { if (!Path) // Noop return false; Index: lib/Support/Windows/Program.inc =================================================================== --- lib/Support/Windows/Program.inc +++ lib/Support/Windows/Program.inc @@ -12,9 +12,11 @@ //===----------------------------------------------------------------------===// #include "WindowsSupport.h" +#include "llvm/ADT/StringExtras.h" #include "llvm/Support/ConvertUTF.h" #include "llvm/Support/FileSystem.h" #include "llvm/Support/raw_ostream.h" +#include "llvm/Support/WindowsError.h" #include #include #include @@ -69,6 +71,68 @@ return std::string(result.data(), result.size()); } +ErrorOr sys::findProgramByName(StringRef Name, + ArrayRef Paths) { + assert(!Name.empty() && "Must have a name!"); + + if (Name.find_first_of("/\\") != StringRef::npos) + return std::string(Name); + + const char16_t *Path = nullptr; + std::u16string PathStorage; + if (!Paths.empty()) { + PathStorage.reserve(Paths.size() * MAX_PATH); + for (int i = 0; i < Paths.size(); ++i) { + if (i) + PathStorage.push_back(';'); + StringRef P = Paths[i]; + SmallVector TmpPath; + if (std::error_code EC = windows::UTF8ToUTF16(P, TmpPath)) + return EC; + PathStorage.append(TmpPath.begin(), TmpPath.end()); + } + Path = PathStorage.c_str(); + } + + SmallVector U16Name; + if (std::error_code EC = windows::UTF8ToUTF16(Name, U16Name)) + return EC; + + SmallVector PathExts; + PathExts.push_back(""); + SplitString(std::getenv("PATHEXT"), PathExts, ";"); + + SmallVector U16Result; + DWORD Len = MAX_PATH; + for (StringRef Ext : PathExts) { + SmallVector U16Ext; + if (std::error_code EC = windows::UTF8ToUTF16(Ext, U16Ext)) + return EC; + + do { + U16Result.reserve(Len); + Len = ::SearchPathW((const wchar_t *)Path, c_str(U16Name), + U16Ext.empty() ? nullptr : c_str(U16Ext), + U16Result.capacity(), U16Result.data(), nullptr); + } while (Len > U16Result.capacity()); + + if (Len != 0) + break; // Found it. + } + + if (Len == 0) + return mapWindowsError(::GetLastError()); + + U16Result.set_size(Len); + + SmallVector U8Result; + if (std::error_code EC = + windows::UTF16ToUTF8(U16Result.data(), U16Result.size(), U8Result)) + return EC; + + return std::string(U8Result.begin(), U8Result.end()); +} + static HANDLE RedirectIO(const StringRef *path, int fd, std::string* ErrMsg) { HANDLE h; if (path == 0) {