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 
~MetaMakefileGenerator()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 
BuildsMetaMakefileGenerator(QMakeProject * p,const QString & n,bool op)69     BuildsMetaMakefileGenerator(QMakeProject *p, const QString &n, bool op) : MetaMakefileGenerator(p, n, op), init_flag(false) { }
~BuildsMetaMakefileGenerator()70     ~BuildsMetaMakefileGenerator() { clearBuilds(); }
71 
72     bool init() override;
type() const73     int type() const override { return BUILDSMETATYPE; }
74     bool write() override;
75 };
76 
77 void
clearBuilds()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
init()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
write()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
processBuild(const ProString & build)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 
accumulateVariableFromBuilds(const ProKey & name,Build * dst) const239 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 
checkForConflictingTargets() const249 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     QVector<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 {
SubdirSubdirsMetaMakefileGenerator::Subdir291         Subdir() : makefile(nullptr), indent(0) { }
~SubdirSubdirsMetaMakefileGenerator::Subdir292         ~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:
SubdirsMetaMakefileGenerator(QMakeProject * p,const QString & n,bool op)302     SubdirsMetaMakefileGenerator(QMakeProject *p, const QString &n, bool op) : MetaMakefileGenerator(p, n, op), init_flag(false) { }
303     ~SubdirsMetaMakefileGenerator();
304 
305     bool init() override;
type() const306     int type() const override { return SUBDIRSMETATYPE; }
307     bool write() override;
308 };
309 
310 bool
init()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
write()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 
~SubdirsMetaMakefileGenerator()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 *
createMakefileGenerator(QMakeProject * proj,bool noIO)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 *
createMetaGenerator(QMakeProject * proj,const QString & name,bool op,bool * success)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