0750eeac3aa8a052e4205ed47b6874232ebd1891
[platform/upstream/cmake.git] / Source / cmCMakeHostSystemInformationCommand.cxx
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"
4
5 #include <algorithm>
6 #include <cassert>
7 #include <cctype>
8 #include <initializer_list>
9 #include <map>
10 #include <string>
11 #include <type_traits>
12 #include <utility>
13
14 #include <cm/optional>
15 #include <cm/string_view>
16 #include <cmext/string_view>
17
18 #include "cmsys/FStream.hxx"
19 #include "cmsys/Glob.hxx"
20 #include "cmsys/SystemInformation.hxx"
21
22 #include "cmArgumentParser.h"
23 #include "cmExecutionStatus.h"
24 #include "cmMakefile.h"
25 #include "cmRange.h"
26 #include "cmStringAlgorithms.h"
27 #include "cmSystemTools.h"
28 #include "cmWindowsRegistry.h"
29
30 #ifdef _WIN32
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
37 #endif
38
39 namespace {
40 std::string const DELIM[2] = { {}, ";" };
41
42 // BEGIN Private functions
43 std::string ValueToString(std::size_t const value)
44 {
45   return std::to_string(value);
46 }
47
48 std::string ValueToString(const char* const value)
49 {
50   return value ? value : std::string{};
51 }
52
53 std::string ValueToString(std::string const& value)
54 {
55   return value;
56 }
57
58 cm::optional<std::string> GetValue(cmsys::SystemInformation& info,
59                                    std::string const& key)
60 {
61   if (key == "NUMBER_OF_LOGICAL_CORES"_s) {
62     return ValueToString(info.GetNumberOfLogicalCPU());
63   }
64   if (key == "NUMBER_OF_PHYSICAL_CORES"_s) {
65     return ValueToString(info.GetNumberOfPhysicalCPU());
66   }
67   if (key == "HOSTNAME"_s) {
68     return ValueToString(info.GetHostname());
69   }
70   if (key == "FQDN"_s) {
71     return ValueToString(info.GetFullyQualifiedDomainName());
72   }
73   if (key == "TOTAL_VIRTUAL_MEMORY"_s) {
74     return ValueToString(info.GetTotalVirtualMemory());
75   }
76   if (key == "AVAILABLE_VIRTUAL_MEMORY"_s) {
77     return ValueToString(info.GetAvailableVirtualMemory());
78   }
79   if (key == "TOTAL_PHYSICAL_MEMORY"_s) {
80     return ValueToString(info.GetTotalPhysicalMemory());
81   }
82   if (key == "AVAILABLE_PHYSICAL_MEMORY"_s) {
83     return ValueToString(info.GetAvailablePhysicalMemory());
84   }
85   if (key == "IS_64BIT"_s) {
86     return ValueToString(info.Is64Bits());
87   }
88   if (key == "HAS_FPU"_s) {
89     return ValueToString(
90       info.DoesCPUSupportFeature(cmsys::SystemInformation::CPU_FEATURE_FPU));
91   }
92   if (key == "HAS_MMX"_s) {
93     return ValueToString(
94       info.DoesCPUSupportFeature(cmsys::SystemInformation::CPU_FEATURE_MMX));
95   }
96   if (key == "HAS_MMX_PLUS"_s) {
97     return ValueToString(info.DoesCPUSupportFeature(
98       cmsys::SystemInformation::CPU_FEATURE_MMX_PLUS));
99   }
100   if (key == "HAS_SSE"_s) {
101     return ValueToString(
102       info.DoesCPUSupportFeature(cmsys::SystemInformation::CPU_FEATURE_SSE));
103   }
104   if (key == "HAS_SSE2"_s) {
105     return ValueToString(
106       info.DoesCPUSupportFeature(cmsys::SystemInformation::CPU_FEATURE_SSE2));
107   }
108   if (key == "HAS_SSE_FP"_s) {
109     return ValueToString(info.DoesCPUSupportFeature(
110       cmsys::SystemInformation::CPU_FEATURE_SSE_FP));
111   }
112   if (key == "HAS_SSE_MMX"_s) {
113     return ValueToString(info.DoesCPUSupportFeature(
114       cmsys::SystemInformation::CPU_FEATURE_SSE_MMX));
115   }
116   if (key == "HAS_AMD_3DNOW"_s) {
117     return ValueToString(info.DoesCPUSupportFeature(
118       cmsys::SystemInformation::CPU_FEATURE_AMD_3DNOW));
119   }
120   if (key == "HAS_AMD_3DNOW_PLUS"_s) {
121     return ValueToString(info.DoesCPUSupportFeature(
122       cmsys::SystemInformation::CPU_FEATURE_AMD_3DNOW_PLUS));
123   }
124   if (key == "HAS_IA64"_s) {
125     return ValueToString(
126       info.DoesCPUSupportFeature(cmsys::SystemInformation::CPU_FEATURE_IA64));
127   }
128   if (key == "HAS_SERIAL_NUMBER"_s) {
129     return ValueToString(info.DoesCPUSupportFeature(
130       cmsys::SystemInformation::CPU_FEATURE_SERIALNUMBER));
131   }
132   if (key == "PROCESSOR_NAME"_s) {
133     return ValueToString(info.GetExtendedProcessorName());
134   }
135   if (key == "PROCESSOR_DESCRIPTION"_s) {
136     return info.GetCPUDescription();
137   }
138   if (key == "PROCESSOR_SERIAL_NUMBER"_s) {
139     return ValueToString(info.GetProcessorSerialNumber());
140   }
141   if (key == "OS_NAME"_s) {
142     return ValueToString(info.GetOSName());
143   }
144   if (key == "OS_RELEASE"_s) {
145     return ValueToString(info.GetOSRelease());
146   }
147   if (key == "OS_VERSION"_s) {
148     return ValueToString(info.GetOSVersion());
149   }
150   if (key == "OS_PLATFORM"_s) {
151     return ValueToString(info.GetOSPlatform());
152   }
153   return {};
154 }
155
156 cm::optional<std::pair<std::string, std::string>> ParseOSReleaseLine(
157   std::string const& line)
158 {
159   std::string key;
160   std::string value;
161
162   char prev = 0;
163   enum ParserState
164   {
165     PARSE_KEY_1ST,
166     PARSE_KEY,
167     FOUND_EQ,
168     PARSE_SINGLE_QUOTE_VALUE,
169     PARSE_DBL_QUOTE_VALUE,
170     PARSE_VALUE,
171     IGNORE_REST
172   } state = PARSE_KEY_1ST;
173
174   for (auto ch : line) {
175     switch (state) {
176       case PARSE_KEY_1ST:
177         if (std::isalpha(ch) || ch == '_') {
178           key += ch;
179           state = PARSE_KEY;
180         } else if (!std::isspace(ch)) {
181           state = IGNORE_REST;
182         }
183         break;
184
185       case PARSE_KEY:
186         if (ch == '=') {
187           state = FOUND_EQ;
188         } else if (std::isalnum(ch) || ch == '_') {
189           key += ch;
190         } else {
191           state = IGNORE_REST;
192         }
193         break;
194
195       case FOUND_EQ:
196         switch (ch) {
197           case '\'':
198             state = PARSE_SINGLE_QUOTE_VALUE;
199             break;
200           case '"':
201             state = PARSE_DBL_QUOTE_VALUE;
202             break;
203           case '#':
204           case '\\':
205             state = IGNORE_REST;
206             break;
207           default:
208             value += ch;
209             state = PARSE_VALUE;
210         }
211         break;
212
213       case PARSE_SINGLE_QUOTE_VALUE:
214         if (ch == '\'') {
215           if (prev != '\\') {
216             state = IGNORE_REST;
217           } else {
218             assert(!value.empty());
219             value[value.size() - 1] = ch;
220           }
221         } else {
222           value += ch;
223         }
224         break;
225
226       case PARSE_DBL_QUOTE_VALUE:
227         if (ch == '"') {
228           if (prev != '\\') {
229             state = IGNORE_REST;
230           } else {
231             assert(!value.empty());
232             value[value.size() - 1] = ch;
233           }
234         } else {
235           value += ch;
236         }
237         break;
238
239       case PARSE_VALUE:
240         if (ch == '#' || std::isspace(ch)) {
241           state = IGNORE_REST;
242         } else {
243           value += ch;
244         }
245         break;
246
247       default:
248         // Unexpected os-release parser state!
249         state = IGNORE_REST;
250         break;
251     }
252
253     if (state == IGNORE_REST) {
254       break;
255     }
256     prev = ch;
257   }
258   if (!(key.empty() || value.empty())) {
259     return std::make_pair(key, value);
260   }
261   return {};
262 }
263
264 std::map<std::string, std::string> GetOSReleaseVariables(
265   cmExecutionStatus& status)
266 {
267   auto& makefile = status.GetMakefile();
268   const auto& sysroot = makefile.GetSafeDefinition("CMAKE_SYSROOT");
269
270   std::map<std::string, std::string> data;
271   // Based on
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());
281         }
282       }
283       break;
284     }
285   }
286   // Got smth?
287   if (!data.empty()) {
288     return data;
289   }
290
291   // Ugh, it could be some pre-os-release distro.
292   // Lets try some fallback getters.
293   // See also:
294   //  - http://linuxmafia.com/faq/Admin/release-files.html
295
296   // 1. CMake provided
297   cmsys::Glob gl;
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();
303   }
304
305   // 2. User provided (append to the CMake prvided)
306   makefile.GetDefExpandList("CMAKE_GET_OS_RELEASE_FALLBACK_SCRIPTS", scripts);
307
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]) ||
315       filename[3] != '-';
316   };
317   scripts.erase(std::remove_if(scripts.begin(), scripts.end(), checkName),
318                 scripts.end());
319
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 {
323               long lhs_order;
324               cmStrToLong(cmSystemTools::GetFilenameName(lhs).substr(0u, 3u),
325                           &lhs_order);
326               long rhs_order;
327               cmStrToLong(cmSystemTools::GetFilenameName(rhs).substr(0u, 3u),
328                           &rhs_order);
329               return lhs_order < rhs_order;
330             });
331
332   // Name of the variable to put the results
333   auto const result_variable = "CMAKE_GET_OS_RELEASE_FALLBACK_RESULT"_s;
334
335   for (auto const& script : scripts) {
336     // Unset the result variable
337     makefile.RemoveDefinition(result_variable.data());
338
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.
343       continue;
344     }
345
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.
349       continue;
350     }
351
352     for (auto const& variable : variables) {
353       auto value = makefile.GetSafeDefinition(variable);
354       makefile.RemoveDefinition(variable);
355
356       if (!cmHasPrefix(variable, cmStrCat(result_variable, '_'))) {
357         // Ignore unknown variable set by the script
358         continue;
359       }
360
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));
364     }
365
366     // Try 'till some script can get anything
367     if (!data.empty()) {
368       data.emplace("USED_FALLBACK_SCRIPT", script);
369       break;
370     }
371   }
372
373   makefile.RemoveDefinition(result_variable.data());
374
375   return data;
376 }
377
378 cm::optional<std::string> GetValue(cmExecutionStatus& status,
379                                    std::string const& key,
380                                    std::string const& variable)
381 {
382   const auto prefix = "DISTRIB_"_s;
383   if (!cmHasPrefix(key, prefix)) {
384     return {};
385   }
386
387   static const std::map<std::string, std::string> s_os_release =
388     GetOSReleaseVariables(status);
389
390   auto& makefile = status.GetMakefile();
391
392   const std::string subkey =
393     key.substr(prefix.size(), key.size() - prefix.size());
394   if (subkey == "INFO"_s) {
395     std::string vars;
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);
400     }
401     return cm::optional<std::string>(std::move(vars));
402   }
403
404   // Query individual variable
405   const auto it = s_os_release.find(subkey);
406   if (it != s_os_release.cend()) {
407     return it->second;
408   }
409
410   // NOTE Empty string means requested variable not set
411   return std::string{};
412 }
413
414 #ifdef HAVE_VS_SETUP_HELPER
415 cm::optional<std::string> GetValue(cmExecutionStatus& status,
416                                    std::string const& key)
417 {
418   auto* const gg = status.GetMakefile().GetGlobalGenerator();
419   for (auto vs : { 15, 16, 17 }) {
420     if (key == cmStrCat("VS_"_s, vs, "_DIR"_s)) {
421       std::string value;
422       // If generating for the VS nn IDE, use the same instance.
423
424       if (cmHasPrefix(gg->GetName(), cmStrCat("Visual Studio "_s, vs, ' '))) {
425         cmGlobalVisualStudioVersionedGenerator* vsNNgen =
426           static_cast<cmGlobalVisualStudioVersionedGenerator*>(gg);
427         if (vsNNgen->GetVSInstance(value)) {
428           return value;
429         }
430       }
431
432       // Otherwise, find a VS nn instance ourselves.
433       cmVSSetupAPIHelper vsSetupAPIHelper(vs);
434       if (vsSetupAPIHelper.GetVSInstanceInfo(value)) {
435         cmSystemTools::ConvertToUnixSlashes(value);
436       }
437       return value;
438     }
439   }
440
441   if (key == "VS_MSBUILD_COMMAND"_s && gg->IsVisualStudioAtLeast10()) {
442     cmGlobalVisualStudio10Generator* vs10gen =
443       static_cast<cmGlobalVisualStudio10Generator*>(gg);
444     return vs10gen->FindMSBuildCommandEarly(&status.GetMakefile());
445   }
446
447   return {};
448 }
449 #endif
450
451 cm::optional<std::string> GetValueChained()
452 {
453   return {};
454 }
455
456 template <typename GetterFn, typename... Next>
457 cm::optional<std::string> GetValueChained(GetterFn current, Next... chain)
458 {
459   auto value = current();
460   if (value.has_value()) {
461     return value;
462   }
463   return GetValueChained(chain...);
464 }
465
466 template <typename Range>
467 bool QueryWindowsRegistry(Range args, cmExecutionStatus& status,
468                           std::string const& variable)
469 {
470   using View = cmWindowsRegistry::View;
471   if (args.empty()) {
472     status.SetError("missing <key> specification.");
473     return false;
474   }
475   std::string const& key = *args.begin();
476
477   struct Arguments
478   {
479     std::string ValueName;
480     bool ValueNames = false;
481     bool SubKeys = false;
482     std::string View;
483     std::string Separator;
484     std::string ErrorVariable;
485   };
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;
494   std::vector<std::string> keywordsMissingValue;
495
496   Arguments const arguments =
497     parser.Parse(args.advance(1), &invalidArgs, &keywordsMissingValue);
498   if (!invalidArgs.empty()) {
499     status.SetError(cmStrCat("given invalid argument(s) \"",
500                              cmJoin(invalidArgs, ", "_s), "\"."));
501     return false;
502   }
503   if (!keywordsMissingValue.empty()) {
504     status.SetError(cmStrCat("missing expected value for argument(s) \"",
505                              cmJoin(keywordsMissingValue, ", "_s), "\"."));
506     return false;
507   }
508   if ((!arguments.ValueName.empty() &&
509        (arguments.ValueNames || arguments.SubKeys)) ||
510       (arguments.ValueName.empty() && arguments.ValueNames &&
511        arguments.SubKeys)) {
512     status.SetError("given mutually exclusive sub-options \"VALUE\", "
513                     "\"VALUE_NAMES\" or \"SUBKEYS\".");
514     return false;
515   }
516
517   if (!arguments.View.empty() && !cmWindowsRegistry::ToView(arguments.View)) {
518     status.SetError(
519       cmStrCat("given invalid value for \"VIEW\": ", arguments.View, '.'));
520     return false;
521   }
522
523   auto& makefile = status.GetMakefile();
524
525   makefile.AddDefinition(variable, ""_s);
526
527   auto view = arguments.View.empty()
528     ? View::Both
529     : *cmWindowsRegistry::ToView(arguments.View);
530   cmWindowsRegistry registry(makefile);
531   if (arguments.ValueNames) {
532     auto result = registry.GetValueNames(key, view);
533     if (result) {
534       makefile.AddDefinition(variable, cmJoin(*result, ";"_s));
535     }
536   } else if (arguments.SubKeys) {
537     auto result = registry.GetSubKeys(key, view);
538     if (result) {
539       makefile.AddDefinition(variable, cmJoin(*result, ";"_s));
540     }
541   } else {
542     auto result =
543       registry.ReadValue(key, arguments.ValueName, view, arguments.Separator);
544     if (result) {
545       makefile.AddDefinition(variable, *result);
546     }
547   }
548
549   // return error message if requested
550   if (!arguments.ErrorVariable.empty()) {
551     makefile.AddDefinition(arguments.ErrorVariable, registry.GetLastError());
552   }
553
554   return true;
555 }
556
557 // END Private functions
558 } // anonymous namespace
559
560 // cmCMakeHostSystemInformation
561 bool cmCMakeHostSystemInformationCommand(std::vector<std::string> const& args,
562                                          cmExecutionStatus& status)
563 {
564   std::size_t current_index = 0;
565
566   if (args.size() < (current_index + 2) || args[current_index] != "RESULT"_s) {
567     status.SetError("missing RESULT specification.");
568     return false;
569   }
570
571   auto const& variable = args[current_index + 1];
572   current_index += 2;
573
574   if (args.size() < (current_index + 2) || args[current_index] != "QUERY"_s) {
575     status.SetError("missing QUERY specification");
576     return false;
577   }
578
579   if (args[current_index + 1] == "WINDOWS_REGISTRY"_s) {
580     return QueryWindowsRegistry(cmMakeRange(args).advance(current_index + 2),
581                                 status, variable);
582   }
583
584   static cmsys::SystemInformation info;
585   static auto initialized = false;
586   if (!initialized) {
587     info.RunCPUCheck();
588     info.RunOSCheck();
589     info.RunMemoryCheck();
590     initialized = true;
591   }
592
593   std::string result_list;
594   for (auto i = current_index + 1; i < args.size(); ++i) {
595     result_list += DELIM[!result_list.empty()];
596
597     auto const& key = args[i];
598     // clang-format off
599     auto value =
600       GetValueChained(
601           [&]() { return GetValue(info, key); }
602         , [&]() { return GetValue(status, key, variable); }
603 #ifdef HAVE_VS_SETUP_HELPER
604         , [&]() { return GetValue(status, key); }
605 #endif
606         );
607     // clang-format on
608     if (!value) {
609       status.SetError("does not recognize <key> " + key);
610       return false;
611     }
612     result_list += value.value();
613   }
614
615   status.GetMakefile().AddDefinition(variable, result_list);
616
617   return true;
618 }