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

source code of qtbase/qmake/generators/metamakefile.cpp