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 if (!Option::output.open(stdout, ioFlags: QIODevice::WriteOnly | QIODevice::Text)) {
144 fprintf(stderr, format: "Failure to open stdout\n");
145 return false;
146 }
147 using_stdout = true;
148 } else {
149 if(Option::output.fileName().isEmpty() &&
150 Option::qmake_mode == Option::QMAKE_GENERATE_MAKEFILE)
151 Option::output.setFileName(project->first(variableName: "QMAKE_MAKEFILE").toQString());
152 QString build_name = build->name;
153 if(!build->build.isEmpty()) {
154 if(!build_name.isEmpty())
155 build_name += ".";
156 build_name += build->build;
157 }
158 if(!build->makefile->openOutput(Option::output, build: build_name)) {
159 fprintf(stderr, format: "Failure to open file: %s\n",
160 Option::output.fileName().isEmpty() ? "(stdout)" :
161 Option::output.fileName().toLatin1().constData());
162 return false;
163 }
164 }
165 }
166 } else {
167 using_stdout = true; //kind of..
168 }
169
170 if(!build->makefile) {
171 ret = false;
172 } else if(build == glue) {
173 checkForConflictingTargets();
174 accumulateVariableFromBuilds(name: "QMAKE_INTERNAL_INCLUDED_FILES", build);
175 ret = build->makefile->writeProjectMakefile();
176 } else {
177 ret = build->makefile->write();
178 if (glue && glue->makefile->supportsMergedBuilds())
179 ret = glue->makefile->mergeBuildProject(build->makefile);
180 }
181 if(!using_stdout) {
182 Option::output.close();
183 if(!ret)
184 Option::output.remove();
185 }
186 }
187 return ret;
188}
189
190MakefileGenerator
191*BuildsMetaMakefileGenerator::processBuild(const ProString &build)
192{
193 if(project) {
194 debug_msg(level: 1, fmt: "Meta Generator: Parsing '%s' for build [%s].",
195 project->projectFile().toLatin1().constData(),build.toLatin1().constData());
196
197 //initialize the base
198 ProValueMap basevars;
199 ProStringList basecfgs = project->values(v: ProKey(build + ".CONFIG"));
200 basecfgs += build;
201 basecfgs += "build_pass";
202 basevars["BUILD_PASS"] = ProStringList(build);
203 ProStringList buildname = project->values(v: ProKey(build + ".name"));
204 basevars["BUILD_NAME"] = (buildname.isEmpty() ? ProStringList(build) : buildname);
205
206 //create project
207 QMakeProject *build_proj = new QMakeProject;
208 build_proj->setExtraVars(basevars);
209 build_proj->setExtraConfigs(basecfgs);
210
211 if (build_proj->read(project: project->projectFile()))
212 return createMakefileGenerator(proj: build_proj);
213 }
214 return nullptr;
215}
216
217void BuildsMetaMakefileGenerator::accumulateVariableFromBuilds(const ProKey &name, Build *dst) const
218{
219 ProStringList &values = dst->makefile->projectFile()->values(v: name);
220 for (auto build : makefiles) {
221 if (build != dst)
222 values += build->makefile->projectFile()->values(v: name);
223 }
224 values.removeDuplicates();
225}
226
227void BuildsMetaMakefileGenerator::checkForConflictingTargets() const
228{
229 if (makefiles.size() < 3) {
230 // Checking for conflicts only makes sense if we have more than one BUILD,
231 // and the last entry in makefiles is the "glue" Build.
232 return;
233 }
234 if (!project->isActiveConfig(config: "build_all")) {
235 // Only complain if we're about to build all configurations.
236 return;
237 }
238 using TargetInfo = std::pair<Build *, ProString>;
239 QList<TargetInfo> targets;
240 const int last = makefiles.size() - 1;
241 targets.resize(size: last);
242 for (int i = 0; i < last; ++i) {
243 Build *b = makefiles.at(i);
244 auto mkf = b->makefile;
245 auto prj = mkf->projectFile();
246 targets[i] = std::make_pair(x&: b, y: prj->first(variableName: mkf->fullTargetVariable()));
247 }
248 std::stable_sort(first: targets.begin(), last: targets.end(),
249 comp: [](const TargetInfo &lhs, const TargetInfo &rhs)
250 {
251 return lhs.second < rhs.second;
252 });
253 for (auto prev = targets.begin(), it = std::next(x: prev); it != targets.end(); ++prev, ++it) {
254 if (prev->second == it->second) {
255 warn_msg(t: WarnLogic, fmt: "Targets of builds '%s' and '%s' conflict: %s.",
256 qPrintable(prev->first->build),
257 qPrintable(it->first->build),
258 qPrintable(prev->second.toQString()));
259 break;
260 }
261 }
262}
263
264class SubdirsMetaMakefileGenerator : public MetaMakefileGenerator
265{
266protected:
267 bool init_flag;
268 struct Subdir {
269 Subdir() : makefile(nullptr), indent(0) { }
270 ~Subdir() { delete makefile; }
271 QString input_dir;
272 QString output_dir, output_file;
273 MetaMakefileGenerator *makefile;
274 int indent;
275 };
276 QList<Subdir *> subs;
277 MakefileGenerator *processBuild(const QString &);
278
279public:
280 SubdirsMetaMakefileGenerator(QMakeProject *p, const QString &n, bool op) : MetaMakefileGenerator(p, n, op), init_flag(false) { }
281 ~SubdirsMetaMakefileGenerator();
282
283 bool init() override;
284 int type() const override { return SUBDIRSMETATYPE; }
285 bool write() override;
286};
287
288bool
289SubdirsMetaMakefileGenerator::init()
290{
291 if(init_flag)
292 return false;
293 init_flag = true;
294 bool hasError = false;
295
296 bool recurse = Option::recursive;
297 if (recurse && project->isActiveConfig(config: "dont_recurse"))
298 recurse = false;
299 if(recurse) {
300 QString old_output_dir = Option::output_dir;
301 QString old_output = Option::output.fileName();
302 QString oldpwd = qmake_getpwd();
303 QString thispwd = oldpwd;
304 if(!thispwd.endsWith(c: '/'))
305 thispwd += '/';
306 const ProStringList &subdirs = project->values(v: "SUBDIRS");
307 Q_CONSTINIT static int recurseDepth = -1;
308 ++recurseDepth;
309 for(int i = 0; i < subdirs.size(); ++i) {
310 Subdir *sub = new Subdir;
311 sub->indent = recurseDepth;
312
313 QFileInfo subdir(subdirs.at(i).toQString());
314 const ProKey fkey(subdirs.at(i) + ".file");
315 if (!project->isEmpty(v: fkey)) {
316 subdir = QFileInfo(project->first(variableName: fkey).toQString());
317 } else {
318 const ProKey skey(subdirs.at(i) + ".subdir");
319 if (!project->isEmpty(v: skey))
320 subdir = QFileInfo(project->first(variableName: skey).toQString());
321 }
322 QString sub_name;
323 if(subdir.isDir())
324 subdir = QFileInfo(subdir.filePath() + "/" + subdir.fileName() + Option::pro_ext);
325 else
326 sub_name = subdir.baseName();
327 if(!subdir.isRelative()) { //we can try to make it relative
328 QString subdir_path = subdir.filePath();
329 if(subdir_path.startsWith(s: thispwd))
330 subdir = QFileInfo(subdir_path.mid(position: thispwd.size()));
331 }
332
333 //handle sub project
334 QMakeProject *sub_proj = new QMakeProject;
335 for (int ind = 0; ind < sub->indent; ++ind)
336 printf(format: " ");
337 sub->input_dir = subdir.absolutePath();
338 if(subdir.isRelative() && old_output_dir != oldpwd) {
339 sub->output_dir = old_output_dir + (subdir.path() != "." ? "/" + subdir.path() : QString());
340 printf(format: "Reading %s [%s]\n", subdir.absoluteFilePath().toLatin1().constData(), sub->output_dir.toLatin1().constData());
341 } else { //what about shadow builds?
342 sub->output_dir = sub->input_dir;
343 printf(format: "Reading %s\n", subdir.absoluteFilePath().toLatin1().constData());
344 }
345 qmake_setpwd(p: sub->input_dir);
346 Option::output_dir = sub->output_dir;
347 bool tmpError = !sub_proj->read(project: subdir.fileName());
348 if (!sub_proj->isEmpty(v: "QMAKE_FAILED_REQUIREMENTS")) {
349 fprintf(stderr, format: "Project file(%s) not recursed because all requirements not met:\n\t%s\n",
350 subdir.fileName().toLatin1().constData(),
351 sub_proj->values(v: "QMAKE_FAILED_REQUIREMENTS").join(sep: ' ').toLatin1().constData());
352 delete sub;
353 delete sub_proj;
354 Option::output_dir = old_output_dir;
355 qmake_setpwd(p: oldpwd);
356 continue;
357 } else {
358 hasError |= tmpError;
359 }
360 sub->makefile = MetaMakefileGenerator::createMetaGenerator(proj: sub_proj, name: sub_name);
361 const QString output_name = Option::output.fileName();
362 Option::output.setFileName(sub->output_file);
363 hasError |= !sub->makefile->write();
364 delete sub;
365 qmakeClearCaches();
366 sub = nullptr;
367 Option::output.setFileName(output_name);
368 Option::output_dir = old_output_dir;
369 qmake_setpwd(p: oldpwd);
370
371 }
372 --recurseDepth;
373 Option::output.setFileName(old_output);
374 Option::output_dir = old_output_dir;
375 qmake_setpwd(p: oldpwd);
376 }
377
378 Subdir *self = new Subdir;
379 self->input_dir = qmake_getpwd();
380 self->output_dir = Option::output_dir;
381 if(!recurse || (!Option::output.fileName().endsWith(s: Option::dir_sep) && !QFileInfo(Option::output).isDir()))
382 self->output_file = Option::output.fileName();
383 self->makefile = new BuildsMetaMakefileGenerator(project, name, false);
384 self->makefile->init();
385 subs.append(t: self);
386
387 return !hasError;
388}
389
390bool
391SubdirsMetaMakefileGenerator::write()
392{
393 bool ret = true;
394 const QString &pwd = qmake_getpwd();
395 const QString &output_dir = Option::output_dir;
396 const QString &output_name = Option::output.fileName();
397 for(int i = 0; ret && i < subs.size(); i++) {
398 const Subdir *sub = subs.at(i);
399 qmake_setpwd(p: sub->input_dir);
400 Option::output_dir = QFileInfo(sub->output_dir).absoluteFilePath();
401 Option::output.setFileName(sub->output_file);
402 if(i != subs.size()-1) {
403 for (int ind = 0; ind < sub->indent; ++ind)
404 printf(format: " ");
405 printf(format: "Writing %s\n", QDir::cleanPath(path: Option::output_dir+"/"+
406 Option::output.fileName()).toLatin1().constData());
407 }
408 if (!(ret = sub->makefile->write()))
409 break;
410 //restore because I'm paranoid
411 qmake_setpwd(p: pwd);
412 Option::output.setFileName(output_name);
413 Option::output_dir = output_dir;
414 }
415 return ret;
416}
417
418SubdirsMetaMakefileGenerator::~SubdirsMetaMakefileGenerator()
419{
420 for(int i = 0; i < subs.size(); i++)
421 delete subs[i];
422 subs.clear();
423}
424
425//Factory things
426QT_BEGIN_INCLUDE_NAMESPACE
427#include "unixmake.h"
428#include "mingw_make.h"
429#include "projectgenerator.h"
430#include "pbuilder_pbx.h"
431#include "msvc_nmake.h"
432#include "msvc_vcproj.h"
433#include "msvc_vcxproj.h"
434QT_END_INCLUDE_NAMESPACE
435
436MakefileGenerator *
437MetaMakefileGenerator::createMakefileGenerator(QMakeProject *proj, bool noIO)
438{
439 Option::postProcessProject(proj);
440
441 MakefileGenerator *mkfile = nullptr;
442 if(Option::qmake_mode == Option::QMAKE_GENERATE_PROJECT) {
443 mkfile = new ProjectGenerator;
444 mkfile->setProjectFile(proj);
445 return mkfile;
446 }
447
448 ProString gen = proj->first(variableName: "MAKEFILE_GENERATOR");
449 if(gen.isEmpty()) {
450 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",
451 proj->projectFile().toLatin1().constData());
452 } else if(gen == "UNIX") {
453 mkfile = new UnixMakefileGenerator;
454 } else if(gen == "MINGW") {
455 mkfile = new MingwMakefileGenerator;
456 } else if(gen == "PROJECTBUILDER" || gen == "XCODE") {
457#ifdef Q_CC_MSVC
458 fprintf(stderr, "Generating Xcode projects is not supported with an MSVC build of Qt.\n");
459#else
460 mkfile = new ProjectBuilderMakefileGenerator;
461#endif
462 } else if(gen == "MSVC.NET") {
463 if (proj->first(variableName: "TEMPLATE").startsWith(sub: "vc"))
464 mkfile = new VcprojGenerator;
465 else
466 mkfile = new NmakeMakefileGenerator;
467 } else if(gen == "MSBUILD") {
468 // Visual Studio >= v11.0
469 if (proj->first(variableName: "TEMPLATE").startsWith(sub: "vc"))
470 mkfile = new VcxprojGenerator;
471 else
472 mkfile = new NmakeMakefileGenerator;
473 } else {
474 fprintf(stderr, format: "Unknown generator specified: %s\n", gen.toLatin1().constData());
475 }
476 if (mkfile) {
477 mkfile->setNoIO(noIO);
478 mkfile->setProjectFile(proj);
479 }
480 return mkfile;
481}
482
483MetaMakefileGenerator *
484MetaMakefileGenerator::createMetaGenerator(QMakeProject *proj, const QString &name, bool op, bool *success)
485{
486 Option::postProcessProject(proj);
487
488 MetaMakefileGenerator *ret = nullptr;
489 if ((Option::qmake_mode == Option::QMAKE_GENERATE_MAKEFILE ||
490 Option::qmake_mode == Option::QMAKE_GENERATE_PRL)) {
491 if (proj->first(variableName: "TEMPLATE").endsWith(sub: "subdirs"))
492 ret = new SubdirsMetaMakefileGenerator(proj, name, op);
493 }
494 if (!ret)
495 ret = new BuildsMetaMakefileGenerator(proj, name, op);
496 bool res = ret->init();
497 if (success)
498 *success = res;
499 return ret;
500}
501
502#endif // QT_QMAKE_PARSER_ONLY
503
504QT_END_NAMESPACE
505

Provided by KDAB

Privacy Policy
Start learning QML with our Intro Training
Find out more

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