1 /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
2 file Copyright.txt or https://cmake.org/licensing for details. */
3 #include "cmCMakeHostSystemInformationCommand.h"
8 #include <initializer_list>
11 #include <type_traits>
14 #include <cm/optional>
15 #include <cm/string_view>
16 #include <cmext/string_view>
18 #include "cmsys/FStream.hxx"
19 #include "cmsys/Glob.hxx"
20 #include "cmsys/SystemInformation.hxx"
22 #include "cmArgumentParser.h"
23 #include "cmExecutionStatus.h"
24 #include "cmMakefile.h"
26 #include "cmStringAlgorithms.h"
27 #include "cmSystemTools.h"
28 #include "cmWindowsRegistry.h"
31 # include "cmAlgorithms.h"
32 # include "cmGlobalGenerator.h"
33 # include "cmGlobalVisualStudio10Generator.h"
34 # include "cmGlobalVisualStudioVersionedGenerator.h"
35 # include "cmVSSetupHelper.h"
36 # define HAVE_VS_SETUP_HELPER
40 std::string const DELIM[2] = { {}, ";" };
42 // BEGIN Private functions
43 std::string ValueToString(std::size_t const value)
45 return std::to_string(value);
48 std::string ValueToString(const char* const value)
50 return value ? value : std::string{};
53 std::string ValueToString(std::string const& value)
58 cm::optional<std::string> GetValue(cmsys::SystemInformation& info,
59 std::string const& key)
61 if (key == "NUMBER_OF_LOGICAL_CORES"_s) {
62 return ValueToString(info.GetNumberOfLogicalCPU());
64 if (key == "NUMBER_OF_PHYSICAL_CORES"_s) {
65 return ValueToString(info.GetNumberOfPhysicalCPU());
67 if (key == "HOSTNAME"_s) {
68 return ValueToString(info.GetHostname());
70 if (key == "FQDN"_s) {
71 return ValueToString(info.GetFullyQualifiedDomainName());
73 if (key == "TOTAL_VIRTUAL_MEMORY"_s) {
74 return ValueToString(info.GetTotalVirtualMemory());
76 if (key == "AVAILABLE_VIRTUAL_MEMORY"_s) {
77 return ValueToString(info.GetAvailableVirtualMemory());
79 if (key == "TOTAL_PHYSICAL_MEMORY"_s) {
80 return ValueToString(info.GetTotalPhysicalMemory());
82 if (key == "AVAILABLE_PHYSICAL_MEMORY"_s) {
83 return ValueToString(info.GetAvailablePhysicalMemory());
85 if (key == "IS_64BIT"_s) {
86 return ValueToString(info.Is64Bits());
88 if (key == "HAS_FPU"_s) {
90 info.DoesCPUSupportFeature(cmsys::SystemInformation::CPU_FEATURE_FPU));
92 if (key == "HAS_MMX"_s) {
94 info.DoesCPUSupportFeature(cmsys::SystemInformation::CPU_FEATURE_MMX));
96 if (key == "HAS_MMX_PLUS"_s) {
97 return ValueToString(info.DoesCPUSupportFeature(
98 cmsys::SystemInformation::CPU_FEATURE_MMX_PLUS));
100 if (key == "HAS_SSE"_s) {
101 return ValueToString(
102 info.DoesCPUSupportFeature(cmsys::SystemInformation::CPU_FEATURE_SSE));
104 if (key == "HAS_SSE2"_s) {
105 return ValueToString(
106 info.DoesCPUSupportFeature(cmsys::SystemInformation::CPU_FEATURE_SSE2));
108 if (key == "HAS_SSE_FP"_s) {
109 return ValueToString(info.DoesCPUSupportFeature(
110 cmsys::SystemInformation::CPU_FEATURE_SSE_FP));
112 if (key == "HAS_SSE_MMX"_s) {
113 return ValueToString(info.DoesCPUSupportFeature(
114 cmsys::SystemInformation::CPU_FEATURE_SSE_MMX));
116 if (key == "HAS_AMD_3DNOW"_s) {
117 return ValueToString(info.DoesCPUSupportFeature(
118 cmsys::SystemInformation::CPU_FEATURE_AMD_3DNOW));
120 if (key == "HAS_AMD_3DNOW_PLUS"_s) {
121 return ValueToString(info.DoesCPUSupportFeature(
122 cmsys::SystemInformation::CPU_FEATURE_AMD_3DNOW_PLUS));
124 if (key == "HAS_IA64"_s) {
125 return ValueToString(
126 info.DoesCPUSupportFeature(cmsys::SystemInformation::CPU_FEATURE_IA64));
128 if (key == "HAS_SERIAL_NUMBER"_s) {
129 return ValueToString(info.DoesCPUSupportFeature(
130 cmsys::SystemInformation::CPU_FEATURE_SERIALNUMBER));
132 if (key == "PROCESSOR_NAME"_s) {
133 return ValueToString(info.GetExtendedProcessorName());
135 if (key == "PROCESSOR_DESCRIPTION"_s) {
136 return info.GetCPUDescription();
138 if (key == "PROCESSOR_SERIAL_NUMBER"_s) {
139 return ValueToString(info.GetProcessorSerialNumber());
141 if (key == "OS_NAME"_s) {
142 return ValueToString(info.GetOSName());
144 if (key == "OS_RELEASE"_s) {
145 return ValueToString(info.GetOSRelease());
147 if (key == "OS_VERSION"_s) {
148 return ValueToString(info.GetOSVersion());
150 if (key == "OS_PLATFORM"_s) {
151 return ValueToString(info.GetOSPlatform());
156 cm::optional<std::pair<std::string, std::string>> ParseOSReleaseLine(
157 std::string const& line)
168 PARSE_SINGLE_QUOTE_VALUE,
169 PARSE_DBL_QUOTE_VALUE,
172 } state = PARSE_KEY_1ST;
174 for (auto ch : line) {
177 if (std::isalpha(ch) || ch == '_') {
180 } else if (!std::isspace(ch)) {
188 } else if (std::isalnum(ch) || ch == '_') {
198 state = PARSE_SINGLE_QUOTE_VALUE;
201 state = PARSE_DBL_QUOTE_VALUE;
213 case PARSE_SINGLE_QUOTE_VALUE:
218 assert(!value.empty());
219 value[value.size() - 1] = ch;
226 case PARSE_DBL_QUOTE_VALUE:
231 assert(!value.empty());
232 value[value.size() - 1] = ch;
240 if (ch == '#' || std::isspace(ch)) {
248 // Unexpected os-release parser state!
253 if (state == IGNORE_REST) {
258 if (!(key.empty() || value.empty())) {
259 return std::make_pair(key, value);
264 std::map<std::string, std::string> GetOSReleaseVariables(
265 cmExecutionStatus& status)
267 auto& makefile = status.GetMakefile();
268 const auto& sysroot = makefile.GetSafeDefinition("CMAKE_SYSROOT");
270 std::map<std::string, std::string> data;
272 // https://www.freedesktop.org/software/systemd/man/os-release.html
273 for (auto name : { "/etc/os-release"_s, "/usr/lib/os-release"_s }) {
274 const auto& filename = cmStrCat(sysroot, name);
275 if (cmSystemTools::FileExists(filename)) {
276 cmsys::ifstream fin(filename.c_str());
277 for (std::string line; !std::getline(fin, line).fail();) {
278 auto kv = ParseOSReleaseLine(line);
279 if (kv.has_value()) {
280 data.emplace(kv.value());
291 // Ugh, it could be some pre-os-release distro.
292 // Lets try some fallback getters.
294 // - http://linuxmafia.com/faq/Admin/release-files.html
298 std::vector<std::string> scripts;
299 auto const findExpr = cmStrCat(cmSystemTools::GetCMakeRoot(),
300 "/Modules/Internal/OSRelease/*.cmake");
301 if (gl.FindFiles(findExpr)) {
302 scripts = gl.GetFiles();
305 // 2. User provided (append to the CMake prvided)
306 makefile.GetDefExpandList("CMAKE_GET_OS_RELEASE_FALLBACK_SCRIPTS", scripts);
308 // Filter out files that are not in format `NNN-name.cmake`
309 auto checkName = [](std::string const& filepath) -> bool {
310 auto const& filename = cmSystemTools::GetFilenameName(filepath);
311 // NOTE Minimum filename length expected:
312 // NNN-<at-least-one-char-name>.cmake --> 11
313 return (filename.size() < 11) || !std::isdigit(filename[0]) ||
314 !std::isdigit(filename[1]) || !std::isdigit(filename[2]) ||
317 scripts.erase(std::remove_if(scripts.begin(), scripts.end(), checkName),
320 // Make sure scripts are running in desired order
321 std::sort(scripts.begin(), scripts.end(),
322 [](std::string const& lhs, std::string const& rhs) -> bool {
324 cmStrToLong(cmSystemTools::GetFilenameName(lhs).substr(0u, 3u),
327 cmStrToLong(cmSystemTools::GetFilenameName(rhs).substr(0u, 3u),
329 return lhs_order < rhs_order;
332 // Name of the variable to put the results
333 auto const result_variable = "CMAKE_GET_OS_RELEASE_FALLBACK_RESULT"_s;
335 for (auto const& script : scripts) {
336 // Unset the result variable
337 makefile.RemoveDefinition(result_variable.data());
339 // include FATAL_ERROR and ERROR in the return status
340 if (!makefile.ReadListFile(script) ||
341 cmSystemTools::GetErrorOccurredFlag()) {
342 // Ok, no worries... go try the next script.
346 std::vector<std::string> variables;
347 if (!makefile.GetDefExpandList(result_variable.data(), variables)) {
348 // Heh, this script didn't found anything... go try the next one.
352 for (auto const& variable : variables) {
353 auto value = makefile.GetSafeDefinition(variable);
354 makefile.RemoveDefinition(variable);
356 if (!cmHasPrefix(variable, cmStrCat(result_variable, '_'))) {
357 // Ignore unknown variable set by the script
361 auto key = variable.substr(result_variable.size() + 1,
362 variable.size() - result_variable.size() - 1);
363 data.emplace(std::move(key), std::move(value));
366 // Try 'till some script can get anything
368 data.emplace("USED_FALLBACK_SCRIPT", script);
373 makefile.RemoveDefinition(result_variable.data());
378 cm::optional<std::string> GetValue(cmExecutionStatus& status,
379 std::string const& key,
380 std::string const& variable)
382 const auto prefix = "DISTRIB_"_s;
383 if (!cmHasPrefix(key, prefix)) {
387 static const std::map<std::string, std::string> s_os_release =
388 GetOSReleaseVariables(status);
390 auto& makefile = status.GetMakefile();
392 const std::string subkey =
393 key.substr(prefix.size(), key.size() - prefix.size());
394 if (subkey == "INFO"_s) {
396 for (const auto& kv : s_os_release) {
397 auto cmake_var_name = cmStrCat(variable, '_', kv.first);
398 vars += DELIM[!vars.empty()] + cmake_var_name;
399 makefile.AddDefinition(cmake_var_name, kv.second);
401 return cm::optional<std::string>(std::move(vars));
404 // Query individual variable
405 const auto it = s_os_release.find(subkey);
406 if (it != s_os_release.cend()) {
410 // NOTE Empty string means requested variable not set
411 return std::string{};
414 #ifdef HAVE_VS_SETUP_HELPER
415 cm::optional<std::string> GetValue(cmExecutionStatus& status,
416 std::string const& key)
418 auto* const gg = status.GetMakefile().GetGlobalGenerator();
419 for (auto vs : { 15, 16, 17 }) {
420 if (key == cmStrCat("VS_"_s, vs, "_DIR"_s)) {
422 // If generating for the VS nn IDE, use the same instance.
424 if (cmHasPrefix(gg->GetName(), cmStrCat("Visual Studio "_s, vs, ' '))) {
425 cmGlobalVisualStudioVersionedGenerator* vsNNgen =
426 static_cast<cmGlobalVisualStudioVersionedGenerator*>(gg);
427 if (vsNNgen->GetVSInstance(value)) {
432 // Otherwise, find a VS nn instance ourselves.
433 cmVSSetupAPIHelper vsSetupAPIHelper(vs);
434 if (vsSetupAPIHelper.GetVSInstanceInfo(value)) {
435 cmSystemTools::ConvertToUnixSlashes(value);
441 if (key == "VS_MSBUILD_COMMAND"_s && gg->IsVisualStudioAtLeast10()) {
442 cmGlobalVisualStudio10Generator* vs10gen =
443 static_cast<cmGlobalVisualStudio10Generator*>(gg);
444 return vs10gen->FindMSBuildCommandEarly(&status.GetMakefile());
451 cm::optional<std::string> GetValueChained()
456 template <typename GetterFn, typename... Next>
457 cm::optional<std::string> GetValueChained(GetterFn current, Next... chain)
459 auto value = current();
460 if (value.has_value()) {
463 return GetValueChained(chain...);
466 template <typename Range>
467 bool QueryWindowsRegistry(Range args, cmExecutionStatus& status,
468 std::string const& variable)
470 using View = cmWindowsRegistry::View;
472 status.SetError("missing <key> specification.");
475 std::string const& key = *args.begin();
477 struct Arguments : public ArgumentParser::ParseResult
479 std::string ValueName;
480 bool ValueNames = false;
481 bool SubKeys = false;
483 std::string Separator;
484 std::string ErrorVariable;
486 cmArgumentParser<Arguments> parser;
487 parser.Bind("VALUE"_s, &Arguments::ValueName)
488 .Bind("VALUE_NAMES"_s, &Arguments::ValueNames)
489 .Bind("SUBKEYS"_s, &Arguments::SubKeys)
490 .Bind("VIEW"_s, &Arguments::View)
491 .Bind("SEPARATOR"_s, &Arguments::Separator)
492 .Bind("ERROR_VARIABLE"_s, &Arguments::ErrorVariable);
493 std::vector<std::string> invalidArgs;
495 Arguments const arguments = parser.Parse(args.advance(1), &invalidArgs);
496 if (!invalidArgs.empty()) {
497 status.SetError(cmStrCat("given invalid argument(s) \"",
498 cmJoin(invalidArgs, ", "_s), "\"."));
501 if (arguments.MaybeReportError(status.GetMakefile())) {
504 if ((!arguments.ValueName.empty() &&
505 (arguments.ValueNames || arguments.SubKeys)) ||
506 (arguments.ValueName.empty() && arguments.ValueNames &&
507 arguments.SubKeys)) {
508 status.SetError("given mutually exclusive sub-options \"VALUE\", "
509 "\"VALUE_NAMES\" or \"SUBKEYS\".");
513 if (!arguments.View.empty() && !cmWindowsRegistry::ToView(arguments.View)) {
515 cmStrCat("given invalid value for \"VIEW\": ", arguments.View, '.'));
519 auto& makefile = status.GetMakefile();
521 makefile.AddDefinition(variable, ""_s);
523 auto view = arguments.View.empty()
525 : *cmWindowsRegistry::ToView(arguments.View);
526 cmWindowsRegistry registry(makefile);
527 if (arguments.ValueNames) {
528 auto result = registry.GetValueNames(key, view);
530 makefile.AddDefinition(variable, cmJoin(*result, ";"_s));
532 } else if (arguments.SubKeys) {
533 auto result = registry.GetSubKeys(key, view);
535 makefile.AddDefinition(variable, cmJoin(*result, ";"_s));
539 registry.ReadValue(key, arguments.ValueName, view, arguments.Separator);
541 makefile.AddDefinition(variable, *result);
545 // return error message if requested
546 if (!arguments.ErrorVariable.empty()) {
547 makefile.AddDefinition(arguments.ErrorVariable, registry.GetLastError());
553 // END Private functions
554 } // anonymous namespace
556 // cmCMakeHostSystemInformation
557 bool cmCMakeHostSystemInformationCommand(std::vector<std::string> const& args,
558 cmExecutionStatus& status)
560 std::size_t current_index = 0;
562 if (args.size() < (current_index + 2) || args[current_index] != "RESULT"_s) {
563 status.SetError("missing RESULT specification.");
567 auto const& variable = args[current_index + 1];
570 if (args.size() < (current_index + 2) || args[current_index] != "QUERY"_s) {
571 status.SetError("missing QUERY specification");
575 if (args[current_index + 1] == "WINDOWS_REGISTRY"_s) {
576 return QueryWindowsRegistry(cmMakeRange(args).advance(current_index + 2),
580 static cmsys::SystemInformation info;
581 static auto initialized = false;
585 info.RunMemoryCheck();
589 std::string result_list;
590 for (auto i = current_index + 1; i < args.size(); ++i) {
591 result_list += DELIM[!result_list.empty()];
593 auto const& key = args[i];
597 [&]() { return GetValue(info, key); }
598 , [&]() { return GetValue(status, key, variable); }
599 #ifdef HAVE_VS_SETUP_HELPER
600 , [&]() { return GetValue(status, key); }
605 status.SetError("does not recognize <key> " + key);
608 result_list += value.value();
611 status.GetMakefile().AddDefinition(variable, result_list);