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