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