| /* 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 <algorithm> |
| #include <cassert> |
| #include <cctype> |
| #include <initializer_list> |
| #include <map> |
| #include <string> |
| #include <type_traits> |
| #include <utility> |
| |
| #include <cm/optional> |
| #include <cm/string_view> |
| #include <cmext/string_view> |
| |
| #include "cmsys/FStream.hxx" |
| #include "cmsys/Glob.hxx" |
| #include "cmsys/SystemInformation.hxx" |
| |
| #include "cmArgumentParser.h" |
| #include "cmExecutionStatus.h" |
| #include "cmMakefile.h" |
| #include "cmRange.h" |
| #include "cmStringAlgorithms.h" |
| #include "cmSystemTools.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<std::string> 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<std::pair<std::string, std::string>> 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<std::string, std::string> GetOSReleaseVariables( |
| cmExecutionStatus& status) |
| { |
| auto& makefile = status.GetMakefile(); |
| const auto& sysroot = makefile.GetSafeDefinition("CMAKE_SYSROOT"); |
| |
| std::map<std::string, std::string> 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<std::string> 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) |
| makefile.GetDefExpandList("CMAKE_GET_OS_RELEASE_FALLBACK_SCRIPTS", 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-<at-least-one-char-name>.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 |
| auto const result_variable = "CMAKE_GET_OS_RELEASE_FALLBACK_RESULT"_s; |
| |
| for (auto const& script : scripts) { |
| // Unset the result variable |
| makefile.RemoveDefinition(result_variable.data()); |
| |
| // include FATAL_ERROR and ERROR in the return status |
| if (!makefile.ReadListFile(script) || |
| cmSystemTools::GetErrorOccurredFlag()) { |
| // Ok, no worries... go try the next script. |
| continue; |
| } |
| |
| std::vector<std::string> variables; |
| if (!makefile.GetDefExpandList(result_variable.data(), variables)) { |
| // 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.data()); |
| |
| return data; |
| } |
| |
| cm::optional<std::string> 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<std::string, std::string> 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::string>(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<std::string> 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<cmGlobalVisualStudioVersionedGenerator*>(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<cmGlobalVisualStudio10Generator*>(gg); |
| return vs10gen->FindMSBuildCommandEarly(&status.GetMakefile()); |
| } |
| |
| return {}; |
| } |
| #endif |
| |
| cm::optional<std::string> GetValueChained() |
| { |
| return {}; |
| } |
| |
| template <typename GetterFn, typename... Next> |
| cm::optional<std::string> GetValueChained(GetterFn current, Next... chain) |
| { |
| auto value = current(); |
| if (value.has_value()) { |
| return value; |
| } |
| return GetValueChained(chain...); |
| } |
| |
| template <typename Range> |
| bool QueryWindowsRegistry(Range args, cmExecutionStatus& status, |
| std::string const& variable) |
| { |
| using View = cmWindowsRegistry::View; |
| if (args.empty()) { |
| status.SetError("missing <key> 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<Arguments> 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<std::string> 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<std::string> 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> " + key); |
| return false; |
| } |
| result_list += value.value(); |
| } |
| |
| status.GetMakefile().AddDefinition(variable, result_list); |
| |
| return true; |
| } |