/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying file Copyright.txt or https://cmake.org/licensing for details. */ #include "cmCMakeHostSystemInformationCommand.h" #include #include #include #include #include #include #include #include #include #include #include #include "cmsys/FStream.hxx" #include "cmsys/Glob.hxx" #include "cmsys/SystemInformation.hxx" #include "cmArgumentParser.h" #include "cmExecutionStatus.h" #include "cmList.h" #include "cmMakefile.h" #include "cmRange.h" #include "cmStringAlgorithms.h" #include "cmSystemTools.h" #include "cmValue.h" #include "cmWindowsRegistry.h" #ifdef _WIN32 # include "cmAlgorithms.h" # include "cmGlobalGenerator.h" # include "cmGlobalVisualStudio10Generator.h" # include "cmGlobalVisualStudioVersionedGenerator.h" # include "cmVSSetupHelper.h" # define HAVE_VS_SETUP_HELPER #endif namespace { std::string const DELIM[2] = { {}, ";" }; // BEGIN Private functions std::string ValueToString(std::size_t const value) { return std::to_string(value); } std::string ValueToString(const char* const value) { return value ? value : std::string{}; } std::string ValueToString(std::string const& value) { return value; } cm::optional GetValue(cmsys::SystemInformation& info, std::string const& key) { if (key == "NUMBER_OF_LOGICAL_CORES"_s) { return ValueToString(info.GetNumberOfLogicalCPU()); } if (key == "NUMBER_OF_PHYSICAL_CORES"_s) { return ValueToString(info.GetNumberOfPhysicalCPU()); } if (key == "HOSTNAME"_s) { return ValueToString(info.GetHostname()); } if (key == "FQDN"_s) { return ValueToString(info.GetFullyQualifiedDomainName()); } if (key == "TOTAL_VIRTUAL_MEMORY"_s) { return ValueToString(info.GetTotalVirtualMemory()); } if (key == "AVAILABLE_VIRTUAL_MEMORY"_s) { return ValueToString(info.GetAvailableVirtualMemory()); } if (key == "TOTAL_PHYSICAL_MEMORY"_s) { return ValueToString(info.GetTotalPhysicalMemory()); } if (key == "AVAILABLE_PHYSICAL_MEMORY"_s) { return ValueToString(info.GetAvailablePhysicalMemory()); } if (key == "IS_64BIT"_s) { return ValueToString(info.Is64Bits()); } if (key == "HAS_FPU"_s) { return ValueToString( info.DoesCPUSupportFeature(cmsys::SystemInformation::CPU_FEATURE_FPU)); } if (key == "HAS_MMX"_s) { return ValueToString( info.DoesCPUSupportFeature(cmsys::SystemInformation::CPU_FEATURE_MMX)); } if (key == "HAS_MMX_PLUS"_s) { return ValueToString(info.DoesCPUSupportFeature( cmsys::SystemInformation::CPU_FEATURE_MMX_PLUS)); } if (key == "HAS_SSE"_s) { return ValueToString( info.DoesCPUSupportFeature(cmsys::SystemInformation::CPU_FEATURE_SSE)); } if (key == "HAS_SSE2"_s) { return ValueToString( info.DoesCPUSupportFeature(cmsys::SystemInformation::CPU_FEATURE_SSE2)); } if (key == "HAS_SSE_FP"_s) { return ValueToString(info.DoesCPUSupportFeature( cmsys::SystemInformation::CPU_FEATURE_SSE_FP)); } if (key == "HAS_SSE_MMX"_s) { return ValueToString(info.DoesCPUSupportFeature( cmsys::SystemInformation::CPU_FEATURE_SSE_MMX)); } if (key == "HAS_AMD_3DNOW"_s) { return ValueToString(info.DoesCPUSupportFeature( cmsys::SystemInformation::CPU_FEATURE_AMD_3DNOW)); } if (key == "HAS_AMD_3DNOW_PLUS"_s) { return ValueToString(info.DoesCPUSupportFeature( cmsys::SystemInformation::CPU_FEATURE_AMD_3DNOW_PLUS)); } if (key == "HAS_IA64"_s) { return ValueToString( info.DoesCPUSupportFeature(cmsys::SystemInformation::CPU_FEATURE_IA64)); } if (key == "HAS_SERIAL_NUMBER"_s) { return ValueToString(info.DoesCPUSupportFeature( cmsys::SystemInformation::CPU_FEATURE_SERIALNUMBER)); } if (key == "PROCESSOR_NAME"_s) { return ValueToString(info.GetExtendedProcessorName()); } if (key == "PROCESSOR_DESCRIPTION"_s) { return info.GetCPUDescription(); } if (key == "PROCESSOR_SERIAL_NUMBER"_s) { return ValueToString(info.GetProcessorSerialNumber()); } if (key == "OS_NAME"_s) { return ValueToString(info.GetOSName()); } if (key == "OS_RELEASE"_s) { return ValueToString(info.GetOSRelease()); } if (key == "OS_VERSION"_s) { return ValueToString(info.GetOSVersion()); } if (key == "OS_PLATFORM"_s) { return ValueToString(info.GetOSPlatform()); } return {}; } cm::optional> ParseOSReleaseLine( std::string const& line) { std::string key; std::string value; char prev = 0; enum ParserState { PARSE_KEY_1ST, PARSE_KEY, FOUND_EQ, PARSE_SINGLE_QUOTE_VALUE, PARSE_DBL_QUOTE_VALUE, PARSE_VALUE, IGNORE_REST } state = PARSE_KEY_1ST; for (auto ch : line) { switch (state) { case PARSE_KEY_1ST: if (std::isalpha(ch) || ch == '_') { key += ch; state = PARSE_KEY; } else if (!std::isspace(ch)) { state = IGNORE_REST; } break; case PARSE_KEY: if (ch == '=') { state = FOUND_EQ; } else if (std::isalnum(ch) || ch == '_') { key += ch; } else { state = IGNORE_REST; } break; case FOUND_EQ: switch (ch) { case '\'': state = PARSE_SINGLE_QUOTE_VALUE; break; case '"': state = PARSE_DBL_QUOTE_VALUE; break; case '#': case '\\': state = IGNORE_REST; break; default: value += ch; state = PARSE_VALUE; } break; case PARSE_SINGLE_QUOTE_VALUE: if (ch == '\'') { if (prev != '\\') { state = IGNORE_REST; } else { assert(!value.empty()); value[value.size() - 1] = ch; } } else { value += ch; } break; case PARSE_DBL_QUOTE_VALUE: if (ch == '"') { if (prev != '\\') { state = IGNORE_REST; } else { assert(!value.empty()); value[value.size() - 1] = ch; } } else { value += ch; } break; case PARSE_VALUE: if (ch == '#' || std::isspace(ch)) { state = IGNORE_REST; } else { value += ch; } break; default: // Unexpected os-release parser state! state = IGNORE_REST; break; } if (state == IGNORE_REST) { break; } prev = ch; } if (!(key.empty() || value.empty())) { return std::make_pair(key, value); } return {}; } std::map GetOSReleaseVariables( cmExecutionStatus& status) { auto& makefile = status.GetMakefile(); const auto& sysroot = makefile.GetSafeDefinition("CMAKE_SYSROOT"); std::map data; // Based on // https://www.freedesktop.org/software/systemd/man/os-release.html for (auto name : { "/etc/os-release"_s, "/usr/lib/os-release"_s }) { const auto& filename = cmStrCat(sysroot, name); if (cmSystemTools::FileExists(filename)) { cmsys::ifstream fin(filename.c_str()); for (std::string line; !std::getline(fin, line).fail();) { auto kv = ParseOSReleaseLine(line); if (kv.has_value()) { data.emplace(kv.value()); } } break; } } // Got smth? if (!data.empty()) { return data; } // Ugh, it could be some pre-os-release distro. // Lets try some fallback getters. // See also: // - http://linuxmafia.com/faq/Admin/release-files.html // 1. CMake provided cmsys::Glob gl; std::vector scripts; auto const findExpr = cmStrCat(cmSystemTools::GetCMakeRoot(), "/Modules/Internal/OSRelease/*.cmake"); if (gl.FindFiles(findExpr)) { scripts = gl.GetFiles(); } // 2. User provided (append to the CMake prvided) cmList::append( scripts, makefile.GetDefinition("CMAKE_GET_OS_RELEASE_FALLBACK_SCRIPTS")); // Filter out files that are not in format `NNN-name.cmake` auto checkName = [](std::string const& filepath) -> bool { auto const& filename = cmSystemTools::GetFilenameName(filepath); // NOTE Minimum filename length expected: // NNN-.cmake --> 11 return (filename.size() < 11) || !std::isdigit(filename[0]) || !std::isdigit(filename[1]) || !std::isdigit(filename[2]) || filename[3] != '-'; }; scripts.erase(std::remove_if(scripts.begin(), scripts.end(), checkName), scripts.end()); // Make sure scripts are running in desired order std::sort(scripts.begin(), scripts.end(), [](std::string const& lhs, std::string const& rhs) -> bool { long lhs_order; cmStrToLong(cmSystemTools::GetFilenameName(lhs).substr(0u, 3u), &lhs_order); long rhs_order; cmStrToLong(cmSystemTools::GetFilenameName(rhs).substr(0u, 3u), &rhs_order); return lhs_order < rhs_order; }); // Name of the variable to put the results std::string const result_variable{ "CMAKE_GET_OS_RELEASE_FALLBACK_RESULT" }; for (auto const& script : scripts) { // Unset the result variable makefile.RemoveDefinition(result_variable); // include FATAL_ERROR and ERROR in the return status if (!makefile.ReadListFile(script) || cmSystemTools::GetErrorOccurredFlag()) { // Ok, no worries... go try the next script. continue; } cmList variables{ makefile.GetDefinition(result_variable) }; if (variables.empty()) { // Heh, this script didn't found anything... go try the next one. continue; } for (auto const& variable : variables) { auto value = makefile.GetSafeDefinition(variable); makefile.RemoveDefinition(variable); if (!cmHasPrefix(variable, cmStrCat(result_variable, '_'))) { // Ignore unknown variable set by the script continue; } auto key = variable.substr(result_variable.size() + 1, variable.size() - result_variable.size() - 1); data.emplace(std::move(key), std::move(value)); } // Try 'till some script can get anything if (!data.empty()) { data.emplace("USED_FALLBACK_SCRIPT", script); break; } } makefile.RemoveDefinition(result_variable); return data; } cm::optional GetValue(cmExecutionStatus& status, std::string const& key, std::string const& variable) { const auto prefix = "DISTRIB_"_s; if (!cmHasPrefix(key, prefix)) { return {}; } static const std::map s_os_release = GetOSReleaseVariables(status); auto& makefile = status.GetMakefile(); const std::string subkey = key.substr(prefix.size(), key.size() - prefix.size()); if (subkey == "INFO"_s) { std::string vars; for (const auto& kv : s_os_release) { auto cmake_var_name = cmStrCat(variable, '_', kv.first); vars += DELIM[!vars.empty()] + cmake_var_name; makefile.AddDefinition(cmake_var_name, kv.second); } return cm::optional(std::move(vars)); } // Query individual variable const auto it = s_os_release.find(subkey); if (it != s_os_release.cend()) { return it->second; } // NOTE Empty string means requested variable not set return std::string{}; } #ifdef HAVE_VS_SETUP_HELPER cm::optional GetValue(cmExecutionStatus& status, std::string const& key) { auto* const gg = status.GetMakefile().GetGlobalGenerator(); for (auto vs : { 15, 16, 17 }) { if (key == cmStrCat("VS_"_s, vs, "_DIR"_s)) { std::string value; // If generating for the VS nn IDE, use the same instance. if (cmHasPrefix(gg->GetName(), cmStrCat("Visual Studio "_s, vs, ' '))) { cmGlobalVisualStudioVersionedGenerator* vsNNgen = static_cast(gg); if (vsNNgen->GetVSInstance(value)) { return value; } } // Otherwise, find a VS nn instance ourselves. cmVSSetupAPIHelper vsSetupAPIHelper(vs); if (vsSetupAPIHelper.GetVSInstanceInfo(value)) { cmSystemTools::ConvertToUnixSlashes(value); } return value; } } if (key == "VS_MSBUILD_COMMAND"_s && gg->IsVisualStudioAtLeast10()) { cmGlobalVisualStudio10Generator* vs10gen = static_cast(gg); return vs10gen->FindMSBuildCommandEarly(&status.GetMakefile()); } return {}; } #endif cm::optional GetValueChained() { return {}; } template cm::optional GetValueChained(GetterFn current, Next... chain) { auto value = current(); if (value.has_value()) { return value; } return GetValueChained(chain...); } template bool QueryWindowsRegistry(Range args, cmExecutionStatus& status, std::string const& variable) { using View = cmWindowsRegistry::View; if (args.empty()) { status.SetError("missing specification."); return false; } std::string const& key = *args.begin(); struct Arguments : public ArgumentParser::ParseResult { std::string ValueName; bool ValueNames = false; bool SubKeys = false; std::string View; std::string Separator; std::string ErrorVariable; }; cmArgumentParser parser; parser.Bind("VALUE"_s, &Arguments::ValueName) .Bind("VALUE_NAMES"_s, &Arguments::ValueNames) .Bind("SUBKEYS"_s, &Arguments::SubKeys) .Bind("VIEW"_s, &Arguments::View) .Bind("SEPARATOR"_s, &Arguments::Separator) .Bind("ERROR_VARIABLE"_s, &Arguments::ErrorVariable); std::vector invalidArgs; Arguments const arguments = parser.Parse(args.advance(1), &invalidArgs); if (!invalidArgs.empty()) { status.SetError(cmStrCat("given invalid argument(s) \"", cmJoin(invalidArgs, ", "_s), "\".")); return false; } if (arguments.MaybeReportError(status.GetMakefile())) { return true; } if ((!arguments.ValueName.empty() && (arguments.ValueNames || arguments.SubKeys)) || (arguments.ValueName.empty() && arguments.ValueNames && arguments.SubKeys)) { status.SetError("given mutually exclusive sub-options \"VALUE\", " "\"VALUE_NAMES\" or \"SUBKEYS\"."); return false; } if (!arguments.View.empty() && !cmWindowsRegistry::ToView(arguments.View)) { status.SetError( cmStrCat("given invalid value for \"VIEW\": ", arguments.View, '.')); return false; } auto& makefile = status.GetMakefile(); makefile.AddDefinition(variable, ""_s); auto view = arguments.View.empty() ? View::Both : *cmWindowsRegistry::ToView(arguments.View); cmWindowsRegistry registry(makefile); if (arguments.ValueNames) { auto result = registry.GetValueNames(key, view); if (result) { makefile.AddDefinition(variable, cmJoin(*result, ";"_s)); } } else if (arguments.SubKeys) { auto result = registry.GetSubKeys(key, view); if (result) { makefile.AddDefinition(variable, cmJoin(*result, ";"_s)); } } else { auto result = registry.ReadValue(key, arguments.ValueName, view, arguments.Separator); if (result) { makefile.AddDefinition(variable, *result); } } // return error message if requested if (!arguments.ErrorVariable.empty()) { makefile.AddDefinition(arguments.ErrorVariable, registry.GetLastError()); } return true; } // END Private functions } // anonymous namespace // cmCMakeHostSystemInformation bool cmCMakeHostSystemInformationCommand(std::vector const& args, cmExecutionStatus& status) { std::size_t current_index = 0; if (args.size() < (current_index + 2) || args[current_index] != "RESULT"_s) { status.SetError("missing RESULT specification."); return false; } auto const& variable = args[current_index + 1]; current_index += 2; if (args.size() < (current_index + 2) || args[current_index] != "QUERY"_s) { status.SetError("missing QUERY specification"); return false; } if (args[current_index + 1] == "WINDOWS_REGISTRY"_s) { return QueryWindowsRegistry(cmMakeRange(args).advance(current_index + 2), status, variable); } static cmsys::SystemInformation info; static auto initialized = false; if (!initialized) { info.RunCPUCheck(); info.RunOSCheck(); info.RunMemoryCheck(); initialized = true; } std::string result_list; for (auto i = current_index + 1; i < args.size(); ++i) { result_list += DELIM[!result_list.empty()]; auto const& key = args[i]; // clang-format off auto value = GetValueChained( [&]() { return GetValue(info, key); } , [&]() { return GetValue(status, key, variable); } #ifdef HAVE_VS_SETUP_HELPER , [&]() { return GetValue(status, key); } #endif ); // clang-format on if (!value) { status.SetError("does not recognize " + key); return false; } result_list += value.value(); } status.GetMakefile().AddDefinition(variable, result_list); return true; }