/* 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 #include #include #include #include #include #include #include #include #include #include "cmCMakePath.h" #include "cmExecutionStatus.h" #include "cmList.h" #include "cmListFileCache.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" cmFindBase::cmFindBase(std::string findCommandName, cmExecutionStatus& status) : cmFindCommon(status) , FindCommandName(std::move(findCommandName)) { } bool cmFindBase::ParseArguments(std::vector 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 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 (args[j] == "VALIDATOR") { if (++j == args.size()) { this->SetError("missing required argument for \"VALIDATOR\""); return false; } auto command = this->Makefile->GetState()->GetCommand(args[j]); if (command == nullptr) { this->SetError(cmStrCat( "command specified for \"VALIDATOR\" is undefined: ", args[j], '.')); return false; } // ensure a macro is not specified as validator const auto& validatorName = args[j]; cmList macros{ this->Makefile->GetProperty("MACROS") }; if (std::find_if(macros.begin(), macros.end(), [&validatorName](const std::string& item) { return cmSystemTools::Strucmp(validatorName.c_str(), item.c_str()) == 0; }) != macros.end()) { this->SetError(cmStrCat( "command specified for \"VALIDATOR\" is not a function: ", args[j], '.')); return false; } this->ValidatorName = args[j]; } 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 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; } bool cmFindBase::Validate(const std::string& path) const { if (this->ValidatorName.empty()) { return true; } // The validator command will be executed in an isolated scope. cmMakefile::ScopePushPop varScope(this->Makefile); cmMakefile::PolicyPushPop polScope(this->Makefile); static_cast(varScope); static_cast(polScope); auto resultName = cmStrCat("CMAKE_"_s, cmSystemTools::UpperCase(this->FindCommandName), "_VALIDATOR_STATUS"_s); this->Makefile->AddDefinitionBool(resultName, true); cmListFileFunction validator( this->ValidatorName, 0, 0, { cmListFileArgument(resultName, cmListFileArgument::Unquoted, 0), cmListFileArgument(path, cmListFileArgument::Quoted, 0) }); cmExecutionStatus status(*this->Makefile); if (this->Makefile->ExecuteCommand(validator, status)) { return this->Makefile->GetDefinition(resultName).IsOn(); } return false; } 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 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); } // Add PATH paths.AddEnvPath("PATH"); paths.AddSuffixes(this->SearchPathSuffixes); } namespace { struct entry_to_remove { entry_to_remove(std::string const& name, cmMakefile* makefile) : value() { if (cmValue to_skip = makefile->GetDefinition( cmStrCat("_CMAKE_SYSTEM_PREFIX_PATH_", name, "_PREFIX_COUNT"))) { cmStrToLong(*to_skip, &count); } if (cmValue prefix_value = makefile->GetDefinition( cmStrCat("_CMAKE_SYSTEM_PREFIX_PATH_", name, "_PREFIX_VALUE"))) { value = *prefix_value; } } bool valid() const { return count > 0 && !value.empty(); } void remove_self(std::vector& entries) const { if (this->valid()) { long to_skip = this->count; long index_to_remove = 0; for (const auto& path : entries) { if (path == this->value && --to_skip == 0) { break; } ++index_to_remove; } entries.erase(entries.begin() + index_to_remove); } } long count = -1; std::string value; }; } 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 ( likewise for `CMAKE_STAGING_PREFIX` ) entry_to_remove install_entry("INSTALL", this->Makefile); entry_to_remove staging_entry("STAGING", this->Makefile); if (remove_install_prefix && install_prefix_in_list && (install_entry.valid() || staging_entry.valid())) { cmValue prefix_paths = this->Makefile->GetDefinition("CMAKE_SYSTEM_PREFIX_PATH"); // remove entries from CMAKE_SYSTEM_PREFIX_PATH cmList expanded{ *prefix_paths }; install_entry.remove_self(expanded); staging_entry.remove_self(expanded); 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_STAGING_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, 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, 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, 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, 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); } }