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
43QT_BEGIN_NAMESPACE
44
45MetaMakefileGenerator::~MetaMakefileGenerator()
46{
47 if(own_project)
48 delete project;
49}
50
51#ifndef QT_QMAKE_PARSER_ONLY
52
53class BuildsMetaMakefileGenerator : public MetaMakefileGenerator
54{
55private:
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
67public:
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
77void
78BuildsMetaMakefileGenerator::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
92bool
93BuildsMetaMakefileGenerator::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
140bool
141BuildsMetaMakefileGenerator::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
212MakefileGenerator
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
239void 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
249void 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
286class SubdirsMetaMakefileGenerator : public MetaMakefileGenerator
287{
288protected:
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
301public:
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
310bool
311SubdirsMetaMakefileGenerator::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
412bool
413SubdirsMetaMakefileGenerator::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
440SubdirsMetaMakefileGenerator::~SubdirsMetaMakefileGenerator()
441{
442 for(int i = 0; i < subs.count(); i++)
443 delete subs[i];
444 subs.clear();
445}
446
447//Factory things
448QT_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"
456QT_END_INCLUDE_NAMESPACE
457
458MakefileGenerator *
459MetaMakefileGenerator::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
505MetaMakefileGenerator *
506MetaMakefileGenerator::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
526QT_END_NAMESPACE
527