| /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying | |
| file LICENSE.rst or https://cmake.org/licensing for details. */ | |
| #include "cmCacheManager.h" | |
| #include <algorithm> | |
| #include <cstdio> | |
| #include <cstring> | |
| #include <string> | |
| #include "cmsys/FStream.hxx" | |
| #include "cmsys/Glob.hxx" | |
| #include "cmGeneratedFileStream.h" | |
| #include "cmList.h" | |
| #include "cmMessageType.h" | |
| #include "cmMessenger.h" | |
| #include "cmState.h" | |
| #include "cmStringAlgorithms.h" | |
| #include "cmSystemTools.h" | |
| #include "cmVersion.h" | |
| void cmCacheManager::CleanCMakeFiles(std::string const& path) | |
| { | |
| std::string glob = cmStrCat(path, "/CMakeFiles/*.cmake"); | |
| cmsys::Glob globIt; | |
| globIt.FindFiles(glob); | |
| std::vector<std::string> files = globIt.GetFiles(); | |
| std::for_each(files.begin(), files.end(), cmSystemTools::RemoveFile); | |
| } | |
| bool cmCacheManager::LoadCache(std::string const& path, bool internal, | |
| std::set<std::string>& excludes, | |
| std::set<std::string>& includes) | |
| { | |
| std::string cacheFile = cmStrCat(path, "/CMakeCache.txt"); | |
| // clear the old cache, if we are reading in internal values | |
| if (internal) { | |
| this->Cache.clear(); | |
| } | |
| if (!cmSystemTools::FileExists(cacheFile)) { | |
| this->CleanCMakeFiles(path); | |
| return false; | |
| } | |
| cmsys::ifstream fin(cacheFile.c_str()); | |
| if (!fin) { | |
| return false; | |
| } | |
| char const* realbuffer; | |
| std::string buffer; | |
| std::string entryKey; | |
| unsigned int lineno = 0; | |
| while (fin) { | |
| // Format is key:type=value | |
| std::string helpString; | |
| CacheEntry e; | |
| cmSystemTools::GetLineFromStream(fin, buffer); | |
| lineno++; | |
| realbuffer = buffer.c_str(); | |
| while (*realbuffer == ' ' || *realbuffer == '\t' || *realbuffer == '\r' || | |
| *realbuffer == '\n') { | |
| if (*realbuffer == '\n') { | |
| lineno++; | |
| } | |
| realbuffer++; | |
| } | |
| // skip blank lines and comment lines | |
| if (realbuffer[0] == '#' || realbuffer[0] == 0) { | |
| continue; | |
| } | |
| while (realbuffer[0] == '/' && realbuffer[1] == '/') { | |
| if ((realbuffer[2] == '\\') && (realbuffer[3] == 'n')) { | |
| helpString = cmStrCat(std::move(helpString), '\n', &realbuffer[4]); | |
| } else { | |
| helpString = cmStrCat(std::move(helpString), &realbuffer[2]); | |
| } | |
| cmSystemTools::GetLineFromStream(fin, buffer); | |
| lineno++; | |
| realbuffer = buffer.c_str(); | |
| if (!fin) { | |
| continue; | |
| } | |
| } | |
| e.SetProperty("HELPSTRING", helpString); | |
| if (cmState::ParseCacheEntry(realbuffer, entryKey, e.Value, e.Type)) { | |
| if (excludes.find(entryKey) == excludes.end()) { | |
| // Load internal values if internal is set. | |
| // If the entry is not internal to the cache being loaded | |
| // or if it is in the list of internal entries to be | |
| // imported, load it. | |
| if (internal || (e.Type != cmStateEnums::INTERNAL) || | |
| (includes.find(entryKey) != includes.end())) { | |
| // If we are loading the cache from another project, | |
| // make all loaded entries internal so that it is | |
| // not visible in the gui | |
| if (!internal) { | |
| e.Type = cmStateEnums::INTERNAL; | |
| helpString = cmStrCat("DO NOT EDIT, ", entryKey, | |
| " loaded from external file. " | |
| "To change this value edit this file: ", | |
| path, "/CMakeCache.txt"); | |
| e.SetProperty("HELPSTRING", helpString); | |
| } | |
| if (!this->ReadPropertyEntry(entryKey, e)) { | |
| e.Initialized = true; | |
| this->Cache[entryKey] = e; | |
| } | |
| } | |
| } | |
| } else { | |
| cmSystemTools::Error(cmStrCat("Parse error in cache file ", cacheFile, | |
| " on line ", lineno, | |
| ". Offending entry: ", realbuffer)); | |
| } | |
| } | |
| this->CacheMajorVersion = 0; | |
| this->CacheMinorVersion = 0; | |
| if (cmValue cmajor = | |
| this->GetInitializedCacheValue("CMAKE_CACHE_MAJOR_VERSION")) { | |
| unsigned int v = 0; | |
| if (sscanf(cmajor->c_str(), "%u", &v) == 1) { | |
| this->CacheMajorVersion = v; | |
| } | |
| if (cmValue cminor = | |
| this->GetInitializedCacheValue("CMAKE_CACHE_MINOR_VERSION")) { | |
| if (sscanf(cminor->c_str(), "%u", &v) == 1) { | |
| this->CacheMinorVersion = v; | |
| } | |
| } | |
| } else { | |
| // CMake version not found in the list file. | |
| // Set as version 0.0 | |
| this->AddCacheEntry("CMAKE_CACHE_MINOR_VERSION", "0", | |
| "Minor version of cmake used to create the " | |
| "current loaded cache", | |
| cmStateEnums::INTERNAL); | |
| this->AddCacheEntry("CMAKE_CACHE_MAJOR_VERSION", "0", | |
| "Major version of cmake used to create the " | |
| "current loaded cache", | |
| cmStateEnums::INTERNAL); | |
| } | |
| // check to make sure the cache directory has not | |
| // been moved | |
| cmValue oldDir = this->GetInitializedCacheValue("CMAKE_CACHEFILE_DIR"); | |
| if (internal && oldDir) { | |
| std::string currentcwd = path; | |
| std::string oldcwd = *oldDir; | |
| cmSystemTools::ConvertToUnixSlashes(currentcwd); | |
| currentcwd = cmStrCat(std::move(currentcwd), "/CMakeCache.txt"); | |
| oldcwd = cmStrCat(std::move(oldcwd), "/CMakeCache.txt"); | |
| if (!cmSystemTools::SameFile(oldcwd, currentcwd)) { | |
| cmValue dir = this->GetInitializedCacheValue("CMAKE_CACHEFILE_DIR"); | |
| std::string message = | |
| cmStrCat("The current CMakeCache.txt directory ", currentcwd, | |
| " is different than the directory ", (dir ? *dir : ""), | |
| " where CMakeCache.txt was created. This may result " | |
| "in binaries being created in the wrong place. If you " | |
| "are not sure, reedit the CMakeCache.txt"); | |
| cmSystemTools::Error(message); | |
| } | |
| } | |
| this->CacheLoaded = true; | |
| return true; | |
| } | |
| char const* cmCacheManager::PersistentProperties[] = { "ADVANCED", "MODIFIED", | |
| "STRINGS" }; | |
| bool cmCacheManager::ReadPropertyEntry(std::string const& entryKey, | |
| CacheEntry const& e) | |
| { | |
| // All property entries are internal. | |
| if (e.Type != cmStateEnums::INTERNAL) { | |
| return false; | |
| } | |
| char const* end = entryKey.c_str() + entryKey.size(); | |
| for (char const* p : cmCacheManager::PersistentProperties) { | |
| std::string::size_type plen = strlen(p) + 1; | |
| if (entryKey.size() > plen && *(end - plen) == '-' && | |
| strcmp(end - plen + 1, p) == 0) { | |
| std::string key = entryKey.substr(0, entryKey.size() - plen); | |
| if (auto* entry = this->GetCacheEntry(key)) { | |
| // Store this property on its entry. | |
| entry->SetProperty(p, e.Value); | |
| } else { | |
| // Create an entry and store the property. | |
| CacheEntry& ne = this->Cache[key]; | |
| ne.SetProperty(p, e.Value); | |
| } | |
| return true; | |
| } | |
| } | |
| return false; | |
| } | |
| void cmCacheManager::WritePropertyEntries(std::ostream& os, | |
| std::string const& entryKey, | |
| CacheEntry const& e, | |
| cmMessenger* messenger) const | |
| { | |
| for (char const* p : cmCacheManager::PersistentProperties) { | |
| if (cmValue value = e.GetProperty(p)) { | |
| std::string helpstring = | |
| cmStrCat(p, " property for variable: ", entryKey); | |
| cmCacheManager::OutputHelpString(os, helpstring); | |
| std::string key = cmStrCat(entryKey, '-', p); | |
| cmCacheManager::OutputKey(os, key); | |
| os << ":INTERNAL="; | |
| cmCacheManager::OutputValue(os, *value); | |
| os << '\n'; | |
| cmCacheManager::OutputNewlineTruncationWarning(os, key, *value, | |
| messenger); | |
| } | |
| } | |
| } | |
| bool cmCacheManager::SaveCache(std::string const& path, cmMessenger* messenger) | |
| { | |
| std::string cacheFile = cmStrCat(path, "/CMakeCache.txt"); | |
| cmGeneratedFileStream fout(cacheFile); | |
| fout.SetCopyIfDifferent(true); | |
| if (!fout) { | |
| cmSystemTools::Error( | |
| cmStrCat("Unable to open cache file for save. ", std::move(cacheFile))); | |
| cmSystemTools::ReportLastSystemError(""); | |
| return false; | |
| } | |
| // before writing the cache, update the version numbers | |
| // to the | |
| this->AddCacheEntry("CMAKE_CACHE_MAJOR_VERSION", | |
| std::to_string(cmVersion::GetMajorVersion()), | |
| "Major version of cmake used to create the " | |
| "current loaded cache", | |
| cmStateEnums::INTERNAL); | |
| this->AddCacheEntry("CMAKE_CACHE_MINOR_VERSION", | |
| std::to_string(cmVersion::GetMinorVersion()), | |
| "Minor version of cmake used to create the " | |
| "current loaded cache", | |
| cmStateEnums::INTERNAL); | |
| this->AddCacheEntry("CMAKE_CACHE_PATCH_VERSION", | |
| std::to_string(cmVersion::GetPatchVersion()), | |
| "Patch version of cmake used to create the " | |
| "current loaded cache", | |
| cmStateEnums::INTERNAL); | |
| // Let us store the current working directory so that if somebody | |
| // Copies it, he will not be surprised | |
| std::string currentcwd = path; | |
| if (currentcwd[0] >= 'A' && currentcwd[0] <= 'Z' && currentcwd[1] == ':') { | |
| // Cast added to avoid compiler warning. Cast is ok because | |
| // value is guaranteed to fit in char by the above if... | |
| currentcwd[0] = static_cast<char>(currentcwd[0] - 'A' + 'a'); | |
| } | |
| cmSystemTools::ConvertToUnixSlashes(currentcwd); | |
| this->AddCacheEntry("CMAKE_CACHEFILE_DIR", currentcwd, | |
| "This is the directory where this CMakeCache.txt" | |
| " was created", | |
| cmStateEnums::INTERNAL); | |
| /* clang-format off */ | |
| fout << "# This is the CMakeCache file.\n" | |
| "# For build in directory: " << currentcwd << "\n" | |
| "# It was generated by CMake: " | |
| << cmSystemTools::GetCMakeCommand() | |
| << "\n" | |
| "# You can edit this file to change values found and used by cmake." | |
| "\n" | |
| "# If you do not want to change any of the values, simply exit the " | |
| "editor.\n" | |
| "# If you do want to change a value, simply edit, save, and exit " | |
| "the editor.\n" | |
| "# The syntax for the file is as follows:\n" | |
| "# KEY:TYPE=VALUE\n" | |
| "# KEY is the name of a variable in the cache.\n" | |
| "# TYPE is a hint to GUIs for the type of VALUE, DO NOT EDIT TYPE!." | |
| "\n" | |
| "# VALUE is the current value for the KEY.\n" | |
| "\n" | |
| "########################\n" | |
| "# EXTERNAL cache entries\n" | |
| "########################\n" | |
| "\n"; | |
| /* clang-format on */ | |
| for (auto const& i : this->Cache) { | |
| CacheEntry const& ce = i.second; | |
| cmStateEnums::CacheEntryType t = ce.Type; | |
| if (!ce.Initialized) { | |
| /* | |
| // This should be added in, but is not for now. | |
| cmSystemTools::Error(cmStrCat("Cache entry \"", i.first, "\" is | |
| uninitialized")); | |
| */ | |
| } else if (t != cmStateEnums::INTERNAL) { | |
| // Format is key:type=value | |
| if (cmValue help = ce.GetProperty("HELPSTRING")) { | |
| cmCacheManager::OutputHelpString(fout, *help); | |
| } else { | |
| cmCacheManager::OutputHelpString(fout, "Missing description"); | |
| } | |
| cmCacheManager::OutputKey(fout, i.first); | |
| fout << ':' << cmState::CacheEntryTypeToString(t) << '='; | |
| cmCacheManager::OutputValue(fout, ce.Value); | |
| fout << '\n'; | |
| cmCacheManager::OutputNewlineTruncationWarning(fout, i.first, ce.Value, | |
| messenger); | |
| fout << '\n'; | |
| } | |
| } | |
| fout << "\n" | |
| "########################\n" | |
| "# INTERNAL cache entries\n" | |
| "########################\n" | |
| "\n"; | |
| for (auto const& i : this->Cache) { | |
| if (!i.second.Initialized) { | |
| continue; | |
| } | |
| cmStateEnums::CacheEntryType t = i.second.GetType(); | |
| this->WritePropertyEntries(fout, i.first, i.second, messenger); | |
| if (t == cmStateEnums::INTERNAL) { | |
| // Format is key:type=value | |
| if (cmValue help = i.second.GetProperty("HELPSTRING")) { | |
| cmCacheManager::OutputHelpString(fout, *help); | |
| } | |
| cmCacheManager::OutputKey(fout, i.first); | |
| fout << ':' << cmState::CacheEntryTypeToString(t) << '='; | |
| cmCacheManager::OutputValue(fout, i.second.GetValue()); | |
| fout << '\n'; | |
| cmCacheManager::OutputNewlineTruncationWarning( | |
| fout, i.first, i.second.GetValue(), messenger); | |
| } | |
| } | |
| fout << '\n'; | |
| fout.Close(); | |
| std::string checkCacheFile = cmStrCat(path, "/CMakeFiles"); | |
| cmSystemTools::MakeDirectory(checkCacheFile); | |
| checkCacheFile = cmStrCat(std::move(checkCacheFile), "/cmake.check_cache"); | |
| cmsys::ofstream checkCache(checkCacheFile.c_str()); | |
| if (!checkCache) { | |
| cmSystemTools::Error( | |
| cmStrCat("Unable to open check cache file for write. ", | |
| std::move(checkCacheFile))); | |
| return false; | |
| } | |
| checkCache << "# This file is generated by cmake for dependency checking " | |
| "of the CMakeCache.txt file\n"; | |
| return true; | |
| } | |
| bool cmCacheManager::DeleteCache(std::string const& path) | |
| { | |
| std::string cacheFile = path; | |
| cmSystemTools::ConvertToUnixSlashes(cacheFile); | |
| std::string cmakeFiles = cacheFile; | |
| cacheFile += "/CMakeCache.txt"; | |
| if (cmSystemTools::FileExists(cacheFile)) { | |
| cmSystemTools::RemoveFile(cacheFile); | |
| // now remove the files in the CMakeFiles directory | |
| // this cleans up language cache files | |
| cmakeFiles += "/CMakeFiles"; | |
| if (cmSystemTools::FileIsDirectory(cmakeFiles)) { | |
| cmSystemTools::RemoveADirectory(cmakeFiles); | |
| } | |
| } | |
| return true; | |
| } | |
| void cmCacheManager::OutputKey(std::ostream& fout, std::string const& key) | |
| { | |
| // support : in key name by double quoting | |
| char const* q = | |
| (key.find(':') != std::string::npos || cmHasLiteralPrefix(key, "//")) | |
| ? "\"" | |
| : ""; | |
| fout << q << key << q; | |
| } | |
| void cmCacheManager::OutputValue(std::ostream& fout, std::string const& value) | |
| { | |
| // look for and truncate newlines | |
| std::string::size_type newline = value.find('\n'); | |
| if (newline != std::string::npos) { | |
| std::string truncated = value.substr(0, newline); | |
| OutputValueNoNewlines(fout, truncated); | |
| } else { | |
| OutputValueNoNewlines(fout, value); | |
| } | |
| } | |
| void cmCacheManager::OutputValueNoNewlines(std::ostream& fout, | |
| std::string const& value) | |
| { | |
| // if value has trailing space or tab, enclose it in single quotes | |
| if (!value.empty() && (value.back() == ' ' || value.back() == '\t')) { | |
| fout << '\'' << value << '\''; | |
| } else { | |
| fout << value; | |
| } | |
| } | |
| void cmCacheManager::OutputHelpString(std::ostream& fout, | |
| std::string const& helpString) | |
| { | |
| std::string::size_type end = helpString.size(); | |
| std::string oneLine; | |
| std::string::size_type pos = 0; | |
| for (std::string::size_type i = 1; i <= end; i++) { | |
| if ((i == end) || (helpString[i] == '\n') || | |
| ((i - pos >= 60) && (helpString[i] == ' '))) { | |
| fout << "//"; | |
| if (helpString[pos] == '\n') { | |
| pos++; | |
| fout << "\\n"; | |
| } | |
| oneLine = helpString.substr(pos, i - pos); | |
| fout << oneLine << '\n'; | |
| pos = i; | |
| } | |
| } | |
| } | |
| void cmCacheManager::OutputWarningComment(std::ostream& fout, | |
| std::string const& message, | |
| bool wrapSpaces) | |
| { | |
| std::string::size_type end = message.size(); | |
| std::string oneLine; | |
| std::string::size_type pos = 0; | |
| for (std::string::size_type i = 1; i <= end; i++) { | |
| if ((i == end) || (message[i] == '\n') || | |
| ((i - pos >= 60) && (message[i] == ' ') && wrapSpaces)) { | |
| fout << "# "; | |
| if (message[pos] == '\n') { | |
| pos++; | |
| fout << "\\n"; | |
| } | |
| oneLine = message.substr(pos, i - pos); | |
| fout << oneLine << '\n'; | |
| pos = i; | |
| } | |
| } | |
| } | |
| void cmCacheManager::OutputNewlineTruncationWarning(std::ostream& fout, | |
| std::string const& key, | |
| std::string const& value, | |
| cmMessenger* messenger) | |
| { | |
| if (value.find('\n') != std::string::npos) { | |
| if (messenger) { | |
| std::string message = | |
| cmStrCat("Value of ", key, " contained a newline; truncating"); | |
| messenger->IssueMessage(MessageType::WARNING, message); | |
| } | |
| std::string comment = | |
| cmStrCat("WARNING: Value of ", key, | |
| " contained a newline and was truncated. Original value:"); | |
| OutputWarningComment(fout, comment, true); | |
| OutputWarningComment(fout, value, false); | |
| } | |
| } | |
| void cmCacheManager::RemoveCacheEntry(std::string const& key) | |
| { | |
| this->Cache.erase(key); | |
| } | |
| cmCacheManager::CacheEntry* cmCacheManager::GetCacheEntry( | |
| std::string const& key) | |
| { | |
| auto i = this->Cache.find(key); | |
| if (i != this->Cache.end()) { | |
| return &i->second; | |
| } | |
| return nullptr; | |
| } | |
| cmCacheManager::CacheEntry const* cmCacheManager::GetCacheEntry( | |
| std::string const& key) const | |
| { | |
| auto i = this->Cache.find(key); | |
| if (i != this->Cache.end()) { | |
| return &i->second; | |
| } | |
| return nullptr; | |
| } | |
| cmValue cmCacheManager::GetInitializedCacheValue(std::string const& key) const | |
| { | |
| if (auto const* entry = this->GetCacheEntry(key)) { | |
| if (entry->Initialized) { | |
| return cmValue(entry->GetValue()); | |
| } | |
| } | |
| return nullptr; | |
| } | |
| void cmCacheManager::PrintCache(std::ostream& out) const | |
| { | |
| out << "=================================================\n" | |
| "CMakeCache Contents:\n"; | |
| for (auto const& i : this->Cache) { | |
| if (i.second.Type != cmStateEnums::INTERNAL) { | |
| out << i.first << " = " << i.second.Value << '\n'; | |
| } | |
| } | |
| out << "\n\n" | |
| "To change values in the CMakeCache, \n" | |
| "edit CMakeCache.txt in your output directory.\n" | |
| "=================================================\n"; | |
| } | |
| void cmCacheManager::AddCacheEntry(std::string const& key, cmValue value, | |
| cmValue helpString, | |
| cmStateEnums::CacheEntryType type) | |
| { | |
| CacheEntry& e = this->Cache[key]; | |
| e.SetValue(value); | |
| e.Type = type; | |
| // make sure we only use unix style paths | |
| if (type == cmStateEnums::FILEPATH || type == cmStateEnums::PATH) { | |
| if (e.Value.find(';') != std::string::npos) { | |
| cmList paths{ e.Value }; | |
| for (std::string& i : paths) { | |
| cmSystemTools::ConvertToUnixSlashes(i); | |
| } | |
| e.Value = paths.to_string(); | |
| } else { | |
| cmSystemTools::ConvertToUnixSlashes(e.Value); | |
| } | |
| } | |
| e.SetProperty( | |
| "HELPSTRING", | |
| helpString ? *helpString | |
| : std::string{ | |
| "(This variable does not exist and should not be used)" }); | |
| } | |
| void cmCacheManager::CacheEntry::SetValue(cmValue value) | |
| { | |
| if (value) { | |
| this->Value = *value; | |
| this->Initialized = true; | |
| } else { | |
| this->Value.clear(); | |
| } | |
| } | |
| std::vector<std::string> cmCacheManager::CacheEntry::GetPropertyList() const | |
| { | |
| return this->Properties.GetKeys(); | |
| } | |
| cmValue cmCacheManager::CacheEntry::GetProperty(std::string const& prop) const | |
| { | |
| if (prop == "TYPE") { | |
| return cmValue(cmState::CacheEntryTypeToString(this->Type)); | |
| } | |
| if (prop == "VALUE") { | |
| return cmValue(this->Value); | |
| } | |
| return this->Properties.GetPropertyValue(prop); | |
| } | |
| bool cmCacheManager::CacheEntry::GetPropertyAsBool( | |
| std::string const& prop) const | |
| { | |
| return this->GetProperty(prop).IsOn(); | |
| } | |
| void cmCacheManager::CacheEntry::SetProperty(std::string const& prop, | |
| std::string const& value) | |
| { | |
| if (prop == "TYPE") { | |
| this->Type = cmState::StringToCacheEntryType(value); | |
| } else if (prop == "VALUE") { | |
| this->Value = value; | |
| } else { | |
| this->Properties.SetProperty(prop, value); | |
| } | |
| } | |
| void cmCacheManager::CacheEntry::SetProperty(std::string const& p, bool v) | |
| { | |
| this->SetProperty(p, v ? std::string{ "ON" } : std::string{ "OFF" }); | |
| } | |
| void cmCacheManager::CacheEntry::RemoveProperty(std::string const& prop) | |
| { | |
| if (prop == "TYPE") { | |
| this->Type = cmState::StringToCacheEntryType("STRING"); | |
| } else if (prop == "VALUE") { | |
| this->Value.clear(); | |
| } else { | |
| this->Properties.RemoveProperty(prop); | |
| } | |
| } | |
| void cmCacheManager::CacheEntry::AppendProperty(std::string const& prop, | |
| std::string const& value, | |
| bool asString) | |
| { | |
| if (prop == "TYPE") { | |
| this->Type = | |
| cmState::StringToCacheEntryType(!value.empty() ? value : "STRING"); | |
| } else if (prop == "VALUE") { | |
| if (!value.empty()) { | |
| if (!this->Value.empty() && !asString) { | |
| this->Value += ";"; | |
| } | |
| this->Value += value; | |
| } | |
| } else { | |
| this->Properties.AppendProperty(prop, value, asString); | |
| } | |
| } |