cmProjectCommand.cxx 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397
  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 "cmProjectCommand.h"
  4. #include <array>
  5. #include <cstddef>
  6. #include <cstdio>
  7. #include <functional>
  8. #include <limits>
  9. #include <utility>
  10. #include "cmsys/RegularExpression.hxx"
  11. #include "cmExecutionStatus.h"
  12. #include "cmMakefile.h"
  13. #include "cmMessageType.h"
  14. #include "cmPolicies.h"
  15. #include "cmProperty.h"
  16. #include "cmStateTypes.h"
  17. #include "cmStringAlgorithms.h"
  18. #include "cmSystemTools.h"
  19. static bool IncludeByVariable(cmExecutionStatus& status,
  20. const std::string& variable);
  21. static void TopLevelCMakeVarCondSet(cmMakefile& mf, std::string const& name,
  22. std::string const& value);
  23. bool cmProjectCommand(std::vector<std::string> const& args,
  24. cmExecutionStatus& status)
  25. {
  26. if (args.empty()) {
  27. status.SetError("PROJECT called with incorrect number of arguments");
  28. return false;
  29. }
  30. cmMakefile& mf = status.GetMakefile();
  31. if (!IncludeByVariable(status, "CMAKE_PROJECT_INCLUDE_BEFORE")) {
  32. return false;
  33. }
  34. std::string const& projectName = args[0];
  35. if (!IncludeByVariable(status,
  36. "CMAKE_PROJECT_" + projectName + "_INCLUDE_BEFORE")) {
  37. return false;
  38. }
  39. mf.SetProjectName(projectName);
  40. mf.AddCacheDefinition(projectName + "_BINARY_DIR",
  41. mf.GetCurrentBinaryDirectory(),
  42. "Value Computed by CMake", cmStateEnums::STATIC);
  43. mf.AddCacheDefinition(projectName + "_SOURCE_DIR",
  44. mf.GetCurrentSourceDirectory(),
  45. "Value Computed by CMake", cmStateEnums::STATIC);
  46. mf.AddDefinition("PROJECT_BINARY_DIR", mf.GetCurrentBinaryDirectory());
  47. mf.AddDefinition("PROJECT_SOURCE_DIR", mf.GetCurrentSourceDirectory());
  48. mf.AddDefinition("PROJECT_NAME", projectName);
  49. // Set the CMAKE_PROJECT_NAME variable to be the highest-level
  50. // project name in the tree. If there are two project commands
  51. // in the same CMakeLists.txt file, and it is the top level
  52. // CMakeLists.txt file, then go with the last one, so that
  53. // CMAKE_PROJECT_NAME will match PROJECT_NAME, and cmake --build
  54. // will work.
  55. if (!mf.GetDefinition("CMAKE_PROJECT_NAME") || mf.IsRootMakefile()) {
  56. mf.AddDefinition("CMAKE_PROJECT_NAME", projectName);
  57. mf.AddCacheDefinition("CMAKE_PROJECT_NAME", projectName,
  58. "Value Computed by CMake", cmStateEnums::STATIC);
  59. }
  60. bool haveVersion = false;
  61. bool haveLanguages = false;
  62. bool haveDescription = false;
  63. bool haveHomepage = false;
  64. bool injectedProjectCommand = false;
  65. std::string version;
  66. std::string description;
  67. std::string homepage;
  68. std::vector<std::string> languages;
  69. std::function<void()> missedValueReporter;
  70. auto resetReporter = [&missedValueReporter]() {
  71. missedValueReporter = std::function<void()>();
  72. };
  73. enum Doing
  74. {
  75. DoingDescription,
  76. DoingHomepage,
  77. DoingLanguages,
  78. DoingVersion
  79. };
  80. Doing doing = DoingLanguages;
  81. for (size_t i = 1; i < args.size(); ++i) {
  82. if (args[i] == "LANGUAGES") {
  83. if (haveLanguages) {
  84. mf.IssueMessage(MessageType::FATAL_ERROR,
  85. "LANGUAGES may be specified at most once.");
  86. cmSystemTools::SetFatalErrorOccured();
  87. return true;
  88. }
  89. haveLanguages = true;
  90. if (missedValueReporter) {
  91. missedValueReporter();
  92. }
  93. doing = DoingLanguages;
  94. if (!languages.empty()) {
  95. std::string msg = cmStrCat(
  96. "the following parameters must be specified after LANGUAGES "
  97. "keyword: ",
  98. cmJoin(languages, ", "), '.');
  99. mf.IssueMessage(MessageType::WARNING, msg);
  100. }
  101. } else if (args[i] == "VERSION") {
  102. if (haveVersion) {
  103. mf.IssueMessage(MessageType::FATAL_ERROR,
  104. "VERSION may be specified at most once.");
  105. cmSystemTools::SetFatalErrorOccured();
  106. return true;
  107. }
  108. haveVersion = true;
  109. if (missedValueReporter) {
  110. missedValueReporter();
  111. }
  112. doing = DoingVersion;
  113. missedValueReporter = [&mf, &resetReporter]() {
  114. mf.IssueMessage(
  115. MessageType::WARNING,
  116. "VERSION keyword not followed by a value or was followed by a "
  117. "value that expanded to nothing.");
  118. resetReporter();
  119. };
  120. } else if (args[i] == "DESCRIPTION") {
  121. if (haveDescription) {
  122. mf.IssueMessage(MessageType::FATAL_ERROR,
  123. "DESCRIPTION may be specified at most once.");
  124. cmSystemTools::SetFatalErrorOccured();
  125. return true;
  126. }
  127. haveDescription = true;
  128. if (missedValueReporter) {
  129. missedValueReporter();
  130. }
  131. doing = DoingDescription;
  132. missedValueReporter = [&mf, &resetReporter]() {
  133. mf.IssueMessage(
  134. MessageType::WARNING,
  135. "DESCRIPTION keyword not followed by a value or was followed "
  136. "by a value that expanded to nothing.");
  137. resetReporter();
  138. };
  139. } else if (args[i] == "HOMEPAGE_URL") {
  140. if (haveHomepage) {
  141. mf.IssueMessage(MessageType::FATAL_ERROR,
  142. "HOMEPAGE_URL may be specified at most once.");
  143. cmSystemTools::SetFatalErrorOccured();
  144. return true;
  145. }
  146. haveHomepage = true;
  147. doing = DoingHomepage;
  148. missedValueReporter = [&mf, &resetReporter]() {
  149. mf.IssueMessage(
  150. MessageType::WARNING,
  151. "HOMEPAGE_URL keyword not followed by a value or was followed "
  152. "by a value that expanded to nothing.");
  153. resetReporter();
  154. };
  155. } else if (i == 1 && args[i] == "__CMAKE_INJECTED_PROJECT_COMMAND__") {
  156. injectedProjectCommand = true;
  157. } else if (doing == DoingVersion) {
  158. doing = DoingLanguages;
  159. version = args[i];
  160. resetReporter();
  161. } else if (doing == DoingDescription) {
  162. doing = DoingLanguages;
  163. description = args[i];
  164. resetReporter();
  165. } else if (doing == DoingHomepage) {
  166. doing = DoingLanguages;
  167. homepage = args[i];
  168. resetReporter();
  169. } else // doing == DoingLanguages
  170. {
  171. languages.push_back(args[i]);
  172. }
  173. }
  174. if (missedValueReporter) {
  175. missedValueReporter();
  176. }
  177. if ((haveVersion || haveDescription || haveHomepage) && !haveLanguages &&
  178. !languages.empty()) {
  179. mf.IssueMessage(MessageType::FATAL_ERROR,
  180. "project with VERSION, DESCRIPTION or HOMEPAGE_URL must "
  181. "use LANGUAGES before language names.");
  182. cmSystemTools::SetFatalErrorOccured();
  183. return true;
  184. }
  185. if (haveLanguages && languages.empty()) {
  186. languages.emplace_back("NONE");
  187. }
  188. cmPolicies::PolicyStatus const cmp0048 =
  189. mf.GetPolicyStatus(cmPolicies::CMP0048);
  190. if (haveVersion) {
  191. // Set project VERSION variables to given values
  192. if (cmp0048 == cmPolicies::OLD || cmp0048 == cmPolicies::WARN) {
  193. mf.IssueMessage(MessageType::FATAL_ERROR,
  194. "VERSION not allowed unless CMP0048 is set to NEW");
  195. cmSystemTools::SetFatalErrorOccured();
  196. return true;
  197. }
  198. cmsys::RegularExpression vx(
  199. R"(^([0-9]+(\.[0-9]+(\.[0-9]+(\.[0-9]+)?)?)?)?$)");
  200. if (!vx.find(version)) {
  201. std::string e = R"(VERSION ")" + version + R"(" format invalid.)";
  202. mf.IssueMessage(MessageType::FATAL_ERROR, e);
  203. cmSystemTools::SetFatalErrorOccured();
  204. return true;
  205. }
  206. cmPolicies::PolicyStatus const cmp0096 =
  207. mf.GetPolicyStatus(cmPolicies::CMP0096);
  208. constexpr std::size_t MAX_VERSION_COMPONENTS = 4u;
  209. std::string version_string;
  210. std::array<std::string, MAX_VERSION_COMPONENTS> version_components;
  211. if (cmp0096 == cmPolicies::OLD || cmp0096 == cmPolicies::WARN) {
  212. char vb[MAX_VERSION_COMPONENTS]
  213. [std::numeric_limits<unsigned>::digits10 + 2];
  214. unsigned v[MAX_VERSION_COMPONENTS] = { 0, 0, 0, 0 };
  215. const int vc = std::sscanf(version.c_str(), "%u.%u.%u.%u", &v[0], &v[1],
  216. &v[2], &v[3]);
  217. for (auto i = 0u; i < MAX_VERSION_COMPONENTS; ++i) {
  218. if (int(i) < vc) {
  219. std::sprintf(vb[i], "%u", v[i]);
  220. version_string += &"."[std::size_t(i == 0)];
  221. version_string += vb[i];
  222. version_components[i] = vb[i];
  223. } else {
  224. vb[i][0] = '\x00';
  225. }
  226. }
  227. } else {
  228. // The regex above verified that we have a .-separated string of
  229. // non-negative integer components. Keep the original string.
  230. version_string = std::move(version);
  231. // Split the integer components.
  232. auto components = cmSystemTools::SplitString(version_string, '.');
  233. for (auto i = 0u; i < components.size(); ++i) {
  234. version_components[i] = std::move(components[i]);
  235. }
  236. }
  237. std::string vv;
  238. vv = projectName + "_VERSION";
  239. mf.AddDefinition("PROJECT_VERSION", version_string);
  240. mf.AddDefinition(vv, version_string);
  241. vv = projectName + "_VERSION_MAJOR";
  242. mf.AddDefinition("PROJECT_VERSION_MAJOR", version_components[0]);
  243. mf.AddDefinition(vv, version_components[0]);
  244. vv = projectName + "_VERSION_MINOR";
  245. mf.AddDefinition("PROJECT_VERSION_MINOR", version_components[1]);
  246. mf.AddDefinition(vv, version_components[1]);
  247. vv = projectName + "_VERSION_PATCH";
  248. mf.AddDefinition("PROJECT_VERSION_PATCH", version_components[2]);
  249. mf.AddDefinition(vv, version_components[2]);
  250. vv = projectName + "_VERSION_TWEAK";
  251. mf.AddDefinition("PROJECT_VERSION_TWEAK", version_components[3]);
  252. mf.AddDefinition(vv, version_components[3]);
  253. // Also, try set top level variables
  254. TopLevelCMakeVarCondSet(mf, "CMAKE_PROJECT_VERSION", version_string);
  255. TopLevelCMakeVarCondSet(mf, "CMAKE_PROJECT_VERSION_MAJOR",
  256. version_components[0]);
  257. TopLevelCMakeVarCondSet(mf, "CMAKE_PROJECT_VERSION_MINOR",
  258. version_components[1]);
  259. TopLevelCMakeVarCondSet(mf, "CMAKE_PROJECT_VERSION_PATCH",
  260. version_components[2]);
  261. TopLevelCMakeVarCondSet(mf, "CMAKE_PROJECT_VERSION_TWEAK",
  262. version_components[3]);
  263. } else if (cmp0048 != cmPolicies::OLD) {
  264. // Set project VERSION variables to empty
  265. std::vector<std::string> vv = { "PROJECT_VERSION",
  266. "PROJECT_VERSION_MAJOR",
  267. "PROJECT_VERSION_MINOR",
  268. "PROJECT_VERSION_PATCH",
  269. "PROJECT_VERSION_TWEAK",
  270. projectName + "_VERSION",
  271. projectName + "_VERSION_MAJOR",
  272. projectName + "_VERSION_MINOR",
  273. projectName + "_VERSION_PATCH",
  274. projectName + "_VERSION_TWEAK" };
  275. if (mf.IsRootMakefile()) {
  276. vv.emplace_back("CMAKE_PROJECT_VERSION");
  277. vv.emplace_back("CMAKE_PROJECT_VERSION_MAJOR");
  278. vv.emplace_back("CMAKE_PROJECT_VERSION_MINOR");
  279. vv.emplace_back("CMAKE_PROJECT_VERSION_PATCH");
  280. vv.emplace_back("CMAKE_PROJECT_VERSION_TWEAK");
  281. }
  282. std::string vw;
  283. for (std::string const& i : vv) {
  284. cmProp v = mf.GetDefinition(i);
  285. if (cmNonempty(v)) {
  286. if (cmp0048 == cmPolicies::WARN) {
  287. if (!injectedProjectCommand) {
  288. vw += "\n ";
  289. vw += i;
  290. }
  291. } else {
  292. mf.AddDefinition(i, "");
  293. }
  294. }
  295. }
  296. if (!vw.empty()) {
  297. mf.IssueMessage(
  298. MessageType::AUTHOR_WARNING,
  299. cmStrCat(cmPolicies::GetPolicyWarning(cmPolicies::CMP0048),
  300. "\nThe following variable(s) would be set to empty:", vw));
  301. }
  302. }
  303. mf.AddDefinition("PROJECT_DESCRIPTION", description);
  304. mf.AddDefinition(projectName + "_DESCRIPTION", description);
  305. TopLevelCMakeVarCondSet(mf, "CMAKE_PROJECT_DESCRIPTION", description);
  306. mf.AddDefinition("PROJECT_HOMEPAGE_URL", homepage);
  307. mf.AddDefinition(projectName + "_HOMEPAGE_URL", homepage);
  308. TopLevelCMakeVarCondSet(mf, "CMAKE_PROJECT_HOMEPAGE_URL", homepage);
  309. if (languages.empty()) {
  310. // if no language is specified do c and c++
  311. languages = { "C", "CXX" };
  312. }
  313. mf.EnableLanguage(languages, false);
  314. if (!IncludeByVariable(status, "CMAKE_PROJECT_INCLUDE")) {
  315. return false;
  316. }
  317. if (!IncludeByVariable(status,
  318. "CMAKE_PROJECT_" + projectName + "_INCLUDE")) {
  319. return false;
  320. }
  321. return true;
  322. }
  323. static bool IncludeByVariable(cmExecutionStatus& status,
  324. const std::string& variable)
  325. {
  326. cmMakefile& mf = status.GetMakefile();
  327. cmProp include = mf.GetDefinition(variable);
  328. if (!include) {
  329. return true;
  330. }
  331. std::string includeFile =
  332. cmSystemTools::CollapseFullPath(*include, mf.GetCurrentSourceDirectory());
  333. if (!cmSystemTools::FileExists(includeFile)) {
  334. status.SetError(cmStrCat("could not find requested file:\n ", *include));
  335. return false;
  336. }
  337. if (cmSystemTools::FileIsDirectory(includeFile)) {
  338. status.SetError(cmStrCat("requested file is a directory:\n ", *include));
  339. return false;
  340. }
  341. const bool readit = mf.ReadDependentFile(*include);
  342. if (readit) {
  343. return true;
  344. }
  345. if (cmSystemTools::GetFatalErrorOccured()) {
  346. return true;
  347. }
  348. status.SetError(cmStrCat("could not load requested file:\n ", *include));
  349. return false;
  350. }
  351. static void TopLevelCMakeVarCondSet(cmMakefile& mf, std::string const& name,
  352. std::string const& value)
  353. {
  354. // Set the CMAKE_PROJECT_XXX variable to be the highest-level
  355. // project name in the tree. If there are two project commands
  356. // in the same CMakeLists.txt file, and it is the top level
  357. // CMakeLists.txt file, then go with the last one.
  358. if (!mf.GetDefinition(name) || mf.IsRootMakefile()) {
  359. mf.AddDefinition(name, value);
  360. mf.AddCacheDefinition(name, value, "Value Computed by CMake",
  361. cmStateEnums::STATIC);
  362. }
  363. }