| /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying |
| file Copyright.txt or https://cmake.org/licensing for details. */ |
| #include "cmFindBase.h" |
| |
| #include <cstddef> |
| #include <deque> |
| #include <map> |
| #include <utility> |
| |
| #include <cm/optional> |
| #include <cmext/algorithm> |
| |
| #include "cmCMakePath.h" |
| #include "cmMakefile.h" |
| #include "cmMessageType.h" |
| #include "cmPolicies.h" |
| #include "cmRange.h" |
| #include "cmSearchPath.h" |
| #include "cmState.h" |
| #include "cmStateTypes.h" |
| #include "cmStringAlgorithms.h" |
| #include "cmSystemTools.h" |
| #include "cmValue.h" |
| #include "cmWindowsRegistry.h" |
| #include "cmake.h" |
| |
| class cmExecutionStatus; |
| |
| cmFindBase::cmFindBase(std::string findCommandName, cmExecutionStatus& status) |
| : cmFindCommon(status) |
| , FindCommandName(std::move(findCommandName)) |
| { |
| } |
| |
| bool cmFindBase::ParseArguments(std::vector<std::string> const& argsIn) |
| { |
| if (argsIn.size() < 2) { |
| this->SetError("called with incorrect number of arguments"); |
| return false; |
| } |
| |
| // copy argsIn into args so it can be modified, |
| // in the process extract the DOC "documentation" |
| // and handle options NO_CACHE and ENV |
| size_t size = argsIn.size(); |
| std::vector<std::string> args; |
| bool foundDoc = false; |
| for (unsigned int j = 0; j < size; ++j) { |
| if (foundDoc || argsIn[j] != "DOC") { |
| if (argsIn[j] == "NO_CACHE") { |
| this->StoreResultInCache = false; |
| } else if (argsIn[j] == "ENV") { |
| if (j + 1 < size) { |
| j++; |
| cmSystemTools::GetPath(args, argsIn[j].c_str()); |
| } |
| } else { |
| args.push_back(argsIn[j]); |
| } |
| } else { |
| if (j + 1 < size) { |
| foundDoc = true; |
| this->VariableDocumentation = argsIn[j + 1]; |
| j++; |
| if (j >= size) { |
| break; |
| } |
| } |
| } |
| } |
| if (args.size() < 2) { |
| this->SetError("called with incorrect number of arguments"); |
| return false; |
| } |
| this->VariableName = args[0]; |
| if (this->CheckForVariableDefined()) { |
| this->AlreadyDefined = true; |
| return true; |
| } |
| |
| // Find what search path locations have been enabled/disable |
| this->SelectDefaultSearchModes(); |
| |
| // Find the current root path mode. |
| this->SelectDefaultRootPathMode(); |
| |
| // Find the current bundle/framework search policy. |
| this->SelectDefaultMacMode(); |
| |
| bool newStyle = false; |
| enum Doing |
| { |
| DoingNone, |
| DoingNames, |
| DoingPaths, |
| DoingPathSuffixes, |
| DoingHints |
| }; |
| Doing doing = DoingNames; // assume it starts with a name |
| for (unsigned int j = 1; j < args.size(); ++j) { |
| if (args[j] == "NAMES") { |
| doing = DoingNames; |
| newStyle = true; |
| } else if (args[j] == "PATHS") { |
| doing = DoingPaths; |
| newStyle = true; |
| } else if (args[j] == "HINTS") { |
| doing = DoingHints; |
| newStyle = true; |
| } else if (args[j] == "PATH_SUFFIXES") { |
| doing = DoingPathSuffixes; |
| newStyle = true; |
| } else if (args[j] == "NAMES_PER_DIR") { |
| doing = DoingNone; |
| if (this->NamesPerDirAllowed) { |
| this->NamesPerDir = true; |
| } else { |
| this->SetError("does not support NAMES_PER_DIR"); |
| return false; |
| } |
| } else if (args[j] == "NO_SYSTEM_PATH") { |
| doing = DoingNone; |
| this->NoDefaultPath = true; |
| } else if (args[j] == "REQUIRED") { |
| doing = DoingNone; |
| this->Required = true; |
| newStyle = true; |
| } else if (args[j] == "REGISTRY_VIEW") { |
| if (++j == args.size()) { |
| this->SetError("missing required argument for \"REGISTRY_VIEW\""); |
| return false; |
| } |
| auto view = cmWindowsRegistry::ToView(args[j]); |
| if (view) { |
| this->RegistryView = *view; |
| } else { |
| this->SetError( |
| cmStrCat("given invalid value for \"REGISTRY_VIEW\": ", args[j])); |
| return false; |
| } |
| } else if (this->CheckCommonArgument(args[j])) { |
| doing = DoingNone; |
| } else { |
| // Some common arguments were accidentally supported by CMake |
| // 2.4 and 2.6.0 in the short-hand form of the command, so we |
| // must support it even though it is not documented. |
| if (doing == DoingNames) { |
| this->Names.push_back(args[j]); |
| } else if (doing == DoingPaths) { |
| this->UserGuessArgs.push_back(args[j]); |
| } else if (doing == DoingHints) { |
| this->UserHintsArgs.push_back(args[j]); |
| } else if (doing == DoingPathSuffixes) { |
| this->AddPathSuffix(args[j]); |
| } |
| } |
| } |
| |
| if (this->VariableDocumentation.empty()) { |
| this->VariableDocumentation = "Where can "; |
| if (this->Names.empty()) { |
| this->VariableDocumentation += "the (unknown) library be found"; |
| } else if (this->Names.size() == 1) { |
| this->VariableDocumentation += |
| "the " + this->Names.front() + " library be found"; |
| } else { |
| this->VariableDocumentation += "one of the "; |
| this->VariableDocumentation += |
| cmJoin(cmMakeRange(this->Names).retreat(1), ", "); |
| this->VariableDocumentation += |
| " or " + this->Names.back() + " libraries be found"; |
| } |
| } |
| |
| // look for old style |
| // FIND_*(VAR name path1 path2 ...) |
| if (!newStyle && !this->Names.empty()) { |
| // All the short-hand arguments have been recorded as names. |
| std::vector<std::string> shortArgs = this->Names; |
| this->Names.clear(); // clear out any values in Names |
| this->Names.push_back(shortArgs[0]); |
| cm::append(this->UserGuessArgs, shortArgs.begin() + 1, shortArgs.end()); |
| } |
| this->ExpandPaths(); |
| |
| this->ComputeFinalPaths(IgnorePaths::Yes); |
| |
| return true; |
| } |
| |
| void cmFindBase::ExpandPaths() |
| { |
| if (!this->NoDefaultPath) { |
| if (!this->NoPackageRootPath) { |
| this->FillPackageRootPath(); |
| } |
| if (!this->NoCMakePath) { |
| this->FillCMakeVariablePath(); |
| } |
| if (!this->NoCMakeEnvironmentPath) { |
| this->FillCMakeEnvironmentPath(); |
| } |
| } |
| this->FillUserHintsPath(); |
| if (!this->NoDefaultPath) { |
| if (!this->NoSystemEnvironmentPath) { |
| this->FillSystemEnvironmentPath(); |
| } |
| if (!this->NoCMakeSystemPath) { |
| this->FillCMakeSystemVariablePath(); |
| } |
| } |
| this->FillUserGuessPath(); |
| } |
| |
| void cmFindBase::FillCMakeEnvironmentPath() |
| { |
| cmSearchPath& paths = this->LabeledPaths[PathLabel::CMakeEnvironment]; |
| |
| // Add CMAKE_*_PATH environment variables |
| std::string var = cmStrCat("CMAKE_", this->CMakePathName, "_PATH"); |
| paths.AddEnvPrefixPath("CMAKE_PREFIX_PATH"); |
| paths.AddEnvPath(var); |
| |
| if (this->CMakePathName == "PROGRAM") { |
| paths.AddEnvPath("CMAKE_APPBUNDLE_PATH"); |
| } else { |
| paths.AddEnvPath("CMAKE_FRAMEWORK_PATH"); |
| } |
| paths.AddSuffixes(this->SearchPathSuffixes); |
| } |
| |
| void cmFindBase::FillPackageRootPath() |
| { |
| cmSearchPath& paths = this->LabeledPaths[PathLabel::PackageRoot]; |
| |
| // Add the PACKAGE_ROOT_PATH from each enclosing find_package call. |
| for (std::vector<std::string> const& pkgPaths : |
| cmReverseRange(this->Makefile->FindPackageRootPathStack)) { |
| paths.AddPrefixPaths(pkgPaths); |
| } |
| |
| paths.AddSuffixes(this->SearchPathSuffixes); |
| } |
| |
| void cmFindBase::FillCMakeVariablePath() |
| { |
| cmSearchPath& paths = this->LabeledPaths[PathLabel::CMake]; |
| |
| // Add CMake variables of the same name as the previous environment |
| // variables CMAKE_*_PATH to be used most of the time with -D |
| // command line options |
| std::string var = cmStrCat("CMAKE_", this->CMakePathName, "_PATH"); |
| paths.AddCMakePrefixPath("CMAKE_PREFIX_PATH"); |
| paths.AddCMakePath(var); |
| |
| if (this->CMakePathName == "PROGRAM") { |
| paths.AddCMakePath("CMAKE_APPBUNDLE_PATH"); |
| } else { |
| paths.AddCMakePath("CMAKE_FRAMEWORK_PATH"); |
| } |
| paths.AddSuffixes(this->SearchPathSuffixes); |
| } |
| |
| void cmFindBase::FillSystemEnvironmentPath() |
| { |
| cmSearchPath& paths = this->LabeledPaths[PathLabel::SystemEnvironment]; |
| |
| // Add LIB or INCLUDE |
| if (!this->EnvironmentPath.empty()) { |
| paths.AddEnvPath(this->EnvironmentPath); |
| #if defined(_WIN32) || defined(__CYGWIN__) |
| paths.AddEnvPrefixPath("PATH", true); |
| #endif |
| } |
| // Add PATH |
| paths.AddEnvPath("PATH"); |
| paths.AddSuffixes(this->SearchPathSuffixes); |
| } |
| |
| void cmFindBase::FillCMakeSystemVariablePath() |
| { |
| cmSearchPath& paths = this->LabeledPaths[PathLabel::CMakeSystem]; |
| |
| const bool install_prefix_in_list = |
| !this->Makefile->IsOn("CMAKE_FIND_NO_INSTALL_PREFIX"); |
| const bool remove_install_prefix = this->NoCMakeInstallPath; |
| const bool add_install_prefix = !this->NoCMakeInstallPath && |
| this->Makefile->IsDefinitionSet("CMAKE_FIND_USE_INSTALL_PREFIX"); |
| |
| // We have 3 possible states for `CMAKE_SYSTEM_PREFIX_PATH` and |
| // `CMAKE_INSTALL_PREFIX`. |
| // Either we need to remove `CMAKE_INSTALL_PREFIX`, add |
| // `CMAKE_INSTALL_PREFIX`, or do nothing. |
| // |
| // When we need to remove `CMAKE_INSTALL_PREFIX` we remove the Nth occurrence |
| // of `CMAKE_INSTALL_PREFIX` from `CMAKE_SYSTEM_PREFIX_PATH`, where `N` is |
| // computed by `CMakeSystemSpecificInformation.cmake` while constructing |
| // `CMAKE_SYSTEM_PREFIX_PATH`. This ensures that if projects / toolchains |
| // have removed `CMAKE_INSTALL_PREFIX` from the list, we don't remove |
| // some other entry by mistake |
| long install_prefix_count = -1; |
| std::string install_path_to_remove; |
| if (cmValue to_skip = this->Makefile->GetDefinition( |
| "_CMAKE_SYSTEM_PREFIX_PATH_INSTALL_PREFIX_COUNT")) { |
| cmStrToLong(to_skip, &install_prefix_count); |
| } |
| if (cmValue install_value = this->Makefile->GetDefinition( |
| "_CMAKE_SYSTEM_PREFIX_PATH_INSTALL_PREFIX_VALUE")) { |
| install_path_to_remove = *install_value; |
| } |
| |
| if (remove_install_prefix && install_prefix_in_list && |
| install_prefix_count > 0 && !install_path_to_remove.empty()) { |
| cmValue prefix_paths = |
| this->Makefile->GetDefinition("CMAKE_SYSTEM_PREFIX_PATH"); |
| |
| // remove entry from CMAKE_SYSTEM_PREFIX_PATH |
| std::vector<std::string> expanded = cmExpandedList(*prefix_paths); |
| long index_to_remove = 0; |
| for (const auto& path : expanded) { |
| if (path == install_path_to_remove && --install_prefix_count == 0) { |
| break; |
| } |
| ++index_to_remove; |
| } |
| expanded.erase(expanded.begin() + index_to_remove); |
| paths.AddPrefixPaths(expanded, |
| this->Makefile->GetCurrentSourceDirectory().c_str()); |
| } else if (add_install_prefix && !install_prefix_in_list) { |
| |
| paths.AddCMakePrefixPath("CMAKE_INSTALL_PREFIX"); |
| paths.AddCMakePrefixPath("CMAKE_SYSTEM_PREFIX_PATH"); |
| } else { |
| // Otherwise the current setup of `CMAKE_SYSTEM_PREFIX_PATH` is correct |
| paths.AddCMakePrefixPath("CMAKE_SYSTEM_PREFIX_PATH"); |
| } |
| |
| std::string var = cmStrCat("CMAKE_SYSTEM_", this->CMakePathName, "_PATH"); |
| paths.AddCMakePath(var); |
| |
| if (this->CMakePathName == "PROGRAM") { |
| paths.AddCMakePath("CMAKE_SYSTEM_APPBUNDLE_PATH"); |
| } else { |
| paths.AddCMakePath("CMAKE_SYSTEM_FRAMEWORK_PATH"); |
| } |
| paths.AddSuffixes(this->SearchPathSuffixes); |
| } |
| |
| void cmFindBase::FillUserHintsPath() |
| { |
| cmSearchPath& paths = this->LabeledPaths[PathLabel::Hints]; |
| |
| for (std::string const& p : this->UserHintsArgs) { |
| paths.AddUserPath(p); |
| } |
| paths.AddSuffixes(this->SearchPathSuffixes); |
| } |
| |
| void cmFindBase::FillUserGuessPath() |
| { |
| cmSearchPath& paths = this->LabeledPaths[PathLabel::Guess]; |
| |
| for (std::string const& p : this->UserGuessArgs) { |
| paths.AddUserPath(p); |
| } |
| paths.AddSuffixes(this->SearchPathSuffixes); |
| } |
| |
| bool cmFindBase::CheckForVariableDefined() |
| { |
| if (cmValue value = this->Makefile->GetDefinition(this->VariableName)) { |
| cmState* state = this->Makefile->GetState(); |
| cmValue cacheEntry = state->GetCacheEntryValue(this->VariableName); |
| bool found = !cmIsNOTFOUND(*value); |
| bool cached = cacheEntry != nullptr; |
| auto cacheType = cached ? state->GetCacheEntryType(this->VariableName) |
| : cmStateEnums::UNINITIALIZED; |
| |
| if (cached && cacheType != cmStateEnums::UNINITIALIZED) { |
| this->VariableType = cacheType; |
| if (const auto& hs = |
| state->GetCacheEntryProperty(this->VariableName, "HELPSTRING")) { |
| this->VariableDocumentation = *hs; |
| } |
| } |
| |
| if (found) { |
| // If the user specifies the entry on the command line without a |
| // type we should add the type and docstring but keep the |
| // original value. Tell the subclass implementations to do |
| // this. |
| if (cached && cacheType == cmStateEnums::UNINITIALIZED) { |
| this->AlreadyInCacheWithoutMetaInfo = true; |
| } |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| void cmFindBase::NormalizeFindResult() |
| { |
| if (this->Makefile->GetPolicyStatus(cmPolicies::CMP0125) == |
| cmPolicies::NEW) { |
| // ensure the path returned by find_* command is absolute |
| const auto& existingValue = |
| this->Makefile->GetDefinition(this->VariableName); |
| std::string value; |
| if (!existingValue->empty()) { |
| value = |
| cmCMakePath(*existingValue, cmCMakePath::auto_format) |
| .Absolute(cmCMakePath( |
| this->Makefile->GetCMakeInstance()->GetCMakeWorkingDirectory())) |
| .Normal() |
| .GenericString(); |
| // value = cmSystemTools::CollapseFullPath(*existingValue); |
| if (!cmSystemTools::FileExists(value, false)) { |
| value = *existingValue; |
| } |
| } |
| |
| if (this->StoreResultInCache) { |
| // If the user specifies the entry on the command line without a |
| // type we should add the type and docstring but keep the original |
| // value. |
| if (value != *existingValue || this->AlreadyInCacheWithoutMetaInfo) { |
| this->Makefile->GetCMakeInstance()->AddCacheEntry( |
| this->VariableName, value, this->VariableDocumentation.c_str(), |
| this->VariableType); |
| if (this->Makefile->GetPolicyStatus(cmPolicies::CMP0126) == |
| cmPolicies::NEW) { |
| if (this->Makefile->IsNormalDefinitionSet(this->VariableName)) { |
| this->Makefile->AddDefinition(this->VariableName, value); |
| } |
| } else { |
| // if there was a definition then remove it |
| // This is required to ensure same behavior as |
| // cmMakefile::AddCacheDefinition. |
| this->Makefile->RemoveDefinition(this->VariableName); |
| } |
| } |
| } else { |
| // ensure a normal variable is defined. |
| this->Makefile->AddDefinition(this->VariableName, value); |
| } |
| } else { |
| // If the user specifies the entry on the command line without a |
| // type we should add the type and docstring but keep the original |
| // value. |
| if (this->StoreResultInCache) { |
| if (this->AlreadyInCacheWithoutMetaInfo) { |
| this->Makefile->AddCacheDefinition(this->VariableName, "", |
| this->VariableDocumentation.c_str(), |
| this->VariableType); |
| if (this->Makefile->GetPolicyStatus(cmPolicies::CMP0126) == |
| cmPolicies::NEW && |
| this->Makefile->IsNormalDefinitionSet(this->VariableName)) { |
| this->Makefile->AddDefinition( |
| this->VariableName, |
| *this->Makefile->GetCMakeInstance()->GetCacheDefinition( |
| this->VariableName)); |
| } |
| } |
| } else { |
| // ensure a normal variable is defined. |
| this->Makefile->AddDefinition( |
| this->VariableName, |
| this->Makefile->GetSafeDefinition(this->VariableName)); |
| } |
| } |
| } |
| |
| void cmFindBase::StoreFindResult(const std::string& value) |
| { |
| bool force = |
| this->Makefile->GetPolicyStatus(cmPolicies::CMP0125) == cmPolicies::NEW; |
| bool updateNormalVariable = |
| this->Makefile->GetPolicyStatus(cmPolicies::CMP0126) == cmPolicies::NEW; |
| |
| if (!value.empty()) { |
| if (this->StoreResultInCache) { |
| this->Makefile->AddCacheDefinition(this->VariableName, value, |
| this->VariableDocumentation.c_str(), |
| this->VariableType, force); |
| if (updateNormalVariable && |
| this->Makefile->IsNormalDefinitionSet(this->VariableName)) { |
| this->Makefile->AddDefinition(this->VariableName, value); |
| } |
| } else { |
| this->Makefile->AddDefinition(this->VariableName, value); |
| } |
| |
| return; |
| } |
| |
| auto notFound = cmStrCat(this->VariableName, "-NOTFOUND"); |
| if (this->StoreResultInCache) { |
| this->Makefile->AddCacheDefinition(this->VariableName, notFound, |
| this->VariableDocumentation.c_str(), |
| this->VariableType, force); |
| if (updateNormalVariable && |
| this->Makefile->IsNormalDefinitionSet(this->VariableName)) { |
| this->Makefile->AddDefinition(this->VariableName, notFound); |
| } |
| } else { |
| this->Makefile->AddDefinition(this->VariableName, notFound); |
| } |
| |
| if (this->Required) { |
| this->Makefile->IssueMessage( |
| MessageType::FATAL_ERROR, |
| cmStrCat("Could not find ", this->VariableName, " using the following ", |
| (this->FindCommandName == "find_file" || |
| this->FindCommandName == "find_path" |
| ? "files" |
| : "names"), |
| ": ", cmJoin(this->Names, ", "))); |
| cmSystemTools::SetFatalErrorOccurred(); |
| } |
| } |
| |
| cmFindBaseDebugState::cmFindBaseDebugState(std::string commandName, |
| cmFindBase const* findBase) |
| : FindCommand(findBase) |
| , CommandName(std::move(commandName)) |
| { |
| } |
| |
| cmFindBaseDebugState::~cmFindBaseDebugState() |
| { |
| if (this->FindCommand->DebugMode) { |
| std::string buffer = |
| cmStrCat(this->CommandName, " called with the following settings:\n"); |
| buffer += cmStrCat(" VAR: ", this->FindCommand->VariableName, "\n"); |
| buffer += cmStrCat( |
| " NAMES: ", cmWrap("\"", this->FindCommand->Names, "\"", "\n "), |
| "\n"); |
| buffer += cmStrCat( |
| " Documentation: ", this->FindCommand->VariableDocumentation, "\n"); |
| buffer += " Framework\n"; |
| buffer += cmStrCat(" Only Search Frameworks: ", |
| this->FindCommand->SearchFrameworkOnly, "\n"); |
| |
| buffer += cmStrCat(" Search Frameworks Last: ", |
| this->FindCommand->SearchFrameworkLast, "\n"); |
| buffer += cmStrCat(" Search Frameworks First: ", |
| this->FindCommand->SearchFrameworkFirst, "\n"); |
| buffer += " AppBundle\n"; |
| buffer += cmStrCat(" Only Search AppBundle: ", |
| this->FindCommand->SearchAppBundleOnly, "\n"); |
| buffer += cmStrCat(" Search AppBundle Last: ", |
| this->FindCommand->SearchAppBundleLast, "\n"); |
| buffer += cmStrCat(" Search AppBundle First: ", |
| this->FindCommand->SearchAppBundleFirst, "\n"); |
| |
| if (this->FindCommand->NoDefaultPath) { |
| buffer += " NO_DEFAULT_PATH Enabled\n"; |
| } else { |
| buffer += cmStrCat( |
| " CMAKE_FIND_USE_CMAKE_PATH: ", !this->FindCommand->NoCMakePath, "\n", |
| " CMAKE_FIND_USE_CMAKE_ENVIRONMENT_PATH: ", |
| !this->FindCommand->NoCMakeEnvironmentPath, "\n", |
| " CMAKE_FIND_USE_SYSTEM_ENVIRONMENT_PATH: ", |
| !this->FindCommand->NoSystemEnvironmentPath, "\n", |
| " CMAKE_FIND_USE_CMAKE_SYSTEM_PATH: ", |
| !this->FindCommand->NoCMakeSystemPath, "\n", |
| " CMAKE_FIND_USE_INSTALL_PREFIX: ", |
| !this->FindCommand->NoCMakeInstallPath, "\n"); |
| } |
| |
| buffer += |
| cmStrCat(this->CommandName, " considered the following locations:\n"); |
| for (auto const& state : this->FailedSearchLocations) { |
| std::string path = cmStrCat(" ", state.path); |
| if (!state.regexName.empty()) { |
| path = cmStrCat(path, "/", state.regexName); |
| } |
| buffer += cmStrCat(path, "\n"); |
| } |
| |
| if (!this->FoundSearchLocation.path.empty()) { |
| buffer += cmStrCat("The item was found at\n ", |
| this->FoundSearchLocation.path, "\n"); |
| } else { |
| buffer += "The item was not found.\n"; |
| } |
| |
| this->FindCommand->DebugMessage(buffer); |
| } |
| } |
| |
| void cmFindBaseDebugState::FoundAt(std::string const& path, |
| std::string regexName) |
| { |
| if (this->FindCommand->DebugMode) { |
| this->FoundSearchLocation = DebugLibState{ std::move(regexName), path }; |
| } |
| } |
| |
| void cmFindBaseDebugState::FailedAt(std::string const& path, |
| std::string regexName) |
| { |
| if (this->FindCommand->DebugMode) { |
| this->FailedSearchLocations.emplace_back(std::move(regexName), path); |
| } |
| } |