1 | /**************************************************************************** |
2 | ** |
3 | ** Copyright (C) 2016 The Qt Company Ltd. |
4 | ** Contact: https://www.qt.io/licensing/ |
5 | ** |
6 | ** This file is part of the qmake application of the Qt Toolkit. |
7 | ** |
8 | ** $QT_BEGIN_LICENSE:GPL-EXCEPT$ |
9 | ** Commercial License Usage |
10 | ** Licensees holding valid commercial Qt licenses may use this file in |
11 | ** accordance with the commercial license agreement provided with the |
12 | ** Software or, alternatively, in accordance with the terms contained in |
13 | ** a written agreement between you and The Qt Company. For licensing terms |
14 | ** and conditions see https://www.qt.io/terms-conditions. For further |
15 | ** information use the contact form at https://www.qt.io/contact-us. |
16 | ** |
17 | ** GNU General Public License Usage |
18 | ** Alternatively, this file may be used under the terms of the GNU |
19 | ** General Public License version 3 as published by the Free Software |
20 | ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT |
21 | ** included in the packaging of this file. Please review the following |
22 | ** information to ensure the GNU General Public License requirements will |
23 | ** be met: https://www.gnu.org/licenses/gpl-3.0.html. |
24 | ** |
25 | ** $QT_END_LICENSE$ |
26 | ** |
27 | ****************************************************************************/ |
28 | |
29 | #include "metamakefile.h" |
30 | #include "qdir.h" |
31 | #include "qdebug.h" |
32 | #include "makefile.h" |
33 | #include "project.h" |
34 | #include "cachekeys.h" |
35 | |
36 | #include <algorithm> |
37 | #include <iterator> |
38 | #include <utility> |
39 | |
40 | #define BUILDSMETATYPE 1 |
41 | #define SUBDIRSMETATYPE 2 |
42 | |
43 | QT_BEGIN_NAMESPACE |
44 | |
45 | MetaMakefileGenerator::~MetaMakefileGenerator() |
46 | { |
47 | if(own_project) |
48 | delete project; |
49 | } |
50 | |
51 | #ifndef QT_QMAKE_PARSER_ONLY |
52 | |
53 | class BuildsMetaMakefileGenerator : public MetaMakefileGenerator |
54 | { |
55 | private: |
56 | bool init_flag; |
57 | struct Build { |
58 | QString name, build; |
59 | MakefileGenerator *makefile; |
60 | }; |
61 | QList<Build *> makefiles; |
62 | void clearBuilds(); |
63 | MakefileGenerator *processBuild(const ProString &); |
64 | void accumulateVariableFromBuilds(const ProKey &name, Build *build) const; |
65 | void checkForConflictingTargets() const; |
66 | |
67 | public: |
68 | |
69 | BuildsMetaMakefileGenerator(QMakeProject *p, const QString &n, bool op) : MetaMakefileGenerator(p, n, op), init_flag(false) { } |
70 | ~BuildsMetaMakefileGenerator() { clearBuilds(); } |
71 | |
72 | bool init() override; |
73 | int type() const override { return BUILDSMETATYPE; } |
74 | bool write() override; |
75 | }; |
76 | |
77 | void |
78 | BuildsMetaMakefileGenerator::clearBuilds() |
79 | { |
80 | for(int i = 0; i < makefiles.count(); i++) { |
81 | Build *build = makefiles[i]; |
82 | if(QMakeProject *p = build->makefile->projectFile()) { |
83 | if(p != project) |
84 | delete p; |
85 | } |
86 | delete build->makefile; |
87 | delete build; |
88 | } |
89 | makefiles.clear(); |
90 | } |
91 | |
92 | bool |
93 | BuildsMetaMakefileGenerator::init() |
94 | { |
95 | if(init_flag) |
96 | return false; |
97 | init_flag = true; |
98 | |
99 | const ProStringList &builds = project->values("BUILDS" ); |
100 | bool use_single_build = builds.isEmpty(); |
101 | if(builds.count() > 1 && Option::output.fileName() == "-" ) { |
102 | use_single_build = true; |
103 | warn_msg(WarnLogic, "Cannot direct to stdout when using multiple BUILDS." ); |
104 | } |
105 | if(!use_single_build) { |
106 | for(int i = 0; i < builds.count(); i++) { |
107 | ProString build = builds[i]; |
108 | MakefileGenerator *makefile = processBuild(build); |
109 | if(!makefile) |
110 | return false; |
111 | if(!makefile->supportsMetaBuild()) { |
112 | warn_msg(WarnLogic, "QMAKESPEC does not support multiple BUILDS." ); |
113 | clearBuilds(); |
114 | use_single_build = true; |
115 | break; |
116 | } else { |
117 | Build *b = new Build; |
118 | b->name = name; |
119 | if(builds.count() != 1) |
120 | b->build = build.toQString(); |
121 | b->makefile = makefile; |
122 | makefiles += b; |
123 | } |
124 | } |
125 | } |
126 | if(use_single_build) { |
127 | Build *build = new Build; |
128 | build->name = name; |
129 | build->makefile = createMakefileGenerator(project, false); |
130 | if (build->makefile){ |
131 | makefiles += build; |
132 | }else { |
133 | delete build; |
134 | return false; |
135 | } |
136 | } |
137 | return true; |
138 | } |
139 | |
140 | bool |
141 | BuildsMetaMakefileGenerator::write() |
142 | { |
143 | Build *glue = nullptr; |
144 | if(!makefiles.isEmpty() && !makefiles.first()->build.isNull() |
145 | && Option::qmake_mode != Option::QMAKE_GENERATE_PRL) { |
146 | glue = new Build; |
147 | glue->name = name; |
148 | glue->makefile = createMakefileGenerator(project, true); |
149 | makefiles += glue; |
150 | } |
151 | |
152 | bool ret = true; |
153 | const QString &output_name = Option::output.fileName(); |
154 | for(int i = 0; ret && i < makefiles.count(); i++) { |
155 | Option::output.setFileName(output_name); |
156 | Build *build = makefiles[i]; |
157 | |
158 | bool using_stdout = false; |
159 | if(build->makefile && (Option::qmake_mode == Option::QMAKE_GENERATE_MAKEFILE || |
160 | Option::qmake_mode == Option::QMAKE_GENERATE_PROJECT) |
161 | && (!build->makefile->supportsMergedBuilds() |
162 | || (build->makefile->supportsMergedBuilds() && (!glue || build == glue)))) { |
163 | //open output |
164 | if(!(Option::output.isOpen())) { |
165 | if(Option::output.fileName() == "-" ) { |
166 | Option::output.setFileName("" ); |
167 | Option::output_dir = qmake_getpwd(); |
168 | Option::output.open(stdout, QIODevice::WriteOnly | QIODevice::Text); |
169 | using_stdout = true; |
170 | } else { |
171 | if(Option::output.fileName().isEmpty() && |
172 | Option::qmake_mode == Option::QMAKE_GENERATE_MAKEFILE) |
173 | Option::output.setFileName(project->first("QMAKE_MAKEFILE" ).toQString()); |
174 | QString build_name = build->name; |
175 | if(!build->build.isEmpty()) { |
176 | if(!build_name.isEmpty()) |
177 | build_name += "." ; |
178 | build_name += build->build; |
179 | } |
180 | if(!build->makefile->openOutput(Option::output, build_name)) { |
181 | fprintf(stderr, "Failure to open file: %s\n" , |
182 | Option::output.fileName().isEmpty() ? "(stdout)" : |
183 | Option::output.fileName().toLatin1().constData()); |
184 | return false; |
185 | } |
186 | } |
187 | } |
188 | } else { |
189 | using_stdout = true; //kind of.. |
190 | } |
191 | |
192 | if(!build->makefile) { |
193 | ret = false; |
194 | } else if(build == glue) { |
195 | checkForConflictingTargets(); |
196 | accumulateVariableFromBuilds("QMAKE_INTERNAL_INCLUDED_FILES" , build); |
197 | ret = build->makefile->writeProjectMakefile(); |
198 | } else { |
199 | ret = build->makefile->write(); |
200 | if (glue && glue->makefile->supportsMergedBuilds()) |
201 | ret = glue->makefile->mergeBuildProject(build->makefile); |
202 | } |
203 | if(!using_stdout) { |
204 | Option::output.close(); |
205 | if(!ret) |
206 | Option::output.remove(); |
207 | } |
208 | } |
209 | return ret; |
210 | } |
211 | |
212 | MakefileGenerator |
213 | *BuildsMetaMakefileGenerator::processBuild(const ProString &build) |
214 | { |
215 | if(project) { |
216 | debug_msg(1, "Meta Generator: Parsing '%s' for build [%s]." , |
217 | project->projectFile().toLatin1().constData(),build.toLatin1().constData()); |
218 | |
219 | //initialize the base |
220 | ProValueMap basevars; |
221 | ProStringList basecfgs = project->values(ProKey(build + ".CONFIG" )); |
222 | basecfgs += build; |
223 | basecfgs += "build_pass" ; |
224 | basevars["BUILD_PASS" ] = ProStringList(build); |
225 | ProStringList buildname = project->values(ProKey(build + ".name" )); |
226 | basevars["BUILD_NAME" ] = (buildname.isEmpty() ? ProStringList(build) : buildname); |
227 | |
228 | //create project |
229 | QMakeProject *build_proj = new QMakeProject; |
230 | build_proj->setExtraVars(basevars); |
231 | build_proj->setExtraConfigs(basecfgs); |
232 | |
233 | if (build_proj->read(project->projectFile())) |
234 | return createMakefileGenerator(build_proj); |
235 | } |
236 | return nullptr; |
237 | } |
238 | |
239 | void BuildsMetaMakefileGenerator::accumulateVariableFromBuilds(const ProKey &name, Build *dst) const |
240 | { |
241 | ProStringList &values = dst->makefile->projectFile()->values(name); |
242 | for (auto build : makefiles) { |
243 | if (build != dst) |
244 | values += build->makefile->projectFile()->values(name); |
245 | } |
246 | values.removeDuplicates(); |
247 | } |
248 | |
249 | void BuildsMetaMakefileGenerator::checkForConflictingTargets() const |
250 | { |
251 | if (makefiles.count() < 3) { |
252 | // Checking for conflicts only makes sense if we have more than one BUILD, |
253 | // and the last entry in makefiles is the "glue" Build. |
254 | return; |
255 | } |
256 | if (!project->isActiveConfig("build_all" )) { |
257 | // Only complain if we're about to build all configurations. |
258 | return; |
259 | } |
260 | using TargetInfo = std::pair<Build *, ProString>; |
261 | QList<TargetInfo> targets; |
262 | const int last = makefiles.count() - 1; |
263 | targets.resize(last); |
264 | for (int i = 0; i < last; ++i) { |
265 | Build *b = makefiles.at(i); |
266 | auto mkf = b->makefile; |
267 | auto prj = mkf->projectFile(); |
268 | targets[i] = std::make_pair(b, prj->first(mkf->fullTargetVariable())); |
269 | } |
270 | std::stable_sort(targets.begin(), targets.end(), |
271 | [](const TargetInfo &lhs, const TargetInfo &rhs) |
272 | { |
273 | return lhs.second < rhs.second; |
274 | }); |
275 | for (auto prev = targets.begin(), it = std::next(prev); it != targets.end(); ++prev, ++it) { |
276 | if (prev->second == it->second) { |
277 | warn_msg(WarnLogic, "Targets of builds '%s' and '%s' conflict: %s." , |
278 | qPrintable(prev->first->build), |
279 | qPrintable(it->first->build), |
280 | qPrintable(prev->second.toQString())); |
281 | break; |
282 | } |
283 | } |
284 | } |
285 | |
286 | class SubdirsMetaMakefileGenerator : public MetaMakefileGenerator |
287 | { |
288 | protected: |
289 | bool init_flag; |
290 | struct Subdir { |
291 | Subdir() : makefile(nullptr), indent(0) { } |
292 | ~Subdir() { delete makefile; } |
293 | QString input_dir; |
294 | QString output_dir, output_file; |
295 | MetaMakefileGenerator *makefile; |
296 | int indent; |
297 | }; |
298 | QList<Subdir *> subs; |
299 | MakefileGenerator *processBuild(const QString &); |
300 | |
301 | public: |
302 | SubdirsMetaMakefileGenerator(QMakeProject *p, const QString &n, bool op) : MetaMakefileGenerator(p, n, op), init_flag(false) { } |
303 | ~SubdirsMetaMakefileGenerator(); |
304 | |
305 | bool init() override; |
306 | int type() const override { return SUBDIRSMETATYPE; } |
307 | bool write() override; |
308 | }; |
309 | |
310 | bool |
311 | SubdirsMetaMakefileGenerator::init() |
312 | { |
313 | if(init_flag) |
314 | return false; |
315 | init_flag = true; |
316 | bool hasError = false; |
317 | |
318 | bool recurse = Option::recursive; |
319 | if (recurse && project->isActiveConfig("dont_recurse" )) |
320 | recurse = false; |
321 | if(recurse) { |
322 | QString old_output_dir = Option::output_dir; |
323 | QString old_output = Option::output.fileName(); |
324 | QString oldpwd = qmake_getpwd(); |
325 | QString thispwd = oldpwd; |
326 | if(!thispwd.endsWith('/')) |
327 | thispwd += '/'; |
328 | const ProStringList &subdirs = project->values("SUBDIRS" ); |
329 | static int recurseDepth = -1; |
330 | ++recurseDepth; |
331 | for(int i = 0; i < subdirs.size(); ++i) { |
332 | Subdir *sub = new Subdir; |
333 | sub->indent = recurseDepth; |
334 | |
335 | QFileInfo subdir(subdirs.at(i).toQString()); |
336 | const ProKey fkey(subdirs.at(i) + ".file" ); |
337 | if (!project->isEmpty(fkey)) { |
338 | subdir = project->first(fkey).toQString(); |
339 | } else { |
340 | const ProKey skey(subdirs.at(i) + ".subdir" ); |
341 | if (!project->isEmpty(skey)) |
342 | subdir = project->first(skey).toQString(); |
343 | } |
344 | QString sub_name; |
345 | if(subdir.isDir()) |
346 | subdir = QFileInfo(subdir.filePath() + "/" + subdir.fileName() + Option::pro_ext); |
347 | else |
348 | sub_name = subdir.baseName(); |
349 | if(!subdir.isRelative()) { //we can try to make it relative |
350 | QString subdir_path = subdir.filePath(); |
351 | if(subdir_path.startsWith(thispwd)) |
352 | subdir = QFileInfo(subdir_path.mid(thispwd.length())); |
353 | } |
354 | |
355 | //handle sub project |
356 | QMakeProject *sub_proj = new QMakeProject; |
357 | for (int ind = 0; ind < sub->indent; ++ind) |
358 | printf(" " ); |
359 | sub->input_dir = subdir.absolutePath(); |
360 | if(subdir.isRelative() && old_output_dir != oldpwd) { |
361 | sub->output_dir = old_output_dir + (subdir.path() != "." ? "/" + subdir.path() : QString()); |
362 | printf("Reading %s [%s]\n" , subdir.absoluteFilePath().toLatin1().constData(), sub->output_dir.toLatin1().constData()); |
363 | } else { //what about shadow builds? |
364 | sub->output_dir = sub->input_dir; |
365 | printf("Reading %s\n" , subdir.absoluteFilePath().toLatin1().constData()); |
366 | } |
367 | qmake_setpwd(sub->input_dir); |
368 | Option::output_dir = sub->output_dir; |
369 | bool tmpError = !sub_proj->read(subdir.fileName()); |
370 | if (!sub_proj->isEmpty("QMAKE_FAILED_REQUIREMENTS" )) { |
371 | fprintf(stderr, "Project file(%s) not recursed because all requirements not met:\n\t%s\n" , |
372 | subdir.fileName().toLatin1().constData(), |
373 | sub_proj->values("QMAKE_FAILED_REQUIREMENTS" ).join(' ').toLatin1().constData()); |
374 | delete sub; |
375 | delete sub_proj; |
376 | Option::output_dir = old_output_dir; |
377 | qmake_setpwd(oldpwd); |
378 | continue; |
379 | } else { |
380 | hasError |= tmpError; |
381 | } |
382 | sub->makefile = MetaMakefileGenerator::createMetaGenerator(sub_proj, sub_name); |
383 | const QString output_name = Option::output.fileName(); |
384 | Option::output.setFileName(sub->output_file); |
385 | hasError |= !sub->makefile->write(); |
386 | delete sub; |
387 | qmakeClearCaches(); |
388 | sub = nullptr; |
389 | Option::output.setFileName(output_name); |
390 | Option::output_dir = old_output_dir; |
391 | qmake_setpwd(oldpwd); |
392 | |
393 | } |
394 | --recurseDepth; |
395 | Option::output.setFileName(old_output); |
396 | Option::output_dir = old_output_dir; |
397 | qmake_setpwd(oldpwd); |
398 | } |
399 | |
400 | Subdir *self = new Subdir; |
401 | self->input_dir = qmake_getpwd(); |
402 | self->output_dir = Option::output_dir; |
403 | if(!recurse || (!Option::output.fileName().endsWith(Option::dir_sep) && !QFileInfo(Option::output).isDir())) |
404 | self->output_file = Option::output.fileName(); |
405 | self->makefile = new BuildsMetaMakefileGenerator(project, name, false); |
406 | self->makefile->init(); |
407 | subs.append(self); |
408 | |
409 | return !hasError; |
410 | } |
411 | |
412 | bool |
413 | SubdirsMetaMakefileGenerator::write() |
414 | { |
415 | bool ret = true; |
416 | const QString &pwd = qmake_getpwd(); |
417 | const QString &output_dir = Option::output_dir; |
418 | const QString &output_name = Option::output.fileName(); |
419 | for(int i = 0; ret && i < subs.count(); i++) { |
420 | const Subdir *sub = subs.at(i); |
421 | qmake_setpwd(sub->input_dir); |
422 | Option::output_dir = QFileInfo(sub->output_dir).absoluteFilePath(); |
423 | Option::output.setFileName(sub->output_file); |
424 | if(i != subs.count()-1) { |
425 | for (int ind = 0; ind < sub->indent; ++ind) |
426 | printf(" " ); |
427 | printf("Writing %s\n" , QDir::cleanPath(Option::output_dir+"/" + |
428 | Option::output.fileName()).toLatin1().constData()); |
429 | } |
430 | if (!(ret = sub->makefile->write())) |
431 | break; |
432 | //restore because I'm paranoid |
433 | qmake_setpwd(pwd); |
434 | Option::output.setFileName(output_name); |
435 | Option::output_dir = output_dir; |
436 | } |
437 | return ret; |
438 | } |
439 | |
440 | SubdirsMetaMakefileGenerator::~SubdirsMetaMakefileGenerator() |
441 | { |
442 | for(int i = 0; i < subs.count(); i++) |
443 | delete subs[i]; |
444 | subs.clear(); |
445 | } |
446 | |
447 | //Factory things |
448 | QT_BEGIN_INCLUDE_NAMESPACE |
449 | #include "unixmake.h" |
450 | #include "mingw_make.h" |
451 | #include "projectgenerator.h" |
452 | #include "pbuilder_pbx.h" |
453 | #include "msvc_nmake.h" |
454 | #include "msvc_vcproj.h" |
455 | #include "msvc_vcxproj.h" |
456 | QT_END_INCLUDE_NAMESPACE |
457 | |
458 | MakefileGenerator * |
459 | MetaMakefileGenerator::createMakefileGenerator(QMakeProject *proj, bool noIO) |
460 | { |
461 | Option::postProcessProject(proj); |
462 | |
463 | MakefileGenerator *mkfile = nullptr; |
464 | if(Option::qmake_mode == Option::QMAKE_GENERATE_PROJECT) { |
465 | mkfile = new ProjectGenerator; |
466 | mkfile->setProjectFile(proj); |
467 | return mkfile; |
468 | } |
469 | |
470 | ProString gen = proj->first("MAKEFILE_GENERATOR" ); |
471 | if(gen.isEmpty()) { |
472 | fprintf(stderr, "MAKEFILE_GENERATOR variable not set as a result of parsing : %s. Possibly qmake was not able to find files included using \"include(..)\" - enable qmake debugging to investigate more.\n" , |
473 | proj->projectFile().toLatin1().constData()); |
474 | } else if(gen == "UNIX" ) { |
475 | mkfile = new UnixMakefileGenerator; |
476 | } else if(gen == "MINGW" ) { |
477 | mkfile = new MingwMakefileGenerator; |
478 | } else if(gen == "PROJECTBUILDER" || gen == "XCODE" ) { |
479 | #ifdef Q_CC_MSVC |
480 | fprintf(stderr, "Generating Xcode projects is not supported with an MSVC build of Qt.\n" ); |
481 | #else |
482 | mkfile = new ProjectBuilderMakefileGenerator; |
483 | #endif |
484 | } else if(gen == "MSVC.NET" ) { |
485 | if (proj->first("TEMPLATE" ).startsWith("vc" )) |
486 | mkfile = new VcprojGenerator; |
487 | else |
488 | mkfile = new NmakeMakefileGenerator; |
489 | } else if(gen == "MSBUILD" ) { |
490 | // Visual Studio >= v11.0 |
491 | if (proj->first("TEMPLATE" ).startsWith("vc" )) |
492 | mkfile = new VcxprojGenerator; |
493 | else |
494 | mkfile = new NmakeMakefileGenerator; |
495 | } else { |
496 | fprintf(stderr, "Unknown generator specified: %s\n" , gen.toLatin1().constData()); |
497 | } |
498 | if (mkfile) { |
499 | mkfile->setNoIO(noIO); |
500 | mkfile->setProjectFile(proj); |
501 | } |
502 | return mkfile; |
503 | } |
504 | |
505 | MetaMakefileGenerator * |
506 | MetaMakefileGenerator::createMetaGenerator(QMakeProject *proj, const QString &name, bool op, bool *success) |
507 | { |
508 | Option::postProcessProject(proj); |
509 | |
510 | MetaMakefileGenerator *ret = nullptr; |
511 | if ((Option::qmake_mode == Option::QMAKE_GENERATE_MAKEFILE || |
512 | Option::qmake_mode == Option::QMAKE_GENERATE_PRL)) { |
513 | if (proj->first("TEMPLATE" ).endsWith("subdirs" )) |
514 | ret = new SubdirsMetaMakefileGenerator(proj, name, op); |
515 | } |
516 | if (!ret) |
517 | ret = new BuildsMetaMakefileGenerator(proj, name, op); |
518 | bool res = ret->init(); |
519 | if (success) |
520 | *success = res; |
521 | return ret; |
522 | } |
523 | |
524 | #endif // QT_QMAKE_PARSER_ONLY |
525 | |
526 | QT_END_NAMESPACE |
527 | |