cmCMakeHostSystemInformationCommand.cxx 14 KB

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