source: trunk/qmake/generators/metamakefile.cpp@ 846

Last change on this file since 846 was 846, checked in by Dmitry A. Kuminov, 14 years ago

trunk: Merged in qt 4.7.2 sources from branches/vendor/nokia/qt.

File size: 20.6 KB
Line 
1/****************************************************************************
2**
3** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
4** All rights reserved.
5** Contact: Nokia Corporation (qt-info@nokia.com)
6**
7** This file is part of the qmake application of the Qt Toolkit.
8**
9** $QT_BEGIN_LICENSE:LGPL$
10** Commercial Usage
11** Licensees holding valid Qt Commercial licenses may use this file in
12** accordance with the Qt Commercial License Agreement provided with the
13** Software or, alternatively, in accordance with the terms contained in
14** a written agreement between you and Nokia.
15**
16** GNU Lesser General Public License Usage
17** Alternatively, this file may be used under the terms of the GNU Lesser
18** General Public License version 2.1 as published by the Free Software
19** Foundation and appearing in the file LICENSE.LGPL included in the
20** packaging of this file. Please review the following information to
21** ensure the GNU Lesser General Public License version 2.1 requirements
22** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
23**
24** In addition, as a special exception, Nokia gives you certain additional
25** rights. These rights are described in the Nokia Qt LGPL Exception
26** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
27**
28** GNU General Public License Usage
29** Alternatively, this file may be used under the terms of the GNU
30** General Public License version 3.0 as published by the Free Software
31** Foundation and appearing in the file LICENSE.GPL included in the
32** packaging of this file. Please review the following information to
33** ensure the GNU General Public License version 3.0 requirements will be
34** met: http://www.gnu.org/copyleft/gpl.html.
35**
36** If you have questions regarding the use of this file, please contact
37** Nokia at qt-info@nokia.com.
38** $QT_END_LICENSE$
39**
40****************************************************************************/
41
42#include "metamakefile.h"
43#include "qregexp.h"
44#include "qdir.h"
45#include "qdebug.h"
46#include "makefile.h"
47#include "project.h"
48#include "cachekeys.h"
49
50#define BUILDSMETATYPE 1
51#define SUBDIRSMETATYPE 2
52
53QT_BEGIN_NAMESPACE
54
55MetaMakefileGenerator::~MetaMakefileGenerator()
56{
57 if(own_project)
58 delete project;
59}
60
61#ifndef QT_QMAKE_PARSER_ONLY
62
63class BuildsMetaMakefileGenerator : public MetaMakefileGenerator
64{
65private:
66 bool init_flag;
67 struct Build {
68 QString name, build;
69 MakefileGenerator *makefile;
70 };
71 QList<Build *> makefiles;
72 void clearBuilds();
73 MakefileGenerator *processBuild(const QString &);
74
75public:
76
77 BuildsMetaMakefileGenerator(QMakeProject *p, const QString &n, bool op) : MetaMakefileGenerator(p, n, op), init_flag(false) { }
78 virtual ~BuildsMetaMakefileGenerator() { clearBuilds(); }
79
80 virtual bool init();
81 virtual int type() const { return BUILDSMETATYPE; }
82 virtual bool write(const QString &);
83};
84
85void
86BuildsMetaMakefileGenerator::clearBuilds()
87{
88 for(int i = 0; i < makefiles.count(); i++) {
89 Build *build = makefiles[i];
90 if(QMakeProject *p = build->makefile->projectFile()) {
91 if(p != project)
92 delete p;
93 }
94 delete build->makefile;
95 delete build;
96 }
97 makefiles.clear();
98}
99
100bool
101BuildsMetaMakefileGenerator::init()
102{
103 if(init_flag)
104 return false;
105 init_flag = true;
106
107 const QStringList &builds = project->variables()["BUILDS"];
108 bool use_single_build = builds.isEmpty();
109 if(builds.count() > 1 && Option::output.fileName() == "-") {
110 use_single_build = true;
111 warn_msg(WarnLogic, "Cannot direct to stdout when using multiple BUILDS.");
112 } else if(0 && !use_single_build && project->first("TEMPLATE") == "subdirs") {
113 use_single_build = true;
114 warn_msg(WarnLogic, "Cannot specify multiple builds with TEMPLATE subdirs.");
115 }
116 if(!use_single_build) {
117 for(int i = 0; i < builds.count(); i++) {
118 QString build = builds[i];
119 MakefileGenerator *makefile = processBuild(build);
120 if(!makefile)
121 return false;
122 if(!makefile->supportsMetaBuild()) {
123 warn_msg(WarnLogic, "QMAKESPEC does not support multiple BUILDS.");
124 clearBuilds();
125 use_single_build = true;
126 break;
127 } else {
128 Build *b = new Build;
129 b->name = name;
130 if(builds.count() != 1)
131 b->build += build;
132 b->makefile = makefile;
133 makefiles += b;
134 }
135 }
136 }
137 if(use_single_build) {
138 Build *build = new Build;
139 build->name = name;
140 build->makefile = createMakefileGenerator(project, false);
141 if (build->makefile){
142 makefiles += build;
143 }else {
144 delete build;
145 return false;
146 }
147 }
148 return true;
149}
150
151bool
152BuildsMetaMakefileGenerator::write(const QString &oldpwd)
153{
154 Build *glue = 0;
155 if(!makefiles.isEmpty() && !makefiles.first()->build.isNull()) {
156 glue = new Build;
157 glue->name = name;
158 glue->makefile = createMakefileGenerator(project, true);
159 makefiles += glue;
160 }
161
162 bool ret = true;
163 const QString &output_name = Option::output.fileName();
164 for(int i = 0; ret && i < makefiles.count(); i++) {
165 Option::output.setFileName(output_name);
166 Build *build = makefiles[i];
167
168 bool using_stdout = false;
169 if(build->makefile && (Option::qmake_mode == Option::QMAKE_GENERATE_MAKEFILE ||
170 Option::qmake_mode == Option::QMAKE_GENERATE_PROJECT)
171 && (!build->makefile->supportsMergedBuilds()
172 || (build->makefile->supportsMergedBuilds() && (!glue || build == glue)))) {
173 //open output
174 if(!(Option::output.isOpen())) {
175 if(Option::output.fileName() == "-") {
176 Option::output.setFileName("");
177 Option::output_dir = qmake_getpwd();
178 Option::output.open(stdout, QIODevice::WriteOnly | QIODevice::Text);
179 using_stdout = true;
180 } else {
181 if(Option::output.fileName().isEmpty() &&
182 Option::qmake_mode == Option::QMAKE_GENERATE_MAKEFILE)
183 Option::output.setFileName(project->first("QMAKE_MAKEFILE"));
184 Option::output_dir = oldpwd;
185 QString build_name = build->name;
186 if(!build->build.isEmpty()) {
187 if(!build_name.isEmpty())
188 build_name += ".";
189 build_name += build->build;
190 }
191 if(!build->makefile->openOutput(Option::output, build_name)) {
192 fprintf(stderr, "Failure to open file: %s\n",
193 Option::output.fileName().isEmpty() ? "(stdout)" :
194 Option::output.fileName().toLatin1().constData());
195 return false;
196 }
197 }
198 }
199 } else {
200 using_stdout = true; //kind of..
201 }
202
203 if(!build->makefile) {
204 ret = false;
205 } else if(build == glue) {
206 ret = build->makefile->writeProjectMakefile();
207 } else {
208 ret = build->makefile->write();
209 if (glue && glue->makefile->supportsMergedBuilds())
210 ret = glue->makefile->mergeBuildProject(build->makefile);
211 }
212 if(!using_stdout) {
213 Option::output.close();
214 if(!ret)
215 Option::output.remove();
216 }
217
218 // debugging
219 if(Option::debug_level) {
220 QMap<QString, QStringList> &vars = project->variables();
221 for(QMap<QString, QStringList>::Iterator it = vars.begin(); it != vars.end(); ++it) {
222 if(!it.key().startsWith(".") && !it.value().isEmpty())
223 debug_msg(1, "%s === %s", it.key().toLatin1().constData(),
224 it.value().join(" :: ").toLatin1().constData());
225 }
226 }
227 }
228 return ret;
229}
230
231MakefileGenerator
232*BuildsMetaMakefileGenerator::processBuild(const QString &build)
233{
234 if(project) {
235 debug_msg(1, "Meta Generator: Parsing '%s' for build [%s].",
236 project->projectFile().toLatin1().constData(),build.toLatin1().constData());
237
238 //initialize the base
239 QMap<QString, QStringList> basevars;
240 if(!project->isEmpty(build + ".CONFIG"))
241 basevars["CONFIG"] += project->values(build + ".CONFIG");
242 basevars["CONFIG"] += build;
243 basevars["CONFIG"] += "build_pass";
244 basevars["BUILD_PASS"] = QStringList(build);
245 QStringList buildname = project->values(build + ".name");
246 basevars["BUILD_NAME"] = (buildname.isEmpty() ? QStringList(build) : buildname);
247
248 //create project
249 QMakeProject *build_proj = new QMakeProject(project->properties(), basevars);
250
251 //all the user configs must be set again afterwards (for .pro tests and for .prf tests)
252 const QStringList old_after_user_config = Option::after_user_configs;
253 const QStringList old_user_config = Option::user_configs;
254 Option::after_user_configs += basevars["CONFIG"];
255 Option::user_configs += basevars["CONFIG"];
256 build_proj->read(project->projectFile());
257 Option::after_user_configs = old_after_user_config;
258 Option::user_configs = old_user_config;
259
260 //done
261 return createMakefileGenerator(build_proj);
262 }
263 return 0;
264}
265
266class SubdirsMetaMakefileGenerator : public MetaMakefileGenerator
267{
268protected:
269 bool init_flag;
270 struct Subdir {
271 Subdir() : makefile(0), indent(0) { }
272 ~Subdir() { delete makefile; }
273 QString input_dir;
274 QString output_dir, output_file;
275 MetaMakefileGenerator *makefile;
276 int indent;
277 };
278 QList<Subdir *> subs;
279 MakefileGenerator *processBuild(const QString &);
280
281public:
282 SubdirsMetaMakefileGenerator(QMakeProject *p, const QString &n, bool op) : MetaMakefileGenerator(p, n, op), init_flag(false) { }
283 virtual ~SubdirsMetaMakefileGenerator();
284
285 virtual bool init();
286 virtual int type() const { return SUBDIRSMETATYPE; }
287 virtual bool write(const QString &);
288};
289
290bool
291SubdirsMetaMakefileGenerator::init()
292{
293 if(init_flag)
294 return false;
295 init_flag = true;
296 bool hasError = false;
297
298 // It might make sense to bequeath the CONFIG option to the recursed
299 // projects. OTOH, one would most likely have it in all projects anyway -
300 // either through a qmakespec, a .qmake.cache or explicitly - as otherwise
301 // running qmake in a subdirectory would have a different auto-recurse
302 // setting than in parent directories.
303 bool recurse = Option::recursive == Option::QMAKE_RECURSIVE_YES
304 || (Option::recursive == Option::QMAKE_RECURSIVE_DEFAULT
305 && project->isRecursive());
306 if(recurse) {
307 QString old_output_dir = Option::output_dir;
308 QString old_output = Option::output.fileName();
309 QString oldpwd = qmake_getpwd();
310 QString thispwd = oldpwd;
311 if(!thispwd.endsWith('/'))
312 thispwd += '/';
313 const QStringList &subdirs = project->values("SUBDIRS");
314 static int recurseDepth = -1;
315 ++recurseDepth;
316 for(int i = 0; i < subdirs.size(); ++i) {
317 Subdir *sub = new Subdir;
318 sub->indent = recurseDepth;
319
320 QFileInfo subdir(subdirs.at(i));
321 if(!project->isEmpty(subdirs.at(i) + ".file"))
322 subdir = project->first(subdirs.at(i) + ".file");
323 else if(!project->isEmpty(subdirs.at(i) + ".subdir"))
324 subdir = project->first(subdirs.at(i) + ".subdir");
325 QString sub_name;
326 if(subdir.isDir())
327 subdir = QFileInfo(subdir.filePath() + "/" + subdir.fileName() + Option::pro_ext);
328 else
329 sub_name = subdir.baseName();
330 if(!subdir.isRelative()) { //we can try to make it relative
331 QString subdir_path = subdir.filePath();
332 if(subdir_path.startsWith(thispwd))
333 subdir = QFileInfo(subdir_path.mid(thispwd.length()));
334 }
335
336 //handle sub project
337 QMakeProject *sub_proj = new QMakeProject(project->properties());
338 for (int ind = 0; ind < sub->indent; ++ind)
339 printf(" ");
340 sub->input_dir = subdir.absolutePath();
341 if(subdir.isRelative() && old_output_dir != oldpwd) {
342 sub->output_dir = old_output_dir + "/" + subdir.path();
343 printf("Reading %s [%s]\n", subdir.absoluteFilePath().toLatin1().constData(), sub->output_dir.toLatin1().constData());
344 } else { //what about shadow builds?
345 sub->output_dir = sub->input_dir;
346 printf("Reading %s\n", subdir.absoluteFilePath().toLatin1().constData());
347 }
348 qmake_setpwd(sub->input_dir);
349 Option::output_dir = sub->output_dir;
350 bool tmpError = !sub_proj->read(subdir.fileName());
351 if(!sub_proj->variables()["QMAKE_FAILED_REQUIREMENTS"].isEmpty()) {
352 fprintf(stderr, "Project file(%s) not recursed because all requirements not met:\n\t%s\n",
353 subdir.fileName().toLatin1().constData(),
354 sub_proj->values("QMAKE_FAILED_REQUIREMENTS").join(" ").toLatin1().constData());
355 delete sub;
356 delete sub_proj;
357 Option::output_dir = old_output_dir;
358 qmake_setpwd(oldpwd);
359 continue;
360 } else {
361 hasError |= tmpError;
362 }
363 sub->makefile = MetaMakefileGenerator::createMetaGenerator(sub_proj, sub_name);
364 if(0 && sub->makefile->type() == SUBDIRSMETATYPE) {
365 subs.append(sub);
366 } else {
367 const QString output_name = Option::output.fileName();
368 Option::output.setFileName(sub->output_file);
369 hasError |= !sub->makefile->write(sub->output_dir);
370 delete sub;
371 qmakeClearCaches();
372 sub = 0;
373 Option::output.setFileName(output_name);
374 }
375 Option::output_dir = old_output_dir;
376 qmake_setpwd(oldpwd);
377
378 }
379 --recurseDepth;
380 Option::output.setFileName(old_output);
381 Option::output_dir = old_output_dir;
382 qmake_setpwd(oldpwd);
383 }
384
385 Subdir *self = new Subdir;
386 self->input_dir = qmake_getpwd();
387 self->output_dir = Option::output_dir;
388 if(!recurse || (!Option::output.fileName().endsWith(Option::dir_sep) && !QFileInfo(Option::output).isDir()))
389 self->output_file = Option::output.fileName();
390 self->makefile = new BuildsMetaMakefileGenerator(project, name, false);
391 self->makefile->init();
392 subs.append(self);
393
394 return !hasError;
395}
396
397bool
398SubdirsMetaMakefileGenerator::write(const QString &oldpwd)
399{
400 bool ret = true;
401 const QString &pwd = qmake_getpwd();
402 const QString &output_dir = Option::output_dir;
403 const QString &output_name = Option::output.fileName();
404 for(int i = 0; ret && i < subs.count(); i++) {
405 const Subdir *sub = subs.at(i);
406 qmake_setpwd(subs.at(i)->input_dir);
407 Option::output_dir = QFileInfo(subs.at(i)->output_dir).absoluteFilePath();
408 if(Option::output_dir.at(Option::output_dir.length()-1) != QLatin1Char('/'))
409 Option::output_dir += QLatin1Char('/');
410 Option::output.setFileName(subs.at(i)->output_file);
411 if(i != subs.count()-1) {
412 for (int ind = 0; ind < sub->indent; ++ind)
413 printf(" ");
414 printf("Writing %s\n", QDir::cleanPath(Option::output_dir+"/"+
415 Option::output.fileName()).toLatin1().constData());
416 }
417 QString writepwd = Option::fixPathToLocalOS(qmake_getpwd());
418 if(!writepwd.startsWith(Option::fixPathToLocalOS(oldpwd)))
419 writepwd = oldpwd;
420 if(!(ret = subs.at(i)->makefile->write(writepwd)))
421 break;
422 //restore because I'm paranoid
423 qmake_setpwd(pwd);
424 Option::output.setFileName(output_name);
425 Option::output_dir = output_dir;
426 }
427 return ret;
428}
429
430SubdirsMetaMakefileGenerator::~SubdirsMetaMakefileGenerator()
431{
432 for(int i = 0; i < subs.count(); i++)
433 delete subs[i];
434 subs.clear();
435}
436
437//Factory things
438QT_BEGIN_INCLUDE_NAMESPACE
439#include "unixmake.h"
440#include "mingw_make.h"
441#include "gnumake.h"
442#include "projectgenerator.h"
443#include "pbuilder_pbx.h"
444#include "msvc_nmake.h"
445#include "borland_bmake.h"
446#include "msvc_vcproj.h"
447#include "msvc_vcxproj.h"
448#include "symmake_abld.h"
449#include "symmake_sbsv2.h"
450#include "symbian_makefile.h"
451QT_END_INCLUDE_NAMESPACE
452
453MakefileGenerator *
454MetaMakefileGenerator::createMakefileGenerator(QMakeProject *proj, bool noIO)
455{
456 MakefileGenerator *mkfile = NULL;
457 if(Option::qmake_mode == Option::QMAKE_GENERATE_PROJECT) {
458 mkfile = new ProjectGenerator;
459 mkfile->setProjectFile(proj);
460 return mkfile;
461 }
462
463 QString gen = proj->first("MAKEFILE_GENERATOR");
464 if(gen.isEmpty()) {
465 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",
466 proj->projectFile().toLatin1().constData());
467 } else if(gen == "UNIX") {
468 mkfile = new UnixMakefileGenerator;
469 } else if(gen == "MINGW") {
470 mkfile = new MingwMakefileGenerator;
471 } else if(gen == "GNUMAKE") {
472 mkfile = new GNUMakefileGenerator;
473 } else if(gen == "PROJECTBUILDER" || gen == "XCODE") {
474 mkfile = new ProjectBuilderMakefileGenerator;
475 } else if(gen == "MSVC.NET") {
476 if (proj->first("TEMPLATE").startsWith("vc"))
477 mkfile = new VcprojGenerator;
478 else
479 mkfile = new NmakeMakefileGenerator;
480 } else if(gen == "MSBUILD") {
481 // Visual Studio >= v11.0
482 if (proj->first("TEMPLATE").startsWith("vc"))
483 mkfile = new VcxprojGenerator;
484 else
485 mkfile = new NmakeMakefileGenerator;
486 } else if(gen == "BMAKE") {
487 mkfile = new BorlandMakefileGenerator;
488 } else if(gen == "SYMBIAN_ABLD") {
489 mkfile = new SymbianAbldMakefileGenerator;
490 } else if(gen == "SYMBIAN_SBSV2") {
491 mkfile = new SymbianSbsv2MakefileGenerator;
492 } else if(gen == "SYMBIAN_UNIX") {
493 mkfile = new SymbianMakefileTemplate<UnixMakefileGenerator>;
494 } else {
495 fprintf(stderr, "Unknown generator specified: %s\n", gen.toLatin1().constData());
496 }
497 if (mkfile) {
498 mkfile->setNoIO(noIO);
499 mkfile->setProjectFile(proj);
500 }
501 return mkfile;
502}
503
504MetaMakefileGenerator *
505MetaMakefileGenerator::createMetaGenerator(QMakeProject *proj, const QString &name, bool op, bool *success)
506{
507 MetaMakefileGenerator *ret = 0;
508 if ((Option::qmake_mode == Option::QMAKE_GENERATE_MAKEFILE ||
509 Option::qmake_mode == Option::QMAKE_GENERATE_PRL)) {
510 if (proj->first("TEMPLATE").endsWith("subdirs"))
511 ret = new SubdirsMetaMakefileGenerator(proj, name, op);
512 }
513 if (!ret)
514 ret = new BuildsMetaMakefileGenerator(proj, name, op);
515 bool res = ret->init();
516 if (success)
517 *success = res;
518 return ret;
519}
520
521#endif // QT_QMAKE_PARSER_ONLY
522
523bool
524MetaMakefileGenerator::modesForGenerator(const QString &gen,
525 Option::HOST_MODE *host_mode, Option::TARG_MODE *target_mode)
526{
527 if (gen == "UNIX") {
528#ifdef Q_OS_MAC
529 *host_mode = Option::HOST_MACX_MODE;
530 *target_mode = Option::TARG_MACX_MODE;
531#else
532 *host_mode = Option::HOST_UNIX_MODE;
533 *target_mode = Option::TARG_UNIX_MODE;
534#endif
535 } else if (gen == "MSVC.NET" || gen == "BMAKE" || gen == "MSBUILD") {
536 *host_mode = Option::HOST_WIN_MODE;
537 *target_mode = Option::TARG_WIN_MODE;
538 } else if (gen == "MINGW") {
539#if defined(Q_OS_MAC)
540 *host_mode = Option::HOST_MACX_MODE;
541#elif defined(Q_OS_UNIX)
542 *host_mode = Option::HOST_UNIX_MODE;
543#else
544 *host_mode = Option::HOST_WIN_MODE;
545#endif
546 *target_mode = Option::TARG_WIN_MODE;
547 } else if (gen == "PROJECTBUILDER" || gen == "XCODE") {
548 *host_mode = Option::HOST_MACX_MODE;
549 *target_mode = Option::TARG_MACX_MODE;
550 } else if (gen == "SYMBIAN_ABLD" || gen == "SYMBIAN_SBSV2" || gen == "SYMBIAN_UNIX") {
551#if defined(Q_OS_MAC)
552 *host_mode = Option::HOST_MACX_MODE;
553#elif defined(Q_OS_UNIX)
554 *host_mode = Option::HOST_UNIX_MODE;
555#else
556 *host_mode = Option::HOST_WIN_MODE;
557#endif
558 *target_mode = Option::TARG_SYMBIAN_MODE;
559 } else {
560 fprintf(stderr, "Unknown generator specified: %s\n", gen.toLatin1().constData());
561 return false;
562 }
563 return true;
564}
565
566QT_END_NAMESPACE
Note: See TracBrowser for help on using the repository browser.