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"
5 #include "cmsys/RegularExpression.hxx"
10 #include "cmAlgorithms.h"
11 #include "cmMakefile.h"
12 #include "cmPolicies.h"
13 #include "cmStateTypes.h"
14 #include "cmSystemTools.h"
17 class cmExecutionStatus;
20 bool cmProjectCommand::InitialPass(std::vector<std::string> const& args,
24 this->SetError("PROJECT called with incorrect number of arguments");
28 std::string const& projectName = args[0];
30 this->Makefile->SetProjectName(projectName);
32 std::string bindir = projectName;
33 bindir += "_BINARY_DIR";
34 std::string srcdir = projectName;
35 srcdir += "_SOURCE_DIR";
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);
44 bindir = "PROJECT_BINARY_DIR";
45 srcdir = "PROJECT_SOURCE_DIR";
47 this->Makefile->AddDefinition(bindir,
48 this->Makefile->GetCurrentBinaryDirectory());
49 this->Makefile->AddDefinition(srcdir,
50 this->Makefile->GetCurrentSourceDirectory());
52 this->Makefile->AddDefinition("PROJECT_NAME", projectName.c_str());
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
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);
68 bool haveVersion = false;
69 bool haveLanguages = false;
70 bool haveDescription = false;
71 bool haveHomepage = false;
72 bool injectedProjectCommand = false;
74 std::string description;
76 std::vector<std::string> languages;
77 std::function<void()> missedValueReporter;
78 auto resetReporter = [&missedValueReporter]() {
79 missedValueReporter = std::function<void()>();
88 Doing doing = DoingLanguages;
89 for (size_t i = 1; i < args.size(); ++i) {
90 if (args[i] == "LANGUAGES") {
92 this->Makefile->IssueMessage(
93 cmake::FATAL_ERROR, "LANGUAGES may be specified at most once.");
94 cmSystemTools::SetFatalErrorOccured();
98 if (missedValueReporter) {
99 missedValueReporter();
101 doing = DoingLanguages;
102 if (!languages.empty()) {
104 "the following parameters must be specified after LANGUAGES "
106 msg += cmJoin(languages, ", ");
108 this->Makefile->IssueMessage(cmake::WARNING, msg);
110 } else if (args[i] == "VERSION") {
112 this->Makefile->IssueMessage(cmake::FATAL_ERROR,
113 "VERSION may be specified at most once.");
114 cmSystemTools::SetFatalErrorOccured();
118 if (missedValueReporter) {
119 missedValueReporter();
121 doing = DoingVersion;
122 missedValueReporter = [this, &resetReporter]() {
123 this->Makefile->IssueMessage(
125 "VERSION keyword not followed by a value or was followed by a "
126 "value that expanded to nothing.");
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();
136 haveDescription = true;
137 if (missedValueReporter) {
138 missedValueReporter();
140 doing = DoingDescription;
141 missedValueReporter = [this, &resetReporter]() {
142 this->Makefile->IssueMessage(
144 "DESCRIPTION keyword not followed by a value or was followed "
145 "by a value that expanded to nothing.");
148 } else if (args[i] == "HOMEPAGE_URL") {
150 this->Makefile->IssueMessage(
151 cmake::FATAL_ERROR, "HOMEPAGE_URL may be specified at most once.");
152 cmSystemTools::SetFatalErrorOccured();
156 doing = DoingHomepage;
157 missedValueReporter = [this, &resetReporter]() {
158 this->Makefile->IssueMessage(
160 "HOMEPAGE_URL keyword not followed by a value or was followed "
161 "by a value that expanded to nothing.");
164 } else if (i == 1 && args[i] == "__CMAKE_INJECTED_PROJECT_COMMAND__") {
165 injectedProjectCommand = true;
166 } else if (doing == DoingVersion) {
167 doing = DoingLanguages;
170 } else if (doing == DoingDescription) {
171 doing = DoingLanguages;
172 description = args[i];
174 } else if (doing == DoingHomepage) {
175 doing = DoingLanguages;
178 } else // doing == DoingLanguages
180 languages.push_back(args[i]);
184 if (missedValueReporter) {
185 missedValueReporter();
188 if ((haveVersion || haveDescription || haveHomepage) && !haveLanguages &&
189 !languages.empty()) {
190 this->Makefile->IssueMessage(
192 "project with VERSION, DESCRIPTION or HOMEPAGE_URL must "
193 "use LANGUAGES before language names.");
194 cmSystemTools::SetFatalErrorOccured();
197 if (haveLanguages && languages.empty()) {
198 languages.push_back("NONE");
201 cmPolicies::PolicyStatus cmp0048 =
202 this->Makefile->GetPolicyStatus(cmPolicies::CMP0048);
204 // Set project VERSION variables to given values
205 if (cmp0048 == cmPolicies::OLD || cmp0048 == cmPolicies::WARN) {
206 this->Makefile->IssueMessage(
208 "VERSION not allowed unless CMP0048 is set to NEW");
209 cmSystemTools::SetFatalErrorOccured();
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();
223 const char* sep = "";
225 unsigned int v[4] = { 0, 0, 0, 0 };
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) {
230 sprintf(vb[i], "%u", v[i]);
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");
282 for (std::string const& i : vv) {
283 const char* v = this->Makefile->GetDefinition(i);
285 if (cmp0048 == cmPolicies::WARN) {
286 if (!injectedProjectCommand) {
291 this->Makefile->AddDefinition(i, "");
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());
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());
311 this->Makefile->AddDefinition("PROJECT_HOMEPAGE_URL", homepage.c_str());
312 this->Makefile->AddDefinition(projectName + "_HOMEPAGE_URL",
314 TopLevelCMakeVarCondSet("CMAKE_PROJECT_HOMEPAGE_URL", homepage.c_str());
317 if (languages.empty()) {
318 // if no language is specified do c and c++
319 languages.push_back("C");
320 languages.push_back("CXX");
322 this->Makefile->EnableLanguage(languages, false);
323 std::string extraInclude = "CMAKE_PROJECT_" + projectName + "_INCLUDE";
324 const char* include = this->Makefile->GetDefinition(extraInclude);
326 bool readit = this->Makefile->ReadDependentFile(include);
327 if (!readit && !cmSystemTools::GetFatalErrorOccured()) {
328 std::string m = "could not find file:\n"
338 void cmProjectCommand::TopLevelCMakeVarCondSet(const char* const name,
339 const char* const value)
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);