cmCMakeHostSystemInformationCommand.cxx 17 KB

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