cmStandardLevelResolver.cxx 13 KB

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