cmStandardLevelResolver.cxx 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395
  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 "cmStandardLevelResolver.h"
  4. #include <algorithm>
  5. #include <cassert>
  6. #include <cstddef>
  7. #include <sstream>
  8. #include <unordered_map>
  9. #include <utility>
  10. #include <vector>
  11. #include <cm/iterator>
  12. #include <cmext/algorithm>
  13. #include "cmGeneratorExpression.h"
  14. #include "cmGeneratorTarget.h"
  15. #include "cmGlobalGenerator.h"
  16. #include "cmMakefile.h"
  17. #include "cmMessageType.h"
  18. #include "cmProperty.h"
  19. #include "cmStringAlgorithms.h"
  20. #include "cmTarget.h"
  21. #include "cmake.h"
  22. namespace {
  23. #define FEATURE_STRING(F) , #F
  24. const char* const C_FEATURES[] = { nullptr FOR_EACH_C_FEATURE(
  25. FEATURE_STRING) };
  26. const char* const CXX_FEATURES[] = { nullptr FOR_EACH_CXX_FEATURE(
  27. FEATURE_STRING) };
  28. const char* const CUDA_FEATURES[] = { nullptr FOR_EACH_CUDA_FEATURE(
  29. FEATURE_STRING) };
  30. #undef FEATURE_STRING
  31. struct StandardNeeded
  32. {
  33. int index;
  34. int value;
  35. };
  36. struct StanardLevelComputer
  37. {
  38. explicit StanardLevelComputer(std::string lang, std::vector<int> levels)
  39. : Language(std::move(lang))
  40. , Levels(std::move(levels))
  41. {
  42. }
  43. bool GetNewRequiredStandard(cmMakefile* makefile,
  44. std::string const& targetName,
  45. const std::string& feature,
  46. cmProp currentLangStandardValue,
  47. std::string& newRequiredStandard,
  48. std::string* error) const
  49. {
  50. newRequiredStandard.clear();
  51. auto needed = this->HighestStandardNeeded(makefile, feature);
  52. cmProp existingStandard = currentLangStandardValue;
  53. if (existingStandard == nullptr) {
  54. cmProp defaultStandard = makefile->GetDef(
  55. cmStrCat("CMAKE_", this->Language, "_STANDARD_DEFAULT"));
  56. if (defaultStandard && !defaultStandard->empty()) {
  57. existingStandard = defaultStandard;
  58. }
  59. }
  60. auto existingLevelIter = cm::cend(this->Levels);
  61. if (existingStandard) {
  62. existingLevelIter =
  63. std::find(cm::cbegin(this->Levels), cm::cend(this->Levels),
  64. std::stoi(*existingStandard));
  65. if (existingLevelIter == cm::cend(this->Levels)) {
  66. const std::string e =
  67. cmStrCat("The ", this->Language, "_STANDARD property on target \"",
  68. targetName, "\" contained an invalid value: \"",
  69. *existingStandard, "\".");
  70. if (error) {
  71. *error = e;
  72. } else {
  73. makefile->IssueMessage(MessageType::FATAL_ERROR, e);
  74. }
  75. return false;
  76. }
  77. }
  78. if (needed.index != -1) {
  79. // Ensure the C++ language level is high enough to support
  80. // the needed C++ features.
  81. if (existingLevelIter == cm::cend(this->Levels) ||
  82. existingLevelIter < this->Levels.begin() + needed.index) {
  83. newRequiredStandard = std::to_string(this->Levels[needed.index]);
  84. }
  85. }
  86. return true;
  87. }
  88. bool HaveStandardAvailable(cmMakefile* makefile,
  89. cmGeneratorTarget const* target,
  90. std::string const& config,
  91. std::string const& feature) const
  92. {
  93. cmProp defaultStandard = makefile->GetDef(
  94. cmStrCat("CMAKE_", this->Language, "_STANDARD_DEFAULT"));
  95. if (!defaultStandard) {
  96. makefile->IssueMessage(
  97. MessageType::INTERNAL_ERROR,
  98. cmStrCat("CMAKE_", this->Language,
  99. "_STANDARD_DEFAULT is not set. COMPILE_FEATURES support "
  100. "not fully configured for this compiler."));
  101. // Return true so the caller does not try to lookup the default standard.
  102. return true;
  103. }
  104. // convert defaultStandard to an integer
  105. if (std::find(cm::cbegin(this->Levels), cm::cend(this->Levels),
  106. std::stoi(*defaultStandard)) == cm::cend(this->Levels)) {
  107. const std::string e = cmStrCat("The CMAKE_", this->Language,
  108. "_STANDARD_DEFAULT variable contains an "
  109. "invalid value: \"",
  110. *defaultStandard, "\".");
  111. makefile->IssueMessage(MessageType::INTERNAL_ERROR, e);
  112. return false;
  113. }
  114. cmProp existingStandard =
  115. target->GetLanguageStandard(this->Language, config);
  116. if (!existingStandard) {
  117. existingStandard = defaultStandard;
  118. }
  119. auto existingLevelIter =
  120. std::find(cm::cbegin(this->Levels), cm::cend(this->Levels),
  121. std::stoi(*existingStandard));
  122. if (existingLevelIter == cm::cend(this->Levels)) {
  123. const std::string e =
  124. cmStrCat("The ", this->Language, "_STANDARD property on target \"",
  125. target->GetName(), "\" contained an invalid value: \"",
  126. *existingStandard, "\".");
  127. makefile->IssueMessage(MessageType::FATAL_ERROR, e);
  128. return false;
  129. }
  130. auto needed = this->HighestStandardNeeded(makefile, feature);
  131. return (needed.index == -1) ||
  132. (this->Levels.begin() + needed.index) <= existingLevelIter;
  133. }
  134. StandardNeeded HighestStandardNeeded(cmMakefile* makefile,
  135. std::string const& feature) const
  136. {
  137. std::string prefix = cmStrCat("CMAKE_", this->Language);
  138. StandardNeeded maxLevel = { -1, -1 };
  139. for (size_t i = 0; i < this->Levels.size(); ++i) {
  140. if (const char* prop = makefile->GetDefinition(cmStrCat(
  141. prefix, std::to_string(this->Levels[i]), "_COMPILE_FEATURES"))) {
  142. std::vector<std::string> props = cmExpandedList(prop);
  143. if (cm::contains(props, feature)) {
  144. maxLevel = { static_cast<int>(i), this->Levels[i] };
  145. }
  146. }
  147. }
  148. return maxLevel;
  149. }
  150. bool IsLaterStandard(int lhs, int rhs) const
  151. {
  152. auto rhsIt =
  153. std::find(cm::cbegin(this->Levels), cm::cend(this->Levels), rhs);
  154. return std::find(rhsIt, cm::cend(this->Levels), lhs) !=
  155. cm::cend(this->Levels);
  156. }
  157. std::string Language;
  158. std::vector<int> Levels;
  159. };
  160. std::unordered_map<std::string, StanardLevelComputer> StandardComputerMapping =
  161. {
  162. { "C", StanardLevelComputer{ "C", std::vector<int>{ 90, 99, 11 } } },
  163. { "CXX",
  164. StanardLevelComputer{ "CXX", std::vector<int>{ 98, 11, 14, 17, 20 } } },
  165. { "CUDA",
  166. StanardLevelComputer{ "CUDA", std::vector<int>{ 03, 11, 14, 17, 20 } } },
  167. { "OBJC", StanardLevelComputer{ "OBJC", std::vector<int>{ 90, 99, 11 } } },
  168. { "OBJCXX",
  169. StanardLevelComputer{ "OBJCXX",
  170. std::vector<int>{ 98, 11, 14, 17, 20 } } },
  171. };
  172. }
  173. bool cmStandardLevelResolver::AddRequiredTargetFeature(
  174. cmTarget* target, const std::string& feature, std::string* error) const
  175. {
  176. if (cmGeneratorExpression::Find(feature) != std::string::npos) {
  177. target->AppendProperty("COMPILE_FEATURES", feature);
  178. return true;
  179. }
  180. std::string lang;
  181. if (!this->CheckCompileFeaturesAvailable(target->GetName(), feature, lang,
  182. error)) {
  183. return false;
  184. }
  185. target->AppendProperty("COMPILE_FEATURES", feature);
  186. // FIXME: Add a policy to avoid updating the <LANG>_STANDARD target
  187. // property due to COMPILE_FEATURES. The language standard selection
  188. // should be done purely at generate time based on whatever the project
  189. // code put in these properties explicitly. That is mostly true now,
  190. // but for compatibility we need to continue updating the property here.
  191. std::string newRequiredStandard;
  192. bool newRequired = this->GetNewRequiredStandard(
  193. target->GetName(), feature,
  194. target->GetProperty(cmStrCat(lang, "_STANDARD")), newRequiredStandard,
  195. error);
  196. if (!newRequiredStandard.empty()) {
  197. target->SetProperty(cmStrCat(lang, "_STANDARD"), newRequiredStandard);
  198. }
  199. return newRequired;
  200. }
  201. bool cmStandardLevelResolver::CheckCompileFeaturesAvailable(
  202. const std::string& targetName, const std::string& feature, std::string& lang,
  203. std::string* error) const
  204. {
  205. if (!this->CompileFeatureKnown(targetName, feature, lang, error)) {
  206. return false;
  207. }
  208. const char* features = this->CompileFeaturesAvailable(lang, error);
  209. if (!features) {
  210. return false;
  211. }
  212. std::vector<std::string> availableFeatures = cmExpandedList(features);
  213. if (!cm::contains(availableFeatures, feature)) {
  214. std::ostringstream e;
  215. e << "The compiler feature \"" << feature << "\" is not known to " << lang
  216. << " compiler\n\""
  217. << this->Makefile->GetDefinition("CMAKE_" + lang + "_COMPILER_ID")
  218. << "\"\nversion "
  219. << this->Makefile->GetDefinition("CMAKE_" + lang + "_COMPILER_VERSION")
  220. << ".";
  221. if (error) {
  222. *error = e.str();
  223. } else {
  224. this->Makefile->IssueMessage(MessageType::FATAL_ERROR, e.str());
  225. }
  226. return false;
  227. }
  228. return true;
  229. }
  230. bool cmStandardLevelResolver::CompileFeatureKnown(
  231. const std::string& targetName, const std::string& feature, std::string& lang,
  232. std::string* error) const
  233. {
  234. assert(cmGeneratorExpression::Find(feature) == std::string::npos);
  235. bool isCFeature =
  236. std::find_if(cm::cbegin(C_FEATURES) + 1, cm::cend(C_FEATURES),
  237. cmStrCmp(feature)) != cm::cend(C_FEATURES);
  238. if (isCFeature) {
  239. lang = "C";
  240. return true;
  241. }
  242. bool isCxxFeature =
  243. std::find_if(cm::cbegin(CXX_FEATURES) + 1, cm::cend(CXX_FEATURES),
  244. cmStrCmp(feature)) != cm::cend(CXX_FEATURES);
  245. if (isCxxFeature) {
  246. lang = "CXX";
  247. return true;
  248. }
  249. bool isCudaFeature =
  250. std::find_if(cm::cbegin(CUDA_FEATURES) + 1, cm::cend(CUDA_FEATURES),
  251. cmStrCmp(feature)) != cm::cend(CUDA_FEATURES);
  252. if (isCudaFeature) {
  253. lang = "CUDA";
  254. return true;
  255. }
  256. std::ostringstream e;
  257. if (error) {
  258. e << "specified";
  259. } else {
  260. e << "Specified";
  261. }
  262. e << " unknown feature \"" << feature
  263. << "\" for "
  264. "target \""
  265. << targetName << "\".";
  266. if (error) {
  267. *error = e.str();
  268. } else {
  269. this->Makefile->IssueMessage(MessageType::FATAL_ERROR, e.str());
  270. }
  271. return false;
  272. }
  273. const char* cmStandardLevelResolver::CompileFeaturesAvailable(
  274. const std::string& lang, std::string* error) const
  275. {
  276. if (!this->Makefile->GetGlobalGenerator()->GetLanguageEnabled(lang)) {
  277. std::ostringstream e;
  278. if (error) {
  279. e << "cannot";
  280. } else {
  281. e << "Cannot";
  282. }
  283. e << " use features from non-enabled language " << lang;
  284. if (error) {
  285. *error = e.str();
  286. } else {
  287. this->Makefile->IssueMessage(MessageType::FATAL_ERROR, e.str());
  288. }
  289. return nullptr;
  290. }
  291. const char* featuresKnown =
  292. this->Makefile->GetDefinition("CMAKE_" + lang + "_COMPILE_FEATURES");
  293. if (!featuresKnown || !*featuresKnown) {
  294. std::ostringstream e;
  295. if (error) {
  296. e << "no";
  297. } else {
  298. e << "No";
  299. }
  300. e << " known features for " << lang << " compiler\n\""
  301. << this->Makefile->GetSafeDefinition("CMAKE_" + lang + "_COMPILER_ID")
  302. << "\"\nversion "
  303. << this->Makefile->GetSafeDefinition("CMAKE_" + lang +
  304. "_COMPILER_VERSION")
  305. << ".";
  306. if (error) {
  307. *error = e.str();
  308. } else {
  309. this->Makefile->IssueMessage(MessageType::FATAL_ERROR, e.str());
  310. }
  311. return nullptr;
  312. }
  313. return featuresKnown;
  314. }
  315. bool cmStandardLevelResolver::GetNewRequiredStandard(
  316. const std::string& targetName, const std::string& feature,
  317. cmProp currentLangStandardValue, std::string& newRequiredStandard,
  318. std::string* error) const
  319. {
  320. std::string lang;
  321. if (!this->CheckCompileFeaturesAvailable(targetName, feature, lang, error)) {
  322. return false;
  323. }
  324. auto mapping = StandardComputerMapping.find(lang);
  325. if (mapping != cm::cend(StandardComputerMapping)) {
  326. return mapping->second.GetNewRequiredStandard(
  327. this->Makefile, targetName, feature, currentLangStandardValue,
  328. newRequiredStandard, error);
  329. }
  330. return false;
  331. }
  332. bool cmStandardLevelResolver::HaveStandardAvailable(
  333. cmGeneratorTarget const* target, std::string const& lang,
  334. std::string const& config, const std::string& feature) const
  335. {
  336. auto mapping = StandardComputerMapping.find(lang);
  337. if (mapping != cm::cend(StandardComputerMapping)) {
  338. return mapping->second.HaveStandardAvailable(this->Makefile, target,
  339. config, feature);
  340. }
  341. return false;
  342. }
  343. bool cmStandardLevelResolver::IsLaterStandard(std::string const& lang,
  344. std::string const& lhs,
  345. std::string const& rhs) const
  346. {
  347. auto mapping = StandardComputerMapping.find(lang);
  348. if (mapping != cm::cend(StandardComputerMapping)) {
  349. return mapping->second.IsLaterStandard(std::stoi(lhs), std::stoi(rhs));
  350. }
  351. return false;
  352. }